11<script setup lang="ts">
2- import firebase from " firebase/compat/app" ;
3- import " firebase/compat/auth" ;
42import {
3+ getAuth ,
4+ signInWithPopup ,
55 GoogleAuthProvider ,
66 GithubAuthProvider ,
7- EmailAuthProvider ,
7+ signInWithEmailAndPassword ,
8+ createUserWithEmailAndPassword ,
89} from " firebase/auth" ;
10+ import { mdiGoogle , mdiGithub , mdiEmail } from " @mdi/js" ;
911
1012const props = withDefaults (
1113 defineProps <{
@@ -18,72 +20,182 @@ const router = useRouter();
1820const authStore = useAuthStore ();
1921const notificationsStore = useNotificationsStore ();
2022const appStore = useAppStore ();
21- const authContainer = ref <HTMLElement | null >(null );
22- const firebaseUIInitialized = ref (false );
23- let ui: any = null ;
2423
25- onMounted (async () => {
26- if (! import .meta .client ) return ;
24+ const loading = ref (false );
25+ const showEmailForm = ref (false );
26+ const isSignUp = ref (false );
27+ const email = ref (" " );
28+ const password = ref (" " );
29+ const emailError = ref (" " );
2730
28- const firebaseui = await import (" firebaseui" );
29- await import (" firebaseui/dist/firebaseui.css" );
31+ async function signInWithGoogle() {
32+ loading .value = true ;
33+ try {
34+ const auth = getAuth ();
35+ const result = await signInWithPopup (auth , new GoogleAuthProvider ());
36+ onSuccess (result .user );
37+ } catch (error : unknown ) {
38+ handleError (error );
39+ } finally {
40+ loading .value = false ;
41+ }
42+ }
3043
31- // FirebaseUI requires the compat auth instance
32- const auth = firebase .auth ();
33- ui = firebaseui .auth .AuthUI .getInstance () || new firebaseui .auth .AuthUI (auth );
34- ui .start (" #firebaseui-auth-container" , {
35- callbacks: {
36- signInSuccessWithAuthResult : (authResult : any ) => {
37- notificationsStore .addNotification ({
38- message: " Login successful!" ,
39- type: " success" ,
40- });
41- authStore .onAuthStateChanged (authResult .user );
42- router .push ({ path: props .to });
43- return false ;
44- },
45- uiShown : () => {
46- firebaseUIInitialized .value = true ;
47- if (authContainer .value ) {
48- Array .from (
49- authContainer .value .getElementsByClassName (
50- " firebaseui-idp-text-long" ,
51- ),
52- ).forEach ((item : Element ) => {
53- item .textContent =
54- item .textContent ?.replace (" Sign in with" , " Continue with" ) ||
55- null ;
56- });
57- }
58- },
59- },
60- signInFlow: " popup" ,
61- signInSuccessUrl: window .location .href ,
62- signInOptions: [
63- GoogleAuthProvider .PROVIDER_ID ,
64- GithubAuthProvider .PROVIDER_ID ,
65- EmailAuthProvider .PROVIDER_ID ,
66- ],
67- tosUrl: appStore .appData .url + " /terms-and-conditions" ,
68- privacyPolicyUrl: appStore .appData .url + " /privacy-policy" ,
44+ async function signInWithGithub() {
45+ loading .value = true ;
46+ try {
47+ const auth = getAuth ();
48+ const result = await signInWithPopup (auth , new GithubAuthProvider ());
49+ onSuccess (result .user );
50+ } catch (error : unknown ) {
51+ handleError (error );
52+ } finally {
53+ loading .value = false ;
54+ }
55+ }
56+
57+ async function submitEmail() {
58+ emailError .value = " " ;
59+ loading .value = true ;
60+ try {
61+ const auth = getAuth ();
62+ let result;
63+ if (isSignUp .value ) {
64+ result = await createUserWithEmailAndPassword (
65+ auth ,
66+ email .value ,
67+ password .value ,
68+ );
69+ } else {
70+ result = await signInWithEmailAndPassword (
71+ auth ,
72+ email .value ,
73+ password .value ,
74+ );
75+ }
76+ onSuccess (result .user );
77+ } catch (error : unknown ) {
78+ handleError (error );
79+ } finally {
80+ loading .value = false ;
81+ }
82+ }
83+
84+ function onSuccess(user : unknown ) {
85+ notificationsStore .addNotification ({
86+ message: " Login successful!" ,
87+ type: " success" ,
6988 });
70- });
89+ authStore .onAuthStateChanged (user );
90+ router .push ({ path: props .to });
91+ }
7192
72- onBeforeUnmount (() => {
73- if (ui ) ui .delete ();
74- });
93+ function handleError(error : unknown ) {
94+ const firebaseError = error as { code? : string ; message? : string };
95+ const code = firebaseError .code || " " ;
96+ if (code === " auth/user-not-found" || code === " auth/invalid-credential" ) {
97+ emailError .value = " Invalid email or password" ;
98+ } else if (code === " auth/email-already-in-use" ) {
99+ emailError .value = " An account with this email already exists" ;
100+ } else if (code === " auth/weak-password" ) {
101+ emailError .value = " Password must be at least 6 characters" ;
102+ } else if (code === " auth/popup-closed-by-user" ) {
103+ // User closed popup, no error to show
104+ } else {
105+ emailError .value = firebaseError .message || " An error occurred" ;
106+ }
107+ }
75108 </script >
76109
77110<template >
78- <div >
79- <div id =" firebaseui-auth-container" ref =" authContainer" />
80- <v-progress-circular
81- v-if =" !firebaseUIInitialized"
82- class =" mx-auto d-block my-16"
83- :size =" 80"
84- :width =" 5"
85- color =" primary"
86- indeterminate
87- />
111+ <div class =" text-center" >
112+ <v-btn
113+ block
114+ size =" large"
115+ variant =" outlined"
116+ class =" mb-3"
117+ :loading =" loading"
118+ @click =" signInWithGoogle"
119+ >
120+ <v-icon :icon =" mdiGoogle" class =" mr-2" />
121+ Continue with Google
122+ </v-btn >
123+
124+ <v-btn
125+ block
126+ size =" large"
127+ variant =" outlined"
128+ class =" mb-3"
129+ :loading =" loading"
130+ @click =" signInWithGithub"
131+ >
132+ <v-icon :icon =" mdiGithub" class =" mr-2" />
133+ Continue with GitHub
134+ </v-btn >
135+
136+ <v-btn
137+ v-if =" !showEmailForm"
138+ block
139+ size =" large"
140+ variant =" outlined"
141+ class =" mb-3"
142+ @click =" showEmailForm = true"
143+ >
144+ <v-icon :icon =" mdiEmail" class =" mr-2" />
145+ Continue with Email
146+ </v-btn >
147+
148+ <v-form v-if =" showEmailForm" class =" mt-4" @submit.prevent =" submitEmail" >
149+ <v-text-field
150+ v-model =" email"
151+ label =" Email"
152+ type =" email"
153+ variant =" outlined"
154+ density =" comfortable"
155+ class =" mb-2"
156+ required
157+ />
158+ <v-text-field
159+ v-model =" password"
160+ label =" Password"
161+ type =" password"
162+ variant =" outlined"
163+ density =" comfortable"
164+ class =" mb-2"
165+ required
166+ />
167+ <v-alert v-if =" emailError" type =" error" density =" compact" class =" mb-3" >
168+ {{ emailError }}
169+ </v-alert >
170+ <v-btn
171+ block
172+ size =" large"
173+ color =" primary"
174+ type =" submit"
175+ :loading =" loading"
176+ >
177+ {{ isSignUp ? "Sign Up" : "Sign In" }}
178+ </v-btn >
179+ <v-btn
180+ block
181+ variant =" text"
182+ size =" small"
183+ class =" mt-2"
184+ @click =" isSignUp = !isSignUp"
185+ >
186+ {{
187+ isSignUp ? "Already have an account? Sign In" : "No account? Sign Up"
188+ }}
189+ </v-btn >
190+ </v-form >
191+
192+ <p class =" text-body-small text-medium-emphasis mt-4" >
193+ By continuing, you agree to the
194+ <a :href =" appStore.appData.url + '/terms-and-conditions'" >
195+ Terms & Conditions
196+ </a >
197+ and
198+ <a :href =" appStore.appData.url + '/privacy-policy'" >Privacy Policy</a >
199+ </p >
88200 </div >
89201</template >
0 commit comments