Skip to content

Commit 6210eb2

Browse files
committed
feat: add UI to stop device sharing
Closes #351
1 parent 1fad340 commit 6210eb2

File tree

5 files changed

+146
-11
lines changed

5 files changed

+146
-11
lines changed

src/base.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,11 @@ input:-webkit-autofill:active {
319319
border-radius: calc(var(--border-radius) / 2);
320320
border-color: var(--color-form-border-default);
321321
background-color: var(--color-form-bg);
322-
height: 32px;
322+
min-height: 32px;
323323
padding: 0 var(--gap-medium);
324324
color: var(--color-form-text);
325325
font-weight: 300;
326326
display: inline-block;
327-
height: 32px;
328327
font-family: var(--condensed-font-family);
329328
}
330329

src/dashboard/Card.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ import type { ParentProps } from 'solid-js'
22

33
import './Card.css'
44

5-
export const Card = (props: ParentProps) => (
6-
<section class="card boxed bg-lighter">{props.children}</section>
5+
export const Card = (props: ParentProps<{ class?: string }>) => (
6+
<section class={`card boxed bg-lighter ${props.class ?? ''}`}>
7+
{props.children}
8+
</section>
79
)
8-
export const CardHeader = (props: ParentProps) => (
9-
<header class="pad bg-light">{props.children}</header>
10+
export const CardHeader = (props: ParentProps<{ class?: string }>) => (
11+
<header class={`pad bg-light ${props.class ?? ''}`}>{props.children}</header>
1012
)
11-
export const CardBody = (props: ParentProps) => (
12-
<div class="pad">{props.children}</div>
13+
export const CardBody = (props: ParentProps<{ class?: string }>) => (
14+
<div class={`pad ${props.class ?? ''}`}>{props.children}</div>
1315
)
1416

15-
export const CardFooter = (props: ParentProps) => (
16-
<footer class="pad bg-light">{props.children}</footer>
17+
export const CardFooter = (props: ParentProps<{ class?: string }>) => (
18+
<footer class={`pad bg-light ${props.class ?? ''}`}>{props.children}</footer>
1719
)

src/dashboard/ShowDevice.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.stop-sharing {
2+
display: flex;
3+
flex-direction: column;
4+
}
5+
6+
.stop-sharing .confirm {
7+
display: flex;
8+
align-items: center;
9+
justify-content: space-between;
10+
}

src/dashboard/ShowDevice.tsx

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useUser } from '#context/User.tsx'
66
import { useViteEnv } from '#context/ViteEnv.tsx'
77
import { extendDeviceSharing } from '#resources/extendDeviceSharing.ts'
88
import { listUserDevices } from '#resources/listUserDevices.ts'
9+
import { stopDeviceSharing } from '#resources/stopDeviceSharing.ts'
910
import { type ModelID, models } from '@hello.nrfcloud.com/proto-map/models'
1011
import {
1112
createEffect,
@@ -15,7 +16,7 @@ import {
1516
Show,
1617
Switch,
1718
} from 'solid-js'
18-
import { Card, CardBody, CardHeader } from './Card.tsx'
19+
import { Card, CardBody, CardFooter, CardHeader } from './Card.tsx'
1920
import { CopyableProp } from './CopyableProp.tsx'
2021

2122
const f = new Intl.DateTimeFormat(undefined, {
@@ -25,6 +26,10 @@ const f = new Intl.DateTimeFormat(undefined, {
2526

2627
const formatAsDate = (d: Date) => f.format(d)
2728

29+
import { Success } from '#component/notifications/Success.tsx'
30+
import { Checked, Unchecked } from '#icons/LucideIcon.tsx'
31+
import './ShowDevice.css'
32+
2833
export const ShowDevice = () => {
2934
const { protoVersion } = useViteEnv()
3035
const deviceId = new URLSearchParams(
@@ -98,7 +103,26 @@ export const ShowDevice = () => {
98103
>
99104
<Problem class="gap-t" problem={devicesRequest.error} />
100105
</Show>
106+
<Show
107+
when={
108+
!devicesRequest.loading &&
109+
devicesRequest.error === undefined &&
110+
deviceInfo() === undefined
111+
}
112+
>
113+
<Problem
114+
problem={{
115+
status: 404,
116+
title: `Device ${deviceId} not found!`,
117+
}}
118+
/>
119+
</Show>
101120
</CardBody>
121+
<Show when={deviceInfo() !== undefined}>
122+
<CardFooter class="stop-sharing">
123+
<StopSharing id={deviceId!} />
124+
</CardFooter>
125+
</Show>
102126
</Card>
103127
)
104128
}
@@ -157,3 +181,84 @@ const ExtendSharing = (props: { id: string; expires: Date }) => {
157181
</>
158182
)
159183
}
184+
185+
const StopSharing = (props: { id: string }) => {
186+
const [stop, setStopped] = createSignal(false)
187+
const [unlocked, setUnlocked] = createSignal(false)
188+
const { jwt } = useUser()
189+
const { apiURL } = useParameters()
190+
const [stopRequest] = createResource(() => {
191+
if (!stop()) return undefined
192+
return {
193+
id: props.id,
194+
jwt: jwt()!,
195+
}
196+
}, stopDeviceSharing(apiURL))
197+
198+
createEffect(() => {
199+
if (stopRequest.loading) return
200+
if (stopRequest.state !== 'ready') return
201+
setStopped(true)
202+
})
203+
204+
return (
205+
<>
206+
<p>
207+
You can stop the publication of this device at any time. If you stop the
208+
publication, the device will no longer be visible on the map. You can
209+
start the publication again at any time. It may take a few minutes for
210+
the device to disappear from the map.
211+
</p>
212+
<p class="confirm">
213+
<button
214+
class="pad-e"
215+
type="button"
216+
onClick={() => setUnlocked((l) => !l)}
217+
>
218+
<Show
219+
when={unlocked()}
220+
fallback={<Unchecked strokeWidth={1} size={20} />}
221+
>
222+
<Checked strokeWidth={1} size={20} />
223+
</Show>
224+
Yes, I want to stop the publication of this device.
225+
</button>
226+
<Show
227+
when={unlocked()}
228+
fallback={
229+
<Show
230+
when={!stopRequest.loading}
231+
fallback={
232+
<button type="button" class="btn" disabled>
233+
stopping ...
234+
</button>
235+
}
236+
>
237+
<button type="button" class="btn" disabled>
238+
stop publication
239+
</button>
240+
</Show>
241+
}
242+
>
243+
<button
244+
type="button"
245+
class="btn"
246+
onClick={() => {
247+
setStopped(true)
248+
}}
249+
>
250+
stop publication
251+
</button>
252+
</Show>
253+
</p>
254+
<Switch>
255+
<Match when={!stopRequest.loading && stopRequest.error !== undefined}>
256+
<Problem class="gap-t" problem={stopRequest.error} />
257+
</Match>
258+
<Match when={!stopRequest.loading && stopRequest.state === 'ready'}>
259+
<Success>The publication of this device has been stopped.</Success>
260+
</Match>
261+
</Switch>
262+
</>
263+
)
264+
}

src/resources/stopDeviceSharing.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ProblemDetailError } from '#component/notifications/Problem.tsx'
2+
import { typedFetch } from '@hello.nrfcloud.com/proto/hello'
3+
import { Type } from '@sinclair/typebox'
4+
5+
export const stopDeviceSharing =
6+
(apiURL: URL) =>
7+
async ({ id, jwt }: { id: string; jwt: string }): Promise<void> => {
8+
const res = await typedFetch({
9+
responseBodySchema: Type.Undefined(),
10+
})(new URL(`./user/device/${id}`, apiURL), undefined, {
11+
method: 'DELETE',
12+
headers: { Authorization: `Bearer ${jwt}` },
13+
})
14+
if ('error' in res) {
15+
console.error(res.error)
16+
throw new ProblemDetailError(res.error)
17+
}
18+
return res.result
19+
}

0 commit comments

Comments
 (0)