@plutocms/supabase 0.0.1-alpha.12 → 0.0.1-alpha.14

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.
@@ -100,6 +100,72 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
100
100
  if (authOptions?.route && authOptions.route.path.startsWith('/admin')) {
101
101
  return navigateTo('/admin/login')
102
102
  }
103
+
104
+ return navigateTo('/admin/login')
105
+ }
106
+
107
+ async function requestPasswordReset(email: string) {
108
+ isSubmitting.value = true
109
+
110
+ try {
111
+ await $fetch('/api/auth/reset-password', {
112
+ method: 'POST',
113
+ body: { email },
114
+ })
115
+
116
+ return { success: true }
117
+ } catch (error) {
118
+ toast.add({
119
+ title: 'Could not send reset email',
120
+ description:
121
+ error instanceof Error ? error.message : 'An unknown error occurred.',
122
+ icon: 'lucide:circle-x',
123
+ color: 'error',
124
+ })
125
+
126
+ return { success: false }
127
+ } finally {
128
+ isSubmitting.value = false
129
+ }
130
+ }
131
+
132
+ async function updatePassword(password: string) {
133
+ isSubmitting.value = true
134
+
135
+ try {
136
+ const { error } = await supabase.auth.updateUser({ password })
137
+
138
+ if (error) {
139
+ toast.add({
140
+ title: 'Could not update password',
141
+ description: error.message,
142
+ icon: 'lucide:circle-x',
143
+ color: 'error',
144
+ })
145
+
146
+ return { success: false }
147
+ }
148
+
149
+ toast.add({
150
+ title: 'Password updated successfully',
151
+ icon: 'lucide:check',
152
+ color: 'success',
153
+ })
154
+
155
+ return { success: true }
156
+ } catch (error) {
157
+ toast.add({
158
+ title: 'Could not update password',
159
+ description:
160
+ error instanceof Error ? error.message : 'An unknown error occurred.',
161
+ icon: 'lucide:circle-x',
162
+ color: 'error',
163
+ })
164
+
165
+ return { success: false }
166
+ } finally {
167
+ isSubmitting.value = false
168
+ }
103
169
  }
104
170
 
105
171
  return {
@@ -108,5 +174,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
108
174
  user,
109
175
  login,
110
176
  logout,
177
+ requestPasswordReset,
178
+ updatePassword,
111
179
  }
112
180
  }
@@ -5,10 +5,15 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
5
5
  route: to as RouteLocationNormalizedGeneric,
6
6
  })
7
7
 
