@plutocms/supabase 0.15.0 → 0.15.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 (50) hide show
  1. package/.prettierignore +6 -0
  2. package/CHANGELOG.md +14 -0
  3. package/app/components/access/DeleteRoleModal.vue +17 -4
  4. package/app/components/access/EditRolesSlideover.vue +11 -5
  5. package/app/components/access/InviteModal.vue +44 -19
  6. package/app/components/access/LinkField.vue +14 -3
  7. package/app/components/access/LinkModal.vue +5 -1
  8. package/app/components/access/RevokeModal.vue +17 -4
  9. package/app/components/access/RoleSlideover.vue +32 -11
  10. package/app/components/mail/AuthEmailsPanel.vue +87 -20
  11. package/app/components/migrations/MigrationsBanner.vue +5 -4
  12. package/app/composables/migrations.ts +13 -6
  13. package/app/pages/admin/(auth)/accept-invite.vue +21 -5
  14. package/app/pages/admin/(auth)/forgot-password.vue +11 -4
  15. package/app/pages/admin/(auth)/signup.vue +10 -9
  16. package/app/pages/admin/(auth)/update-password.vue +8 -3
  17. package/app/pages/admin/migrations.vue +8 -3
  18. package/app/pages/admin/settings/roles.vue +58 -15
  19. package/app/pages/admin/settings/users.vue +105 -30
  20. package/app/pages/admin/setup.vue +26 -7
  21. package/app/plugins/pluto-extension.ts +21 -8
  22. package/app/utils/access-ui.ts +29 -8
  23. package/app/utils/api-failure.ts +3 -1
  24. package/app/utils/auth-driver.ts +1 -3
  25. package/app/utils/auth-error.ts +1 -1
  26. package/app/utils/invite-link.ts +3 -3
  27. package/app/utils/supabase-project.ts +8 -3
  28. package/modules/pluto-migrations.ts +14 -3
  29. package/package.json +6 -4
  30. package/server/api/_pluto/supabase/access/users/[id]/resend.post.ts +15 -5
  31. package/server/api/_pluto/supabase/access/users/[id]/roles.put.ts +4 -1
  32. package/server/api/_pluto/supabase/access/users/invite-link.post.ts +12 -3
  33. package/server/api/_pluto/supabase/access/users/invite.post.ts +7 -2
  34. package/server/api/_pluto/supabase/auth/signup.post.ts +5 -1
  35. package/server/api/_pluto/supabase/me/capabilities.get.ts +4 -1
  36. package/server/api/_pluto/supabase/migrations/run.post.ts +9 -2
  37. package/server/api/_pluto/supabase/setup/index.post.ts +4 -1
  38. package/server/utils/access-errors.ts +2 -1
  39. package/server/utils/access-invite.ts +1 -1
  40. package/server/utils/access-mail.ts +72 -28
  41. package/server/utils/capability-guard.ts +5 -2
  42. package/server/utils/content-adapter.ts +75 -24
  43. package/server/utils/migrations.ts +0 -0
  44. package/server/utils/pending-migrations.ts +0 -0
  45. package/server/utils/service-client.ts +3 -1
  46. package/server/utils/sign-in-link.ts +25 -9
  47. package/server/utils/typegen.ts +6 -4
  48. package/server/utils/validate.ts +4 -1
  49. package/shared/types/supabase.ts +62 -65
  50. package/tailwind.lint.css +9 -0
package/.prettierignore CHANGED
@@ -1,2 +1,8 @@
1
1
  # Agent skill files keep their own formatting. Prettier must not rewrite them.
2
2
  .agents/**/*.md
