Docs/vue nuxt social auth (#40662)
* docs: add vue social auth * docs: add nuxt social auth * docs: nuxt social auth typo * docs: vue-nuxt-social-auth cr fix * Apply suggestion from @saltcod --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com> Co-authored-by: Terry Sutton <saltcod@gmail.com>
This commit is contained in:
committed by
GitHub
parent
380dfbc91d
commit
c538489ab2
@@ -68,7 +68,7 @@ export const componentPages: SidebarNavGroup = {
|
||||
},
|
||||
{
|
||||
title: 'Social Auth',
|
||||
supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
|
||||
supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react', 'vue', 'nuxtjs'],
|
||||
href: '/docs/nextjs/social-auth',
|
||||
items: [],
|
||||
new: true,
|
||||
|
||||
@@ -70,7 +70,7 @@ NUXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY=
|
||||
### Setting up routes and redirect URLs
|
||||
|
||||
1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard.
|
||||
1. Set up the Next.js route that users will visit to reset or update their password. Go to the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings and add the `forgot-password` route to the list of Redirect URLs. It should look something like: `http://example.com/auth/forgot-password`.
|
||||
1. Set up the Nuxt.js route that users will visit to reset or update their password. Go to the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings and add the `forgot-password` route to the list of Redirect URLs. It should look something like: `http://example.com/auth/forgot-password`.
|
||||
|
||||
1. Update the redirect paths in `login-form.vue` and `update-password-form.vue` components to point to the logged-in routes in your app. Our examples use `/protected`, but you can set this to whatever fits your app.
|
||||
|
||||
|
||||
61
apps/ui-library/content/docs/nuxtjs/social-auth.mdx
Normal file
61
apps/ui-library/content/docs/nuxtjs/social-auth.mdx
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Social Authentication
|
||||
description: Social authentication block for Nuxt.js
|
||||
---
|
||||
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
|
||||
<Callout className="mt-4">
|
||||
The block is using Github provider by default, but can be easily switched by changing a single
|
||||
parameter.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="social-auth-nuxtjs" description="All needed components for the social auth flow" />
|
||||
|
||||
## Folder structure
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
|
||||
|
||||
<RegistryBlock itemName="social-auth-nuxtjs" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your Nuxt.js project, you'll get all the necessary pages and components to set up a social authentication flow.
|
||||
|
||||
### Getting started
|
||||
|
||||
After installing the block, you'll have the following environment variables in your `.env.local` file:
|
||||
|
||||
```env
|
||||
NUXT_PUBLIC_SUPABASE_URL=
|
||||
NUXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY=
|
||||
```
|
||||
|
||||
- If you're using supabase.com, you can find these values in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true&connectTab=frameworks&framework=nuxtjs&using=supabasejs) under App Frameworks or in your project's [API settings](https://supabase.com/dashboard/project/_/settings/api).
|
||||
|
||||
- If you're using a local instance of Supabase, you can find these values by running `supabase start` or `supabase status` (if you already have it running).
|
||||
|
||||
### Setting up third party providers
|
||||
|
||||
We support a wide variety of social providers that you can use to integrate with your application. The full list is available [here](https://supabase.com/docs/guides/auth/social-login).
|
||||
This block uses the PKCE flow with GitHub as the provider. To switch providers, just update the `provider` field in the `supabase.auth.signInWithOAuth` call. Enable the provider you want to use under [Auth Providers](https://supabase.com/dashboard/project/_/auth/providers) in the Supabase Dashboard and add the necessary credentials.
|
||||
|
||||
### Setting up routes and redirect URLs
|
||||
|
||||
1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard.
|
||||
1. Update the redirect paths in `login-form.vue` to point to your app’s logged-in routes. Our examples use `/protected`, but you can set this to whatever fits your app.
|
||||
1. Visit `http://your-site-url/auth/login` to see this component in action.
|
||||
|
||||
### Combining social auth with password-based auth
|
||||
|
||||
If you want to combine this block with the password-based auth, you need to:
|
||||
|
||||
- Copy the `handleSocialLogin` function into the password-based `login-form.vue` component and bind it to a "Login with ..." button.
|
||||
- Copy the `@/server/routes/auth/oauth.ts` in your app under the same route.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Social login](https://supabase.com/docs/guides/auth/social-login)
|
||||
- [Authentication error codes](https://supabase.com/docs/guides/auth/debugging/error-codes)
|
||||
57
apps/ui-library/content/docs/vue/social-auth.mdx
Normal file
57
apps/ui-library/content/docs/vue/social-auth.mdx
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Social Authentication
|
||||
description: Social authentication block for Vue Single Page Applications
|
||||
---
|
||||
|
||||
<BlockPreview name="social-auth/auth/login" />
|
||||
|
||||
<Callout className="mt-4">
|
||||
The block is using GitHub provider by default, but can be easily switched by changing a single
|
||||
parameter.
|
||||
</Callout>
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="social-auth-vue" description="All needed components for the social auth flow" />
|
||||
|
||||
## Folder structure
|
||||
|
||||
This block assumes that you have already installed a Supabase client for Vue from the previous step.
|
||||
|
||||
<RegistryBlock itemName="social-auth-vue" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your Vue project, you'll get all the necessary pages and components to set up a social authentication flow.
|
||||
|
||||
### Getting started
|
||||
|
||||
After installing the block, you'll have the following environment variables in your `.env.local` file:
|
||||
|
||||
```env
|
||||
VITE_SUPABASE_URL=
|
||||
VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY=
|
||||
```
|
||||
|
||||
- If you're using supabase.com, you can find these values in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true&connectTab=frameworks&framework=react&using=vite&with=supabasejs) under App Frameworks or in your project's [API settings](https://supabase.com/dashboard/project/_/settings/api).
|
||||
|
||||
- If you're using a local instance of Supabase, you can find these values by running `supabase start` or `supabase status` (if you already have it running).
|
||||
|
||||
### Setting up third party providers
|
||||
|
||||
We support a wide variety of social providers that you can use to integrate with your application. The full list is available [here](https://supabase.com/docs/guides/auth/social-login).
|
||||
This block uses the implicit flow with GitHub as the provider. To switch providers, just update the `provider` field in the `supabase.auth.signInWithOAuth` call. Enable the provider you want to use under [Auth Providers](https://supabase.com/dashboard/project/_/auth/providers) in the Supabase Dashboard and add the necessary credentials.
|
||||
|
||||
### Setting up routes and redirect URLs
|
||||
|
||||
1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard.
|
||||
1. Update the redirect paths in `login-form.vue` to point to your app’s logged-in routes. Our examples use `/protected`, but you can set this to whatever fits your app.
|
||||
|
||||
### Combining social auth with password-based auth
|
||||
|
||||
If you want to combine this block with the password-based auth, you need to copy the `handleSocialLogin` function into the password-based `login-form.vue` component and bind it to a button.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Social login](https://supabase.com/docs/guides/auth/social-login)
|
||||
- [Authentication error codes](https://supabase.com/docs/guides/auth/debugging/error-codes)
|
||||
@@ -1,5 +1,5 @@
|
||||
# Supabase UI Library
|
||||
Last updated: 2025-11-06T16:46:13.786Z
|
||||
Last updated: 2025-11-20T11:14:14.146Z
|
||||
|
||||
## Overview
|
||||
Library of components for your project. The components integrate with Supabase and are shadcn compatible.
|
||||
@@ -35,6 +35,8 @@ Library of components for your project. The components integrate with Supabase a
|
||||
- Supabase client for Nuxt.js
|
||||
- [Password-based Authentication](https://supabase.com/ui/docs/nuxtjs/password-based-auth)
|
||||
- Password-based authentication block for Nuxt.js
|
||||
- [Social Authentication](https://supabase.com/ui/docs/nuxtjs/social-auth)
|
||||
- Social authentication block for Nuxt.js
|
||||
- [Platform Kit](https://supabase.com/ui/docs/platform/platform-kit)
|
||||
- The easiest way to build platforms on top of Supabase
|
||||
- [Supabase Client Libraries](https://supabase.com/ui/docs/react-router/client)
|
||||
@@ -89,3 +91,5 @@ Library of components for your project. The components integrate with Supabase a
|
||||
- Supabase client for Vue Single Page Applications
|
||||
- [Password-based Authentication](https://supabase.com/ui/docs/vue/password-based-auth)
|
||||
- Password-based authentication block for Vue Single Page Applications
|
||||
- [Social Authentication](https://supabase.com/ui/docs/vue/social-auth)
|
||||
- Social authentication block for Vue Single Page Applications
|
||||
|
||||
@@ -17,22 +17,26 @@
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/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:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/login-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/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:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/sign-up-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/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:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/forgot-password-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/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:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/update-password-form.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1853,25 +1853,102 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/login-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/login-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/sign-up-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/sign-up-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/forgot-password-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/forgot-password-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/update-password-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/update-password-form.vue"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"@supabase/supabase-js@latest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "social-auth-vue",
|
||||
"type": "registry:block",
|
||||
"title": "Social Auth flow for Vue and Supabase",
|
||||
"description": "Social Auth flow for Vue and Supabase",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/social-auth/vue/components/login-form.vue",
|
||||
"type": "registry:component",
|
||||
"target": "components/login-form.vue"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"@supabase/supabase-js@latest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "social-auth-nuxtjs",
|
||||
"type": "registry:block",
|
||||
"title": "Social Auth flow for Nuxt and Supabase",
|
||||
"description": "Social Auth flow for Nuxt and Supabase",
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/components/login-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/components/logout-button.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/components/logout-button.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/pages/auth/login.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/auth/login.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/pages/auth/error.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/auth/error.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/pages/protected/index.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/protected/index.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/server/middleware/auth.ts",
|
||||
"type": "registry:file",
|
||||
"target": "server/middleware/auth.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts",
|
||||
"type": "registry:file",
|
||||
"target": "server/routes/auth/oauth.ts"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "ai-editor-rules",
|
||||
|
||||
61
apps/ui-library/public/r/social-auth-nuxtjs.json
Normal file
61
apps/ui-library/public/r/social-auth-nuxtjs.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "social-auth-nuxtjs",
|
||||
"type": "registry:block",
|
||||
"title": "Social Auth flow for Nuxt and Supabase",
|
||||
"description": "Social Auth flow for Nuxt and Supabase",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { ref } from \"vue\"\nimport { createClient } from \"@/lib/supabase/client\"\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"@/components/ui/button.vue\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card.vue\"\n\n// Reactive state\nconst error = ref<string | null>(null)\nconst isLoading = ref(false)\n\n// GitHub OAuth login handler\nconst handleSocialLogin = 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.signInWithOAuth({\n provider: \"github\",\n options: {\n redirectTo: `${window.location.origin}/api/routes/oauth?next=/protected`,\n },\n })\n\n if (supabaseError) throw supabaseError\n } catch (err: unknown) {\n error.value = err instanceof Error ? err.message : \"An error occurred\"\n isLoading.value = false\n }\n}\n</script>\n\n<template>\n <div :class=\"cn('flex flex-col gap-6')\">\n <Card>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Welcome!</CardTitle>\n <CardDescription>Sign in to your account to continue</CardDescription>\n </CardHeader>\n <CardContent>\n <form @submit=\"handleSocialLogin\">\n <div class=\"flex flex-col gap-6\">\n <p v-if=\"error\" class=\"text-sm text-destructive-500\">{{ error }}</p>\n <Button type=\"submit\" class=\"w-full\" :disabled=\"isLoading\">\n {{ isLoading ? \"Logging in...\" : \"Continue with GitHub\" }}\n </Button>\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/social-auth/nuxtjs/app/components/logout-button.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { createClient } from \"@/lib/supabase/client\"\nimport { useRouter } from \"vue-router\"\nimport { Button } from \"@/components/ui/button.vue\"\n\nconst router = useRouter()\n\nconst logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push(\"/auth/login\")\n}\n</script>\n\n<template>\n <Button @click=\"logout\">Logout</Button>\n</template>\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/components/logout-button.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-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/social-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/social-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\"\nimport LogoutButton from \"@/components/logout-button.vue\"\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</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 <LogoutButton />\n </div>\n </div>\n</template>\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/protected/index.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/server/middleware/auth.ts",
|
||||
"content": "import { defineEventHandler, sendRedirect } from 'h3'\nimport { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'\n\nexport default defineEventHandler(async (event) => {\n const supabase = createSupabaseServerClient(event)\n\n // Get user claims\n const { data } = await supabase.auth.getClaims()\n const user = data?.claims\n\n const pathname = event.node.req.url || '/'\n\n // Redirect if no user and not already on login/auth route\n if (\n !user &&\n !pathname.startsWith('/login') &&\n !pathname.startsWith('/auth')\n ) {\n return sendRedirect(event, '/auth/login')\n }\n\n // Return event as-is (you could return any object if needed)\n return { user }\n})\n",
|
||||
"type": "registry:file",
|
||||
"target": "server/middleware/auth.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts",
|
||||
"content": "import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'\nimport { defineEventHandler, getQuery, sendRedirect, getRequestURL } from \"h3\"\n\nexport default defineEventHandler(async (event) => {\n const url = getRequestURL(event) // URL object of the current request\n const query = getQuery(event)\n\n const code = query.code as string | undefined\n let next = (query.next as string | undefined) ?? \"/\"\n\n if (!next.startsWith(\"/\")) {\n next = \"/\"\n }\n\n if (code) {\n const supabase = createSupabaseServerClient(event)\n const { error } = await supabase.auth.exchangeCodeForSession(code)\n\n if (!error) {\n // Determine origin\n const forwardedHost = event.node.req.headers[\"x-forwarded-host\"] as string | undefined\n const isLocalEnv = process.env.NODE_ENV === \"development\"\n const origin = `${url.protocol}//${url.host}`\n\n if (isLocalEnv) {\n return sendRedirect(event, `${origin}${next}`)\n } else if (forwardedHost) {\n return sendRedirect(event, `https://${forwardedHost}${next}`)\n } else {\n return sendRedirect(event, `${origin}${next}`)\n }\n }\n }\n\n // fallback to error page\n const origin = `${url.protocol}//${url.host}`\n return sendRedirect(event, `${origin}/auth/error`)\n})\n",
|
||||
"type": "registry:file",
|
||||
"target": "server/routes/auth/oauth.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
22
apps/ui-library/public/r/social-auth-vue.json
Normal file
22
apps/ui-library/public/r/social-auth-vue.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "social-auth-vue",
|
||||
"type": "registry:block",
|
||||
"title": "Social Auth flow for Vue and Supabase",
|
||||
"description": "Social Auth flow for Vue and Supabase",
|
||||
"dependencies": [
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/social-auth/vue/components/login-form.vue",
|
||||
"content": "<script setup lang=\"ts\">\nimport { ref } from \"vue\"\nimport { createClient } from \"@/lib/supabase/client\"\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"@/components/ui/card\"\n\nconst error = ref<string | null>(null)\nconst isLoading = ref(false)\n\nconst handleSocialLogin = 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.signInWithOAuth({\n provider: \"github\",\n })\n\n if (supabaseError) throw supabaseError\n window.location.href = \"/protected\"\n } catch (err: unknown) {\n error.value = err instanceof Error ? err.message : \"An error occurred\"\n isLoading.value = false\n }\n}\n</script>\n\n<template>\n <div :class=\"cn('flex flex-col gap-6')\">\n <Card>\n <CardHeader>\n <CardTitle class=\"text-2xl\">Welcome!</CardTitle>\n <CardDescription>Sign in to your account to continue</CardDescription>\n </CardHeader>\n <CardContent>\n <form @submit=\"handleSocialLogin\">\n <div class=\"flex flex-col gap-6\">\n <p v-if=\"error\" class=\"text-sm text-destructive-500\">{{ error }}</p>\n <Button type=\"submit\" class=\"w-full\" :disabled=\"isLoading\">\n {{ isLoading ? \"Logging in...\" : \"Continue with GitHub\" }}\n </Button>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n</template>\n",
|
||||
"type": "registry:component",
|
||||
"target": "components/login-form.vue"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,19 +7,23 @@
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/login-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/login-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/sign-up-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/sign-up-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/forgot-password-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/forgot-password-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/password-based-auth/vue/components/update-password-form.vue",
|
||||
"type": "registry:component"
|
||||
"type": "registry:component",
|
||||
"target": "components/update-password-form.vue"
|
||||
}
|
||||
],
|
||||
"dependencies": ["@supabase/supabase-js@latest"]
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { createClient } from "@/lib/supabase/client"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { Button } from "@/components/ui/button.vue"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card.vue"
|
||||
|
||||
// Reactive state
|
||||
const error = ref<string | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
// GitHub OAuth login handler
|
||||
const handleSocialLogin = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
const supabase = createClient()
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const { error: supabaseError } = await supabase.auth.signInWithOAuth({
|
||||
provider: "github",
|
||||
options: {
|
||||
redirectTo: `${window.location.origin}/api/routes/oauth?next=/protected`,
|
||||
},
|
||||
})
|
||||
|
||||
if (supabaseError) throw supabaseError
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : "An error occurred"
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('flex flex-col gap-6')">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-2xl">Welcome!</CardTitle>
|
||||
<CardDescription>Sign in to your account to continue</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form @submit="handleSocialLogin">
|
||||
<div class="flex flex-col gap-6">
|
||||
<p v-if="error" class="text-sm text-destructive-500">{{ error }}</p>
|
||||
<Button type="submit" class="w-full" :disabled="isLoading">
|
||||
{{ isLoading ? "Logging in..." : "Continue with GitHub" }}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { createClient } from "@/lib/supabase/client"
|
||||
import { useRouter } from "vue-router"
|
||||
import { Button } from "@/components/ui/button.vue"
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const logout = async () => {
|
||||
const supabase = createClient()
|
||||
await supabase.auth.signOut()
|
||||
router.push("/auth/login")
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button @click="logout">Logout</Button>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from "vue-router"
|
||||
import { computed } from "vue"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
|
||||
const route = useRoute()
|
||||
const errorMessage = computed(() => route.query.error as string || null)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
|
||||
<div class="w-full max-w-sm">
|
||||
<div class="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-2xl">Sorry, something went wrong.</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p v-if="errorMessage" class="text-sm text-muted-foreground">
|
||||
Code error: {{ errorMessage }}
|
||||
</p>
|
||||
<p v-else class="text-sm text-muted-foreground">
|
||||
An unspecified error occurred.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import LoginForm from "@/components/login-form.vue"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-screen w-full items-center justify-center p-6 md:p-10">
|
||||
<div class="w-full max-w-sm">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { createClient } from "@/lib/supabase/client"
|
||||
import LogoutButton from "@/components/logout-button.vue"
|
||||
|
||||
const router = useRouter()
|
||||
const supabase = createClient()
|
||||
const email = ref<string | null>(null)
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
|
||||
if (error || !data?.user) {
|
||||
router.replace("/auth/login")
|
||||
return
|
||||
}
|
||||
|
||||
email.value = data.user.email
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-screen w-full items-center justify-center">
|
||||
<div v-if="loading" class="text-muted-foreground">Checking authentication...</div>
|
||||
|
||||
<div v-else class="flex items-center gap-2">
|
||||
<p>
|
||||
Hello <span class="font-semibold">{{ email }}</span>
|
||||
</p>
|
||||
<LogoutButton />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "social-auth-nuxtjs",
|
||||
"type": "registry:block",
|
||||
"title": "Social Auth flow for Nuxt and Supabase",
|
||||
"description": "Social Auth flow for Nuxt and Supabase",
|
||||
"registryDependencies": ["button", "card", "input", "label"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/components/login-form.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/components/login-form.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/components/logout-button.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/components/logout-button.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/pages/auth/login.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/auth/login.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/pages/auth/error.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/auth/error.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/app/pages/protected/index.vue",
|
||||
"type": "registry:file",
|
||||
"target": "app/pages/protected/index.vue"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/server/middleware/auth.ts",
|
||||
"type": "registry:file",
|
||||
"target": "server/middleware/auth.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/social-auth/nuxtjs/server/routes/auth/oauth.ts",
|
||||
"type": "registry:file",
|
||||
"target": "server/routes/auth/oauth.ts"
|
||||
}
|
||||
],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineEventHandler, sendRedirect } from 'h3'
|
||||
import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const supabase = createSupabaseServerClient(event)
|
||||
|
||||
// Get user claims
|
||||
const { data } = await supabase.auth.getClaims()
|
||||
const user = data?.claims
|
||||
|
||||
const pathname = event.node.req.url || '/'
|
||||
|
||||
// Redirect if no user and not already on login/auth route
|
||||
if (
|
||||
!user &&
|
||||
!pathname.startsWith('/login') &&
|
||||
!pathname.startsWith('/auth')
|
||||
) {
|
||||
return sendRedirect(event, '/auth/login')
|
||||
}
|
||||
|
||||
// Return event as-is (you could return any object if needed)
|
||||
return { user }
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createSupabaseServerClient } from '@/registry/default/clients/nuxtjs/server/supabase/client'
|
||||
import { defineEventHandler, getQuery, sendRedirect, getRequestURL } from "h3"
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const url = getRequestURL(event) // URL object of the current request
|
||||
const query = getQuery(event)
|
||||
|
||||
const code = query.code as string | undefined
|
||||
let next = (query.next as string | undefined) ?? "/"
|
||||
|
||||
if (!next.startsWith("/")) {
|
||||
next = "/"
|
||||
}
|
||||
|
||||
if (code) {
|
||||
const supabase = createSupabaseServerClient(event)
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
||||
|
||||
if (!error) {
|
||||
// Determine origin
|
||||
const forwardedHost = event.node.req.headers["x-forwarded-host"] as string | undefined
|
||||
const isLocalEnv = process.env.NODE_ENV === "development"
|
||||
const origin = `${url.protocol}//${url.host}`
|
||||
|
||||
if (isLocalEnv) {
|
||||
return sendRedirect(event, `${origin}${next}`)
|
||||
} else if (forwardedHost) {
|
||||
return sendRedirect(event, `https://${forwardedHost}${next}`)
|
||||
} else {
|
||||
return sendRedirect(event, `${origin}${next}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to error page
|
||||
const origin = `${url.protocol}//${url.host}`
|
||||
return sendRedirect(event, `${origin}/auth/error`)
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { createClient } from "@/lib/supabase/client"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card"
|
||||
|
||||
const error = ref<string | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
const handleSocialLogin = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
const supabase = createClient()
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const { error: supabaseError } = await supabase.auth.signInWithOAuth({
|
||||
provider: "github",
|
||||
})
|
||||
|
||||
if (supabaseError) throw supabaseError
|
||||
window.location.href = "/protected"
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : "An error occurred"
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('flex flex-col gap-6')">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-2xl">Welcome!</CardTitle>
|
||||
<CardDescription>Sign in to your account to continue</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form @submit="handleSocialLogin">
|
||||
<div class="flex flex-col gap-6">
|
||||
<p v-if="error" class="text-sm text-destructive-500">{{ error }}</p>
|
||||
<Button type="submit" class="w-full" :disabled="isLoading">
|
||||
{{ isLoading ? "Logging in..." : "Continue with GitHub" }}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "social-auth-vue",
|
||||
"type": "registry:block",
|
||||
"title": "Social Auth flow for Vue and Supabase",
|
||||
"description": "Social Auth flow for Vue and Supabase",
|
||||
"registryDependencies": ["button", "card"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/social-auth/vue/components/login-form.vue",
|
||||
"type": "registry:component",
|
||||
"target": "components/login-form.vue"
|
||||
}
|
||||
],
|
||||
"dependencies": ["@supabase/supabase-js@latest"]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { clients } from './clients'
|
||||
import { passwordBasedAuth } from './password-based-auth'
|
||||
import { socialAuth } from './social-auth'
|
||||
|
||||
const blocks = [...clients, ...passwordBasedAuth]
|
||||
const blocks = [...clients, ...passwordBasedAuth, ...socialAuth]
|
||||
|
||||
export { blocks }
|
||||
|
||||
5
blocks/vue/registry/social-auth.ts
Normal file
5
blocks/vue/registry/social-auth.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { type Registry } from 'shadcn/schema'
|
||||
import vue from './default/social-auth/vue/registry-item.json' with { type: 'json' }
|
||||
import nuxt from './default/social-auth/nuxtjs/registry-item.json' with { type: 'json' }
|
||||
|
||||
export const socialAuth = [vue, nuxt] as Registry['items']
|
||||
Reference in New Issue
Block a user