@plutocms/supabase 0.9.1 → 0.11.0

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 (41) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +19 -0
  3. package/FEATURES.md +9 -0
  4. package/README.md +10 -1
  5. package/app/components/EnvPersistWarning.vue +30 -13
  6. package/app/components/SupabaseMessageParts.vue +29 -0
  7. package/app/components/auth/AuthSignIn.vue +56 -0
  8. package/app/components/migrations/MigrationsBanner.vue +28 -15
  9. package/app/components/settings/SettingsGeneralPanel.vue +21 -6
  10. package/app/composables/auth.ts +38 -101
  11. package/app/pages/admin/(auth)/confirm.vue +20 -23
  12. package/app/pages/admin/(auth)/forgot-password.vue +60 -63
  13. package/app/pages/admin/(auth)/signup.vue +115 -110
  14. package/app/pages/admin/(auth)/update-password.vue +78 -82
  15. package/app/pages/admin/migrations.vue +87 -51
  16. package/app/pages/admin/setup.vue +88 -75
  17. package/app/plugins/pluto-extension.ts +143 -10
  18. package/app/utils/auth-driver.ts +110 -0
  19. package/app/utils/auth-error.ts +53 -0
  20. package/db/migrations/005_profile_locale.sql +44 -0
  21. package/i18n/locales/en.json +193 -0
  22. package/i18n/locales/pt-BR.json +193 -0
  23. package/package.json +3 -3
  24. package/server/api/profile/locale.get.ts +39 -0
  25. package/server/api/profile/locale.post.ts +71 -0
  26. package/server/api/setup/create.post.ts +6 -4
  27. package/server/api/users/[id].get.ts +4 -2
  28. package/server/api/users/index.get.ts +4 -2
  29. package/server/utils/admin-guard.ts +10 -2
  30. package/server/utils/capability-guard.ts +18 -4
  31. package/server/utils/content-adapter.ts +6 -4
  32. package/server/utils/pending-migrations.ts +0 -0
  33. package/server/utils/validate.ts +6 -1
  34. package/shared/types/supabase.ts +7 -0
  35. package/app/components/navbar/NavbarAdmin.vue +0 -213
  36. package/app/components/navbar/NavbarAdminProvider.vue +0 -18
  37. package/app/middleware/auth.ts +0 -34
  38. package/app/middleware/setup-check.ts +0 -44
  39. package/app/pages/admin/(auth)/login.vue +0 -93
  40. package/app/pages/admin.vue +0 -56
  41. package/app/plugins/pluto-permissions-sync.ts +0 -38