3
+
4
+ # Generated by release-please.
5
+ CHANGELOG.md
6
+
7
+ # Generated by scripts/supabase-typegen.ts.
8
+ shared/types/supabase.ts
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.15.2](https://github.com/plutocms/supabase/compare/v0.15.1...v0.15.2) (2026-10-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **deps:** use @plutocms/pluto 0.32 ([#117](https://github.com/plutocms/supabase/issues/117)) ([51e841d](https://github.com/plutocms/supabase/commit/51e841dc33cd887b22e14d05335eaa75a953ed09))
9
+
10
+ ## [0.15.1](https://github.com/plutocms/supabase/compare/v0.15.0...v0.15.1) (2026-10-28)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **deps:** use @plutocms/pluto 0.30; Prettier formatting and canonical Tailwind classes ([#111](https://github.com/plutocms/supabase/issues/111)) ([925c138](https://github.com/plutocms/supabase/commit/925c1386ad2c4380e2e3217766c0ae342d96cae8))
16
+
3
17
  ## [0.15.0](https://github.com/plutocms/supabase/compare/v0.14.1...v0.15.0) (2026-10-28)
4
18
 
5
19
 
@@ -23,7 +23,9 @@ async function remove() {
23
23
  working.value = true
24
24
 
25
25
  try {
26
- await $fetch(`/api/_pluto/supabase/access/roles/${props.role.key}`, { method: 'DELETE' })
26
+ await $fetch(`/api/_pluto/supabase/access/roles/${props.role.key}`, {
27
+ method: 'DELETE',
28
+ })
27
29
 
28
30
  toast.add({
29
31
  title: t('supabase.access.toasts.roleDeleted'),
@@ -52,7 +54,9 @@ async function remove() {
52
54
  >
53
55
  <template #body>
54
56
  <UAlert
55
- :description="t('supabase.access.deleteRole.usersLose', role.user_count)"
57
+ :description="
58
+ t('supabase.access.deleteRole.usersLose', role.user_count)
59
+ "
56
60
  :color="role.user_count > 0 ? 'warning' : 'neutral'"
57
61
  variant="subtle"
58
62
  icon="lucide:users"
@@ -61,10 +65,19 @@ async function remove() {
61
65
 
62
66
  <template #footer>
63
67
  <div class="flex w-full justify-end gap-2">
64
- <UButton color="neutral" variant="outline" @click="emit('close', false)">
68
+ <UButton
69
+ color="neutral"
70
+ variant="outline"
71
+ @click="emit('close', false)"
72
+ >
65
73
  {{ t('pluto.common.cancel') }}
66
74
  </UButton>
67
- <UButton :loading="working" color="error" icon="lucide:trash-2" @click="remove">
75
+ <UButton
76
+ :loading="working"
77
+ color="error"
78
+ icon="lucide:trash-2"
79
+ @click="remove"
80
+ >
68
81
  {{ t('supabase.access.deleteRole.confirm') }}
69
82
  </UButton>
70
83
  </div>
@@ -3,7 +3,11 @@
3
3
  // roles of the signed-in admin loads the permissions again, so the menu and
4
4
  // the tabs follow at once.
5
5
  import type { AccessRole, AccessUser } from '../../../shared/types/access'
6
- import { ADMIN_ROLE_KEY, sameKeys, userDisplayName } from '../../utils/access-ui'
6
+ import {
7
+ ADMIN_ROLE_KEY,
8
+ sameKeys,
9
+ userDisplayName,
10
+ } from '../../utils/access-ui'
7
11
 
8
12
  const props = defineProps<{
9
13
  user: AccessUser | null
@@ -43,14 +47,16 @@ const items = computed(() =>
43
47
  )
44
48
 
45
49
  const name = computed(() => (props.user ? userDisplayName(props.user) : ''))
46
- const unchanged = computed(() => !props.user || sameKeys(roleKeys.value, props.user.roles))
50
+ const unchanged = computed(
51
+ () => !props.user || sameKeys(roleKeys.value, props.user.roles)
52
+ )
47
53
 
48
54
  // The warning shows when the admin removes their own admin role.
49
55
  const removesOwnAdmin = computed(
50
56
  () =>
51
- props.self
52
- && (props.user?.roles.includes(ADMIN_ROLE_KEY) ?? false)
53
- && !roleKeys.value.includes(ADMIN_ROLE_KEY)
57
+ props.self &&
58
+ (props.user?.roles.includes(ADMIN_ROLE_KEY) ?? false) &&
59
+ !roleKeys.value.includes(ADMIN_ROLE_KEY)
54
60
  )
55
61
 
56
62
  async function save() {
@@ -85,16 +85,21 @@ async function submit() {
85
85
  mailProblem.value = null
86
86
 
87
87
  try {
88
- const result = await $fetch<AccessInviteResult>('/api/_pluto/supabase/access/users/invite', {
89
- method: 'POST',
90
- body: { ...body(), locale: emailLocale.value },
91
- })
88
+ const result = await $fetch<AccessInviteResult>(
89
+ '/api/_pluto/supabase/access/users/invite',
90
+ {
91
+ method: 'POST',
92
+ body: { ...body(), locale: emailLocale.value },
93
+ }
94
+ )
92
95
 
93
96
  emit('done')
94
97
 
95
98
  if (result.status === 'granted') {
96
99
  toast.add({
97
- title: t('supabase.access.toasts.granted', { email: email.value.trim() }),
100
+ title: t('supabase.access.toasts.granted', {
101
+ email: email.value.trim(),
102
+ }),
98
103
  color: 'success',
99
104
  icon: 'lucide:circle-check',
100
105
  })
@@ -103,13 +108,17 @@ async function submit() {
103
108
  // The user exists and has the roles. Only the email failed.
104
109
  link.value = result.link
105
110
  toast.add({
106
- title: t('supabase.access.toasts.emailFailed', { email: email.value.trim() }),
111
+ title: t('supabase.access.toasts.emailFailed', {
112
+ email: email.value.trim(),
113
+ }),
107
114
  color: 'warning',
108
115
  icon: 'lucide:triangle-alert',
109
116
  })
110
117
  } else {
111
118
  toast.add({
112
- title: t('supabase.access.toasts.invited', { email: email.value.trim() }),
119
+ title: t('supabase.access.toasts.invited', {
120
+ email: email.value.trim(),
121
+ }),
113
122
  color: 'success',
114
123
  icon: 'lucide:circle-check',
115
124
  })
@@ -139,10 +148,13 @@ async function copyLink() {
139
148
  submitting.value = true
140
149
 
141
150
  try {
142
- const result = await $fetch<{ status: 'invited' | 'granted', link: string | null }>(
143
- '/api/_pluto/supabase/access/users/invite-link',
144
- { method: 'POST', body: body() }
145
- )
151
+ const result = await $fetch<{
152
+ status: 'invited' | 'granted'
153
+ link: string | null
154
+ }>('/api/_pluto/supabase/access/users/invite-link', {
155
+ method: 'POST',
156
+ body: body(),
157
+ })
146
158
 
147
159
  mailProblem.value = null
148
160
  emit('done')
@@ -180,8 +192,16 @@ async function setupEmail() {
180
192
  <template>
181
193
  <UModal
182
194
  v-model:open="open"
183
- :title="link ? t('supabase.access.invite.linkTitle') : t('supabase.access.invite.title')"
184
- :description="link ? t('supabase.access.invite.linkDescription') : t('supabase.access.invite.description')"
195
+ :title="
196
+ link
197
+ ? t('supabase.access.invite.linkTitle')
198
+ : t('supabase.access.invite.title')
199
+ "
200
+ :description="
201
+ link
202
+ ? t('supabase.access.invite.linkDescription')
203
+ : t('supabase.access.invite.description')
204
+ "
185
205
  >
186
206
  <template #body>
187
207
  <AccessLinkField v-if="link" :link="link" />
@@ -219,10 +239,7 @@ async function setupEmail() {
219
239
  </template>
220
240
  </UAlert>
221
241
 
222
- <p
223
- v-if="mailProblem && !canCopyLink"
224
- class="text-muted text-sm"
225
- >
242
+ <p v-if="mailProblem && !canCopyLink" class="text-muted text-sm">
226
243
  {{ t('supabase.access.invite.needsKey') }}
227
244
  </p>
228
245
 
@@ -248,7 +265,11 @@ async function setupEmail() {
248
265
 
249
266
  <UFormField
250
267
  :label="t('supabase.access.invite.roles')"
251
- :error="showRolesError && roleKeys.length === 0 ? t('supabase.access.invite.rolesRequired') : undefined"
268
+ :error="
269
+ showRolesError && roleKeys.length === 0
270
+ ? t('supabase.access.invite.rolesRequired')
271
+ : undefined
272
+ "
252
273
  required
253
274
  >
254
275
  <UCheckboxGroup v-model="roleKeys" :items="roleItems" />
@@ -258,7 +279,11 @@ async function setupEmail() {
258
279
  :label="t('supabase.access.invite.locale')"
259
280
  :description="t('supabase.access.invite.localeHint')"
260
281
  >
261
- <USelect v-model="emailLocale" :items="localeItems" class="w-full sm:w-64" />
282
+ <USelect
283
+ v-model="emailLocale"
284
+ :items="localeItems"
285
+ class="w-full sm:w-64"
286
+ />
262
287
  </UFormField>
263
288
 
264
289
  <!-- Enter in a field submits the form. -->
@@ -9,10 +9,21 @@ const { t } = usePlutoI18n()
9
9
  </script>
10
10
 
11
11
  <template>
12
- <UFormField :label="t('supabase.access.link.label')" :help="t('supabase.access.link.note')">
13
- <UInput :model-value="link" class="w-full" readonly @focus="($event.target as HTMLInputElement).select()">
12
+ <UFormField
13
+ :label="t('supabase.access.link.label')"
14
+ :help="t('supabase.access.link.note')"
15
+ >
16
+ <UInput
17
+ :model-value="link"
18
+ class="w-full"
19
+ readonly
20
+ @focus="($event.target as HTMLInputElement).select()"
21
+ >
14
22
  <template #trailing>
15
- <AccessCopyButton :text="link" :label="t('supabase.access.link.copy')" />
23
+ <AccessCopyButton
24
+ :text="link"
25
+ :label="t('supabase.access.link.copy')"
26
+ />
16
27
  </template>
17
28
  </UInput>
18
29
  </UFormField>
@@ -20,7 +20,11 @@ const { t } = usePlutoI18n()
20
20
 
21
21
  <template #footer>
22
22
  <div class="flex w-full justify-end">
23
- <UButton color="neutral" variant="outline" @click="emit('close', false)">
23
+ <UButton
24
+ color="neutral"
25
+ variant="outline"
26
+ @click="emit('close', false)"
27
+ >
24
28
  {{ t('pluto.common.close') }}
25
29
  </UButton>
26
30
  </div>
@@ -27,7 +27,9 @@ async function revoke() {
27
27
  working.value = true
28
28
 
29
29
  try {
30
- await $fetch(`/api/_pluto/supabase/access/users/${props.user.id}/roles`, { method: 'DELETE' })
30
+ await $fetch(`/api/_pluto/supabase/access/users/${props.user.id}/roles`, {
31
+ method: 'DELETE',
32
+ })
31
33
 
32
34
  toast.add({
33
35
  title: t('supabase.access.toasts.revoked'),
@@ -56,7 +58,9 @@ async function revoke() {
56
58
  <template>
57
59
  <UModal
58
60
  :title="t('supabase.access.revoke.title')"
59
- :description="t('supabase.access.revoke.body', { name: userDisplayName(user) })"
61
+ :description="
62
+ t('supabase.access.revoke.body', { name: userDisplayName(user) })
63
+ "
60
64
  :close="{ onClick: () => emit('close', false) }"
61
65
  >
62
66
  <template #body>
@@ -71,10 +75,19 @@ async function revoke() {
71
75
 
72
76
  <template #footer>
73
77
  <div class="flex w-full justify-end gap-2">
74
- <UButton color="neutral" variant="outline" @click="emit('close', false)">
78
+ <UButton
79
+ color="neutral"
80
+ variant="outline"
81
+ @click="emit('close', false)"
82
+ >
75
83
  {{ t('pluto.common.cancel') }}
76
84
  </UButton>
77
- <UButton :loading="working" color="error" icon="lucide:user-x" @click="revoke">
85
+ <UButton
86
+ :loading="working"
87
+ color="error"
88
+ icon="lucide:user-x"
89
+ @click="revoke"
90
+ >
78
91
  {{ t('supabase.access.revoke.confirm') }}
79
92
  </UButton>
80
93
  </div>
@@ -94,7 +94,10 @@ function groupValue(group: CapabilityGroup): string[] {
94
94
  function setGroup(group: CapabilityGroup, value: string[]) {
95
95
  const keys = new Set(group.items.map((item) => item.key))
96
96
 
97
- selected.value = [...selected.value.filter((current) => !keys.has(current)), ...value]
97
+ selected.value = [
98
+ ...selected.value.filter((current) => !keys.has(current)),
99
+ ...value,
100
+ ]
98
101
  }
99
102
 
100
103
  const keyError = computed(() =>
@@ -112,11 +115,11 @@ async function save() {
112
115
  submitted.value = true
113
116
 
114
117
  if (
115
- saving.value
116
- || fullAccess.value
117
- || !label.value.trim()
118
- || keyError.value
119
- || capabilitiesError.value
118
+ saving.value ||
119
+ fullAccess.value ||
120
+ !label.value.trim() ||
121
+ keyError.value ||
122
+ capabilitiesError.value
120
123
  ) {
121
124
  return
122
125
  }
@@ -136,7 +139,10 @@ async function save() {
136
139
  body: { ...body, key: key.value },
137
140
  })
138
141
  } else {
139
- await $fetch(`/api/_pluto/supabase/access/roles/${props.role!.key}`, { method: 'PUT', body })
142
+ await $fetch(`/api/_pluto/supabase/access/roles/${props.role!.key}`, {
143
+ method: 'PUT',
144
+ body,
145
+ })
140
146
  }
141
147
 
142
148
  toast.add({
@@ -180,7 +186,11 @@ async function save() {
180
186
  <form v-else class="flex flex-col gap-y-5" @submit.prevent="save">
181
187
  <UFormField
182
188
  :label="t('supabase.access.role.label')"
183
- :error="submitted && !label.trim() ? t('supabase.access.errors.invalidLabel') : undefined"
189
+ :error="
190
+ submitted && !label.trim()
191
+ ? t('supabase.access.errors.invalidLabel')
192
+ : undefined
193
+ "
184
194
  required
185
195
  >
186
196
  <UInput
@@ -222,8 +232,14 @@ async function save() {
222
232
  required
223
233
  >
224
234
  <div class="flex flex-col gap-y-4">
225
- <div v-for="group in groups" :key="group.id" class="flex flex-col gap-y-2">
226
- <h3 class="text-highlighted text-sm font-semibold">{{ group.label }}</h3>
235
+ <div
236
+ v-for="group in groups"
237
+ :key="group.id"
238
+ class="flex flex-col gap-y-2"
239
+ >
240
+ <h3 class="text-highlighted text-sm font-semibold">
241
+ {{ group.label }}
242
+ </h3>
227
243
  <UCheckboxGroup
228
244
  :model-value="groupValue(group)"
229
245
  :items="groupItems(group)"
@@ -242,7 +258,12 @@ async function save() {
242
258
  <UButton color="neutral" variant="outline" @click="open = false">
243
259
  {{ fullAccess ? t('pluto.common.close') : t('pluto.common.cancel') }}
244
260
  </UButton>
245
- <UButton v-if="!fullAccess" :loading="saving" icon="lucide:save" @click="save">
261
+ <UButton
262
+ v-if="!fullAccess"
263
+ :loading="saving"
264
+ icon="lucide:save"
265
+ @click="save"
266
+ >
246
267
  {{ t('supabase.access.role.save') }}
247
268
  </UButton>
248
269
  </div>
@@ -12,24 +12,68 @@ import {
12
12
 
13
13
  const { t } = usePlutoI18n()
14
14
 
15
- const projectRef = computed(() => supabaseProjectRef(useRuntimeConfig().public.supabase?.url))
16
- const links = computed(() => (projectRef.value ? supabaseDashboardLinks(projectRef.value) : null))
15
+ const projectRef = computed(() =>
16
+ supabaseProjectRef(useRuntimeConfig().public.supabase?.url)
17
+ )
18
+ const links = computed(() =>
19
+ projectRef.value ? supabaseDashboardLinks(projectRef.value) : null
20
+ )
17
21
 
18
22
  const smtpRows = computed(() => [
19
- { key: 'host', label: t('supabase.mail.authEmails.host'), value: RESEND_SMTP.host, copy: RESEND_SMTP.host },
20
- { key: 'port', label: t('supabase.mail.authEmails.port'), value: RESEND_SMTP.port, copy: RESEND_SMTP.port },
21
- { key: 'username', label: t('supabase.mail.authEmails.username'), value: RESEND_SMTP.username, copy: RESEND_SMTP.username },
23
+ {
24
+ key: 'host',
25
+ label: t('supabase.mail.authEmails.host'),
26
+ value: RESEND_SMTP.host,
27
+ copy: RESEND_SMTP.host,
28
+ },
29
+ {
30
+ key: 'port',
31
+ label: t('supabase.mail.authEmails.port'),
32
+ value: RESEND_SMTP.port,
33
+ copy: RESEND_SMTP.port,
34
+ },
35
+ {
36
+ key: 'username',
37
+ label: t('supabase.mail.authEmails.username'),
38
+ value: RESEND_SMTP.username,
39
+ copy: RESEND_SMTP.username,
40
+ },
22
41
  // The password is the API key. Pluto never shows it, so there is nothing to copy.
23
- { key: 'password', label: t('supabase.mail.authEmails.password'), value: t('supabase.mail.authEmails.passwordValue'), copy: null },
42
+ {
43
+ key: 'password',
44
+ label: t('supabase.mail.authEmails.password'),
45
+ value: t('supabase.mail.authEmails.passwordValue'),
46
+ copy: null,
47
+ },
24
48
  ])
25
49
 
26
50
  const dashboardLinks = computed(() =>
27
51
  links.value
28
52
  ? [
29
- { key: 'smtp', label: t('supabase.mail.authEmails.dashboardSmtp'), to: links.value.smtp, icon: 'lucide:mail' },
30
- { key: 'templates', label: t('supabase.mail.authEmails.dashboardTemplates'), to: links.value.templates, icon: 'lucide:layout-template' },
31
- { key: 'rateLimits', label: t('supabase.mail.authEmails.dashboardRateLimits'), to: links.value.rateLimits, icon: 'lucide:gauge' },
32
- { key: 'project', label: t('supabase.mail.authEmails.dashboardHome'), to: links.value.project, icon: 'lucide:external-link' },
53
+ {
54
+ key: 'smtp',
55
+ label: t('supabase.mail.authEmails.dashboardSmtp'),
56
+ to: links.value.smtp,
57
+ icon: 'lucide:mail',
58
+ },
59
+ {
60
+ key: 'templates',
61
+ label: t('supabase.mail.authEmails.dashboardTemplates'),
62
+ to: links.value.templates,
63
+ icon: 'lucide:layout-template',
64
+ },
65
+ {
66
+ key: 'rateLimits',
67
+ label: t('supabase.mail.authEmails.dashboardRateLimits'),
68
+ to: links.value.rateLimits,
69
+ icon: 'lucide:gauge',
70
+ },
71
+ {
72
+ key: 'project',
73
+ label: t('supabase.mail.authEmails.dashboardHome'),
74
+ to: links.value.project,
75
+ icon: 'lucide:external-link',
76
+ },
33
77
  ]
34
78
  : []
35
79
  )
@@ -40,8 +84,12 @@ const dashboardLinks = computed(() =>
40
84
  <p class="text-muted text-sm">{{ t('supabase.mail.authEmails.intro') }}</p>
41
85
 
42
86
  <section class="flex flex-col gap-y-2">
43
- <h3 class="text-highlighted text-sm font-semibold">{{ t('supabase.mail.authEmails.resendTitle') }}</h3>
44
- <p class="text-muted text-sm">{{ t('supabase.mail.authEmails.resendBody') }}</p>
87
+ <h3 class="text-highlighted text-sm font-semibold">
88
+ {{ t('supabase.mail.authEmails.resendTitle') }}
89
+ </h3>
90
+ <p class="text-muted text-sm">
91
+ {{ t('supabase.mail.authEmails.resendBody') }}
92
+ </p>
45
93
  <div>
46
94
  <UButton
47
95
  :to="RESEND_SUPABASE_INTEGRATION_URL"
@@ -57,24 +105,41 @@ const dashboardLinks = computed(() =>
57
105
  </section>
58
106
 
59
107
  <section class="flex flex-col gap-y-2">
60
- <h3 class="text-highlighted text-sm font-semibold">{{ t('supabase.mail.authEmails.manualTitle') }}</h3>
61
- <p class="text-muted text-sm">{{ t('supabase.mail.authEmails.manualBody') }}</p>
108
+ <h3 class="text-highlighted text-sm font-semibold">
109
+ {{ t('supabase.mail.authEmails.manualTitle') }}
110
+ </h3>
111
+ <p class="text-muted text-sm">
112
+ {{ t('supabase.mail.authEmails.manualBody') }}
113
+ </p>
62
114
 
63
- <ul class="border-default bg-elevated/50 divide-default divide-y rounded-md border">
64
- <li v-for="row in smtpRows" :key="row.key" class="flex items-center gap-3 px-3 py-2">
115
+ <ul
116
+ class="border-default bg-elevated/50 divide-default divide-y rounded-md border"
117
+ >
118
+ <li
119
+ v-for="row in smtpRows"
120
+ :key="row.key"
121
+ class="flex items-center gap-3 px-3 py-2"
122
+ >
65
123
  <span class="text-muted w-24 shrink-0 text-sm">{{ row.label }}</span>
66
- <code class="min-w-0 flex-1 overflow-x-auto text-sm whitespace-nowrap">{{ row.value }}</code>
124
+ <code
125
+ class="min-w-0 flex-1 overflow-x-auto text-sm whitespace-nowrap"
126
+ >{{ row.value }}</code
127
+ >
67
128
  <AccessCopyButton
68
129
  v-if="row.copy"
69
130
  :text="row.copy"
70
- :label="t('supabase.mail.authEmails.copyValue', { label: row.label })"
131
+ :label="
132
+ t('supabase.mail.authEmails.copyValue', { label: row.label })
133
+ "
71
134
  />
72
135
  </li>
73
136
  </ul>
74
137
  </section>
75
138
 
76
139
  <section class="flex flex-col gap-y-2">
77
- <h3 class="text-highlighted text-sm font-semibold">{{ t('supabase.mail.authEmails.dashboardTitle') }}</h3>
140
+ <h3 class="text-highlighted text-sm font-semibold">
141
+ {{ t('supabase.mail.authEmails.dashboardTitle') }}
142
+ </h3>
78
143
 
79
144
  <div v-if="dashboardLinks.length > 0" class="flex flex-wrap gap-2">
80
145
  <UButton
@@ -91,7 +156,9 @@ const dashboardLinks = computed(() =>
91
156
  {{ link.label }}
92
157
  </UButton>
93
158
  </div>
94
- <p v-else class="text-muted text-sm">{{ t('supabase.mail.authEmails.noProject') }}</p>
159
+ <p v-else class="text-muted text-sm">
160
+ {{ t('supabase.mail.authEmails.noProject') }}
161
+ </p>
95
162
 
96
163
  <p v-if="dashboardLinks.length > 0" class="text-muted text-xs">
97
164
  {{ t('supabase.mail.authEmails.dashboardNote') }}
@@ -14,9 +14,9 @@ const title = computed(() =>
14
14
 
15
15
  const isVisible = computed(
16
16
  () =>
17
- Boolean(status.value)
18
- && pendingCount.value > 0
19
- && !route.path.startsWith('/admin/migrations')
17
+ Boolean(status.value) &&
18
+ pendingCount.value > 0 &&
19
+ !route.path.startsWith('/admin/migrations')
20
20
  )
21
21
  </script>
22
22
 
@@ -30,7 +30,8 @@ const isVisible = computed(
30
30
  icon="lucide:triangle-alert"
31
31
  >
32
32
  <template #description>
33
- <ULink to="/admin/migrations" class="underline"> {{ t('supabase.migrations.goTo') }} </ULink
33
+ <ULink to="/admin/migrations" class="underline">
34
+ {{ t('supabase.migrations.goTo') }} </ULink
34
35
  >.
35
36
  </template>
36
37
  </UAlert>
@@ -73,10 +73,13 @@ export function useMigrations() {
73
73
  let result: RunMigrationsResponse
74
74
 
75
75
  try {
76
- result = await $fetch<RunMigrationsResponse>('/api/_pluto/supabase/migrations/run', {
77
- method: 'POST',
78
- body: connectionString ? { connectionString } : undefined,
79
- })
76
+ result = await $fetch<RunMigrationsResponse>(
77
+ '/api/_pluto/supabase/migrations/run',
78
+ {
79
+ method: 'POST',
80
+ body: connectionString ? { connectionString } : undefined,
81
+ }
82
+ )
80
83
  } catch (error) {
81
84
  // The route answers a failed run with an HTTP error that carries
82
85
  // `needsConnectionString` or `error` in its data. Turn it back into
@@ -84,13 +87,17 @@ export function useMigrations() {
84
87
  // (for example a dropped connection) goes on to the caller.
85
88
  const failure = failureData(error)
86
89
 
87
- if (failure?.needsConnectionString !== true && typeof failure?.error !== 'string') {
90
+ if (
91
+ failure?.needsConnectionString !== true &&
92
+ typeof failure?.error !== 'string'
93
+ ) {
88
94
  throw error
89
95
  }
90
96
 
91
97
  result = {
92
98
  success: false,
93
- needsConnectionString: failure.needsConnectionString === true ? true : undefined,
99
+ needsConnectionString:
100
+ failure.needsConnectionString === true ? true : undefined,
94
101
  error: typeof failure.error === 'string' ? failure.error : undefined,
95
102
  }
96
103
  }
@@ -86,11 +86,23 @@ async function submitForm() {
86
86
 
87
87
  <template>
88
88
  <PlutoAdminAuthCard
89
- :title="state === 'ready' ? t('supabase.auth.invite.title') : t('supabase.auth.invite.pageTitle')"
90
- :description="state === 'ready' ? t('supabase.auth.invite.description') : undefined"
89
+ :title="
90
+ state === 'ready'
91
+ ? t('supabase.auth.invite.title')
92
+ : t('supabase.auth.invite.pageTitle')
93
+ "
94
+ :description="
95
+ state === 'ready' ? t('supabase.auth.invite.description') : undefined
96
+ "
91
97
  >
92
- <div v-if="state === 'checking'" class="flex items-center justify-center py-10">
93
- <UIcon name="lucide:loader-circle" class="size-6 animate-spin text-muted" />
98
+ <div
99
+ v-if="state === 'checking'"
100
+ class="flex items-center justify-center py-10"
101
+ >
102
+ <UIcon
103
+ name="lucide:loader-circle"
104
+ class="size-6 animate-spin text-muted"
105
+ />
94
106
  </div>
95
107
 
96
108
  <div v-else-if="state !== 'ready'" class="space-y-6 py-10 text-center">
@@ -99,7 +111,11 @@ async function submitForm() {
99
111
  </span>
100
112
 
101
113
  <p>
102
- {{ state === 'expired' ? t('supabase.auth.invite.expired') : t('supabase.auth.invite.invalid') }}
114
+ {{
115
+ state === 'expired'
116
+ ? t('supabase.auth.invite.expired')
117
+ : t('supabase.auth.invite.invalid')
118
+ }}
103
119
  </p>
104
120
 
105
121
  <div class="flex items-center justify-center">