Files
supabase/apps/ui-library/public/r/password-based-auth-nuxtjs.json
T
Ivan Vasilov 7fd37e6150 chore: Bump shadcn (#50517)
This PR bumps `shadcn` and regenerates all blocks with the latest CLI.
The blocks have no meaningful change (only a json property reorder).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated internal development tooling used by the UI library and Vue
blocks.
* **Tests**
* Improved type consistency in registry-related test utilities,
supporting more reliable validation without changing user-facing
behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 10:38:49 +10:00

91 lines
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "password-based-auth-nuxtjs",
"title": "Password Based Auth flow for Nuxt.js and Supabase",
"description": "Password Based Auth flow for Nuxt.js and Supabase",
"dependencies": [
"@supabase/ssr@latest",
"@supabase/supabase-js@latest"
],
"registryDependencies": [
"button",
"card",
"input",
"label"
],
"files": [
{
"path": "registry/default/password-based-auth/nuxtjs/app/components/login-form.vue",
"content": "<script setup lang=\"ts\">\nimport { ref } from \"vue\"\nimport { createClient } from \"@/lib/supabase/client\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\n\nconst email = ref(\"\")\nconst error = ref<string | null>(null)\nconst success = ref(false)\nconst isLoading = ref(false)\n\nconst handleForgotPassword = async (e: Event) => {\n e.preventDefault()\n const supabase = createClient()\n isLoading.value = true\n error.value = null\n\n try {\n const { error: supabaseError } = await supabase.auth.resetPasswordForEmail(email.value, {\n redirectTo: \"http://localhost:3000/update-password\",\n })\n if (supabaseError) throw supabaseError\n success.value = true\n } catch (err: unknown) {\n error.value = err instanceof Error ? err.message : \"An error occurred\"\n } finally {\n isLoading.value = false\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-6\">\n <Card v-if=\"success\">\n <CardHeader>\n <CardTitle class=\"text-2xl\">Check Your Email</CardTitle>\n <CardDescription>Password reset instructions sent</CardDescription>\n </CardHeader>\n <CardContent>\n <p class=\"text-sm text-muted-foreground\">\n If you registered using your email and password, you will receive a password reset email.\n </p>\n </CardContent>\n </Card>\n\n <Card v-else>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we&apos;ll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form @submit=\"handleForgotPassword\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n v-model=\"email\"\n />\n </div>\n <p v-if=\"error\" class=\"text-sm text-red-500\">{{ error }}</p>\n <Button type=\"submit\" class=\"w-full\" :disabled=\"isLoading\">\n {{ isLoading ? \"Sending...\" : \"Send reset email\" }}\n </Button>\n </div>\n <div class=\"mt-4 text-center text-sm\">\n Already have an account?\n <a href=\"/login\" class=\"underline underline-offset-4\">Sign in</a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/components/login-form.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/components/sign-up-form.vue",
"content": "<script setup lang=\"ts\">\nimport { ref } from \"vue\"\nimport { createClient } from \"@/lib/supabase/client\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\n\nconst email = ref(\"\")\nconst password = ref(\"\")\nconst repeatPassword = ref(\"\")\nconst error = ref<string | null>(null)\nconst isLoading = ref(false)\nconst success = ref(false)\n\nconst handleSignUp = async () => {\n const supabase = createClient()\n error.value = null\n\n if (password.value !== repeatPassword.value) {\n error.value = \"Passwords do not match\"\n return\n }\n\n isLoading.value = true\n try {\n const { error: supabaseError } = await supabase.auth.signUp({\n email: email.value,\n password: password.value,\n })\n if (supabaseError) throw supabaseError\n success.value = true\n } catch (err: unknown) {\n error.value = err instanceof Error ? err.message : \"An error occurred\"\n } finally {\n isLoading.value = false\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-6\">\n <Card v-if=\"success\">\n <CardHeader>\n <CardTitle class=\"text-2xl\">Thank you for signing up!</CardTitle>\n <CardDescription>Check your email to confirm</CardDescription>\n </CardHeader>\n <CardContent>\n <p class=\"text-sm text-muted-foreground\">\n You've successfully signed up. Please check your email to confirm your account before\n signing in.\n </p>\n </CardContent>\n </Card>\n\n <Card v-else>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Sign up</CardTitle>\n <CardDescription>Create a new account</CardDescription>\n </CardHeader>\n <CardContent>\n <form @submit.prevent=\"handleSignUp\">\n <div class=\"flex flex-col gap-6\">\n <!-- Email -->\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n v-model=\"email\"\n />\n </div>\n\n <!-- Password -->\n <div class=\"grid gap-2\">\n <div class=\"flex items-center\">\n <Label for=\"password\">Password</Label>\n </div>\n <Input\n id=\"password\"\n type=\"password\"\n required\n v-model=\"password\"\n />\n </div>\n\n <!-- Repeat Password -->\n <div class=\"grid gap-2\">\n <div class=\"flex items-center\">\n <Label for=\"repeat-password\">Repeat Password</Label>\n </div>\n <Input\n id=\"repeat-password\"\n type=\"password\"\n required\n v-model=\"repeatPassword\"\n />\n </div>\n\n <!-- Error -->\n <p v-if=\"error\" class=\"text-sm text-red-500\">{{ error }}</p>\n\n <!-- Submit -->\n <Button type=\"submit\" class=\"w-full\" :disabled=\"isLoading\">\n {{ isLoading ? \"Creating an account...\" : \"Sign up\" }}\n </Button>\n </div>\n\n <div class=\"mt-4 text-center text-sm\">\n Already have an account?\n <a href=\"/login\" class=\"underline underline-offset-4\">Sign in</a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/components/sign-up-form.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/components/forgot-password-form.vue",
"content": "<script setup lang=\"ts\">\nimport { ref } from \"vue\"\nimport { createClient } from \"@/lib/supabase/client\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\n\n\nconst email = ref(\"\")\nconst error = ref<string | null>(null)\nconst success = ref(false)\nconst isLoading = ref(false)\n\nconst handleForgotPassword = async (e: Event) => {\n e.preventDefault()\n const supabase = createClient()\n isLoading.value = true\n error.value = null\n\n try {\n const { error: supabaseError } = await supabase.auth.resetPasswordForEmail(email.value, {\n redirectTo: \"http://localhost:3000/update-password\",\n })\n if (supabaseError) throw supabaseError\n success.value = true\n } catch (err: unknown) {\n error.value = err instanceof Error ? err.message : \"An error occurred\"\n } finally {\n isLoading.value = false\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-6\">\n <Card v-if=\"success\">\n <CardHeader>\n <CardTitle class=\"text-2xl\">Check Your Email</CardTitle>\n <CardDescription>Password reset instructions sent</CardDescription>\n </CardHeader>\n <CardContent>\n <p class=\"text-sm text-muted-foreground\">\n If you registered using your email and password, you will receive a password reset email.\n </p>\n </CardContent>\n </Card>\n\n <Card v-else>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we&apos;ll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form @submit=\"handleForgotPassword\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"grid gap-2\">\n <Label for=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n v-model=\"email\"\n />\n </div>\n <p v-if=\"error\" class=\"text-sm text-red-500\">{{ error }}</p>\n <Button type=\"submit\" class=\"w-full\" :disabled=\"isLoading\">\n {{ isLoading ? \"Sending...\" : \"Send reset email\" }}\n </Button>\n </div>\n <div class=\"mt-4 text-center text-sm\">\n Already have an account?\n <a href=\"/login\" class=\"underline underline-offset-4\">Sign in</a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/components/forgot-password-form.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/components/update-password-form.vue",
"content": "<script setup lang=\"ts\">\nimport { ref } from \"vue\"\nimport { createClient } from \"@/lib/supabase/client\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\n\nconst password = ref(\"\")\nconst error = ref<string | null>(null)\nconst isLoading = ref(false)\n\nconst handleUpdatePassword = async () => {\n const supabase = createClient()\n isLoading.value = true\n error.value = null\n\n try {\n const { error: supabaseError } = await supabase.auth.updateUser({\n password: password.value,\n })\n if (supabaseError) throw supabaseError\n // Redirect user after successful password update\n location.href = \"/protected\"\n } catch (err: unknown) {\n error.value = err instanceof Error ? err.message : \"An error occurred\"\n } finally {\n isLoading.value = false\n }\n}\n</script>\n\n<template>\n <div class=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>Please enter your new password below.</CardDescription>\n </CardHeader>\n <CardContent>\n <form @submit.prevent=\"handleUpdatePassword\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"grid gap-2\">\n <Label for=\"password\">New password</Label>\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n v-model=\"password\"\n />\n </div>\n <p v-if=\"error\" class=\"text-sm text-red-500\">{{ error }}</p>\n <Button type=\"submit\" class=\"w-full\" :disabled=\"isLoading\">\n {{ isLoading ? \"Saving...\" : \"Save new password\" }}\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/components/update-password-form.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/auth/login.vue",
"content": "<script setup lang=\"ts\">\nimport LoginForm from \"@/components/login-form.vue\"\n</script>\n\n<template>\n <div class=\"flex min-h-screen w-full items-center justify-center p-6 md:p-10\">\n <div class=\"w-full max-w-sm\">\n <LoginForm />\n </div>\n </div>\n</template>",
"type": "registry:file",
"target": "app/pages/auth/login.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/auth/sign-up.vue",
"content": "<script setup lang=\"ts\">\nimport SignUpForm from \"@/components/sign-up-form.vue\"\n</script>\n\n<template>\n <div class=\"flex min-h-screen w-full items-center justify-center p-6 md:p-10\">\n <div class=\"w-full max-w-sm\">\n <SignUpForm />\n </div>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/pages/auth/sign-up.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/auth/forgot-password.vue",
"content": "<script setup lang=\"ts\">\nimport ForgotPasswordForm from \"@/components/forgot-password-form.vue\"\n</script>\n\n<template>\n <div class=\"flex min-h-screen w-full items-center justify-center p-6 md:p-10\">\n <div class=\"w-full max-w-sm\">\n <ForgotPasswordForm />\n </div>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/pages/auth/forgot-password.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/auth/update-password.vue",
"content": "<script setup lang=\"ts\">\nimport UpdatePasswordForm from \"@/components/update-password-form.vue\"\n</script>\n\n<template>\n <div class=\"flex min-h-screen w-full items-center justify-center p-6 md:p-10\">\n <div class=\"w-full max-w-sm\">\n <UpdatePasswordForm />\n </div>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/pages/auth/update-password.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/auth/error.vue",
"content": "<script setup lang=\"ts\">\nimport { useRoute } from \"vue-router\"\nimport { computed } from \"vue\"\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\"\n\nconst route = useRoute()\nconst errorMessage = computed(() => route.query.error as string || null)\n</script>\n\n<template>\n <div class=\"flex min-h-screen w-full items-center justify-center p-6 md:p-10\">\n <div class=\"w-full max-w-sm\">\n <div class=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Sorry, something went wrong.</CardTitle>\n </CardHeader>\n <CardContent>\n <p v-if=\"errorMessage\" class=\"text-sm text-muted-foreground\">\n Code error: {{ errorMessage }}\n </p>\n <p v-else class=\"text-sm text-muted-foreground\">\n An unspecified error occurred.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n</template>",
"type": "registry:file",
"target": "app/pages/auth/error.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/auth/sign-up-success.vue",
"content": "<script setup lang=\"ts\">\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\n</script>\n\n<template>\n <div class=\"flex min-h-screen w-full items-center justify-center p-6 md:p-10\">\n <div class=\"w-full max-w-sm\">\n <div class=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Thank you for signing up!</CardTitle>\n <CardDescription>Check your email to confirm</CardDescription>\n </CardHeader>\n <CardContent>\n <p class=\"text-sm text-muted-foreground\">\n You’ve successfully signed up. Please check your email to confirm your account\n before signing in.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/pages/auth/sign-up-success.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/app/pages/protected/index.vue",
"content": "<script setup lang=\"ts\">\nimport { onMounted, ref } from \"vue\"\nimport { useRouter } from \"vue-router\"\nimport { createClient } from \"@/lib/supabase/client\"\n\nconst router = useRouter()\nconst supabase = createClient()\nconst email = ref<string | null>(null)\nconst loading = ref(true)\n\nonMounted(async () => {\n const { data, error } = await supabase.auth.getUser()\n\n if (error || !data?.user) {\n router.replace(\"/auth/login\")\n return\n }\n\n email.value = data.user.email\n loading.value = false\n})\n\nconst handleLogout = async () => {\n await supabase.auth.signOut()\n router.replace(\"/auth/login\")\n}\n</script>\n\n<template>\n <div class=\"flex h-screen w-full items-center justify-center\">\n <div v-if=\"loading\" class=\"text-muted-foreground\">Checking authentication...</div>\n\n <div v-else class=\"flex items-center gap-2\">\n <p>\n Hello <span class=\"font-semibold\">{{ email }}</span>\n </p>\n <button\n class=\"rounded-md bg-primary text-primary-foreground px-3 py-2 text-sm font-medium hover:bg-primary/90\"\n @click=\"handleLogout\"\n >\n Sign out\n </button>\n </div>\n </div>\n</template>\n",
"type": "registry:file",
"target": "app/pages/protected/index.vue"
},
{
"path": "registry/default/password-based-auth/nuxtjs/server/routes/auth/confirm.get.ts",
"content": "import type { EmailOtpType } from '@supabase/supabase-js'\nimport { defineEventHandler, getQuery, sendRedirect } from 'h3'\n\nimport { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'\n\nexport default defineEventHandler(async (event) => {\n const query = getQuery(event)\n const token_hash = query.token_hash as string | null\n const type = query.type as EmailOtpType | null\n const _next = query.next as string | undefined\n const next = _next?.startsWith('/') ? _next : '/'\n\n if (token_hash && type) {\n const supabase = createSupabaseServerClient(event)\n\n const { error } = await supabase.auth.verifyOtp({\n type,\n token_hash,\n })\n\n if (!error) {\n return sendRedirect(event, next)\n } else {\n return sendRedirect(event, `/auth/error?error=${encodeURIComponent(error.message)}`)\n }\n }\n\n return sendRedirect(event, `/auth/error?error=${encodeURIComponent('No token hash or type')}`)\n})\n",
"type": "registry:file",
"target": "server/routes/auth/confirm.get.ts"
}
],
"type": "registry:block"
}