Skip to content

Commit 0b6b8a0

Browse files
committed
Added verifiy email address route and hooked up appearance tab
1 parent 5e93e96 commit 0b6b8a0

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

src/Exceptionless.Web/ClientApp/src/routes/(app)/account/+layout.svelte

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
<script lang="ts">
22
import * as Card from '$comp/ui/card';
33
import { Separator } from '$comp/ui/separator';
4+
import { accessToken } from '$features/auth/index.svelte';
5+
import { getMeQuery } from '$features/users/api.svelte';
6+
7+
import type { NavigationItemContext } from '../../routes';
48
59
import SidebarNav from './(components)/sidebar-nav.svelte';
610
import { routes } from './routes';
711
812
let { children } = $props();
13+
14+
const userQuery = getMeQuery();
15+
let isAuthenticated = $derived(accessToken.value !== null);
16+
const filteredRoutes = $derived.by(() => {
17+
const context: NavigationItemContext = { authenticated: isAuthenticated, user: userQuery.data };
18+
return routes.filter((route) => (route.show ? route.show(context) : true));
19+
});
920
</script>
1021

1122
<Card.Root>
@@ -17,7 +28,7 @@
1728
<Card.Content>
1829
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
1930
<aside class="-mx-4 lg:w-1/5">
20-
<SidebarNav {routes} />
31+
<SidebarNav routes={filteredRoutes} />
2132
</aside>
2233
<div class="flex-1">
2334
{@render children()}

src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/+page.svelte

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
<script lang="ts">
22
import { H3, Muted } from '$comp/typography';
3-
import { Button } from '$comp/ui/button';
43
import { Label } from '$comp/ui/label';
54
import * as RadioGroup from '$comp/ui/radio-group';
65
import { Separator } from '$comp/ui/separator';
6+
import { setMode, userPrefersMode } from 'mode-watcher';
77
88
import ThemePreview from './(components)/ThemePreview.svelte';
99
10-
async function onSave() {}
10+
function onUserThemePreferenceChange(mode?: string) {
11+
setMode(mode as 'dark' | 'light' | 'system');
12+
}
1113
</script>
1214

1315
<div class="space-y-6">
@@ -17,8 +19,13 @@
1719
</div>
1820
<Separator />
1921

20-
<form onsubmit={onSave}>
21-
<RadioGroup.Root class="grid max-w-xl grid-cols-3 gap-8 pt-2" orientation="horizontal" value="light">
22+
<form>
23+
<RadioGroup.Root
24+
class="grid max-w-xl grid-cols-3 gap-8 pt-2"
25+
orientation="horizontal"
26+
onValueChange={onUserThemePreferenceChange}
27+
value={$userPrefersMode}
28+
>
2229
<Label class="[&:has([data-state=checked])>div]:border-primary" for="light">
2330
<RadioGroup.Item class="sr-only" id="light" value="light" />
2431
<ThemePreview mode="light" />
@@ -35,9 +42,5 @@
3542
<div class="pt-2 text-center">System</div>
3643
</Label>
3744
</RadioGroup.Root>
38-
39-
<div class="pt-2">
40-
<Button type="submit">Save</Button>
41-
</div>
4245
</form>
4346
</div>

src/Exceptionless.Web/ClientApp/src/routes/(app)/account/routes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import IconAccount from '~icons/mdi/account';
22
import IconSessions from '~icons/mdi/account-multiple';
33
import IconNotifications from '~icons/mdi/bell';
44
import IconPassword from '~icons/mdi/form-textbox-password';
5+
import IconVerify from '~icons/mdi/shield-check';
56
import IconAppearance from '~icons/mdi/theme-light-dark';
67

78
import type { NavigationItem } from '../../routes';
@@ -36,5 +37,12 @@ export const routes: NavigationItem[] = [
3637
href: '/next/account/sessions',
3738
icon: IconSessions,
3839
title: 'Sessions'
40+
},
41+
{
42+
group: 'My Account',
43+
href: '/next/account/verify',
44+
icon: IconVerify,
45+
show: () => false,
46+
title: 'Verify'
3947
}
4048
];
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script lang="ts">
2+
import { goto } from '$app/navigation';
3+
import { page } from '$app/stores';
4+
import { useFetchClient } from '@exceptionless/fetchclient';
5+
import { toast } from 'svelte-sonner';
6+
7+
const client = useFetchClient();
8+
const token = $page.url.searchParams.get('token');
9+
10+
async function verifyAccount() {
11+
if (token) {
12+
try {
13+
await client.post('/users/verify-email-address', undefined, {
14+
params: { token }
15+
});
16+
toast.success('Your account has been successfully verified.');
17+
} catch {
18+
toast.error('An error occurred while verifying your account.');
19+
}
20+
}
21+
22+
await goto('/next/account/manage');
23+
}
24+
25+
verifyAccount();
26+
</script>

src/Exceptionless.Web/Controllers/UserController.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ public async Task<ActionResult<UpdateEmailAddressResult>> UpdateEmailAddressAsyn
212212
/// Verify email address
213213
/// </summary>
214214
/// <param name="token">The token identifier.</param>
215-
/// <response code="400">Verify Email Address Token has expired.</response>
216215
/// <response code="404">The user could not be found.</response>
216+
/// <response code="422">Verify Email Address Token has expired.</response>
217217
[HttpGet("verify-email-address/{token:token}")]
218218
public async Task<IActionResult> VerifyAsync(string token)
219219
{
@@ -228,7 +228,10 @@ public async Task<IActionResult> VerifyAsync(string token)
228228
}
229229

230230
if (!user.HasValidVerifyEmailAddressTokenExpiration(_timeProvider))
231-
return BadRequest("Verify Email Address Token has expired.");
231+
{
232+
ModelState.AddModelError<User>(m => m.VerifyEmailAddressTokenExpiration, "Verify Email Address Token has expired.");
233+
return ValidationProblem(ModelState);
234+
}
232235

233236
user.MarkEmailAddressVerified();
234237
await _repository.SaveAsync(user, o => o.Cache());
@@ -240,6 +243,7 @@ public async Task<IActionResult> VerifyAsync(string token)
240243
/// Resend verification email
241244
/// </summary>
242245
/// <param name="id">The identifier of the user.</param>
246+
/// <response code="200">The user verification email has been sent.</response>
243247
/// <response code="404">The user could not be found.</response>
244248
[HttpGet("{id:objectid}/resend-verification-email")]
245249
public async Task<IActionResult> ResendVerificationEmailAsync(string id)

0 commit comments

Comments
 (0)