@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
@@ -3,11 +3,13 @@ definePageMeta({
3
3
  layout: 'auth',
4
4
  })
5
5
 
6
+ const { t } = usePlutoI18n()
7
+
6
8
  useHead({
7
- title: 'Forgot Password',
9
+ title: () => t('supabase.auth.forgot.pageTitle'),
8
10
  })
9
11
 
10
- const { isSubmitting, requestPasswordReset } = await useAuth()
12
+ const { isSubmitting, requestPasswordReset } = useAuth()
11
13
 
12
14
  const email = ref('')
13
15
  const isEmailSent = ref(false)
@@ -22,74 +24,69 @@ async function submitForm() {
22
24
  </script>
23
25
 
24
26
  <template>
25
- <div class="grid min-h-dvh place-items-center p-4">
26
- <UCard class="w-full max-w-100">
27
- <div v-if="isEmailSent" class="space-y-6 py-10">
28
- <div class="text-center space-y-4">
29
- <hgroup class="flex flex-col gap-y-1">
30
- <span class="text-5xl">
31
- <UIcon name="lucide:mail" class="inline-block" />
32
- </span>
33
-
34
- <h1 class="text-3xl font-bold">Check your inbox</h1>
35
- </hgroup>
27
+ <PlutoAdminAuthCard
28
+ :title="isEmailSent ? t('supabase.auth.forgot.sentTitle') : t('supabase.auth.forgot.title')"
29
+ :description="isEmailSent ? undefined : t('supabase.auth.forgot.description')"
30
+ >
31
+ <div v-if="isEmailSent" class="space-y-6 py-10">
32
+ <div class="text-center space-y-4">
33
+ <hgroup class="flex flex-col gap-y-1">
34
+ <span class="text-5xl">
35
+ <UIcon name="lucide:mail" class="inline-block" />
36
+ </span>
37
+ </hgroup>
36
38
 
37
- <p>
38
- If an account exists for <strong>{{ email }}</strong
39
- >, a password reset link has been sent. Check your email and follow
40
- the link.
41
- </p>
39
+ <p>
40
+ <SupabaseMessageParts :message="t('supabase.auth.forgot.sentDescription')">
41
+ <template #email>
42
+ <strong>{{ email }}</strong>
43
+ </template>
44
+ </SupabaseMessageParts>
45
+ </p>
42
46
 
43
- <div class="flex items-center justify-center">
44
- <UButton variant="link" to="/admin/login" class="px-0">
45
- Back to Login
46
- </UButton>
47
- </div>
47
+ <div class="flex items-center justify-center">
48
+ <UButton variant="link" to="/admin/login" class="px-0">
49
+ {{ t('supabase.auth.backToLogin') }}
50
+ </UButton>
48
51
  </div>
49
52
  </div>
53
+ </div>
50
54
 
51
- <form v-else @submit.prevent="submitForm">
52
- <div class="flex flex-col gap-y-6">
53
- <hgroup class="flex flex-col gap-y-1">
54
- <h1 class="text-3xl font-bold">Forgot Password</h1>
55
- <p class="text-sm text-muted">
56
- Enter your email and we'll send you a reset link.
57
- </p>
58
- </hgroup>
55
+ <form v-else @submit.prevent="submitForm">
56
+ <div class="flex flex-col gap-y-6">
59
57
 
60
- <UFormField label="Email">
61
- <UInput
62
- v-model="email"
63
- placeholder="victor@example.com"
64
- type="email"
65
- class="w-full"
66
- autocomplete="email"
67
- required
68
- />
69
- </UFormField>
58
+ <UFormField :label="t('supabase.auth.fields.email')">
59
+ <UInput
60
+ v-model="email"
61
+ placeholder="victor@example.com"
62
+ type="email"
63
+ class="w-full"
64
+ autocomplete="email"
65
+ required
66
+ />
67
+ </UFormField>
70
68
 
71
- <div
72
- class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
73
- >
74
- <UFormField>
75
- <UButton
76
- :loading="isSubmitting"
77
- type="submit"
78
- icon="lucide:send"
79
- class="w-full justify-center sm:w-auto"
80
- >
81
- Send Reset Link
82
- </UButton>
83
- </UFormField>
69
+ <div
70
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
71
+ >
72
+ <UFormField>
73
+ <UButton
74
+ :loading="isSubmitting"
75
+ type="submit"
76
+ icon="lucide:send"
77
+ class="w-full justify-center sm:w-auto"
78
+ >
79
+ {{ t('supabase.auth.forgot.submit') }}
80
+ </UButton>
81
+ </UFormField>
84
82
 
85
- <UFormField>
86
- <UButton variant="link" to="/admin/login" class="px-0">
87
- Back to Login
88
- </UButton>
89
- </UFormField>
90
- </div>
83
+ <UFormField>
84
+ <UButton variant="link" to="/admin/login" class="px-0">
85
+ {{ t('supabase.auth.backToLogin') }}
86
+ </UButton>
87
+ </UFormField>
91
88
  </div>
92
- </form>
93
- </UCard>
94
- </div>
89
+ </div>
90
+ </form>
91
+ </PlutoAdminAuthCard>
95
92
  </template>
@@ -3,8 +3,11 @@ definePageMeta({
3
3
  layout: 'auth',
4
4
  })
5
5
 
6
+ const { t } = usePlutoI18n()
7
+ const errorText = usePlutoErrorText()
8
+
6
9
  useHead({
7
- title: 'Sign Up',
10
+ title: () => t('supabase.auth.signup.pageTitle'),
8
11
  })
9
12
 
10
13
  const toast = useToast()
@@ -44,8 +47,8 @@ async function submitForm() {
44
47
  })
