sveltekit-auth-example 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.eslintrc.cjs +19 -7
  2. package/.prettierignore +1 -0
  3. package/.yarn/install-state.gz +0 -0
  4. package/CHANGELOG.md +162 -100
  5. package/README.md +6 -1
  6. package/package.json +67 -67
  7. package/prettier.config.mjs +7 -6
  8. package/src/app.d.ts +31 -29
  9. package/src/app.html +8 -3
  10. package/src/hooks.server.ts +15 -15
  11. package/src/lib/focus.ts +6 -6
  12. package/src/lib/google.ts +48 -49
  13. package/src/lib/server/db.ts +7 -6
  14. package/src/lib/server/sendgrid.ts +11 -11
  15. package/src/routes/+error.svelte +1 -1
  16. package/src/routes/+layout.server.ts +5 -5
  17. package/src/routes/+layout.svelte +133 -100
  18. package/src/routes/admin/+page.server.ts +1 -1
  19. package/src/routes/admin/+page.svelte +2 -2
  20. package/src/routes/api/v1/user/+server.ts +13 -14
  21. package/src/routes/auth/[slug]/+server.ts +11 -6
  22. package/src/routes/auth/forgot/+server.ts +23 -23
  23. package/src/routes/auth/google/+server.ts +44 -45
  24. package/src/routes/auth/reset/+server.ts +1 -1
  25. package/src/routes/auth/reset/[token]/+page.svelte +117 -95
  26. package/src/routes/auth/reset/[token]/+page.ts +4 -4
  27. package/src/routes/forgot/+page.svelte +74 -63
  28. package/src/routes/info/+page.svelte +1 -1
  29. package/src/routes/login/+page.svelte +140 -120
  30. package/src/routes/profile/+page.server.ts +9 -9
  31. package/src/routes/profile/+page.svelte +142 -88
  32. package/src/routes/register/+page.server.ts +3 -2
  33. package/src/routes/register/+page.svelte +159 -104
  34. package/src/routes/teachers/+page.server.ts +5 -5
  35. package/src/routes/teachers/+page.svelte +2 -2
  36. package/src/stores.ts +1 -1
  37. package/svelte.config.js +1 -1
package/src/app.d.ts CHANGED
@@ -5,50 +5,52 @@
5
5
  // and what to do when importing types
6
6
  declare namespace App {
7
7
  interface Locals {
8
- user: User
9
- }
8
+ user: User
9
+ }
10
10
 
11
11
  // interface Platform {}
12
12
 
13
- interface PrivateEnv { // $env/static/private
14
- DATABASE_URL: string
15
- DOMAIN: string
16
- JWT_SECRET: string
17
- SENDGRID_KEY: string
18
- SENDGRID_SENDER: string
19
- }
20
-
21
- interface PublicEnv { // $env/static/public
22
- PUBLIC_GOOGLE_CLIENT_ID: string
23
- }
13
+ interface PrivateEnv {
14
+ // $env/static/private
15
+ DATABASE_URL: string
16
+ DOMAIN: string
17
+ JWT_SECRET: string
18
+ SENDGRID_KEY: string
19
+ SENDGRID_SENDER: string
20
+ }
21
+
22
+ interface PublicEnv {
23
+ // $env/static/public
24
+ PUBLIC_GOOGLE_CLIENT_ID: string
25
+ }
24
26
  }
25
27
 
26
28
  interface AuthenticationResult {
27
- statusCode: NumericRange<400, 599>
28
- status: string
29
- user: User
30
- sessionId: string
29
+ statusCode: NumericRange<400, 599>
30
+ status: string
31
+ user: User
32
+ sessionId: string
31
33
  }
32
34
 
33
35
  interface Credentials {
34
- email: string
35
- password: string
36
+ email: string
37
+ password: string
36
38
  }
37
39
 
38
40
  interface UserProperties {
39
- id: number
40
- expires?: string // ISO-8601 datetime
41
- role: 'student' | 'teacher' | 'admin'
42
- password?: string
43
- firstName?: string
44
- lastName?: string
45
- email?: string
46
- phone?: string
41
+ id: number
42
+ expires?: string // ISO-8601 datetime
43
+ role: 'student' | 'teacher' | 'admin'
44
+ password?: string
45
+ firstName?: string
46
+ lastName?: string
47
+ email?: string
48
+ phone?: string
47
49
  }