8
- if (
9
- isLoggedIn.value &&
10
- (to.path === '/admin/login' || to.path === '/admin/signup')
11
- ) {
8
+ const allowedUnauthenticatedPaths = [
9
+ '/admin/login',
10
+ '/admin/signup',
11
+ '/admin/confirm',
12
+ '/admin/setup',
13
+ '/admin/forgot-password',
14
+ ]
15
+
16
+ if (isLoggedIn.value && allowedUnauthenticatedPaths.includes(to.path)) {
12
17
  return navigateTo(
13
18
  to.query.redirect
14
19
  ? decodeURIComponent(to.query.redirect as string)
@@ -19,14 +24,15 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
19
24
  if (
20
25
  !isLoggedIn.value &&
21
26
  to.path.startsWith('/admin') &&
22
- to.path !== '/admin/confirm' &&
23
- to.path !== '/admin/signup' &&
24
- to.path !== '/admin/login'
27
+ !allowedUnauthenticatedPaths.includes(to.path)
25
28
  ) {
26
29
  if (from.query.redirect) {
27
- logout({ redirectTo: from.query.redirect as string, showToast: true })
30
+ return logout({
31
+ redirectTo: from.query.redirect as string,
32
+ showToast: true,
33
+ })
28
34
  } else {
29
- logout()
35
+ return logout()
30
36
  }
31
37
  }
32
38
  })
@@ -0,0 +1,88 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: 'auth',
4
+ })
5
+
6
+ useHead({
7
+ title: 'Forgot Password',
8
+ })
9
+
10
+ const { isSubmitting, requestPasswordReset } = await useAuth()
11
+
12
+ const email = ref('')
13
+ const isEmailSent = ref(false)
14
+
15
+ async function submitForm() {
16
+ const result = await requestPasswordReset(email.value)
17
+
18
+ if (result.success) {
19
+ isEmailSent.value = true
20
+ }
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <div class="grid h-full place-items-center">
26
+ <UCard class="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>
36
+
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>
42
+
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>
48
+ </div>
49
+ </div>
50
+
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>
59
+
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>
70
+
71
+ <div class="flex items-center justify-between gap-x-4">
72
+ <UFormField>
73
+ <UButton :loading="isSubmitting" type="submit" icon="lucide:send">
74
+ Send Reset Link
75
+ </UButton>
76
+ </UFormField>
77
+
78
+ <UFormField>
79
+ <UButton variant="link" to="/admin/login" class="px-0">
80
+ Back to Login
81
+ </UButton>
82
+ </UFormField>
83
+ </div>
84
+ </div>
85
+ </form>
86
+ </UCard>
87
+ </div>
88
+ </template>
@@ -67,7 +67,7 @@ function submitForm() {
67
67
  </UFormField>
68
68
  </div>
69
69
 
70
- <div>
70
+ <div class="flex items-center justify-between">
71
71
  <UButton
72
72
  variant="link"
73
73
  icon="lucide:arrow-left"
@@ -76,6 +76,10 @@ function submitForm() {
76
76
  >
77
77
  Back to Home
78
78
  </UButton>
79
+
80
+ <UButton variant="link" to="/admin/forgot-password" class="px-0">
81
+ Forgot password?
82
+ </UButton>
79
83
  </div>
80
84
  </div>
81
85
  </form>
@@ -0,0 +1,120 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: 'auth',
4
+ })
5
+
6
+ useHead({
7
+ title: 'Update Password',
8
+ })
9
+
10
+ const { isSubmitting, updatePassword } = await useAuth()
11
+
12
+ const form = ref({
13
+ password: '',
14
+ confirm_password: '',
15
+ })
16
+
17
+ const passwordMatch = computed(() => {
18
+ return form.value.password === form.value.confirm_password
19
+ })
20
+
21
+ const isPasswordUpdated = ref(false)
22
+
23
+ async function submitForm() {
24
+ if (!passwordMatch.value) {
25
+ return
26
+ }
27
+
28
+ const result = await updatePassword(form.value.password)
29
+
30
+ if (result.success) {
31
+ isPasswordUpdated.value = true
32
+ }
33
+ }
34
+ </script>
35
+
36
+ <template>
37
+ <div class="grid h-full place-items-center">
38
+ <UCard class="w-100">
39
+ <div v-if="isPasswordUpdated" class="space-y-6 py-10">
40
+ <div class="text-center space-y-4">
41
+ <hgroup class="flex flex-col gap-y-1">
42
+ <span class="text-5xl">
43
+ <UIcon name="lucide:lock-keyhole" class="inline-block" />
44
+ </span>
45
+
46
+ <h1 class="text-3xl font-bold">Password Updated!</h1>
47
+ </hgroup>
48
+
49
+ <p>
50
+ Your password has been changed. You can now log in with your new
51
+ password.
52
+ </p>
53
+
54
+ <div class="flex items-center justify-center">
55
+ <UButton to="/admin/login" color="primary" icon="lucide:log-in">
56
+ Go to Login
57
+ </UButton>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <form v-else @submit.prevent="submitForm">
63
+ <div class="flex flex-col gap-y-6">
64
+ <hgroup class="flex flex-col gap-y-1">
65
+ <h1 class="text-3xl font-bold">Update Password</h1>
66
+ <p class="text-sm text-muted">Enter your new password below.</p>
67
+ </hgroup>
68
+
69
+ <UFormField label="New Password">
70
+ <UInput
71
+ v-model="form.password"
72
+ placeholder="••••"
73
+ type="password"
74
+ class="w-full"
75
+ autocomplete="new-password"
76
+ required
77
+ />
78
+ </UFormField>
79
+
80
+ <UFormField
81
+ :error="
82
+ form.confirm_password && !passwordMatch
83
+ ? 'Passwords do not match.'
84
+ : undefined
85
+ "
86
+ label="Confirm New Password"
87
+ >
88
+ <UInput
89
+ v-model="form.confirm_password"
90
+ placeholder="••••"
91
+ type="password"
92
+ class="w-full"
93
+ autocomplete="new-password"
94
+ required
95
+ />
96
+ </UFormField>
97
+
98
+ <div class="flex items-center justify-between gap-x-4">
99
+ <UFormField>
100
+ <UButton
101
+ :loading="isSubmitting"
102
+ :disabled="!passwordMatch"
103
+ type="submit"
104
+ icon="lucide:lock-keyhole"
105
+ >
106
+ Update Password
107
+ </UButton>
108
+ </UFormField>
109
+
110
+ <UFormField>
111
+ <UButton variant="link" to="/admin/login" class="px-0">
112
+ Back to Login
113
+ </UButton>
114
+ </UFormField>
115
+ </div>
116
+ </div>
117
+ </form>
118
+ </UCard>
119
+ </div>
120
+ </template>