registration handling

This commit is contained in:
Anton Tranelis 2025-05-28 16:46:40 +02:00
parent 9be07334f5
commit 013c08659c
5 changed files with 210 additions and 86 deletions

View File

@ -9,22 +9,22 @@ export function Modal({
}) { }) {
useEffect(() => { useEffect(() => {
if (showOnStartup) { if (showOnStartup) {
window.my_modal_3?.showModal() window.my_modal_3.showModal()
} }
}, []) }, [showOnStartup])
return ( return (
<dialog id="my_modal_3" className="tw:modal tw:transition-all tw:duration-300"> <dialog id='my_modal_3' className='tw:modal tw:transition-all tw:duration-300'>
<div className="tw:modal-box tw:transition-none"> <div className='tw:modal-box tw:transition-none'>
<button <button
className="tw:btn tw:btn-sm tw:btn-circle tw:btn-ghost tw:absolute tw:right-2 tw:top-2" className='tw:btn tw:btn-sm tw:btn-circle tw:btn-ghost tw:absolute tw:right-2 tw:top-2'
onClick={() => window.my_modal_3?.close()} onClick={() => window.my_modal_3.close()}
> >
</button> </button>
{children} {children}
</div> </div>
<form method="dialog" className="tw:modal-backdrop"> <form method='dialog' className='tw:modal-backdrop'>
<button>close</button> <button>close</button>
</form> </form>
</dialog> </dialog>

View File

@ -1,13 +1,17 @@
import { useState } from 'react' import { useRef, useState } from 'react'
import SVG from 'react-inlinesvg' import SVG from 'react-inlinesvg'
import { Link, useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
import { useAuth } from '#components/Auth/useAuth'
import { useLayers } from '#components/Map/hooks/useLayers' import { useLayers } from '#components/Map/hooks/useLayers'
import { AvatarWidget } from '#components/Profile/Subcomponents/AvatarWidget'
import type { LayerProps } from '#src/index' import { SetAvatar } from './Steps/SetAvatar'
import { Signup } from './Steps/Signup'
import type { LayerProps, UserItem } from '#src/index'
import type { SignupHandle } from './Steps/Signup'
// Schritt-Komponenten
const Step1 = () => { const Step1 = () => {
const layers = useLayers() const layers = useLayers()
return ( return (
@ -34,67 +38,6 @@ const Step1 = () => {
) )
} }
const Step2 = () => {
const [userName, setUserName] = useState<string>('')
const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>('')
return (
<div className='tw:space-y-2'>
<h3 className='tw:text-lg tw:font-bold'>Erstelle dir deinen Acount</h3>
<p className='tw:my-4'>
Werde Teil des Netzwerks und erstelle dir dein Profil und zeige dich auf der Karte!
</p>
<input
type='text'
placeholder='Name'
value={userName}
onChange={(e) => setUserName(e.target.value)}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='email'
placeholder='E-Mail'
value={email}
onChange={(e) => setEmail(e.target.value)}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='password'
placeholder='Password'
onChange={(e) => setPassword(e.target.value)}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<p className='tw:mt-4 tw:mb-8'>
Du hast schon einen Account?{' '}
<Link
className='tw:inline-block tw:hover:text-primary tw:hover:underline tw:hover:cursor-pointer tw:transition tw:duration-200 tw:text-primary'
onClick={() => close()}
to='/login'
>
Dann logge dich ein!
</Link>
</p>
</div>
)
}
const Step3 = () => {
const [avatar, setAvatar] = useState<string>('')
return (
<div>
<h3 className='tw:text-lg tw:font-bold tw:text-center'>Lade ein Bild von dir hoch</h3>
<div className='tw:mt-4 tw:flex tw:justify-center tw:items-center'>
<AvatarWidget avatar={avatar} setAvatar={setAvatar} />
</div>
<div className='tw:mt-4 tw:flex tw:justify-center'>
<button className='tw:btn tw:justify-center' onClick={() => setAvatar('')}>
Select
</button>
</div>
</div>
)
}
const Step4 = () => ( const Step4 = () => (
<div> <div>
<h3 className='tw:text-lg tw:font-bold'>Place your Profile on the Map!</h3> <h3 className='tw:text-lg tw:font-bold'>Place your Profile on the Map!</h3>
@ -102,18 +45,111 @@ const Step4 = () => (
</div> </div>
) )
const stepsTitles = ['Willkommen', 'Account', 'Avatar', 'Marker']
export const Onboarding = () => { export const Onboarding = () => {
const close = () => { const signupRef = useRef<SignupHandle>(null)
navigate('/') const [loading, setLoading] = useState(false)
}
const navigate = useNavigate() const navigate = useNavigate()
const { isAuthenticated } = useAuth()
const handleNext = async () => {
const currentStep = steps[stepIndex]
if (loading) return
if (currentStep.onNext) {
try {
setLoading(true)
const result = await currentStep.onNext()
if (result) {
const user = result as UserItem
const successMessage = currentStep.toastSuccess?.message?.replace(
'{firstName}',
user.first_name ?? 'Traveler',
)
toast.success(successMessage ?? `Hi ${user.first_name ?? 'Traveler'}`, {
icon: currentStep.toastSuccess?.icon ?? '✌️',
})
setStepIndex((i) => i + 1)
}
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
toast.error(
currentStep.toastError?.message ??
(error instanceof Error ? error.message : 'Ein Fehler ist aufgetreten'),
{
autoClose: currentStep.toastError?.autoClose ?? 10000,
},
)
} finally {
setLoading(false)
}
}
}
const [stepIndex, setStepIndex] = useState(0) const [stepIndex, setStepIndex] = useState(0)
const steps = [<Step1 key={1} />, <Step2 key={2} />, <Step3 key={3} />, <Step4 key={4} />] interface StepDefinition {
component: JSX.Element
onNext?: () => Promise<unknown> | void
toastSuccess?: {
message?: string
icon?: string
}
toastError?: {
message?: string
autoClose?: number
}
loading?: {
message?: string
}
}
const steps: StepDefinition[] = [
{
component: <Step1 />,
onNext: () => setStepIndex((i) => i + 1),
},
{
component: <Signup ref={signupRef} />,
onNext: async () => {
return await signupRef.current?.submit()
},
toastSuccess: {
message: 'Hi {firstName}!',
icon: '✌️',
},
toastError: {
message: 'Registration failed. Please try again.',
autoClose: 8000,
},
loading: {
message: 'Creating your account...',
},
},
{
component: <SetAvatar />,
onNext: () => setStepIndex((i) => i + 1),
toastSuccess: {
message: 'Avatar uploaded successfully!',
icon: '🖼️',
},
loading: {
message: 'Uploading avatar...',
},
},
{
component: <Step4 />,
onNext: () => navigate('/'),
toastSuccess: {
message: 'Welcome to the community!',
icon: '🎉',
},
loading: {
message: 'Finalizing your profile...',
},
},
]
const isLast = stepIndex === steps.length - 1 const isLast = stepIndex === steps.length - 1
const isFirst = stepIndex === 0 const isFirst = stepIndex === 0
@ -121,7 +157,7 @@ export const Onboarding = () => {
return ( return (
<div className='tw:max-w-xl tw:w-full tw:mx-auto tw:p-4'> <div className='tw:max-w-xl tw:w-full tw:mx-auto tw:p-4'>
<div> <div>
{steps[stepIndex]} {steps[stepIndex].component}
<div className='tw:flex tw:justify-between tw:mt-6'> <div className='tw:flex tw:justify-between tw:mt-6'>
<button <button
@ -141,11 +177,15 @@ export const Onboarding = () => {
))} ))}
</div> </div>
{!isLast ? ( {!isLast ? (
<button className='tw:btn tw:btn-primary' onClick={() => setStepIndex((i) => i + 1)}> <button
Next className={`tw:btn ${loading ? 'tw:btn-disabled' : 'tw:btn-primary'}`}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onClick={handleNext}
>
{loading ? <span className='tw:loading tw:loading-spinner'></span> : 'Next'}
</button> </button>
) : ( ) : (
<button className='tw:btn tw:btn-success' onClick={() => close()}> <button className='tw:btn tw:btn-success' onClick={close}>
Close Close
</button> </button>
)} )}

View File

@ -0,0 +1,20 @@
import { useState } from 'react'
import { AvatarWidget } from '#components/Profile/Subcomponents/AvatarWidget'
export const SetAvatar = () => {
const [avatar, setAvatar] = useState<string>('')
return (
<div>
<h3 className='tw:text-lg tw:font-bold tw:text-center'>Lade ein Bild von dir hoch</h3>
<div className='tw:mt-4 tw:flex tw:justify-center tw:items-center'>
<AvatarWidget avatar={avatar} setAvatar={setAvatar} />
</div>
<div className='tw:mt-4 tw:flex tw:justify-center'>
<button className='tw:btn tw:justify-center' onClick={() => setAvatar('')}>
Select
</button>
</div>
</div>
)
}

View File

@ -0,0 +1,64 @@
import { forwardRef, useImperativeHandle, useState } from 'react'
import { Link } from 'react-router-dom'
import { useAuth } from '#components/Auth/useAuth'
import type { UserItem } from '#components/Auth/useAuth'
export interface SignupHandle {
submit: () => Promise<UserItem | undefined>
}
export const Signup = forwardRef<SignupHandle>((_, ref) => {
const [userName, setUserName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const { register } = useAuth()
useImperativeHandle(ref, () => ({
submit: async () => {
return register({ email, password }, userName)
},
}))
return (
<div className='tw:space-y-2'>
<h3 className='tw:text-lg tw:font-bold'>Erstelle dir deinen Account</h3>
<p className='tw:my-4'>
Werde Teil des Netzwerks und erstelle dein Profil und zeige dich auf der Karte!
</p>
<input
type='text'
placeholder='Name'
value={userName}
onChange={(e) => setUserName(e.target.value)}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='email'
placeholder='E-Mail'
value={email}
onChange={(e) => setEmail(e.target.value)}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<input
type='password'
placeholder='Password'
value={password}
onChange={(e) => setPassword(e.target.value)}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
<p className='tw:mt-4 tw:mb-8'>
Du hast schon einen Account?{' '}
<Link
className='tw:inline-block tw:hover:text-primary tw:hover:underline tw:hover:cursor-pointer tw:transition tw:duration-200 tw:text-primary'
to='/login'
>
Dann logge dich ein!
</Link>
</p>
</div>
)
})
Signup.displayName = 'Signup'

View File

@ -14,7 +14,7 @@ export * from './Components/Onboarding'
declare global { declare global {
interface Window { interface Window {
my_modal_3: { my_modal_3: {
[x: string]: any close(): void
showModal(): void showModal(): void
} }
} }