48
50
 
49
51
  type User = UserProperties | undefined | null
50
52
 
51
53
  interface UserSession {
52
- id: string,
53
- user: User
54
+ id: string
55
+ user: User
54
56
  }
package/src/app.html CHANGED
@@ -1,13 +1,18 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <link rel="icon" href="%sveltekit.assets%/favicon.png" sizes="any" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <script nonce="%sveltekit.nonce%" src="https://accounts.google.com/gsi/client" async defer></script>
7
+ <script
8
+ nonce="%sveltekit.nonce%"
9
+ src="https://accounts.google.com/gsi/client"
10
+ async
11
+ defer
12
+ ></script>
8
13
  %sveltekit.head%
9
14
  </head>
10
15
  <body>
11
16
  <div id="svelte">%sveltekit.body%</div>
12
17
  </body>
13
- </html>
18
+ </html>
@@ -3,28 +3,28 @@ import { query } from '$lib/server/db'
3
3
 
4
4
  // Attach authorization to each server request (role may have changed)
5
5
  async function attachUserToRequestEvent(sessionId: string, event: RequestEvent) {
6
- const sql = `SELECT * FROM get_session($1);`
7
- const { rows } = await query(sql, [sessionId])
8
- if (rows?.length > 0) {
9
- event.locals.user = <User> rows[0].get_session
10
- }
6
+ const sql = `SELECT * FROM get_session($1);`
7
+ const { rows } = await query(sql, [sessionId])
8
+ if (rows?.length > 0) {
9
+ event.locals.user = <User>rows[0].get_session
10
+ }
11
11
  }
12
12
 
13
13
  // Invoked for each endpoint called and initially for SSR router
14
14
  export const handle: Handle = async ({ event, resolve }) => {
15
- const { cookies } = event
16
- const sessionId = cookies.get('session')
15
+ const { cookies } = event
16
+ const sessionId = cookies.get('session')
17
17
 
18
- // before endpoint or page is called
19
- if (sessionId) {
20
- await attachUserToRequestEvent(sessionId, event)
21
- }
18
+ // before endpoint or page is called
19
+ if (sessionId) {
20
+ await attachUserToRequestEvent(sessionId, event)
21
+ }
22
22
 
23
- if (!event.locals.user) cookies.delete('session', { path: '/' })
23
+ if (!event.locals.user) cookies.delete('session', { path: '/' })
24
24
 
25
- const response = await resolve(event)
25
+ const response = await resolve(event)
26
26
 
27
- // after endpoint or page is called
27
+ // after endpoint or page is called
28
28
 
29
- return response
29
+ return response
30
30
  }
package/src/lib/focus.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export const focusOnFirstError = (form: HTMLFormElement) => {
2
- for(const field of form.elements) {
3
- if (field instanceof HTMLInputElement && !field.checkValidity()) {
4
- field.focus()
5
- break
6
- }
7
- }
2
+ for (const field of form.elements) {
3
+ if (field instanceof HTMLInputElement && !field.checkValidity()) {
4
+ field.focus()
5
+ break
6
+ }
7
+ }
8
8
  }
package/src/lib/google.ts CHANGED
@@ -4,58 +4,57 @@ import { PUBLIC_GOOGLE_CLIENT_ID } from '$env/static/public'
4
4
  import { googleInitialized, loginSession } from '../stores'
5
5
 
6
6
  export function renderGoogleButton() {
7
- const btn = document.getElementById('googleButton')
8
- if (btn) {
9
- google.accounts.id.renderButton(btn, {
10
- type: 'standard',
11
- theme: 'filled_blue',
12
- size: 'large',
13
- width: 367
14
- })
15
- }
7
+ const btn = document.getElementById('googleButton')
8
+ if (btn) {
9
+ google.accounts.id.renderButton(btn, {
10
+ type: 'standard',
11
+ theme: 'filled_blue',
12
+ size: 'large',
13
+ width: 367
14
+ })
15
+ }
16
16
  }
17
17
 
