@bagelink/auth 1.6.51 → 1.6.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +284 -219
- package/dist/Callback-BHqVaZZm.cjs +4 -0
- package/dist/Callback-C-XghN_z.js +4 -0
- package/dist/ForgotPasswordPage-BV9tyhHl.cjs +4 -0
- package/dist/ForgotPasswordPage-DvttMGb0.js +4 -0
- package/dist/LoginPage-hv1wc54S.cjs +4 -0
- package/dist/LoginPage-klj1NV4J.js +4 -0
- package/dist/ResetPasswordPage-COPrJmW8.cjs +4 -0
- package/dist/ResetPasswordPage-nvQ4uupb.js +4 -0
- package/dist/SignupPage-m36w9PLJ.cjs +4 -0
- package/dist/SignupPage-oUFYApYW.js +4 -0
- package/dist/api.d.ts +115 -0
- package/dist/components/auth/ForgotPasswordForm.vue.d.ts +23 -0
- package/dist/components/auth/LoginForm.vue.d.ts +58 -0
- package/dist/components/auth/ResetPasswordForm.vue.d.ts +28 -0
- package/dist/components/auth/SignupForm.vue.d.ts +34 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/constants.d.ts +34 -0
- package/dist/index.cjs +1227 -30
- package/dist/index.d.ts +16 -631
- package/dist/index.mjs +1242 -24
- package/dist/pages/Callback.vue.d.ts +2 -0
- package/dist/pages/ForgotPasswordPage.vue.d.ts +13 -0
- package/dist/pages/LoginPage.vue.d.ts +38 -0
- package/dist/pages/ResetPasswordPage.vue.d.ts +13 -0
- package/dist/pages/SignupPage.vue.d.ts +16 -0
- package/dist/routes.d.ts +49 -0
- package/dist/sso.d.ts +145 -0
- package/dist/types/index.d.ts +41 -0
- package/dist/types.d.ts +254 -0
- package/dist/useAuth.d.ts +112 -0
- package/dist/utils.d.ts +11 -0
- package/package.json +11 -13
- package/src/components/auth/ForgotPasswordForm.vue +97 -0
- package/src/components/auth/LoginForm.vue +257 -0
- package/src/components/auth/ResetPasswordForm.vue +146 -0
- package/src/components/auth/SignupForm.vue +224 -0
- package/src/components/index.ts +5 -0
- package/src/constants.ts +10 -0
- package/src/index.ts +26 -1
- package/src/pages/Callback.vue +183 -0
- package/src/pages/ForgotPasswordPage.vue +42 -0
- package/src/pages/LoginPage.vue +68 -0
- package/src/pages/ResetPasswordPage.vue +47 -0
- package/src/pages/SignupPage.vue +46 -0
- package/src/routes.ts +122 -0
- package/src/types/index.ts +45 -0
- package/dist/index.d.cts +0 -631
- package/dist/index.d.mts +0 -631
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { useAuth } from '@bagelink/auth'
|
|
3
|
+
import { Btn, PasswordInput, TextInput } from '@bagelink/vue'
|
|
4
|
+
import { computed, ref } from 'vue'
|
|
5
|
+
|
|
6
|
+
export interface SignupTexts {
|
|
7
|
+
title?: string
|
|
8
|
+
firstNameLabel?: string
|
|
9
|
+
lastNameLabel?: string
|
|
10
|
+
emailLabel?: string
|
|
11
|
+
passwordLabel?: string
|
|
12
|
+
confirmPasswordLabel?: string
|
|
13
|
+
signupButton?: string
|
|
14
|
+
alreadyHaveAccount?: string
|
|
15
|
+
// Validation errors
|
|
16
|
+
emailRequiredError?: string
|
|
17
|
+
emailInvalidError?: string
|
|
18
|
+
passwordRequiredError?: string
|
|
19
|
+
passwordTooShortError?: string
|
|
20
|
+
passwordMismatchError?: string
|
|
21
|
+
firstNameRequiredError?: string
|
|
22
|
+
lastNameRequiredError?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
showNames?: boolean
|
|
27
|
+
useHebrewDefaults?: boolean
|
|
28
|
+
errorState?: 'normal' | 'email-required' | 'email-invalid' | 'password-short' | 'password-mismatch' | 'name-required' | 'server-error'
|
|
29
|
+
texts?: Partial<SignupTexts>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
33
|
+
showNames: true,
|
|
34
|
+
useHebrewDefaults: false,
|
|
35
|
+
errorState: 'normal',
|
|
36
|
+
texts: () => ({})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
defineEmits<{
|
|
40
|
+
switchForm: [form: string]
|
|
41
|
+
}>()
|
|
42
|
+
|
|
43
|
+
const { signup, login } = useAuth()
|
|
44
|
+
|
|
45
|
+
// Internal error for actual validation
|
|
46
|
+
const internalError = ref<string>('')
|
|
47
|
+
|
|
48
|
+
// Use computed for error that reacts to errorState changes
|
|
49
|
+
const error = computed(() => {
|
|
50
|
+
if (props.errorState !== 'normal') {
|
|
51
|
+
return getDevError()
|
|
52
|
+
}
|
|
53
|
+
return internalError.value
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const form = ref({
|
|
57
|
+
email: '',
|
|
58
|
+
password: '',
|
|
59
|
+
confirmPassword: '',
|
|
60
|
+
first_name: '',
|
|
61
|
+
last_name: '',
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Text configuration with conditional defaults
|
|
65
|
+
const texts = computed(() => {
|
|
66
|
+
const hebrewDefaults = {
|
|
67
|
+
title: 'יצירת חשבון',
|
|
68
|
+
firstNameLabel: 'שם פרטי',
|
|
69
|
+
lastNameLabel: 'שם משפחה',
|
|
70
|
+
emailLabel: 'איימיל',
|
|
71
|
+
passwordLabel: 'סיסמה',
|
|
72
|
+
confirmPasswordLabel: 'אימות סיסמה',
|
|
73
|
+
signupButton: 'יצירת חשבון',
|
|
74
|
+
alreadyHaveAccount: 'יש לך כבר חשבון?',
|
|
75
|
+
// Validation errors
|
|
76
|
+
emailRequiredError: 'איימיל נדרש',
|
|
77
|
+
emailInvalidError: 'יש להזין כתובת איימיל תקינה',
|
|
78
|
+
passwordRequiredError: 'סיסמה נדרשת',
|
|
79
|
+
passwordTooShortError: 'הסיסמה חייבת להיות לפחות 8 תווים',
|
|
80
|
+
passwordMismatchError: 'הסיסמאות לא תואמות',
|
|
81
|
+
firstNameRequiredError: 'שם פרטי נדרש',
|
|
82
|
+
lastNameRequiredError: 'שם משפחה נדרש'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const englishDefaults = {
|
|
86
|
+
title: 'Create Account',
|
|
87
|
+
firstNameLabel: 'First Name',
|
|
88
|
+
lastNameLabel: 'Last Name',
|
|
89
|
+
emailLabel: 'Email',
|
|
90
|
+
passwordLabel: 'Password',
|
|
91
|
+
confirmPasswordLabel: 'Confirm Password',
|
|
92
|
+
signupButton: 'Create Account',
|
|
93
|
+
alreadyHaveAccount: 'Already have an account?',
|
|
94
|
+
// Validation errors
|
|
95
|
+
emailRequiredError: 'Email is required',
|
|
96
|
+
emailInvalidError: 'Please enter a valid email address',
|
|
97
|
+
passwordRequiredError: 'Password is required',
|
|
98
|
+
passwordTooShortError: 'Password must be at least 8 characters',
|
|
99
|
+
passwordMismatchError: 'Passwords do not match',
|
|
100
|
+
firstNameRequiredError: 'First name is required',
|
|
101
|
+
lastNameRequiredError: 'Last name is required'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const defaults = props.useHebrewDefaults ? hebrewDefaults : englishDefaults
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
title: props.texts.title || defaults.title,
|
|
108
|
+
firstNameLabel: props.texts.firstNameLabel || defaults.firstNameLabel,
|
|
109
|
+
lastNameLabel: props.texts.lastNameLabel || defaults.lastNameLabel,
|
|
110
|
+
emailLabel: props.texts.emailLabel || defaults.emailLabel,
|
|
111
|
+
passwordLabel: props.texts.passwordLabel || defaults.passwordLabel,
|
|
112
|
+
confirmPasswordLabel: props.texts.confirmPasswordLabel || defaults.confirmPasswordLabel,
|
|
113
|
+
signupButton: props.texts.signupButton || defaults.signupButton,
|
|
114
|
+
alreadyHaveAccount: props.texts.alreadyHaveAccount || defaults.alreadyHaveAccount,
|
|
115
|
+
// Validation errors
|
|
116
|
+
emailRequiredError: props.texts.emailRequiredError || defaults.emailRequiredError,
|
|
117
|
+
emailInvalidError: props.texts.emailInvalidError || defaults.emailInvalidError,
|
|
118
|
+
passwordRequiredError: props.texts.passwordRequiredError || defaults.passwordRequiredError,
|
|
119
|
+
passwordTooShortError: props.texts.passwordTooShortError || defaults.passwordTooShortError,
|
|
120
|
+
passwordMismatchError: props.texts.passwordMismatchError || defaults.passwordMismatchError,
|
|
121
|
+
firstNameRequiredError: props.texts.firstNameRequiredError || defaults.firstNameRequiredError,
|
|
122
|
+
lastNameRequiredError: props.texts.lastNameRequiredError || defaults.lastNameRequiredError
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const textDirection = computed(() => ({ 'auth-rtl': props.useHebrewDefaults }))
|
|
127
|
+
|
|
128
|
+
function getDevError() {
|
|
129
|
+
switch (props.errorState) {
|
|
130
|
+
case 'email-required': return texts.value.emailRequiredError
|
|
131
|
+
case 'email-invalid': return texts.value.emailInvalidError
|
|
132
|
+
case 'password-short': return texts.value.passwordTooShortError
|
|
133
|
+
case 'password-mismatch': return texts.value.passwordMismatchError
|
|
134
|
+
case 'name-required': return props.showNames ? texts.value.firstNameRequiredError : null
|
|
135
|
+
case 'server-error': return props.useHebrewDefaults ? 'כתובת האימייל כבר קיימת או שגיאת שרת' : 'Email already exists or server error'
|
|
136
|
+
default: return null
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function validateForm() {
|
|
141
|
+
// Email validation
|
|
142
|
+
if (!form.value.email.trim()) {
|
|
143
|
+
return texts.value.emailRequiredError
|
|
144
|
+
}
|
|
145
|
+
const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/
|
|
146
|
+
if (!emailRegex.test(form.value.email)) {
|
|
147
|
+
return texts.value.emailInvalidError
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Password validation
|
|
151
|
+
if (!form.value.password) {
|
|
152
|
+
return texts.value.passwordRequiredError
|
|
153
|
+
}
|
|
154
|
+
if (form.value.password.length < 8) {
|
|
155
|
+
return texts.value.passwordTooShortError
|
|
156
|
+
}
|
|
157
|
+
if (form.value.password !== form.value.confirmPassword) {
|
|
158
|
+
return texts.value.passwordMismatchError
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Name validation (only if names are shown)
|
|
162
|
+
if (props.showNames) {
|
|
163
|
+
if (!form.value.first_name.trim()) {
|
|
164
|
+
return texts.value.firstNameRequiredError
|
|
165
|
+
}
|
|
166
|
+
if (!form.value.last_name.trim()) {
|
|
167
|
+
return texts.value.lastNameRequiredError
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function handleSignup() {
|
|
175
|
+
// Don't process if we're showing a development error
|
|
176
|
+
if (props.errorState !== 'normal') {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Clear previous errors
|
|
181
|
+
internalError.value = ''
|
|
182
|
+
|
|
183
|
+
// Validate form first
|
|
184
|
+
const validationError = validateForm()
|
|
185
|
+
if (validationError) {
|
|
186
|
+
internalError.value = validationError
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
const { message, success } = await signup(form.value).catch((error) => {
|
|
190
|
+
console.error(error)
|
|
191
|
+
// Handle specific server errors
|
|
192
|
+
const errorMessage = error.response?.data?.detail as string
|
|
193
|
+
if (errorMessage?.toLowerCase().includes('email')) {
|
|
194
|
+
return { success: false, message: 'Email already exists or invalid' }
|
|
195
|
+
}
|
|
196
|
+
if (errorMessage?.toLowerCase().includes('password')) {
|
|
197
|
+
return { success: false, message: 'Password does not meet requirements' }
|
|
198
|
+
}
|
|
199
|
+
return { success: false, message: errorMessage || 'Registration failed. Please try again.' }
|
|
200
|
+
})
|
|
201
|
+
if (!success) {
|
|
202
|
+
internalError.value = message as string
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
await login({ email: form.value.email, password: form.value.password })
|
|
206
|
+
}
|
|
207
|
+
</script>
|
|
208
|
+
|
|
209
|
+
<template>
|
|
210
|
+
<form :class="textDirection" @submit.prevent="handleSignup">
|
|
211
|
+
<h1 class="txt20 bold txt-center mb-1">
|
|
212
|
+
{{ texts.title }}
|
|
213
|
+
</h1>
|
|
214
|
+
|
|
215
|
+
<TextInput v-if="props.showNames" v-model="form.first_name" :label="texts.firstNameLabel" />
|
|
216
|
+
<TextInput v-if="props.showNames" v-model="form.last_name" :label="texts.lastNameLabel" />
|
|
217
|
+
<TextInput v-model="form.email" type="email" :label="texts.emailLabel" autocomplete="username" />
|
|
218
|
+
<PasswordInput v-model="form.password" class="mb-05" :label="texts.passwordLabel" />
|
|
219
|
+
<PasswordInput v-model="form.confirmPassword" :label="texts.confirmPasswordLabel" />
|
|
220
|
+
<Btn class="w-100 mt-2" :value="texts.signupButton" type="submit" />
|
|
221
|
+
<Btn thin flat class="txt-12 mt-075 underline block" :value="texts.alreadyHaveAccount" @click="$emit('switchForm', 'login')" />
|
|
222
|
+
<p v-if="error" class="txt-center color-red txt-12 mt-1" v-text="error" />
|
|
223
|
+
</form>
|
|
224
|
+
</template>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Export form components
|
|
2
|
+
export { default as ForgotPasswordForm } from './auth/ForgotPasswordForm.vue'
|
|
3
|
+
export { default as LoginForm } from './auth/LoginForm.vue'
|
|
4
|
+
export { default as ResetPasswordForm } from './auth/ResetPasswordForm.vue'
|
|
5
|
+
export { default as SignupForm } from './auth/SignupForm.vue'
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const INTAKE_WORKFLOW_ID = 'fdba1933-0964-4850-b52a-7a4324175790'
|
|
2
|
+
export const DEFAULT_AGENT_ID = 'fdba1933-0964-4850-b52a-7a4324175790'
|
|
3
|
+
export const providers = {
|
|
4
|
+
github: { name: 'GitHub', icon: 'github', color: '#24292F' },
|
|
5
|
+
google: { name: 'Google', icon: 'google', color: '#DB4437' },
|
|
6
|
+
microsoft: { name: 'Microsoft', icon: 'microsoft', color: '#00A4EF' },
|
|
7
|
+
apple: { name: 'Apple', icon: 'apple', color: '#000000' },
|
|
8
|
+
okta: { name: 'Okta', icon: 'sun', color: '#FFB600' },
|
|
9
|
+
facebook: { name: 'Facebook', icon: 'facebook', color: '#1877F3' },
|
|
10
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
+
// Core auth functionality
|
|
1
2
|
export * from './api'
|
|
3
|
+
// Components
|
|
4
|
+
export { default as ForgotPasswordForm } from './components/auth/ForgotPasswordForm.vue'
|
|
5
|
+
export { default as LoginForm } from './components/auth/LoginForm.vue'
|
|
6
|
+
export { default as ResetPasswordForm } from './components/auth/ResetPasswordForm.vue'
|
|
7
|
+
|
|
8
|
+
export { default as SignupForm } from './components/auth/SignupForm.vue'
|
|
9
|
+
export * from './constants'
|
|
10
|
+
// Page components
|
|
11
|
+
export { default as Callback } from './pages/Callback.vue'
|
|
12
|
+
export { default as ForgotPasswordPage } from './pages/ForgotPasswordPage.vue'
|
|
13
|
+
|
|
14
|
+
export { default as LoginPage } from './pages/LoginPage.vue'
|
|
15
|
+
export { default as ResetPasswordPage } from './pages/ResetPasswordPage.vue'
|
|
16
|
+
export { default as SignupPage } from './pages/SignupPage.vue'
|
|
17
|
+
// Routes
|
|
18
|
+
export * from './routes'
|
|
2
19
|
export * from './sso'
|
|
20
|
+
|
|
21
|
+
// Types (re-export from types file and module)
|
|
3
22
|
export * from './types'
|
|
4
|
-
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
ForgotPasswordTexts,
|
|
26
|
+
LoginTexts,
|
|
27
|
+
ResetPasswordTexts,
|
|
28
|
+
SignupTexts,
|
|
29
|
+
} from './types/'
|
|
5
30
|
export * from './useAuth'
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AuthenticationResponse, SSOProvider } from '@bagelink/auth'
|
|
3
|
+
import { useAuth } from '@bagelink/auth'
|
|
4
|
+
import { Btn, Card, Icon, Loading } from '@bagelink/vue'
|
|
5
|
+
import { computed, onMounted, ref } from 'vue'
|
|
6
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
7
|
+
|
|
8
|
+
import { providers } from '../constants'
|
|
9
|
+
|
|
10
|
+
const isLoading = ref(true)
|
|
11
|
+
const error = ref<string | null>(null)
|
|
12
|
+
const success = ref(false)
|
|
13
|
+
const isLinking = ref(false)
|
|
14
|
+
const provider = ref<SSOProvider | null>(null)
|
|
15
|
+
const authResponse = ref<AuthenticationResponse | null>(null)
|
|
16
|
+
|
|
17
|
+
const { sso, user, accountInfo } = useAuth()
|
|
18
|
+
const route = useRoute()
|
|
19
|
+
const router = useRouter()
|
|
20
|
+
const timeout = 4000
|
|
21
|
+
// Get provider info for better UI
|
|
22
|
+
const providerInfo = computed(() => {
|
|
23
|
+
if (provider.value === null) return null
|
|
24
|
+
return providers[provider.value]
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
async function linkCallback() {
|
|
28
|
+
isLinking.value = true
|
|
29
|
+
try {
|
|
30
|
+
await sso.handleLinkCallback()
|
|
31
|
+
success.value = true
|
|
32
|
+
setTimeout(() => router.push('/'), timeout)
|
|
33
|
+
} catch (err: unknown) {
|
|
34
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to link account'
|
|
35
|
+
error.value = errorMessage
|
|
36
|
+
} finally {
|
|
37
|
+
isLoading.value = false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function handleCallback() {
|
|
42
|
+
const { state } = route.query
|
|
43
|
+
provider.value = sessionStorage.getItem(`oauth_provider:${state}`) as SSOProvider
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const response = await sso.handleCallback()
|
|
47
|
+
if (response === null) {
|
|
48
|
+
error.value = `Failed to authenticate with ${providerInfo.value?.name ?? 'provider'}`
|
|
49
|
+
} else {
|
|
50
|
+
authResponse.value = response
|
|
51
|
+
success.value = true
|
|
52
|
+
setTimeout(() => router.push('/'), timeout)
|
|
53
|
+
}
|
|
54
|
+
} catch (err: unknown) {
|
|
55
|
+
const errorMessage = err instanceof Error ? err.message : 'Authentication failed'
|
|
56
|
+
error.value = errorMessage
|
|
57
|
+
} finally {
|
|
58
|
+
isLoading.value = false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onMounted(async () => {
|
|
63
|
+
if (user.value) {
|
|
64
|
+
await linkCallback()
|
|
65
|
+
} else {
|
|
66
|
+
await handleCallback()
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<div class="flex justify-content-center align-items-center vh-100 px-1">
|
|
73
|
+
<Card class="p-2 txt-center w450px shadow">
|
|
74
|
+
<!-- Loading State -->
|
|
75
|
+
<div v-if="isLoading" class="flex column align-items-center gap-1">
|
|
76
|
+
<Loading />
|
|
77
|
+
<div class="mt-1">
|
|
78
|
+
<h2 class="mb-05 pb-0 mt-0 txt24 m_txt20">
|
|
79
|
+
{{ isLinking ? 'Linking Account' : 'Authenticating' }}
|
|
80
|
+
</h2>
|
|
81
|
+
<p class="opacity-7 txt-14">
|
|
82
|
+
Please wait while we {{ isLinking ? 'link your' : 'complete the' }} {{ providerInfo?.name
|
|
83
|
+
|| 'OAuth' }} {{ isLinking ? 'account' : 'authentication' }}.
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Success State -->
|
|
89
|
+
<div v-else-if="success && !error" class="flex column align-items-center gap-1">
|
|
90
|
+
<div class="flex justify-content-center align-items-center mb-1">
|
|
91
|
+
<div class="relative">
|
|
92
|
+
<Icon name="check_circle" size="5" class="txt-green line-height-1" />
|
|
93
|
+
<div
|
|
94
|
+
v-if="providerInfo"
|
|
95
|
+
class="absolute flex justify-content-center align-items-center bg-white rounded"
|
|
96
|
+
style="bottom: -8px; right: -8px; width: 40px; height: 40px; border: 3px solid white;"
|
|
97
|
+
>
|
|
98
|
+
<Icon :name="providerInfo.icon" size="1.5" :style="{ color: providerInfo.color }" />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div>
|
|
104
|
+
<h2 class="mb-05 pb-0 mt-0 txt24 m_txt20">
|
|
105
|
+
{{ isLinking ? 'Account Linked!' : 'Welcome Back!' }}
|
|
106
|
+
</h2>
|
|
107
|
+
<p class="opacity-7 txt-14 mb-1">
|
|
108
|
+
{{ isLinking
|
|
109
|
+
? `Successfully linked your ${providerInfo?.name || 'account'}.`
|
|
110
|
+
: `You've successfully signed in with ${providerInfo?.name || 'your account'}.`
|
|
111
|
+
}}
|
|
112
|
+
</p>
|
|
113
|
+
|
|
114
|
+
<!-- Show user info if available -->
|
|
115
|
+
<div v-if="user || accountInfo" class="bg-gray-light rounded p-1 mt-1">
|
|
116
|
+
<div class="flex column gap-05">
|
|
117
|
+
<div v-if="user?.name" class="flex align-items-center gap-05 justify-content-center">
|
|
118
|
+
<Icon name="person" size="1.2" class="opacity-7" />
|
|
119
|
+
<span class="txt-14 bold">{{ user.name }}</span>
|
|
120
|
+
</div>
|
|
121
|
+
<div v-if="user?.email" class="flex align-items-center gap-05 justify-content-center">
|
|
122
|
+
<Icon name="email" size="1.2" class="opacity-7" />
|
|
123
|
+
<span class="txt-14 opacity-7">{{ user.email }}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div
|
|
126
|
+
v-if="accountInfo?.authentication_methods"
|
|
127
|
+
class="flex gap-05 justify-content-center mt-05"
|
|
128
|
+
>
|
|
129
|
+
<Icon
|
|
130
|
+
v-for="method in accountInfo.authentication_methods" :key="method.id"
|
|
131
|
+
:name="method.type === 'sso' ? (method.provider || 'link') : 'password'" size="1.2"
|
|
132
|
+
class="opacity-5"
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<p class="txt-12 opacity-5 mt-1">
|
|
139
|
+
Redirecting you to home...
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Error State -->
|
|
145
|
+
<div v-else-if="error" class="flex column align-items-center gap-1">
|
|
146
|
+
<Icon name="error" size="5" class="txt-red mb-1 line-height-1" />
|
|
147
|
+
<div>
|
|
148
|
+
<h2 class="mb-05 pb-0 mt-0 txt24 m_txt20">
|
|
149
|
+
Authentication Failed
|
|
150
|
+
</h2>
|
|
151
|
+
<p class="opacity-7 txt-14 mb-1">
|
|
152
|
+
{{ error }}
|
|
153
|
+
</p>
|
|
154
|
+
|
|
155
|
+
<div class="bg-red-light rounded p-1 mt-1 mb-1">
|
|
156
|
+
<p class="txt-12 opacity-7">
|
|
157
|
+
This could happen if:
|
|
158
|
+
</p>
|
|
159
|
+
<ul class="txt-12 opacity-7 txt-start mt-05" style="list-style: none; padding-left: 0;">
|
|
160
|
+
<li class="mb-025">
|
|
161
|
+
• The authorization was cancelled
|
|
162
|
+
</li>
|
|
163
|
+
<li class="mb-025">
|
|
164
|
+
• The state parameter was invalid
|
|
165
|
+
</li>
|
|
166
|
+
<li class="mb-025">
|
|
167
|
+
• The OAuth provider denied access
|
|
168
|
+
</li>
|
|
169
|
+
<li class="mb-025">
|
|
170
|
+
• Network connectivity issues
|
|
171
|
+
</li>
|
|
172
|
+
</ul>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="flex gap-05 justify-content-center">
|
|
176
|
+
<Btn outline value="Try Again" to="/login" />
|
|
177
|
+
<Btn value="Go Home" to="/" />
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</Card>
|
|
182
|
+
</div>
|
|
183
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ForgotPasswordTexts } from '../components/auth/ForgotPasswordForm.vue'
|
|
3
|
+
import { Card } from '@bagelink/vue'
|
|
4
|
+
import { useRouter } from 'vue-router'
|
|
5
|
+
import ForgotPasswordForm from '../components/auth/ForgotPasswordForm.vue'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Custom texts for the forgot password form */
|
|
9
|
+
texts?: Partial<ForgotPasswordTexts>
|
|
10
|
+
/** Card styling */
|
|
11
|
+
cardWidth?: string
|
|
12
|
+
cardShadow?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
16
|
+
cardWidth: '450px',
|
|
17
|
+
cardShadow: true
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const router = useRouter()
|
|
21
|
+
|
|
22
|
+
function switchForm(form: string) {
|
|
23
|
+
if (form === 'login') {
|
|
24
|
+
router.push('/login')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div class="flex justify-content-center align-items-center min-vh-100 px-1 py-2">
|
|
31
|
+
<Card
|
|
32
|
+
:class="{ shadow: cardShadow }"
|
|
33
|
+
:style="{ width: cardWidth, maxWidth: '100%' }"
|
|
34
|
+
class="p-2"
|
|
35
|
+
>
|
|
36
|
+
<ForgotPasswordForm
|
|
37
|
+
:texts="texts"
|
|
38
|
+
@switch-form="switchForm"
|
|
39
|
+
/>
|
|
40
|
+
</Card>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { LoginTexts } from '../components/auth/LoginForm.vue'
|
|
3
|
+
import { Card } from '@bagelink/vue'
|
|
4
|
+
import { useRouter } from 'vue-router'
|
|
5
|
+
import LoginForm from '../components/auth/LoginForm.vue'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Custom texts for the login form */
|
|
9
|
+
texts?: Partial<LoginTexts>
|
|
10
|
+
/** Show/hide specific SSO providers */
|
|
11
|
+
showGithub?: boolean
|
|
12
|
+
showGoogle?: boolean
|
|
13
|
+
showMicrosoft?: boolean
|
|
14
|
+
showApple?: boolean
|
|
15
|
+
showOkta?: boolean
|
|
16
|
+
showFacebook?: boolean
|
|
17
|
+
/** SSO button styling */
|
|
18
|
+
ssoOutline?: boolean
|
|
19
|
+
ssoShowValue?: boolean
|
|
20
|
+
ssoBrandBackground?: boolean
|
|
21
|
+
/** Show/hide form elements */
|
|
22
|
+
showForgotPassword?: boolean
|
|
23
|
+
showSignupButton?: boolean
|
|
24
|
+
/** Card styling */
|
|
25
|
+
cardWidth?: string
|
|
26
|
+
cardShadow?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
30
|
+
showGithub: true,
|
|
31
|
+
showGoogle: true,
|
|
32
|
+
showMicrosoft: true,
|
|
33
|
+
showApple: true,
|
|
34
|
+
showOkta: true,
|
|
35
|
+
showFacebook: true,
|
|
36
|
+
ssoOutline: true,
|
|
37
|
+
ssoShowValue: true,
|
|
38
|
+
ssoBrandBackground: true,
|
|
39
|
+
showForgotPassword: true,
|
|
40
|
+
showSignupButton: true,
|
|
41
|
+
cardWidth: '450px',
|
|
42
|
+
cardShadow: true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const router = useRouter()
|
|
46
|
+
|
|
47
|
+
function switchForm(form: string) {
|
|
48
|
+
if (form === 'signup') {
|
|
49
|
+
router.push('/signup')
|
|
50
|
+
} else if (form === 'forgot-password') {
|
|
51
|
+
router.push('/forgot-password')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<div class="flex justify-content-center align-items-center min-vh-100 px-1 py-2">
|
|
58
|
+
<Card :class="{ shadow: cardShadow }" :style="{ width: cardWidth, maxWidth: '100%' }" class="p-2">
|
|
59
|
+
<LoginForm
|
|
60
|
+
:texts="texts" :github="showGithub" :google="showGoogle" :microsoft="showMicrosoft"
|
|
61
|
+
:apple="showApple" :okta="showOkta" :facebook="showFacebook" :sso-outline="ssoOutline"
|
|
62
|
+
:sso-show-value="ssoShowValue" :sso-brand-background="ssoBrandBackground"
|
|
63
|
+
:show-forgot-password="showForgotPassword" :show-signup-button="showSignupButton"
|
|
64
|
+
@switch-form="switchForm"
|
|
65
|
+
/>
|
|
66
|
+
</Card>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ResetPasswordTexts } from '../components/auth/ResetPasswordForm.vue'
|
|
3
|
+
import { Card } from '@bagelink/vue'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
import { useRouter, useRoute } from 'vue-router'
|
|
6
|
+
import ResetPasswordForm from '../components/auth/ResetPasswordForm.vue'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** Custom texts for the reset password form */
|
|
10
|
+
texts?: Partial<ResetPasswordTexts>
|
|
11
|
+
/** Card styling */
|
|
12
|
+
cardWidth?: string
|
|
13
|
+
cardShadow?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
cardWidth: '450px',
|
|
18
|
+
cardShadow: true
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const router = useRouter()
|
|
22
|
+
const route = useRoute()
|
|
23
|
+
|
|
24
|
+
const token = computed(() => route.query.token as string)
|
|
25
|
+
|
|
26
|
+
function switchForm(form: string) {
|
|
27
|
+
if (form === 'login') {
|
|
28
|
+
router.push('/login')
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="flex justify-content-center align-items-center min-vh-100 px-1 py-2">
|
|
35
|
+
<Card
|
|
36
|
+
:class="{ shadow: cardShadow }"
|
|
37
|
+
:style="{ width: cardWidth, maxWidth: '100%' }"
|
|
38
|
+
class="p-2"
|
|
39
|
+
>
|
|
40
|
+
<ResetPasswordForm
|
|
41
|
+
:texts="texts"
|
|
42
|
+
:token="token"
|
|
43
|
+
@switch-form="switchForm"
|
|
44
|
+
/>
|
|
45
|
+
</Card>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SignupTexts } from '../components/auth/SignupForm.vue'
|
|
3
|
+
import { Card } from '@bagelink/vue'
|
|
4
|
+
import { useRouter } from 'vue-router'
|
|
5
|
+
import SignupForm from '../components/auth/SignupForm.vue'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Custom texts for the signup form */
|
|
9
|
+
texts?: Partial<SignupTexts>
|
|
10
|
+
/** Show name fields (first name, last name) */
|
|
11
|
+
showNames?: boolean
|
|
12
|
+
/** Card styling */
|
|
13
|
+
cardWidth?: string
|
|
14
|
+
cardShadow?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
showNames: true,
|
|
19
|
+
cardWidth: '450px',
|
|
20
|
+
cardShadow: true
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const router = useRouter()
|
|
24
|
+
|
|
25
|
+
function switchForm(form: string) {
|
|
26
|
+
if (form === 'login') {
|
|
27
|
+
router.push('/login')
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="flex justify-content-center align-items-center min-vh-100 px-1 py-2">
|
|
34
|
+
<Card
|
|
35
|
+
:class="{ shadow: cardShadow }"
|
|
36
|
+
:style="{ width: cardWidth, maxWidth: '100%' }"
|
|
37
|
+
class="p-2"
|
|
38
|
+
>
|
|
39
|
+
<SignupForm
|
|
40
|
+
:texts="texts"
|
|
41
|
+
:show-names="showNames"
|
|
42
|
+
@switch-form="switchForm"
|
|
43
|
+
/>
|
|
44
|
+
</Card>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|