45
48
 
46
49
  toast.add({
47
- title: 'Account created successfully',
48
- description: 'You still need to verify your email before you can log in.',
50
+ title: t('supabase.auth.signup.created'),
51
+ description: t('supabase.auth.signup.createdDescription'),
49
52
  icon: 'lucide:check',
50
53
  color: 'success',
51
54
  })
@@ -55,9 +58,8 @@ async function submitForm() {
55
58
  console.error('Error signing up:', error)
56
59
 
57
60
  toast.add({
58
- title: 'Could not create account',
59
- description:
60
- error instanceof Error ? error.message : 'An unknown error occurred.',
61
+ title: t('supabase.auth.signup.failed'),
62
+ description: errorText(error, 'supabase.auth.errors.unknown'),
61
63
  icon: 'lucide:circle-x',
62
64
  color: 'error',
63
65
  })
@@ -68,124 +70,127 @@ async function submitForm() {
68
70
  </script>
69
71
 
70
72
  <template>
71
- <div class="grid min-h-dvh place-items-center p-4">
72
- <UCard class="w-full max-w-120">
73
- <div v-if="isEmailVerificationMessageVisible" class="space-y-6 py-10">
74
- <div class="text-center space-y-4">
75
- <hgroup class="flex flex-col gap-y-1">
76
- <span class="text-5xl">
77
- <UIcon name="lucide:mail-warning" class="inline-block" />
78
- </span>
79
-
80
- <h1 class="text-3xl font-bold">Almost there!</h1>
81
- </hgroup>
82
-
83
- <p>
84
- A verification link has been sent to your email. Please check your
85
- inbox and follow the instructions to verify your account.
86
- </p>
87
-
88
- <UAlert icon="lucide:check" color="success" variant="outline">
89
- <template #description>
90
- You can close this page. You will be logged in automatically once
91
- you verify your email.
92
- </template>
93
- </UAlert>
94
-
95
- <UAlert
96
- icon="lucide:alert-triangle"
97
- color="warning"
98
- variant="outline"
99
- >
100
- <template #description>
101
- If you don't see the email, please check your spam folder.
102
- </template>
103
- </UAlert>
104
- </div>
73
+ <PlutoAdminAuthCard
74
+ :title="isEmailVerificationMessageVisible ? t('supabase.auth.signup.almostThere') : t('supabase.auth.signup.title')"
75
+ >
76
+ <div v-if="isEmailVerificationMessageVisible" class="space-y-6 py-10">
77
+ <div class="text-center space-y-4">
78
+ <hgroup class="flex flex-col gap-y-1">
79
+ <span class="text-5xl">
80
+ <UIcon name="lucide:mail-warning" class="inline-block" />
81
+ </span>
82
+ </hgroup>
83
+
84
+ <p>
85
+ {{ t('supabase.auth.signup.verificationSent') }}
86
+ </p>
87
+
88
+ <UAlert icon="lucide:check" color="success" variant="outline">
89
+ <template #description>
90
+ {{ t('supabase.auth.signup.closePage') }}
91
+ </template>
92
+ </UAlert>
93
+
94
+ <UAlert
95
+ icon="lucide:alert-triangle"
96
+ color="warning"
97
+ variant="outline"
98
+ >
99
+ <template #description>
100
+ {{ t('supabase.auth.signup.checkSpam') }}
101
+ </template>
102
+ </UAlert>
105
103
  </div>
106
-
107
- <form v-else @submit.prevent="submitForm">
108
- <div class="flex flex-col gap-y-6">
109
- <h1 class="text-3xl font-bold">Create Account</h1>
110
-
111
- <UFormField label="Email" required>
104
+ </div>
105
+
106
+ <form v-else @submit.prevent="submitForm">
107
+ <div class="flex flex-col gap-y-6">
108
+
109
+ <UFormField :label="t('supabase.auth.fields.email')" required>
110
+ <UInput
111
+ v-model="form.email"
112
+ type="email"
113
+ placeholder="victor@example.com"
114
+ class="w-full"
115
+ required
116
+ />
117
+ </UFormField>
118
+
119
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
120
+ <UFormField :label="t('supabase.auth.fields.username')" required>
112
121
  <UInput
113
- v-model="form.email"
114
- type="email"
115
- placeholder="victor@example.com"
122
+ v-model="form.username"
123
+ :placeholder="t('supabase.auth.signup.usernamePlaceholder')"
116
124
  class="w-full"
125
+ minlength="3"
117
126
  required
118
127
  />
119
128
  </UFormField>
120
129
 
121
- <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
122
- <UFormField label="Username" required>
123
- <UInput
124
- v-model="form.username"
125
- placeholder="e.g: johndoe"
126
- class="w-full"
127
- minlength="3"
128
- required
129
- />
130
- </UFormField>
131
-
132
- <UFormField label="Display Name" required>
133
- <UInput
134
- v-model="form.display_name"
135
- placeholder="e.g: John Doe"
136
- class="w-full"
137
- required
138
- />
139
- </UFormField>
140
- </div>
141
-
142
- <UFormField :error="!passwordMatch" label="Password" required>
130
+ <UFormField :label="t('supabase.auth.fields.displayName')" required>
143
131
  <UInput
144
- v-model="form.password"
145
- placeholder="••••"
132
+ v-model="form.display_name"
133
+ :placeholder="t('supabase.auth.signup.displayNamePlaceholder')"
146
134
  class="w-full"
147
- type="password"
148
135
  required
149
136
  />
150
137
  </UFormField>
138
+ </div>
151
139
 
152
- <UFormField :error="!passwordMatch" label="Confirm Password" required>
153
- <UInput
154
- v-model="form.confirm_password"
155
- placeholder="••••"
156
- class="w-full"
157
- type="password"
158
- required
159
- />
140
+ <UFormField
141
+ :error="!passwordMatch"
142
+ :label="t('supabase.auth.fields.password')"
143
+ required
144
+ >
145
+ <UInput
146
+ v-model="form.password"
147
+ placeholder="••••"
148
+ class="w-full"
149
+ type="password"
150
+ required
151
+ />
152
+ </UFormField>
153
+
154
+ <UFormField
155
+ :error="!passwordMatch"
156
+ :label="t('supabase.auth.fields.confirmPassword')"
157
+ required
158
+ >
159
+ <UInput
160
+ v-model="form.confirm_password"
161
+ placeholder="••••"
162
+ class="w-full"
163
+ type="password"
164
+ required
165
+ />
166
+ </UFormField>
167
+
168
+ <div
169
+ class="flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
170
+ >
171
+ <UFormField>
172
+ <UButton
173
+ to="/admin/login"
174
+ icon="lucide:arrow-left"
175
+ variant="link"
176
+ class="px-0"
177
+ >
178
+ {{ t('supabase.auth.backToLogin') }}
179
+ </UButton>
160
180
  </UFormField>
161
181
 
162
- <div
163
- class="flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
164
- >
165
- <UFormField>
166
- <UButton
167
- to="/admin/login"
168
- icon="lucide:arrow-left"
169
- variant="link"
170
- class="px-0"
171
- >
172
- Back to Login
173
- </UButton>
174
- </UFormField>
175
-
176
- <UFormField>
177
- <UButton
178
- :disabled="isSubmitting || !passwordMatch"
179
- :loading="isSubmitting"
180
- type="submit"
181
- class="w-full justify-center sm:w-auto"
182
- >
183
- Create account
184
- </UButton>
185
- </UFormField>
186
- </div>
182
+ <UFormField>
183
+ <UButton
184
+ :disabled="isSubmitting || !passwordMatch"
185
+ :loading="isSubmitting"
186
+ type="submit"
187
+ class="w-full justify-center sm:w-auto"
188
+ >
189
+ {{ t('supabase.auth.signup.submit') }}
190
+ </UButton>
191
+ </UFormField>
187
192
  </div>
188
- </form>
189
- </UCard>
190
- </div>
193
+ </div>
194
+ </form>
195
+ </PlutoAdminAuthCard>
191
196
  </template>
@@ -3,8 +3,10 @@ definePageMeta({
3
3
  layout: 'auth',
4
4
  })
5
5
 
6
+ const { t } = usePlutoI18n()
7
+
6
8
  useHead({
7
- title: 'Update Password',
9
+ title: () => t('supabase.auth.update.title'),
8
10
  })
9
11
 
10
12
  const route = useRoute()
@@ -19,7 +21,7 @@ onMounted(async () => {
19
21
  }
20
22
  })
21
23
 
22
- const { isSubmitting, updatePassword } = await useAuth()
24
+ const { isSubmitting, updatePassword } = useAuth()
23
25
 
24
26
  const form = ref({
25
27
  password: '',
@@ -48,90 +50,84 @@ async function submitForm() {
48
50
  </script>
49
51
 
50
52
  <template>
51
- <div class="grid min-h-dvh place-items-center p-4">
52
- <UCard class="w-full max-w-100">
53
- <div v-if="isPasswordUpdated" class="space-y-6 py-10">
54
- <div class="text-center space-y-4">
55
- <hgroup class="flex flex-col gap-y-1">
56
- <span class="text-5xl">
57
- <UIcon name="lucide:lock-keyhole" class="inline-block" />
58
- </span>
59
-
60
- <h1 class="text-3xl font-bold">Password Updated!</h1>
61
- </hgroup>
62
-
63
- <p>
64
- Your password has been changed. You can now log in with your new
65
- password.
66
- </p>
67
-
68
- <div class="flex items-center justify-center">
69
- <UButton to="/admin/login" color="primary" icon="lucide:log-in">
70
- Go to Login
71
- </UButton>
72
- </div>
53
+ <PlutoAdminAuthCard
54
+ :title="isPasswordUpdated ? t('supabase.auth.update.doneTitle') : t('supabase.auth.update.title')"
55
+ :description="isPasswordUpdated ? undefined : t('supabase.auth.update.description')"
56
+ >
57
+ <div v-if="isPasswordUpdated" class="space-y-6 py-10">
58
+ <div class="text-center space-y-4">
59
+ <hgroup class="flex flex-col gap-y-1">
60
+ <span class="text-5xl">
61
+ <UIcon name="lucide:lock-keyhole" class="inline-block" />
62
+ </span>
63
+ </hgroup>
64
+
65
+ <p>
66
+ {{ t('supabase.auth.update.doneDescription') }}
67
+ </p>
68
+
69
+ <div class="flex items-center justify-center">
70
+ <UButton to="/admin/login" color="primary" icon="lucide:log-in">
71
+ {{ t('supabase.auth.update.goToLogin') }}
72
+ </UButton>
73
73
  </div>
74
74
  </div>
75
-
76
- <form v-else @submit.prevent="submitForm">
77
- <div class="flex flex-col gap-y-6">
78
- <hgroup class="flex flex-col gap-y-1">
79
- <h1 class="text-3xl font-bold">Update Password</h1>
80
- <p class="text-sm text-muted">Enter your new password below.</p>
81
- </hgroup>
82
-
83
- <UFormField label="New Password">
84
- <UInput
85
- v-model="form.password"
86
- placeholder="••••"
87
- type="password"
88
- class="w-full"
89
- autocomplete="new-password"
90
- required
91
- />
75
+ </div>
76
+
77
+ <form v-else @submit.prevent="submitForm">
78
+ <div class="flex flex-col gap-y-6">
79
+
80
+ <UFormField :label="t('supabase.auth.update.newPassword')">
81
+ <UInput
82
+ v-model="form.password"
83
+ placeholder="••••"
84
+ type="password"
85
+ class="w-full"
86
+ autocomplete="new-password"
87
+ required
88
+ />
89
+ </UFormField>
90
+
91
+ <UFormField
92
+ :error="
93
+ form.confirm_password && !passwordMatch
94
+ ? t('supabase.auth.passwordMismatch')
95
+ : undefined
96
+ "
97
+ :label="t('supabase.auth.update.confirmNewPassword')"
98
+ >
99
+ <UInput
100
+ v-model="form.confirm_password"
101
+ placeholder="••••"
102
+ type="password"
103
+ class="w-full"
104
+ autocomplete="new-password"
105
+ required
106
+ />
107
+ </UFormField>
108
+
109
+ <div
110
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
111
+ >
112
+ <UFormField>
113
+ <UButton
114
+ :loading="isSubmitting"
115
+ :disabled="!passwordMatch"
116
+ type="submit"
117
+ icon="lucide:lock-keyhole"
118
+ class="w-full justify-center sm:w-auto"
119
+ >
120
+ {{ t('supabase.auth.update.submit') }}
121
+ </UButton>
92
122
  </UFormField>
93
123
 
94
- <UFormField
95
- :error="
96
- form.confirm_password && !passwordMatch
97
- ? 'Passwords do not match.'
98
- : undefined
99
- "
100
- label="Confirm New Password"
101
- >
102
- <UInput
103
- v-model="form.confirm_password"
104
- placeholder="••••"
105
- type="password"
106
- class="w-full"
107
- autocomplete="new-password"
108
- required
109
- />
124
+ <UFormField>
125
+ <UButton variant="link" to="/admin/login" class="px-0">
126
+ {{ t('supabase.auth.backToLogin') }}
127
+ </UButton>
110
128
  </UFormField>
111
-
112
- <div
113
- class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
114
- >
115
- <UFormField>
116
- <UButton
117
- :loading="isSubmitting"
118
- :disabled="!passwordMatch"
119
- type="submit"
120
- icon="lucide:lock-keyhole"
121
- class="w-full justify-center sm:w-auto"
122
- >
123
- Update Password
124
- </UButton>
125
- </UFormField>
126
-
127
- <UFormField>
128
- <UButton variant="link" to="/admin/login" class="px-0">
129
- Back to Login
130
- </UButton>
131
- </UFormField>
132
- </div>
133
129
  </div>
134
- </form>
135
- </UCard>
136
- </div>
130
+ </div>
131
+ </form>
132
+ </PlutoAdminAuthCard>
137
133
  </template>