@plutocms/supabase 0.9.1 → 0.10.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.
- package/.prettierignore +2 -0
- package/CHANGELOG.md +8 -0
- package/FEATURES.md +6 -0
- package/app/components/EnvPersistWarning.vue +30 -13
- package/app/components/SupabaseMessageParts.vue +29 -0
- package/app/components/migrations/MigrationsBanner.vue +3 -4
- package/app/components/navbar/NavbarAdmin.vue +15 -9
- package/app/components/settings/SettingsGeneralPanel.vue +21 -6
- package/app/composables/auth.ts +17 -14
- package/app/pages/admin/(auth)/confirm.vue +6 -5
- package/app/pages/admin/(auth)/forgot-password.vue +15 -11
- package/app/pages/admin/(auth)/login.vue +10 -8
- package/app/pages/admin/(auth)/signup.vue +30 -22
- package/app/pages/admin/(auth)/update-password.vue +15 -12
- package/app/pages/admin/migrations.vue +87 -51
- package/app/pages/admin/setup.vue +88 -74
- package/app/pages/admin.vue +3 -2
- package/app/plugins/pluto-extension.ts +24 -3
- package/app/plugins/pluto-locale-sync.client.ts +30 -0
- package/app/utils/auth-error.ts +53 -0
- package/db/migrations/005_profile_locale.sql +44 -0
- package/i18n/locales/en.json +209 -0
- package/i18n/locales/pt-BR.json +209 -0
- package/package.json +3 -3
- package/server/api/profile/locale.get.ts +39 -0
- package/server/api/profile/locale.post.ts +71 -0
- package/server/api/setup/create.post.ts +6 -4
- package/server/api/users/[id].get.ts +4 -2
- package/server/api/users/index.get.ts +4 -2
- package/server/utils/admin-guard.ts +10 -2
- package/server/utils/capability-guard.ts +18 -4
- package/server/utils/content-adapter.ts +6 -4
- package/server/utils/pending-migrations.ts +0 -0
- package/server/utils/validate.ts +6 -1
- package/shared/types/supabase.ts +7 -0
package/.prettierignore
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.0](https://github.com/plutocms/supabase/compare/v0.9.1...v0.10.0) (2026-10-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **i18n:** store each user's admin language ([#79](https://github.com/plutocms/supabase/issues/79)) ([2ca171a](https://github.com/plutocms/supabase/commit/2ca171a97acbc2de4537b82b6718acbcc2c1fbd5))
|
|
9
|
+
* **i18n:** translate the supabase admin pages ([#82](https://github.com/plutocms/supabase/issues/82)) ([bae1372](https://github.com/plutocms/supabase/commit/bae1372fdb89414fcb20ec74c428812f25077f6b))
|
|
10
|
+
|
|
3
11
|
## [0.9.1](https://github.com/plutocms/supabase/compare/v0.9.0...v0.9.1) (2026-10-18)
|
|
4
12
|
|
|
5
13
|
|
package/FEATURES.md
CHANGED
|
@@ -13,3 +13,9 @@ 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
|
+
- Profile language: stores each user's admin language in `profiles.locale`, written through a
|
|
17
|
+
security-definer function, and plugs it into pluto's i18n as a locale driver.
|
|
18
|
+
@.claude/skills/profile-language/SKILL.md
|
|
19
|
+
- Admin translation: every admin page, component, toast, and server message of this layer is
|
|
20
|
+
translated (`en`, `pt-BR`) in the `supabase.*` namespace of pluto's i18n system.
|
|
21
|
+
@.claude/skills/admin-translation/SKILL.md
|
|
@@ -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: '
|
|
26
|
-
description: '
|
|
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: '
|
|
33
|
-
description: '
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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>
|
|
59
|
-
|
|
60
|
-
|
|
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>
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// Renders nothing when there is no logged-in admin (status reads as null)
|
|
3
3
|
// or when no layer is pending. See `useMigrations` for the 401/403 handling.
|
|
4
4
|
const { status, pendingCount } = useMigrations()
|
|
5
|
+
const { t } = usePlutoI18n()
|
|
5
6
|
|
|
6
7
|
const title = computed(() =>
|
|
7
|
-
pendingCount.value
|
|
8
|
-
? '1 layer needs a database migration'
|
|
9
|
-
: `${pendingCount.value} layers need a database migration`
|
|
8
|
+
t('supabase.migrations.banner', pendingCount.value)
|
|
10
9
|
)
|
|
11
10
|
</script>
|
|
12
11
|
|
|
@@ -19,7 +18,7 @@ const title = computed(() =>
|
|
|
19
18
|
icon="lucide:triangle-alert"
|
|
20
19
|
>
|
|
21
20
|
<template #description>
|
|
22
|
-
<ULink to="/admin/migrations" class="underline">
|
|
21
|
+
<ULink to="/admin/migrations" class="underline"> {{ t('supabase.migrations.goTo') }} </ULink
|
|
23
22
|
>.
|
|
24
23
|
</template>
|
|
25
24
|
</UAlert>
|
|
@@ -11,6 +11,8 @@ function openSidebar() {
|
|
|
11
11
|
isSidebarOpen.value = true
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const { t } = usePlutoI18n()
|
|
15
|
+
|
|
14
16
|
const { isLoggedIn, user, logout } = await useAuth()
|
|
15
17
|
|
|
16
18
|
const displayName = computed(() => {
|
|
@@ -19,7 +21,7 @@ const displayName = computed(() => {
|
|
|
19
21
|
user.value?.full_name ||
|
|
20
22
|
user.value?.name ||
|
|
21
23
|
user.value?.username ||
|
|
22
|
-
'
|
|
24
|
+
t('supabase.navbar.defaultUserName')
|
|
23
25
|
)
|
|
24
26
|
})
|
|
25
27
|
|
|
@@ -66,7 +68,7 @@ const items = computed<DropdownItems>(() => [
|
|
|
66
68
|
|
|
67
69
|
[
|
|
68
70
|
{
|
|
69
|
-
label: '
|
|
71
|
+
label: t('pluto.common.settings'),
|
|
70
72
|
icon: 'i-lucide-cog',
|
|
71
73
|
type: 'link',
|
|
72
74
|
to: '/admin/settings',
|
|
@@ -81,7 +83,7 @@ const items = computed<DropdownItems>(() => [
|
|
|
81
83
|
|
|
82
84
|
[
|
|
83
85
|
{
|
|
84
|
-
label: '
|
|
86
|
+
label: t('supabase.navbar.github'),
|
|
85
87
|
icon: 'i-simple-icons-github',
|
|
86
88
|
type: 'link',
|
|
87
89
|
to: 'https://github.com/ojvribeiro/pluto',
|
|
@@ -92,7 +94,7 @@ const items = computed<DropdownItems>(() => [
|
|
|
92
94
|
|
|
93
95
|
[
|
|
94
96
|
{
|
|
95
|
-
label: '
|
|
97
|
+
label: t('supabase.navbar.logout'),
|
|
96
98
|
icon: 'i-lucide-log-out',
|
|
97
99
|
kbds: ['meta', 'shift', 'q'],
|
|
98
100
|
class: 'cursor-pointer',
|
|
@@ -113,18 +115,18 @@ defineShortcuts(extractShortcuts(items.value))
|
|
|
113
115
|
>
|
|
114
116
|
<div class="flex min-w-0 items-center gap-x-1 lg:gap-x-3">
|
|
115
117
|
<UButton
|
|
118
|
+
:aria-label="t('supabase.navbar.openNavigation')"
|
|
116
119
|
class="lg:hidden"
|
|
117
120
|
icon="lucide:menu"
|
|
118
121
|
color="neutral"
|
|
119
122
|
variant="ghost"
|
|
120
|
-
aria-label="Open navigation"
|
|
121
123
|
square
|
|
122
124
|
@click="openSidebar"
|
|
123
125
|
/>
|
|
124
126
|
|
|
125
127
|
<div class="h-full shrink-0 py-1">
|
|
126
128
|
<NuxtLink
|
|
127
|
-
title="
|
|
129
|
+
:title="t('supabase.navbar.goHome')"
|
|
128
130
|
to="/admin/home"
|
|
129
131
|
class="group block h-full"
|
|
130
132
|
>
|
|
@@ -139,7 +141,11 @@ defineShortcuts(extractShortcuts(items.value))
|
|
|
139
141
|
<ul class="flex h-full min-w-0 items-center text-sm">
|
|
140
142
|
<NavbarAdminActionButton
|
|
141
143
|
:show="route.path.startsWith('/admin')"
|
|
142
|
-
:title="
|
|
144
|
+
:title="
|
|
145
|
+
t('supabase.navbar.visitSite', {
|
|
146
|
+
domain: domainFromUrl(maybeDevUrl),
|
|
147
|
+
})
|
|
148
|
+
"
|
|
143
149
|
:icon="
|
|
144
150
|
status === 'pending'
|
|
145
151
|
? 'lucide:loader-circle'
|
|
@@ -147,8 +153,8 @@ defineShortcuts(extractShortcuts(items.value))
|
|
|
147
153
|
"
|
|
148
154
|
:disabled="status === 'pending'"
|
|
149
155
|
:to="maybeDevUrl"
|
|
156
|
+
:label="t('supabase.navbar.viewSite')"
|
|
150
157
|
target="_blank"
|
|
151
|
-
label="View site"
|
|
152
158
|
/>
|
|
153
159
|
|
|
154
160
|
<ClientOnly>
|
|
@@ -180,8 +186,8 @@ defineShortcuts(extractShortcuts(items.value))
|
|
|
180
186
|
:modal="false"
|
|
181
187
|
>
|
|
182
188
|
<button
|
|
189
|
+
:aria-label="t('supabase.navbar.openUserMenu')"
|
|
183
190
|
class="group h-full py-1"
|
|
184
|
-
aria-label="Open user menu"
|
|
185
191
|
type="button"
|
|
186
192
|
>
|
|
187
193
|
<div
|
|
@@ -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
|
|
12
|
-
|
|
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
|
|
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="
|
|
28
|
+
:placeholder="
|
|
29
|
+
t('supabase.settings.general.websiteDescriptionPlaceholder')
|
|
30
|
+
"
|
|
19
31
|
/>
|
|
20
32
|
</UFormField>
|
|
21
33
|
|
|
22
|
-
<UFormField
|
|
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>
|
package/app/composables/auth.ts
CHANGED
|
@@ -29,6 +29,11 @@ export const allowedUnauthenticatedPaths: RouteLocationRaw[] = [
|
|
|
29
29
|
|
|
30
30
|
export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
31
31
|
const toast = useToast()
|
|
32
|
+
// Read at setup, while the Nuxt context exists. The functions below run
|
|
33
|
+
// later, after an await, when the context is gone.
|
|
34
|
+
const { t, te } = usePlutoI18n()
|
|
35
|
+
const authErrorText = (error: unknown) =>
|
|
36
|
+
resolveAuthErrorText(error, { t, te })
|
|
32
37
|
|
|
33
38
|
const supabase = useSupabaseClient()
|
|
34
39
|
const supabaseSession = useSupabaseSession()
|
|
@@ -79,8 +84,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
79
84
|
console.error(`Error signing in user: ${error?.message}`)
|
|
80
85
|
|
|
81
86
|
toast.add({
|
|
82
|
-
title: '
|
|
83
|
-
description: error
|
|
87
|
+
title: t('supabase.auth.toasts.loginFailed'),
|
|
88
|
+
description: authErrorText(error),
|
|
84
89
|
icon: 'lucide:circle-x',
|
|
85
90
|
color: 'error',
|
|
86
91
|
})
|
|
@@ -133,7 +138,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
133
138
|
console.warn(`User logged out`)
|
|
134
139
|
|
|
135
140
|
toast.add({
|
|
136
|
-
title: '
|
|
141
|
+
title: t('supabase.auth.toasts.loggedOut'),
|
|
137
142
|
icon: 'lucide:check',
|
|
138
143
|
color: 'success',
|
|
139
144
|
})
|
|
@@ -160,8 +165,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
160
165
|
|
|
161
166
|
if (error) {
|
|
162
167
|
toast.add({
|
|
163
|
-
title: '
|
|
164
|
-
description: error
|
|
168
|
+
title: t('supabase.auth.toasts.resetFailed'),
|
|
169
|
+
description: authErrorText(error),
|
|
165
170
|
icon: 'lucide:circle-x',
|
|
166
171
|
color: 'error',
|
|
167
172
|
})
|
|
@@ -172,9 +177,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
172
177
|
return { success: true }
|
|
173
178
|
} catch (error) {
|
|
174
179
|
toast.add({
|
|
175
|
-
title: '
|
|
176
|
-
description:
|
|
177
|
-
error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
180
|
+
title: t('supabase.auth.toasts.resetFailed'),
|
|
181
|
+
description: authErrorText(error),
|
|
178
182
|
icon: 'lucide:circle-x',
|
|
179
183
|
color: 'error',
|
|
180
184
|
})
|
|
@@ -193,8 +197,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
193
197
|
|
|
194
198
|
if (error) {
|
|
195
199
|
toast.add({
|
|
196
|
-
title: '
|
|
197
|
-
description: error
|
|
200
|
+
title: t('supabase.auth.toasts.updateFailed'),
|
|
201
|
+
description: authErrorText(error),
|
|
198
202
|
icon: 'lucide:circle-x',
|
|
199
203
|
color: 'error',
|
|
200
204
|
})
|
|
@@ -203,7 +207,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
toast.add({
|
|
206
|
-
title: '
|
|
210
|
+
title: t('supabase.auth.toasts.passwordUpdated'),
|
|
207
211
|
icon: 'lucide:check',
|
|
208
212
|
color: 'success',
|
|
209
213
|
})
|
|
@@ -211,9 +215,8 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
|
|
|
211
215
|
return { success: true }
|
|
212
216
|
} catch (error) {
|
|
213
217
|
toast.add({
|
|
214
|
-
title: '
|
|
215
|
-
description:
|
|
216
|
-
error instanceof Error ? error.message : 'An unknown error occurred.',
|
|
218
|
+
title: t('supabase.auth.toasts.updateFailed'),
|
|
219
|
+
description: authErrorText(error),
|
|
217
220
|
icon: 'lucide:circle-x',
|
|
218
221
|
color: 'error',
|
|
219
222
|
})
|
|
@@ -3,8 +3,10 @@ definePageMeta({
|
|
|
3
3
|
layout: 'auth',
|
|
4
4
|
})
|
|
5
5
|
|
|
6
|
+
const { t } = usePlutoI18n()
|
|
7
|
+
|
|
6
8
|
useHead({
|
|
7
|
-
title:
|
|
9
|
+
title: () => t('supabase.auth.confirm.pageTitle'),
|
|
8
10
|
})
|
|
9
11
|
</script>
|
|
10
12
|
|
|
@@ -18,17 +20,16 @@ useHead({
|
|
|
18
20
|
<UIcon name="lucide:mail-check" class="inline-block" />
|
|
19
21
|
</span>
|
|
20
22
|
|
|
21
|
-
<h1 class="text-3xl font-bold">
|
|
23
|
+
<h1 class="text-3xl font-bold">{{ t('supabase.auth.confirm.title') }}</h1>
|
|
22
24
|
</hgroup>
|
|
23
25
|
|
|
24
26
|
<p>
|
|
25
|
-
|
|
26
|
-
the Dashboard.
|
|
27
|
+
{{ t('supabase.auth.confirm.description') }}
|
|
27
28
|
</p>
|
|
28
29
|
|
|
29
30
|
<div class="flex items-center justify-center gap-x-4">
|
|
30
31
|
<UButton to="/admin" color="primary" icon="lucide:layout-dashboard">
|
|
31
|
-
|
|
32
|
+
{{ t('supabase.auth.confirm.goToDashboard') }}
|
|
32
33
|
</UButton>
|
|
33
34
|
</div>
|
|
34
35
|
</div>
|
|
@@ -3,8 +3,10 @@ definePageMeta({
|
|
|
3
3
|
layout: 'auth',
|
|
4
4
|
})
|
|
5
5
|
|
|
6
|
+
const { t } = usePlutoI18n()
|
|
7
|
+
|
|
6
8
|
useHead({
|
|
7
|
-
title:
|
|
9
|
+
title: () => t('supabase.auth.forgot.pageTitle'),
|
|
8
10
|
})
|
|
9
11
|
|
|
10
12
|
const { isSubmitting, requestPasswordReset } = await useAuth()
|
|
@@ -31,18 +33,20 @@ async function submitForm() {
|
|
|
31
33
|
<UIcon name="lucide:mail" class="inline-block" />
|
|
32
34
|
</span>
|
|
33
35
|
|
|
34
|
-
<h1 class="text-3xl font-bold">
|
|
36
|
+
<h1 class="text-3xl font-bold">{{ t('supabase.auth.forgot.sentTitle') }}</h1>
|
|
35
37
|
</hgroup>
|
|
36
38
|
|
|
37
39
|
<p>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
<SupabaseMessageParts :message="t('supabase.auth.forgot.sentDescription')">
|
|
41
|
+
<template #email>
|
|
42
|
+
<strong>{{ email }}</strong>
|
|
43
|
+
</template>
|
|
44
|
+
</SupabaseMessageParts>
|
|
41
45
|
</p>
|
|
42
46
|
|
|
43
47
|
<div class="flex items-center justify-center">
|
|
44
48
|
<UButton variant="link" to="/admin/login" class="px-0">
|
|
45
|
-
|
|
49
|
+
{{ t('supabase.auth.backToLogin') }}
|
|
46
50
|
</UButton>
|
|
47
51
|
</div>
|
|
48
52
|
</div>
|
|
@@ -51,13 +55,13 @@ async function submitForm() {
|
|
|
51
55
|
<form v-else @submit.prevent="submitForm">
|
|
52
56
|
<div class="flex flex-col gap-y-6">
|
|
53
57
|
<hgroup class="flex flex-col gap-y-1">
|
|
54
|
-
<h1 class="text-3xl font-bold">
|
|
58
|
+
<h1 class="text-3xl font-bold">{{ t('supabase.auth.forgot.title') }}</h1>
|
|
55
59
|
<p class="text-sm text-muted">
|
|
56
|
-
|
|
60
|
+
{{ t('supabase.auth.forgot.description') }}
|
|
57
61
|
</p>
|
|
58
62
|
</hgroup>
|
|
59
63
|
|
|
60
|
-
<UFormField label="
|
|
64
|
+
<UFormField :label="t('supabase.auth.fields.email')">
|
|
61
65
|
<UInput
|
|
62
66
|
v-model="email"
|
|
63
67
|
placeholder="victor@example.com"
|
|
@@ -78,13 +82,13 @@ async function submitForm() {
|
|
|
78
82
|
icon="lucide:send"
|
|
79
83
|
class="w-full justify-center sm:w-auto"
|
|
80
84
|
>
|
|
81
|
-
|
|
85
|
+
{{ t('supabase.auth.forgot.submit') }}
|
|
82
86
|
</UButton>
|
|
83
87
|
</UFormField>
|
|
84
88
|
|
|
85
89
|
<UFormField>
|
|
86
90
|
<UButton variant="link" to="/admin/login" class="px-0">
|
|
87
|
-
|
|
91
|
+
{{ t('supabase.auth.backToLogin') }}
|
|
88
92
|
</UButton>
|
|
89
93
|
</UFormField>
|
|
90
94
|
</div>
|
|
@@ -8,8 +8,10 @@ definePageMeta({
|
|
|
8
8
|
layout: 'auth',
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
+
const { t } = usePlutoI18n()
|
|
12
|
+
|
|
11
13
|
useHead({
|
|
12
|
-
title: '
|
|
14
|
+
title: () => t('supabase.auth.login.title'),
|
|
13
15
|
})
|
|
14
16
|
|
|
15
17
|
const { isSubmitting, login } = await useAuth()
|
|
@@ -29,9 +31,9 @@ function submitForm() {
|
|
|
29
31
|
<UCard class="w-full max-w-100">
|
|
30
32
|
<form @submit.prevent="submitForm">
|
|
31
33
|
<div class="flex flex-col gap-y-6">
|
|
32
|
-
<h1 class="text-3xl font-bold">
|
|
34
|
+
<h1 class="text-3xl font-bold">{{ t('supabase.auth.login.title') }}</h1>
|
|
33
35
|
|
|
34
|
-
<UFormField label="
|
|
36
|
+
<UFormField :label="t('supabase.auth.fields.email')">
|
|
35
37
|
<UInput
|
|
36
38
|
v-model="form.email"
|
|
37
39
|
placeholder="victor@example.com"
|
|
@@ -40,7 +42,7 @@ function submitForm() {
|
|
|
40
42
|
/>
|
|
41
43
|
</UFormField>
|
|
42
44
|
|
|
43
|
-
<UFormField label="
|
|
45
|
+
<UFormField :label="t('supabase.auth.fields.password')">
|
|
44
46
|
<UInput
|
|
45
47
|
v-model="form.password"
|
|
46
48
|
placeholder="••••"
|
|
@@ -59,13 +61,13 @@ function submitForm() {
|
|
|
59
61
|
icon="lucide:log-in"
|
|
60
62
|
class="w-full justify-center sm:w-auto"
|
|
61
63
|
>
|
|
62
|
-
|
|
64
|
+
{{ t('supabase.auth.login.submit') }}
|
|
63
65
|
</UButton>
|
|
64
66
|
</UFormField>
|
|
65
67
|
|
|
66
68
|
<UFormField>
|
|
67
69
|
<UButton variant="link" to="/admin/signup" class="px-0">
|
|
68
|
-
|
|
70
|
+
{{ t('supabase.auth.login.createAccount') }}
|
|
69
71
|
</UButton>
|
|
70
72
|
</UFormField>
|
|
71
73
|
</div>
|
|
@@ -79,11 +81,11 @@ function submitForm() {
|
|
|
79
81
|
to="/"
|
|
80
82
|
class="px-0"
|
|
81
83
|
>
|
|
82
|
-
|
|
84
|
+
{{ t('supabase.auth.login.backToHome') }}
|
|
83
85
|
</UButton>
|
|
84
86
|
|
|
85
87
|
<UButton variant="link" to="/admin/forgot-password" class="px-0">
|
|
86
|
-
|
|
88
|
+
{{ t('supabase.auth.login.forgotPassword') }}
|
|
87
89
|
</UButton>
|
|
88
90
|
</div>
|
|
89
91
|
</div>
|