{
  "$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"
}