Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 126 additions & 60 deletions app/routes/device.$deviceId.overview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArrowLeft } from 'lucide-react'
import { ArrowLeft, ClipboardCopy, CopyCheck } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
redirect,
Expand All @@ -14,7 +15,6 @@ import { getDeviceWithoutSensors } from '~/models/device.server'
import { getSensorsFromDevice } from '~/models/sensor.server'
import { getUserId } from '~/utils/session.server'

//*****************************************************
export async function loader({ request, params }: LoaderFunctionArgs) {
const userId = await getUserId(request)
if (!userId) return redirect('/')
Expand All @@ -25,109 +25,159 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
const deviceData = await getDeviceWithoutSensors({ id: params.deviceId })
const sensorsData = await getSensorsFromDevice(params.deviceId)

return { deviceData, sensorsData, userId }
}
// If the user is accessing someone elses device, the apiKey should not be leaked
if (deviceData && userId !== deviceData.userId) deviceData.apiKey = null

//*****************************************************
export async function action() {
return {}
return { deviceData, sensorsData, userId }
}

//**********************************
export default function DeviceOverview() {
const { deviceData, sensorsData, userId } = useLoaderData<typeof loader>()
const {t} = useTranslation('device-overview')
const { t } = useTranslation('device-overview')
const [copiedToClipboard, setCopiedToClipboard] = useState<string | null>(
null,
)

const copyToClipboard = async (
id: string,
value: string | undefined | null,
) => {
if (value === undefined || value === null) return
await navigator.clipboard.writeText(value)
setCopiedToClipboard(id)
}

useEffect(() => {
if (copiedToClipboard === null) return
const timer = window.setTimeout(() => {
setCopiedToClipboard(null)
}, 2_500)

return () => window.clearTimeout(timer)
}, [copiedToClipboard])

return (
<div className="space-y-6 px-4 sm:px-6 lg:px-8 pb-16 font-helvetica">
<div className="space-y-6 px-4 pb-16 font-helvetica sm:px-6 lg:px-8">
<NavBar />
<div className="grid grid-cols-8 gap-10 font-helvetica text-[15px] tracking-wide max-md:grid-cols-2 lg:grid-rows-1">
<nav className="col-span-2 md:col-span-2">
<ul>
<li className="rounded p-3 text-[#676767] hover:bg-[#eee]">
<ArrowLeft className="mr-2 inline h-5 w-5" />
<Link to="/profile/me">
{t('back_to_dashboard')}
</Link>
</li>
</ul>
</nav>

<main className="px-4 py-12 col-span-6 md:col-span-6">

<p className="inline-block rounded p-3 text-[#676767] hover:bg-[#eee]">
<ArrowLeft className="mr-2 inline h-5 w-5" />
<Link to="/profile/me">{t('back_to_dashboard')}</Link>
</p>

<main className="mx-auto max-w-screen-xl">
<div className="space-y-0.5 text-center">
<h2 className="text-3xl font-bold tracking-normal">{t('device_overview')}</h2>
<p className="text-muted-foreground">
{t('show_details')}
</p>
<h2 className="text-3xl font-bold tracking-normal">
{t('device_overview')}
</h2>
<p className="text-muted-foreground">{t('show_details')}</p>
</div>

{/* sensebox table */}
<Card className="mt-10">
<Card className="mt-5">
<CardHeader>
<CardTitle>{t('device')}</CardTitle>
</CardHeader>
<CardContent className="pt-0">
<CardContent>
<Table>
<TableBody className="border-[1px]">
<TableRow>
<TableCell className="w-[50%] border-r-[1px]">
Name
<TableCell className="border-r-[1px]">
{t('name_label')}
</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
{deviceData?.name}
<TableCell className="border-r-[1px] font-semibold">
<div className="flex items-center">
<div className="flex-grow">{deviceData?.name}</div>
<div>
{copiedToClipboard === 'name' ? (
<CopyCheck />
) : (
<ClipboardCopy
onClick={() =>
copyToClipboard('name', deviceData?.name)
}
/>
)}
</div>
</div>
</TableCell>
</TableRow>

<TableRow>
<TableCell className="w-[50%] border-r-[1px]">
Model
<TableCell className="border-r-[1px]">
{t('model_label')}
</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
<TableCell className="border-r-[1px] font-semibold">
{deviceData?.model}
</TableCell>
</TableRow>

<TableRow>
<TableCell className="w-[50%] border-r-[1px]">
Tag
<TableCell className="border-r-[1px]">
{t('tags_label')}
</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
<TableCell className="border-r-[1px] font-semibold">
{deviceData?.tags}
</TableCell>
</TableRow>

<TableRow>
<TableCell className="w-[50%] border-r-[1px]">{t('exposure')}</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
<TableCell className="border-r-[1px]">
{t('exposure')}
</TableCell>
<TableCell className="border-r-[1px] font-semibold">
{deviceData?.exposure}
</TableCell>
</TableRow>

<TableRow>
<TableCell className="w-[50%] border-r-[1px]">ID</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
{deviceData?.id}
<TableCell className="border-r-[1px]">ID</TableCell>
<TableCell className="border-r-[1px] font-semibold">
<div className="flex items-center">
<div className="flex-grow">{deviceData?.id}</div>
<div>
{copiedToClipboard === 'id' ? (
<CopyCheck />
) : (
<ClipboardCopy
onClick={() =>
copyToClipboard('id', deviceData?.id)
}
/>
)}
</div>
</div>
</TableCell>
</TableRow>

{userId === deviceData?.userId && (
<TableRow>
<TableCell className="w-[50%] border-r-[1px]">
API Key
</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
{deviceData?.apiKey}
</TableCell>
</TableRow>
<TableRow>
<TableCell className="border-r-[1px]">
{t('api_key_label')}
</TableCell>
<TableCell className="border-r-[1px] font-semibold">
<div className="flex items-center">
<div className="flex-grow">{deviceData?.apiKey}</div>
<div>
{copiedToClipboard === 'apiKey' ? (
<CopyCheck />
) : (
<ClipboardCopy
onClick={() =>
copyToClipboard('apiKey', deviceData?.apiKey)
}
/>
)}
</div>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>

{/* sensers table */}
<Card className="mt-10">
<Card className="mt-5">
<CardHeader>
<CardTitle>{t('sensors')}</CardTitle>
</CardHeader>
Expand All @@ -136,20 +186,36 @@ export default function DeviceOverview() {
<TableBody className="border-[1px]">
{sensorsData.map((sensor) => (
<TableRow key={sensor.id}>
<TableCell className="w-[50%] border-r-[1px]">
<TableCell className="border-r-[1px]">
{sensor?.title}
</TableCell>
<TableCell className="w-[50%] border-r-[1px] font-semibold">
{sensor?.id}
<TableCell className="border-r-[1px] font-semibold">
<div className="flex items-center">
<div className="flex-grow">{sensor?.id}</div>
<div>
{copiedToClipboard ===
`${sensor?.title}_${sensor?.id}` ? (
<CopyCheck />
) : (
<ClipboardCopy
onClick={() =>
copyToClipboard(
`${sensor?.title}_${sensor?.id}`,
sensor?.id,
)
}
/>
)}
</div>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</main>
</div>
</main>
</div>
)
}
Expand Down
18 changes: 11 additions & 7 deletions public/locales/de/device-overview.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"back_to_dashboard": "Zurück zum Dashboard",
"device_overview": "Übersicht des Geräts",
"show_details": "Gerätedetails und Sensoren",
"device": "Gerät",
"exposure": "Standort",
"sensors": "Sensoren"
}
"back_to_dashboard": "Zurück zum Dashboard",
"device_overview": "Übersicht des Geräts",
"show_details": "Gerätedetails und Sensoren",
"device": "Gerät",
"exposure": "Standort",
"sensors": "Sensoren",
"name_label": "Name",
"model_label": "Modell",
"tags_label": "Tags",
"api_key_label": "API Key"
}
18 changes: 11 additions & 7 deletions public/locales/en/device-overview.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"back_to_dashboard": "Back to Dashboard",
"device_overview": "Device overview",
"show_details": "Device details and sensors.",
"device": "Device",
"exposure": "Exposure",
"sensors": "Sensors"
}
"back_to_dashboard": "Back to Dashboard",
"device_overview": "Device overview",
"show_details": "Device details and sensors.",
"device": "Device",
"exposure": "Exposure",
"sensors": "Sensors",
"name_label": "Name",
"model_label": "Model",
"tags_label": "Tags",
"api_key_label": "API Key"
}
Loading