mirror of
https://github.com/supabase/supabase.git
synced 2026-06-01 18:34:37 +08:00
This PR renames all `SUPABASE_PUBLISHABLE_OR_ANON_KEY` env vars into `SUPABASE_PUBLISHABLE_KEY` to make the new API keys default. This is in coordination with the rest of the docs. I've also cleaned up the `blocks/vue` package from unused files. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Breaking Changes** * Public environment variable names renamed from PUBLISHABLE_OR_ANON_KEY → PUBLISHABLE_KEY across all framework integrations; update your environment configs. * **Documentation** * All framework guides, .env examples and registry docs updated to use the new variable names. * **Chores** * Cleaned up UI registry/templates: some example Vue registry items and autogenerated registry artifacts were removed or simplified. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
91 lines
19 KiB
JSON
91 lines
19 KiB
JSON
{
|
||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||
"name": "password-based-auth-nuxtjs",
|
||
"type": "registry:block",
|
||
"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'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\">Login</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\">Login</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'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\">Login</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 Logout\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"
|
||
}
|
||
]
|
||
} |