18
18
  export function initializeGoogleAccounts() {
19
- let initialized = false
20
- const unsubscribe = googleInitialized.subscribe(value => {
21
- initialized = value;
19
+ let initialized = false
20
+ const unsubscribe = googleInitialized.subscribe((value) => {
21
+ initialized = value
22
22
  })
23
23
 
24
- if (!initialized) {
25
- google.accounts.id.initialize({
26
- client_id: PUBLIC_GOOGLE_CLIENT_ID,
27
- callback: googleCallback
28
- })
29
-
30
- googleInitialized.set(true)
31
- }
32
- unsubscribe()
33
-
34
- async function googleCallback(response: google.accounts.id.CredentialResponse) {
35
- const res = await fetch('/auth/google', {
36
- method: 'POST',
37
- headers: {
38
- 'Content-Type': 'application/json'
39
- },
40
- body: JSON.stringify({ token: response.credential })
41
- })
42
-
43
- if (res.ok) {
44
- const fromEndpoint = await res.json()
45
- loginSession.set(fromEndpoint.user) // update loginSession store
46
- const { role } = fromEndpoint.user
47
-
48
- let referrer
49
- const unsubscribe = page.subscribe(p => {
50
- referrer = p.url.searchParams.get('referrer');
51
- })
52
- unsubscribe()
53
-
54
- if (referrer) return goto(referrer)
55
- if (role === 'teacher') return goto('/teachers')
56
- if (role === 'admin') return goto('/admin')
57
- if (location.pathname === '/login') goto('/') // logged in so go home
58
- }
59
- }
24
+ if (!initialized) {
25
+ google.accounts.id.initialize({
26
+ client_id: PUBLIC_GOOGLE_CLIENT_ID,
27
+ callback: googleCallback
28
+ })
29
+
30
+ googleInitialized.set(true)
31
+ }
32
+ unsubscribe()
33
+
34
+ async function googleCallback(response: google.accounts.id.CredentialResponse) {
35
+ const res = await fetch('/auth/google', {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json'
39
+ },
40
+ body: JSON.stringify({ token: response.credential })
41
+ })
42
+
43
+ if (res.ok) {
44
+ const fromEndpoint = await res.json()
45
+ loginSession.set(fromEndpoint.user) // update loginSession store
46
+ const { role } = fromEndpoint.user
47
+
48
+ let referrer
49
+ const unsubscribe = page.subscribe((p) => {
50
+ referrer = p.url.searchParams.get('referrer')
51
+ })
52
+ unsubscribe()
53
+
54
+ if (referrer) return goto(referrer)
55
+ if (role === 'teacher') return goto('/teachers')
56
+ if (role === 'admin') return goto('/admin')
57
+ if (location.pathname === '/login') goto('/') // logged in so go home
58
+ }
59
+ }
60
60
  }
61
-
@@ -1,14 +1,15 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { QueryResult} from 'pg'
2
+ import type { QueryResult } from 'pg'
3
3
  import pg from 'pg'
4
4
  import { DATABASE_URL } from '$env/static/private'
5
5
 
6
6
  const pool = new pg.Pool({
7
- max: 10, // default
8
- connectionString: DATABASE_URL,
9
- ssl: { // If your postgresql.conf does not have `ssl = on`, remove the entire ssl property or you will get an error
10
- rejectUnauthorized: false
11
- }
7
+ max: 10, // default
8
+ connectionString: DATABASE_URL,
9
+ ssl: {
10
+ // If your postgresql.conf does not have `ssl = on`, remove the entire ssl property or you will get an error
11
+ rejectUnauthorized: false
12
+ }
12
13
  })
13
14
 
14
15
  type PostgresQueryResult = (sql: string, params?: any[]) => Promise<QueryResult<any>>
@@ -3,15 +3,15 @@ import sgMail from '@sendgrid/mail'
3
3
  import { env } from '$env/dynamic/private'
4
4
 
5
5
  export const sendMessage = async (message: Partial<MailDataRequired>) => {
6
- const { SENDGRID_SENDER, SENDGRID_KEY } = env
7
- try {
8
- sgMail.setApiKey(SENDGRID_KEY)
9
- const completeMessage = <MailDataRequired> {
10
- from: SENDGRID_SENDER, // default sender can be altered
11
- ...message
12
- }
13
- await sgMail.send(completeMessage)
14
- } catch (errSendingMail) {
15
- console.error(errSendingMail)
16
- }
6
+ const { SENDGRID_SENDER, SENDGRID_KEY } = env
7
+ try {
8
+ sgMail.setApiKey(SENDGRID_KEY)
9
+ const completeMessage = <MailDataRequired>{
10
+ from: SENDGRID_SENDER, // default sender can be altered
11
+ ...message
12
+ }
13
+ await sgMail.send(completeMessage)
14
+ } catch (errSendingMail) {
15
+ console.error(errSendingMail)
16
+ }
17
17
  }
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" context="module">
2
- import { page } from '$app/stores'
2
+ import { page } from '$app/stores'
3
3
  </script>
4
4
 
5
5
  <h1>{$page.status}</h1>
@@ -1,8 +1,8 @@
1
1
  import type { LayoutServerLoad } from './$types'
2
2
 
3
3
  export const load: LayoutServerLoad = ({ locals }) => {
4
- const { user } = locals // locals.user set by hooks.server.ts/handle(), undefined if not logged in
5
- return {
6
- user
7
- }
8
- }
4
+ const { user } = locals // locals.user set by hooks.server.ts/handle(), undefined if not logged in
5
+ return {
6
+ user
7
+ }
8
+ }
@@ -1,21 +1,21 @@
1
1
  <script lang="ts">