@@ -0,0 +1,2 @@
1
+ # Agent skill files keep their own formatting. Prettier must not rewrite them.
2
+ .agents/**/*.md
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0](https://github.com/plutocms/supabase/compare/v0.10.0...v0.11.0) (2026-10-20)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * **admin:** move the admin shell to the pluto auth driver ([#83](https://github.com/plutocms/supabase/issues/83))
9
+
10
+ ### Features
11
+
12
+ * **admin:** move the admin shell to the pluto auth driver ([#83](https://github.com/plutocms/supabase/issues/83)) ([52ba78f](https://github.com/plutocms/supabase/commit/52ba78f934255e70a7b5188982bdb83f9fa5f86a))
13
+
14
+ ## [0.10.0](https://github.com/plutocms/supabase/compare/v0.9.1...v0.10.0) (2026-10-18)
15
+
16
+
17
+ ### Features
18
+
19
+ * **i18n:** store each user's admin language ([#79](https://github.com/plutocms/supabase/issues/79)) ([2ca171a](https://github.com/plutocms/supabase/commit/2ca171a97acbc2de4537b82b6718acbcc2c1fbd5))
20
+ * **i18n:** translate the supabase admin pages ([#82](https://github.com/plutocms/supabase/issues/82)) ([bae1372](https://github.com/plutocms/supabase/commit/bae1372fdb89414fcb20ec74c428812f25077f6b))
21
+
3
22
  ## [0.9.1](https://github.com/plutocms/supabase/compare/v0.9.0...v0.9.1) (2026-10-18)
4
23
 
5
24
 
package/FEATURES.md CHANGED
@@ -13,3 +13,12 @@ policies shared by every project here.
13
13
  - Content adapter: a Supabase/PostgREST-backed `PlutoContentAdapter`, plugging any layer's
14
14
  declared content type into `@plutocms/pluto`'s generic content routes.
15
15
  @.claude/skills/content-adapter/SKILL.md
16
+ - Auth driver: plugs Supabase sign-in into the admin shell that pluto core owns (navbar, sign-in
17
+ page, route guard), and keeps the setup guard in the driver.
18
+ @.claude/skills/auth-driver/SKILL.md
19
+ - Profile language: stores each user's admin language in `profiles.locale`, written through a
20
+ security-definer function, and plugs it into pluto's i18n as a locale driver.
21
+ @.claude/skills/profile-language/SKILL.md
22
+ - Admin translation: every admin page, component, toast, and server message of this layer is
23
+ translated (`en`, `pt-BR`) in the `supabase.*` namespace of pluto's i18n system.
24
+ @.claude/skills/admin-translation/SKILL.md
package/README.md CHANGED
@@ -45,7 +45,16 @@ export default defineNuxtConfig({
45
45
 
46
46
  This layer provides the following composables:
47
47
 
48
- - `useAuth`: Manage user authentication with Supabase.
48
+ - `useAuth`: Sign in, request a password reset, and update the password, with Supabase.
49
+
50
+ ### Admin shell
51
+
52
+ Pluto core owns the admin shell: the `/admin` route, the sign-in page, the route guard and the
53
+ top navbar. This layer registers an auth driver, and core renders the shell from it. See
54
+ `.claude/skills/auth-driver/SKILL.md`.
55
+
56
+ An app that overrode `NavbarAdmin` or `pages/admin.vue` of an older version must move to registry
57
+ entries. See "Migrating an app" in that skill.
49
58
 
50
59
  ### API Routes
51
60
 
@@ -14,6 +14,7 @@ const props = defineProps<{
14
14
  }>()
15
15
 
16
16
  const toast = useToast()
17
+ const { t } = usePlutoI18n()
17
18
 
18
19
  const envLine = computed(() => `DATABASE_URL="${props.connectionString}"`)
19
20
 
@@ -22,15 +23,15 @@ async function copyEnvLine() {
22
23
  await navigator.clipboard.writeText(envLine.value)
23
24
 
24
25
  toast.add({
25
- title: 'Copied',
26
- description: 'The DATABASE_URL line is on your clipboard.',
26
+ title: t('supabase.envWarning.copied'),
27
+ description: t('supabase.envWarning.copiedDescription'),
27
28
  icon: 'lucide:check-circle',
28
29
  color: 'success',
29
30
  })
30
31
  } catch {
31
32
  toast.add({
32
- title: 'Could not copy',
33
- description: 'Select and copy the line by hand instead.',
33
+ title: t('supabase.envWarning.copyFailed'),
34
+ description: t('supabase.envWarning.copyFailedDescription'),
34
35
  icon: 'lucide:circle-x',
35
36
  color: 'error',
36
37
  })
@@ -40,24 +41,40 @@ async function copyEnvLine() {
40
41
 
41
42
  <template>
42
43
  <UAlert
44
+ :title="t('supabase.envWarning.title')"
43
45
  color="warning"
44
46
  variant="outline"
45
47
  icon="lucide:triangle-alert"
46
- title="Connection string not saved automatically"
47
48
  >
48
49
  <template #description>
49
50
  <div class="flex flex-col gap-y-3">
50
51
  <p>
51
- The database change already succeeded. Pluto could not write
52
- <code>DATABASE_URL</code> to your project's
53
- <code>.env</code> file, so it will ask again next time unless you
54
- add it by hand:
52
+ <SupabaseMessageParts :message="t('supabase.envWarning.intro')">
53
+ <template #variable>
54
+ <code>DATABASE_URL</code>
55
+ </template>
56
+ <template #file>
57
+ <code>.env</code>
58
+ </template>
59
+ </SupabaseMessageParts>
55
60
  </p>
56
61
 
57
62
  <ol class="list-inside list-decimal">
58
- <li>Open (or create) <code>.env</code> in your project's root folder.</li>
59
- <li>Add this line. Replace any existing <code>DATABASE_URL</code> line with it.</li>
60
- <li>Restart the server.</li>
63
+ <li>
64
+ <SupabaseMessageParts :message="t('supabase.envWarning.step1')">
65
+ <template #file>
66
+ <code>.env</code>
67
+ </template>
68
+ </SupabaseMessageParts>
69
+ </li>
70
+ <li>
71
+ <SupabaseMessageParts :message="t('supabase.envWarning.step2')">
72
+ <template #variable>
73
+ <code>DATABASE_URL</code>
74
+ </template>
75
+ </SupabaseMessageParts>
76
+ </li>
77
+ <li>{{ t('supabase.envWarning.step3') }}</li>
61
78
  </ol>
62
79
 
63
80
  <div class="flex items-center gap-x-2">
@@ -68,11 +85,11 @@ async function copyEnvLine() {
68
85
  </code>
69
86
 
70
87
  <UButton
88
+ :aria-label="t('supabase.envWarning.copyLine')"
71
89
  icon="lucide:copy"
72
90
  variant="soft"
73
91
  color="neutral"
74
92
  size="sm"
75
- aria-label="Copy the DATABASE_URL line"
76
93
  @click="copyEnvLine"
77
94
  />
78
95
  </div>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ // Renders one translated message that holds links or markup in the middle of
3
+ // a sentence. Each `{name}` in the message becomes the slot with that name.
4
+ // The word order stays in the translation, so each language can move the
5
+ // parts. Example: <SupabaseMessageParts :message="t('…')"><template #link>…
6
+ const props = defineProps<{
7
+ /** The translated message. Write each slot as `{name}`. */
8
+ message: string
9
+ }>()
10
+
11
+ const parts = computed(() =>
12
+ props.message
13
+ .split(/(\{\w+\})/)
14
+ .filter((part) => part !== '')
15
+ .map((part) => {
16
+ const slot = /^\{(\w+)\}$/.exec(part)?.[1]
17
+
18
+ return slot ? { slot } : { text: part }
19
+ })
20
+ )
21
+ </script>
22
+
23
+ <template>
24
+ <template v-for="(part, index) in parts" :key="index">
25
+ <slot v-if="part.slot" :name="part.slot" />
26
+
27
+ <template v-else>{{ part.text }}</template>
28
+ </template>
29
+ </template>
@@ -0,0 +1,56 @@
1
+ <script setup lang="ts">
2
+ // The email and password form of the supabase layer. It is the `signIn`
3
+ // component of the auth driver. Core renders it inside the login frame
4
+ // (PlutoAdminAuthCard), so this file has no frame. The file lives under
5
+ // `app/` so Tailwind scans it.
6
+ const props = defineProps<{
7
+ /** A safe local path to open after sign-in. Core checks it. */
8
+ redirect: string
9
+ }>()
10
+
11
+ const { t } = usePlutoI18n()
12
+
13
+ const { isSubmitting, login } = useAuth({ redirect: () => props.redirect })
14
+
15
+ const form = ref({
16
+ email: '',
17
+ password: '',
18
+ })
19
+
20
+ function submitForm() {
21
+ login(form.value)
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <form @submit.prevent="submitForm">
27
+ <div class="flex flex-col gap-y-6">
28
+ <UFormField :label="t('supabase.auth.fields.email')">
29
+ <UInput
30
+ v-model="form.email"
31
+ placeholder="victor@example.com"
32
+ class="w-full"
33
+ autocomplete="email"
34
+ />
35
+ </UFormField>
36
+
37
+ <UFormField :label="t('supabase.auth.fields.password')">
38
+ <UInput
39
+ v-model="form.password"
40
+ placeholder="••••"
41
+ class="w-full"
42
+ type="password"
43
+ />
44
+ </UFormField>
45
+
46
+ <UButton
47
+ :loading="isSubmitting"
48
+ type="submit"
49
+ icon="lucide:log-in"
50
+ class="w-full justify-center"
51
+ >
52
+ {{ t('supabase.auth.login.submit') }}
53
+ </UButton>
54
+ </div>
55
+ </form>
56
+ </template>
@@ -1,26 +1,39 @@
1
1
  <script setup lang="ts">
2
+ // The pending-migrations notice. It is an `adminBanners` entry of this layer,
3
+ // so the core admin layout renders it at the top of the admin pages.
2
4
  // Renders nothing when there is no logged-in admin (status reads as null)
3
5
  // or when no layer is pending. See `useMigrations` for the 401/403 handling.
6
+ // It also hides on the migrations page itself.
7
+ const route = useRoute()
4
8
  const { status, pendingCount } = useMigrations()
9
+ const { t } = usePlutoI18n()
5
10
 
6
11
  const title = computed(() =>
7
- pendingCount.value === 1
8
- ? '1 layer needs a database migration'
9
- : `${pendingCount.value} layers need a database migration`
12
+ t('supabase.migrations.banner', pendingCount.value)
13
+ )
14
+
15
+ const isVisible = computed(
16
+ () =>
17
+ Boolean(status.value)
18
+ && pendingCount.value > 0
19
+ && !route.path.startsWith('/admin/migrations')
10
20
  )
11
21
  </script>
12
22
 
13
23
  <template>
14
- <UAlert
15
- v-if="status && pendingCount > 0"
16
- :title="title"
17
- color="warning"
18
- variant="subtle"
19
- icon="lucide:triangle-alert"
20
- >
21
- <template #description>
22
- <ULink to="/admin/migrations" class="underline"> Go to Migrations </ULink
23
- >.
24
- </template>
25
- </UAlert>
24
+ <ClientOnly>
25
+ <Container v-if="isVisible" class="pt-4">
26
+ <UAlert
27
+ :title="title"
28
+ color="warning"
29
+ variant="subtle"
30
+ icon="lucide:triangle-alert"
31
+ >
32
+ <template #description>
33
+ <ULink to="/admin/migrations" class="underline"> {{ t('supabase.migrations.goTo') }} </ULink
34
+ >.
35
+ </template>
36
+ </UAlert>
37
+ </Container>
38
+ </ClientOnly>
26
39
  </template>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  const { field } = usePlutoSettings()
3
+ const { t } = usePlutoI18n()
3
4
 
4
5
  const websiteTitle = field('website_title')
5
6
  const websiteDescription = field('website_description')
@@ -8,22 +9,36 @@ const websiteUrl = field('website_url')
8
9
 
9
10
  <template>
10
11
  <div class="flex flex-col gap-y-6">
11
- <UFormField label="Website Title" class="w-full lg:w-1/2">
12
- <UInput v-model="websiteTitle" placeholder="e.g. My Website" />
12
+ <UFormField
13
+ :label="t('supabase.settings.general.websiteTitle')"
14
+ class="w-full lg:w-1/2"
15
+ >
16
+ <UInput
17
+ v-model="websiteTitle"
18
+ :placeholder="t('supabase.settings.general.websiteTitlePlaceholder')"
19
+ />
13
20
  </UFormField>
14
21
 
15
- <UFormField label="Website Description" class="w-full lg:w-1/2">
22
+ <UFormField
23
+ :label="t('supabase.settings.general.websiteDescription')"
24
+ class="w-full lg:w-1/2"
25
+ >
16
26
  <UInput
17
27
  v-model="websiteDescription"
18
- placeholder="e.g. My Website Description"
28
+ :placeholder="
29
+ t('supabase.settings.general.websiteDescriptionPlaceholder')
30
+ "
19
31
  />
20
32
  </UFormField>
21
33
 
22
- <UFormField label="Website URL" class="w-full lg:w-1/2">
34
+ <UFormField
35
+ :label="t('supabase.settings.general.websiteUrl')"
36
+ class="w-full lg:w-1/2"
37
+ >
23
38
  <UInput
24
39
  v-model="websiteUrl"
40
+ :placeholder="t('supabase.settings.general.websiteUrlPlaceholder')"
25
41
  type="url"
26
- placeholder="e.g. https://example.com"
27
42
  />
28
43
  </UFormField>
29
44
  </div>
@@ -1,64 +1,26 @@
1
- import type {
2
- RouteLocationNormalizedGeneric,
3
- RouteLocationRaw,
4
- } from 'vue-router'
1
+ import type { MaybeRefOrGetter } from 'vue'
5
2
 
6
3
  interface PlutoSupabaseAuthOptions {
7
- route?: RouteLocationNormalizedGeneric
4
+ /**
5
+ * A safe local path to open after a sign-in. Core checks it with
6
+ * `safeRedirect`. When it is empty, the sign-in opens the admin home.
7
+ */
8
+ redirect?: MaybeRefOrGetter<string | undefined>
8
9
  }
9
10
 
10
- interface PlutoUserMetadata {
11
- display_name?: string
12
- full_name?: string
13
- name?: string
14
- username?: string
15
- is_admin?: boolean
16
- [key: string]: unknown
17
- }
18
-
19
- // Admin paths that a signed-out visitor may load. Keep this list in sync
20
- // with every public `/admin/*` page.
21
- export const allowedUnauthenticatedPaths: RouteLocationRaw[] = [
22
- '/admin/login',
23
- '/admin/signup',
24
- '/admin/confirm',
25
- '/admin/setup',
26
- '/admin/forgot-password',
27
- '/admin/update-password',
28
- ]
29
-
30
- export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
11
+ // Sign-in, password reset and password update for the supabase auth pages.
12
+ // The session, the user and the sign-out are in the auth driver
13
+ // (app/plugins/pluto-extension.ts), not here.
14
+ export function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
31
15
  const toast = useToast()
16
+ // Read at setup, while the Nuxt context exists. The functions below run
17
+ // later, after an await, when the context is gone.
18
+ const { t, te } = usePlutoI18n()
19
+ const authErrorText = (error: unknown) =>
20
+ resolveAuthErrorText(error, { t, te })
32
21
 
33
22
  const supabase = useSupabaseClient()
34
- const supabaseSession = useSupabaseSession()
35
-
36
- const supabaseUser = useSupabaseUser()
37
- const userMetadata = useState<PlutoUserMetadata | null>(
38
- 'pluto-auth-user-metadata',
39
- () => null
40
- )
41
-
42
- const { data: authenticatedUser } = await supabase.auth.getUser()
43
23
 
44
- if (authenticatedUser.user?.user_metadata) {
45
- userMetadata.value = authenticatedUser.user.user_metadata
46
- }
47
-
48
- watch(
49
- supabaseUser,
50
- (currentUser) => {
51
- if (currentUser?.user_metadata) {
52
- userMetadata.value = currentUser.user_metadata
53
- }
54
- },
55
- { immediate: true }
56
- )
57
-
58
- const user = computed(
59
- () => supabaseUser.value?.user_metadata ?? userMetadata.value
60
- )
61
- const isLoggedIn = computed<boolean>(() => !!supabaseSession.value)
62
24
  const isSubmitting = ref<boolean>(false)
63
25
 
64
26
  interface LoginForm {
@@ -79,8 +41,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
79
41
  console.error(`Error signing in user: ${error?.message}`)
80
42
 
81
43
  toast.add({
82
- title: 'Could not log in',
83
- description: error?.message,
44
+ title: t('supabase.auth.toasts.loginFailed'),
45
+ description: authErrorText(error),
84
46
  icon: 'lucide:circle-x',
85
47
  color: 'error',
86
48
  })
@@ -98,6 +60,17 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
98
60
  .eq('config_name', 'first_setup')
99
61
 
100
62
  if (healthcheckError) {
63
+ console.error(`Error saving first setup: ${healthcheckError.message}`)
64
+
65
+ toast.add({
66
+ title: t('supabase.auth.toasts.loginFailed'),
67
+ description: authErrorText(healthcheckError),
68
+ icon: 'lucide:circle-x',
69
+ color: 'error',
70
+ })
71
+
72
+ isSubmitting.value = false
73
+
101
74
  return {
102
75
  success: false,
103
76
  message: healthcheckError.message,
@@ -107,7 +80,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
107
80
  }
108
81
 
109
82
  return navigateTo(
110
- (authOptions?.route?.query.redirect as string) ?? '/admin/home'
83
+ toValue(authOptions?.redirect) || SUPABASE_SIGN_IN_FALLBACK
111
84
  )
112
85
  } catch (error) {
113
86
  if (import.meta.dev) {
@@ -120,36 +93,6 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
120
93
  }
121
94
  }
122
95
 
123
- interface LogoutOptions {
124
- redirectTo?: string
125
- showToast?: boolean
126
- }
127
-
128
- async function logout(options?: LogoutOptions) {
129
- await supabase.auth.signOut()
130
- userMetadata.value = null
131
-
132
- if (options?.showToast) {
133
- console.warn(`User logged out`)
134
-
135
- toast.add({
136
- title: 'Logged out successfully',
137
- icon: 'lucide:check',
138
- color: 'success',
139
- })
140
- }
141
-
142
- if (options?.redirectTo) {
143
- return navigateTo(`/admin/login?redirect=${options.redirectTo}`)
144
- }
145
-
146
- if (authOptions?.route && authOptions.route.path.startsWith('/admin')) {
147
- return navigateTo('/admin/login')
148
- }
149
-
150
- return navigateTo('/admin/login')
151
- }
152
-
153
96
  async function requestPasswordReset(email: string) {
154
97
  isSubmitting.value = true
155
98
 
@@ -160,8 +103,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
160
103
 
161
104
  if (error) {
162
105
  toast.add({
163
- title: 'Could not send reset email',
164
- description: error.message,
106
+ title: t('supabase.auth.toasts.resetFailed'),
107
+ description: authErrorText(error),
165
108
  icon: 'lucide:circle-x',
166
109
  color: 'error',
167
110
  })
@@ -172,9 +115,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
172
115
  return { success: true }
173
116
  } catch (error) {
174
117
  toast.add({
175
- title: 'Could not send reset email',
176
- description:
177
- error instanceof Error ? error.message : 'An unknown error occurred.',
118
+ title: t('supabase.auth.toasts.resetFailed'),
119
+ description: authErrorText(error),
178
120
  icon: 'lucide:circle-x',
179
121
  color: 'error',
180
122
  })
@@ -193,8 +135,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
193
135
 
194
136
  if (error) {
195
137
  toast.add({
196
- title: 'Could not update password',
197
- description: error.message,
138
+ title: t('supabase.auth.toasts.updateFailed'),
139
+ description: authErrorText(error),
198
140
  icon: 'lucide:circle-x',
199
141
  color: 'error',
200
142
  })
@@ -203,7 +145,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
203
145
  }
204
146
 
205
147
  toast.add({
206
- title: 'Password updated successfully',
148
+ title: t('supabase.auth.toasts.passwordUpdated'),
207
149
  icon: 'lucide:check',
208
150
  color: 'success',
209
151
  })
@@ -211,9 +153,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
211
153
  return { success: true }
212
154
  } catch (error) {
213
155
  toast.add({
214
- title: 'Could not update password',
215
- description:
216
- error instanceof Error ? error.message : 'An unknown error occurred.',
156
+ title: t('supabase.auth.toasts.updateFailed'),
157
+ description: authErrorText(error),
217
158
  icon: 'lucide:circle-x',
218
159
  color: 'error',
219
160
  })
@@ -225,13 +166,9 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
225
166
  }
226
167
 
227
168
  return {
228
- isLoggedIn,
229
169
  isSubmitting,
230
- user,
231
170
  login,
232
- logout,
233
171
  requestPasswordReset,
234
172
  updatePassword,
235
- allowedUnauthenticatedPaths,
236
173
  }
237
174
  }
@@ -3,36 +3,33 @@ definePageMeta({
3
3
  layout: 'auth',
4
4
  })
5
5
 
6
+ const { t } = usePlutoI18n()
7
+
6
8
  useHead({
7
- title: 'Account Verification',
9
+ title: () => t('supabase.auth.confirm.pageTitle'),
8
10
  })
9
11
  </script>
10
12
 
11
13
  <template>
12
- <div class="grid min-h-dvh place-items-center p-4">
13
- <UCard class="w-full max-w-100">
14
- <div class="space-y-6 py-10">
15
- <div class="text-center space-y-4">
16
- <hgroup class="flex flex-col gap-y-1">
17
- <span class="text-5xl">
18
- <UIcon name="lucide:mail-check" class="inline-block" />
19
- </span>
20
-
21
- <h1 class="text-3xl font-bold">Account verified!</h1>
22
- </hgroup>
14
+ <PlutoAdminAuthCard :title="t('supabase.auth.confirm.title')">
15
+ <div class="space-y-6 py-10">
16
+ <div class="text-center space-y-4">
17
+ <hgroup class="flex flex-col gap-y-1">
18
+ <span class="text-5xl">
19
+ <UIcon name="lucide:mail-check" class="inline-block" />
20
+ </span>
21
+ </hgroup>
23
22
 
24
- <p>
25
- Your account has been successfully verified. You can now start using
26
- the Dashboard.
27
- </p>
23
+ <p>
24
+ {{ t('supabase.auth.confirm.description') }}
25
+ </p>
28
26
 
29
- <div class="flex items-center justify-center gap-x-4">
30
- <UButton to="/admin" color="primary" icon="lucide:layout-dashboard">
31
- Go to Dashboard
32
- </UButton>
33
- </div>
27
+ <div class="flex items-center justify-center gap-x-4">
28
+ <UButton to="/admin" color="primary" icon="lucide:layout-dashboard">
29
+ {{ t('supabase.auth.confirm.goToDashboard') }}
30
+ </UButton>
34
31
  </div>
35
32
  </div>
36
- </UCard>
37
- </div>
33
+ </div>
34
+ </PlutoAdminAuthCard>
38
35
  </template>