@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.
- package/.prettierignore +2 -0
- package/CHANGELOG.md +19 -0
- package/FEATURES.md +9 -0
- package/README.md +10 -1
- package/app/components/EnvPersistWarning.vue +30 -13
- package/app/components/SupabaseMessageParts.vue +29 -0
- package/app/components/auth/AuthSignIn.vue +56 -0
- package/app/components/migrations/MigrationsBanner.vue +28 -15
- package/app/components/settings/SettingsGeneralPanel.vue +21 -6
- package/app/composables/auth.ts +38 -101
- package/app/pages/admin/(auth)/confirm.vue +20 -23
- package/app/pages/admin/(auth)/forgot-password.vue +60 -63
- package/app/pages/admin/(auth)/signup.vue +115 -110
- package/app/pages/admin/(auth)/update-password.vue +78 -82
- package/app/pages/admin/migrations.vue +87 -51
- package/app/pages/admin/setup.vue +88 -75
- package/app/plugins/pluto-extension.ts +143 -10
- package/app/utils/auth-driver.ts +110 -0
- package/app/utils/auth-error.ts +53 -0
- package/db/migrations/005_profile_locale.sql +44 -0
- package/i18n/locales/en.json +193 -0
- package/i18n/locales/pt-BR.json +193 -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/app/components/navbar/NavbarAdmin.vue +0 -213
- package/app/components/navbar/NavbarAdminProvider.vue +0 -18
- package/app/middleware/auth.ts +0 -34
- package/app/middleware/setup-check.ts +0 -44
- package/app/pages/admin/(auth)/login.vue +0 -93
- package/app/pages/admin.vue +0 -56
- package/app/plugins/pluto-permissions-sync.ts +0 -38
package/.prettierignore
ADDED
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`:
|
|
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: '
|
|
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>
|
|
@@ -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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
<
|
|
15
|
-
v-if="
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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: '
|
|
83
|
-
description: error
|
|
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?.
|
|
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: '
|
|
164
|
-
description: error
|
|
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: '
|
|
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: '
|
|
197
|
-
description: error
|
|
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: '
|
|
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: '
|
|
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:
|
|
9
|
+
title: () => t('supabase.auth.confirm.pageTitle'),
|
|
8
10
|
})
|
|
9
11
|
</script>
|
|
10
12
|
|
|
11
13
|
<template>
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
<div class="space-y-
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</p>
|
|
23
|
+
<p>
|
|
24
|
+
{{ t('supabase.auth.confirm.description') }}
|
|
25
|
+
</p>
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
</
|
|
37
|
-
</
|
|
33
|
+
</div>
|
|
34
|
+
</PlutoAdminAuthCard>
|
|
38
35
|
</template>
|