Skip to content

Commit 642ec87

Browse files
committed
next: move notification settings into a form.
1 parent 4ef8c26 commit 642ec87

File tree

2 files changed

+87
-35
lines changed

2 files changed

+87
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,105 @@
11
<script lang="ts">
2-
import type { NotificationSettings } from '$features/projects/models';
3-
2+
import ErrorMessage from '$comp/error-message.svelte';
43
import { Label } from '$comp/ui/label';
54
import { Skeleton } from '$comp/ui/skeleton';
65
import { Switch } from '$comp/ui/switch';
6+
import { NotificationSettings } from '$features/projects/models';
7+
import { applyServerSideErrors } from '$features/shared/validation';
8+
import { ProblemDetails } from '@exceptionless/fetchclient';
9+
import { toast } from 'svelte-sonner';
10+
import { defaults, superForm } from 'sveltekit-superforms';
11+
import { classvalidatorClient } from 'sveltekit-superforms/adapters';
12+
import { debounce } from 'throttle-debounce';
713
814
interface Props {
9-
changed: (settings: NotificationSettings) => Promise<void>;
15+
save: (settings: NotificationSettings) => Promise<void>;
1016
settings?: NotificationSettings;
1117
}
1218
13-
// TODO: Clone settings?
14-
// TODO: Add Skeletons
15-
// TODO: Use switch primitive in shared?
16-
let { changed, settings }: Props = $props();
19+
let { save, settings }: Props = $props();
20+
let toastId = $state<number | string>();
21+
22+
const form = superForm(defaults(settings || new NotificationSettings(), classvalidatorClient(NotificationSettings)), {
23+
dataType: 'json',
24+
async onUpdate({ form, result }) {
25+
if (!form.valid) {
26+
return;
27+
}
28+
29+
toast.dismiss(toastId);
30+
if (save) {
31+
try {
32+
await save(form.data);
33+
34+
// HACK: This is to prevent sveltekit from stealing focus
35+
result.type = 'failure';
36+
} catch (error: unknown) {
37+
if (error instanceof ProblemDetails) {
38+
applyServerSideErrors(form, error);
39+
result.status = error.status ?? 500;
40+
toastId = toast.error(form.message ?? 'Error saving notification settings. Please try again.');
41+
}
42+
}
43+
}
44+
},
45+
SPA: true,
46+
validators: classvalidatorClient(NotificationSettings)
47+
});
48+
49+
// TODO: Use the Switch primitive component?
50+
const { enhance, form: formData, message, submit, submitting, tainted } = form;
51+
const debouncedFormSubmit = debounce(500, () => submit());
52+
53+
$effect(() => {
54+
if (settings && !$submitting && !$tainted) {
55+
form.reset({ data: settings, keepMessage: true });
56+
}
57+
});
1758
</script>
1859

1960
{#if settings}
20-
<div class="flex items-center space-x-2">
21-
<Switch id="send_daily_summary" bind:checked={settings.send_daily_summary} onCheckedChange={async () => await changed(settings)} />
22-
<Label for="send_daily_summary">
23-
Send daily project summary <strong>(Coming soon!)</strong>
24-
</Label>
25-
</div>
61+
<form method="POST" use:enhance>
62+
<ErrorMessage message={$message} />
2663

27-
<div class="flex items-center space-x-2">
28-
<Switch id="report_new_errors" bind:checked={settings.report_new_errors} onCheckedChange={async () => await changed(settings)} />
29-
<Label for="report_new_errors">Notify me on new errors</Label>
30-
</div>
64+
<div class="flex items-center space-x-2">
65+
<Switch id="send_daily_summary" bind:checked={$formData.send_daily_summary} onCheckedChange={debouncedFormSubmit} />
66+
<Label for="send_daily_summary">
67+
Send daily project summary <strong>(Coming soon!)</strong>
68+
</Label>
69+
</div>
3170

32-
<div class="flex items-center space-x-2">
33-
<Switch id="report_critical_errors" bind:checked={settings.report_critical_errors} onCheckedChange={async () => await changed(settings)} />
34-
<Label for="report_critical_errors">Notify me on critical errors</Label>
35-
</div>
71+
<div class="flex items-center space-x-2">
72+
<Switch id="report_new_errors" bind:checked={$formData.report_new_errors} onCheckedChange={debouncedFormSubmit} />
73+
<Label for="report_new_errors">Notify me on new errors</Label>
74+
</div>
3675

37-
<div class="flex items-center space-x-2">
38-
<Switch id="report_event_regressions" bind:checked={settings.report_event_regressions} onCheckedChange={async () => await changed(settings)} />
39-
<Label for="report_event_regressions">Notify me on error regressions</Label>
40-
</div>
76+
<div class="flex items-center space-x-2">
77+
<Switch id="report_critical_errors" bind:checked={$formData.report_critical_errors} onCheckedChange={debouncedFormSubmit} />
78+
<Label for="report_critical_errors">Notify me on critical errors</Label>
79+
</div>
4180

42-
<div class="flex items-center space-x-2">
43-
<Switch id="report_new_events" bind:checked={settings.report_new_events} onCheckedChange={async () => await changed(settings)} />
44-
<Label for="report_new_events">Notify me on new events</Label>
45-
</div>
81+
<div class="flex items-center space-x-2">
82+
<Switch id="report_event_regressions" bind:checked={$formData.report_event_regressions} onCheckedChange={debouncedFormSubmit} />
83+
<Label for="report_event_regressions">Notify me on error regressions</Label>
84+
</div>
4685

47-
<div class="flex items-center space-x-2">
48-
<Switch id="report_critical_events" bind:checked={settings.report_critical_events} onCheckedChange={async () => await changed(settings)} />
49-
<Label for="report_critical_events">Notify me on critical events</Label>
50-
</div>
86+
<div class="flex items-center space-x-2">
87+
<Switch id="report_new_events" bind:checked={$formData.report_new_events} onCheckedChange={debouncedFormSubmit} />
88+
<Label for="report_new_events">Notify me on new events</Label>
89+
</div>
90+
91+
<div class="flex items-center space-x-2">
92+
<Switch id="report_critical_events" bind:checked={$formData.report_critical_events} onCheckedChange={debouncedFormSubmit} />
93+
<Label for="report_critical_events">Notify me on critical events</Label>
94+
</div>
95+
</form>
5196
{:else}
52-
<Skeleton />
97+
<div class="space-y-4">
98+
{#each { length: 6 } as name, index (`${name}-${index}`)}
99+
<div class="flex items-center space-x-2">
100+
<Skeleton class="size-5 rounded-sm" />
101+
<Skeleton class="size-5 w-64 rounded-md" />
102+
</div>
103+
{/each}
104+
</div>
53105
{/if}

src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/integrations/+page.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
team's Slack channels. Keep your team informed and respond faster to issues without constantly checking the dashboard.</P
189189
>
190190

191-
<NotificationSettingsForm settings={slackNotificationSettingsResponse.data} changed={updateSlackNotificationSettings} />
191+
<NotificationSettingsForm settings={slackNotificationSettingsResponse.data} save={updateSlackNotificationSettings} />
192192

193193
{#if hasSlackIntegration}
194194
<Button onclick={() => (showRemoveSlackDialog = true)}><img class="text- mr-2 size-4" alt="Slack" src={Slack} /> Remove Slack</Button>

0 commit comments

Comments
 (0)