2
- import { onMount } from 'svelte'
3
- import type { LayoutServerData } from './$types'
4
- import { goto, beforeNavigate } from '$app/navigation'
5
- import { loginSession, toast } from '../stores'
6
- import { initializeGoogleAccounts } from '$lib/google'
2
+ import { onMount } from 'svelte'
3
+ import type { LayoutServerData } from './$types'
4
+ import { goto, beforeNavigate } from '$app/navigation'
5
+ import { loginSession, toast } from '../stores'
6
+ import { initializeGoogleAccounts } from '$lib/google'
7
7
 
8
- import 'bootstrap/scss/bootstrap.scss' // preferred way to load Bootstrap SCSS for hot module reloading
8
+ import 'bootstrap/scss/bootstrap.scss' // preferred way to load Bootstrap SCSS for hot module reloading
9
9
 
10
10
  export let data: LayoutServerData
11
11
 
12
- // If returning from different website, runs once (as it's an SPA) to restore user session if session cookie is still valid
13
- const { user } = data
14
- $loginSession = user
12
+ // If returning from different website, runs once (as it's an SPA) to restore user session if session cookie is still valid
13
+ const { user } = data
14
+ $loginSession = user
15
15
 
16
- let Toast: any
16
+ let Toast: any
17
17
 
18
- beforeNavigate( () => {
18
+ beforeNavigate(() => {
19
19
  let expirationDate = $loginSession?.expires ? new Date($loginSession.expires) : undefined
20
20
 
21
21
  if (expirationDate && expirationDate < new Date()) {
@@ -24,17 +24,17 @@
24
24
  }
25
25
  })
26
26
 
27
- onMount(async () => {
28
- initializeGoogleAccounts()
27
+ onMount(async () => {
28
+ initializeGoogleAccounts()
29
29
 
30
- await import('bootstrap/js/dist/collapse') // lots of ways to load Bootstrap but prefer this approach to avoid SSR issues
31
- await import('bootstrap/js/dist/dropdown')
32
- Toast = (await import('bootstrap/js/dist/toast')).default
30
+ await import('bootstrap/js/dist/collapse') // lots of ways to load Bootstrap but prefer this approach to avoid SSR issues
31
+ await import('bootstrap/js/dist/dropdown')
32
+ Toast = (await import('bootstrap/js/dist/toast')).default
33
33
 
34
- if (!$loginSession) google.accounts.id.prompt()
34
+ if (!$loginSession) google.accounts.id.prompt()
35
35
  })
36
36
 
37
- async function logout() {
37
+ async function logout() {
38
38
  // Request server delete httpOnly cookie called loginSession
39
39
  const url = '/auth/logout'
40
40
  const res = await fetch(url, {
@@ -46,95 +46,128 @@
46
46
  } else console.error(`Logout not successful: ${res.statusText} (${res.status})`)
47
47
  }
48
48
 
49
- const openToast = (open: boolean) => {
50
- if (open) {
51
- const toastDiv = <HTMLDivElement> document.getElementById('authToast')
52
- const t = new Toast(toastDiv)
53
- t.show()
54
- }
55
- }
49
+ const openToast = (open: boolean) => {
50
+ if (open) {
51
+ const toastDiv = <HTMLDivElement>document.getElementById('authToast')
52
+ const t = new Toast(toastDiv)
53
+ t.show()
54
+ }
55
+ }
56
56
 
57
- $: openToast($toast.isOpen)
57
+ $: openToast($toast.isOpen)
58
58
  </script>
59
59
 
60
60
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
61
- <div class="container">
62
- <a class="navbar-brand" href="/">SvelteKit-Auth-Example</a>
63
- <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMain" aria-controls="navbarMain" aria-expanded="false" aria-label="Toggle navigation">
64
- <span class="navbar-toggler-icon"></span>
65
- </button>
66
- <div class="collapse navbar-collapse" id="navbarMain">
67
-
68
- <ul class="navbar-nav me-5">
69
- <li class="nav-item"><a class="nav-link active" aria-current="page" href="/">Home</a></li>
70
- <li class="nav-item"><a class="nav-link" href="/info">Info</a></li>
71
-
72
- {#if $loginSession}
73
- {#if $loginSession.role == 'admin'}
74
- <li class="nav-item"><a class="nav-link" href="/admin">Admin</a></li>
75
- {/if}
76
- {#if $loginSession.role != 'student'}
77
- <li class="nav-item"><a class="nav-link" href="/teachers">Teachers</a></li>
78
- {/if}
79
- {/if}
80
- </ul>
81
- <ul class="navbar-nav">
82
- {#if $loginSession}
83
- <li class="nav-item dropdown">
84
- <a class="nav-link dropdown-toggle" href={'#'} role="button" data-bs-toggle="dropdown" aria-expanded="false">
85
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="avatar" viewBox="0 0 16 16">
86
- <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
87
- <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
88
- </svg>
89
- {$loginSession.firstName}
90
- </a>
91
- <ul class="dropdown-menu">
92
- <li>
93
- <a class="dropdown-item" href="/profile">Profile</a>
94
- </li>
95
- <li>
96
- <a on:click|preventDefault={logout} class="dropdown-item" class:d-none={!$loginSession || $loginSession.id === 0} href={'#'}>Logout</a>
97
- </li>
98
- </ul>
99
- </li>
100
- {:else}
101
- <li class="nav-item">
102
- <a class="nav-link" href="/login">Login</a>
103
- </li>
104
- {/if}
105
- </ul>
106
- </div>
107
- </div>
61
+ <div class="container">
62
+ <a class="navbar-brand" href="/">SvelteKit-Auth-Example</a>
63
+ <button
64
+ class="navbar-toggler"
65
+ type="button"
66
+ data-bs-toggle="collapse"
67
+ data-bs-target="#navbarMain"
68
+ aria-controls="navbarMain"
69
+ aria-expanded="false"
70
+ aria-label="Toggle navigation"
71
+ >
72
+ <span class="navbar-toggler-icon"></span>
73
+ </button>
74
+ <div class="collapse navbar-collapse" id="navbarMain">
75
+ <ul class="navbar-nav me-5">
76
+ <li class="nav-item"><a class="nav-link active" aria-current="page" href="/">Home</a></li>
77
+ <li class="nav-item"><a class="nav-link" href="/info">Info</a></li>
78
+
79
+ {#if $loginSession}
80
+ {#if $loginSession.role == 'admin'}
81
+ <li class="nav-item"><a class="nav-link" href="/admin">Admin</a></li>
82
+ {/if}
83
+ {#if $loginSession.role != 'student'}
84
+ <li class="nav-item"><a class="nav-link" href="/teachers">Teachers</a></li>
85
+ {/if}
86
+ {/if}
87
+ </ul>
88
+ <ul class="navbar-nav">
89
+ {#if $loginSession}
90
+ <li class="nav-item dropdown">
91
+ <a
92
+ class="nav-link dropdown-toggle"
93
+ href={'#'}
94
+ role="button"
95
+ data-bs-toggle="dropdown"
96
+ aria-expanded="false"
97
+ >
98
+ <svg
99
+ xmlns="http://www.w3.org/2000/svg"
100
+ width="16"
101
+ height="16"
102
+ fill="currentColor"
103
+ class="avatar"
104
+ viewBox="0 0 16 16"
105
+ >
106
+ <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
107
+ <path
108
+ fill-rule="evenodd"
109
+ d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"
110
+ />
111
+ </svg>
112
+ {$loginSession.firstName}
113
+ </a>
114
+ <ul class="dropdown-menu">
115
+ <li>
116
+ <a class="dropdown-item" href="/profile">Profile</a>
117
+ </li>
118
+ <li>
119
+ <a
120
+ on:click|preventDefault={logout}
121
+ class="dropdown-item"
122
+ class:d-none={!$loginSession || $loginSession.id === 0}
123
+ href={'#'}>Logout</a
124
+ >
125
+ </li>
126
+ </ul>
127
+ </li>
128
+ {:else}
129
+ <li class="nav-item">
130
+ <a class="nav-link" href="/login">Login</a>
131
+ </li>
132
+ {/if}
133
+ </ul>
134
+ </div>
135
+ </div>
108
136
  </nav>
109
137
 
110
138
  <main class="container">
111
- <slot/>
112
-
113
- <div id="authToast" class="toast position-fixed top-0 end-0 m-3" role="alert" aria-live="assertive" aria-atomic="true">
114
- <div class="toast-header bg-primary text-white">
115
- <strong class="me-auto">{$toast.title}</strong>
116
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
117
- </div>
118
- <div class="toast-body">
119
- {$toast.body}
120
- </div>
121
- </div>
122
-
139
+ <slot />
140
+
141
+ <div
142
+ id="authToast"
143
+ class="toast position-fixed top-0 end-0 m-3"
144
+ role="alert"
145
+ aria-live="assertive"
146
+ aria-atomic="true"
147
+ >
148
+ <div class="toast-header bg-primary text-white">
149
+ <strong class="me-auto">{$toast.title}</strong>
150
+ <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
151
+ </div>
152
+ <div class="toast-body">
153
+ {$toast.body}
154
+ </div>
155
+ </div>
123
156
  </main>
124
157
 
125
158
  <style lang="scss" global>
126
- // Make Retina displays crisper
127
- * {
128
- -webkit-font-smoothing: antialiased;
129
- -moz-osx-font-smoothing: grayscale;
130
- }
131
-
132
- .toast {
133
- z-index: 9999;
134
- }
135
-
136
- .avatar {
137
- position: relative;
138
- top: -1.5px;
139
- }
140
- </style>
159
+ // Make Retina displays crisper
160
+ * {
161
+ -webkit-font-smoothing: antialiased;
162
+ -moz-osx-font-smoothing: grayscale;
163
+ }
164
+
165
+ .toast {
166
+ z-index: 9999;
167
+ }
168
+
169
+ .avatar {
170
+ position: relative;
171
+ top: -1.5px;
172
+ }
173
+ </style>
@@ -5,7 +5,7 @@ export const load: PageServerLoad = async ({ locals }) => {
5
5
  const { user } = locals
6
6
  const authorized = ['admin']
7
7
  if (!user || !authorized.includes(user.role)) {
8
- redirect(302, '/login?referrer=/admin');
8
+ redirect(302, '/login?referrer=/admin')
9
9
  }
10
10
 
11
11
  return { message: 'Admin-only content from server.' }
@@ -4,9 +4,9 @@
4
4
  </script>
5
5
 
6
6
  <svelte:head>
7
- <title>Administration</title>
7
+ <title>Administration</title>
8
8
  </svelte:head>
9
9
 
10
10
  <h1>Admin</h1>
11
11
  <h4>Admin Role</h4>
12
- <p>{data.message}</p>
12
+ <p>{data.message}</p>