import { Box, Link, Typography } from '@mui/joy'
import { useQueryClient } from '@tanstack/react-query'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../../hooks/useAuth.jsx'
import { useNotification } from '../../service/NotificationProvider'
import { signUp } from '../../utils/Fetcher'
import { getPendingInvite, joinCirclePath } from '../../utils/PendingInvite'
import {
AuthPasswordField,
AuthSubmitButton,
AuthTextField,
LegalLinks,
} from './AuthFields'
import AuthShell from './AuthShell'
const SignupView = () => {
const { t } = useTranslation('auth')
const [username, setUsername] = React.useState('')
const [password, setPassword] = React.useState('')
const Navigate = useNavigate()
const queryClient = useQueryClient()
const [displayName, setDisplayName] = React.useState('')
const [email, setEmail] = React.useState('')
const [usernameError, setUsernameError] = React.useState('')
const [passwordError, setPasswordError] = React.useState('')
const [emailError, setEmailError] = React.useState('')
const [displayNameError, setDisplayNameError] = React.useState('')
const [isSubmitting, setIsSubmitting] = React.useState(false)
const { showError } = useNotification()
const { login: authLogin } = useAuth()
// Sign-in goes through the auth context, not a bare fetch: it stores the
// refresh token and updates the provider's own state, so the rest of the app
// sees the new session without a reload.
const handleLogin = async (username, password) => {
const result = await authLogin({ username, password })
if (!result.success) {
showError({
title: 'Almost there',
message: t('signupSignInFailed'),
})
Navigate('/login')
return
}
// Invalidate user profile queries to ensure fresh data
queryClient.invalidateQueries(['userProfile'])
// Someone who signed up from a circle invite is joining an existing
// circle, so sending them through "name your circle" is both a dead
// end for the invite and the wrong question.
const pendingInvite = getPendingInvite()
if (pendingInvite) {
Navigate(joinCirclePath(pendingInvite), { replace: true })
return
}
// "How did you hear about us" (cloud) / privacy preferences (self-hosted)
// that view forwards to '/circle-setup' once the user answers.
Navigate('/heard-about', { replace: true })
}
const handleSignUpValidation = () => {
// Reset errors before validation
setUsernameError(null)
setPasswordError(null)
setDisplayNameError(null)
setEmailError(null)
let isValid = true
if (!username.trim()) {
setUsernameError(t('usernameRequired'))
isValid = false
}
if (username.length < 4) {
setUsernameError(t('usernameMinLength'))
isValid = false
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
setEmailError(t('invalidEmail'))
isValid = false
}
if (password.length < 8) {
setPasswordError(t('passwordLength'))
isValid = false
}
if (password.length > 64) {
setPasswordError(t('passwordLength'))
isValid = false
}
if (!displayName.trim()) {
setDisplayNameError(t('displayNameRequired'))
isValid = false
}
// display name should only contain letters and spaces and numbers:
if (!/^[a-zA-Z0-9 ]+$/.test(displayName)) {
setDisplayNameError(t('displayNameChars'))
isValid = false
}
// username should only contain lowercase letters, numbers, dot and dash:
if (!/^[a-z0-9.-]+$/.test(username)) {
setUsernameError(
'Username can only contain lowercase letters, numbers, dot and dash',
)
isValid = false
}
return isValid
}
const handleSubmit = async e => {
e.preventDefault()
if (!handleSignUpValidation()) {
return
}
setIsSubmitting(true)
signUp(username, password, displayName, email)
.then(response => {
if (response.status === 201) {
handleLogin(username, password)
} else if (response.status === 403) {
showError({
title: t('signupFailed'),
message: t('signupDisabled'),
})
} else {
console.log('Signup failed')
response.json().then(res => {
showError({
title: t('signupFailed'),
message: res.error || 'An error occurred during signup',
})
})
}
})
.finally(() => setIsSubmitting(false))
}
return (
}
logoSize={0}
>
{
setDisplayNameError(null)
setDisplayName(e.target.value)
}}
/>
{
setUsernameError(null)
setUsername(e.target.value.trim())
}}
/>
{
setEmailError(null)
setEmail(e.target.value.trim())
}}
/>
{
setPasswordError(null)
setPassword(e.target.value)
}}
/>
{t('createAccountButton')}
{t('termsShort')}
Policy.
Already have an account?{' '}
Navigate('/login')}
>
Sign in
)
}
export default SignupView