feat: register Backend link

This commit is contained in:
DeathKaioken 2025-10-16 08:48:43 +02:00
parent 943079d94f
commit 604556ca06
2 changed files with 206 additions and 23 deletions

View File

@ -2,6 +2,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline' import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline'
import { useRegister } from '../hooks/useRegister'
interface RegisterFormProps { interface RegisterFormProps {
mode: 'personal' | 'company' mode: 'personal' | 'company'
@ -69,6 +70,9 @@ export default function RegisterForm({
const [error, setError] = useState('') const [error, setError] = useState('')
const [formFade, setFormFade] = useState('fade-in') const [formFade, setFormFade] = useState('fade-in')
// Hook for backend calls
const { registerPersonalReferral, registerCompanyReferral, error: regError } = useRegister()
// Animate form when mode changes // Animate form when mode changes
useEffect(() => { useEffect(() => {
setFormFade('fade-out') setFormFade('fade-out')
@ -108,7 +112,9 @@ export default function RegisterForm({
const validatePersonalForm = (): boolean => { const validatePersonalForm = (): boolean => {
if (!personalForm.firstName.trim() || !personalForm.lastName.trim() || if (!personalForm.firstName.trim() || !personalForm.lastName.trim() ||
!personalForm.email.trim() || !personalForm.confirmEmail.trim() || !personalForm.email.trim() || !personalForm.confirmEmail.trim() ||
!personalForm.password.trim() || !personalForm.confirmPassword.trim()) { !personalForm.password.trim() || !personalForm.confirmPassword.trim() ||
!personalForm.phoneNumber.trim() // now required by backend
) {
setError('Alle Felder sind erforderlich') setError('Alle Felder sind erforderlich')
return false return false
} }
@ -135,7 +141,9 @@ export default function RegisterForm({
const validateCompanyForm = (): boolean => { const validateCompanyForm = (): boolean => {
if (!companyForm.companyName.trim() || !companyForm.companyEmail.trim() || if (!companyForm.companyName.trim() || !companyForm.companyEmail.trim() ||
!companyForm.confirmCompanyEmail.trim() || !companyForm.contactPersonName.trim() || !companyForm.confirmCompanyEmail.trim() || !companyForm.contactPersonName.trim() ||
!companyForm.password.trim() || !companyForm.confirmPassword.trim()) { !companyForm.password.trim() || !companyForm.confirmPassword.trim() ||
!companyForm.companyPhone.trim() || !companyForm.contactPersonPhone.trim() // now required
) {
setError('Alle Felder sind erforderlich') setError('Alle Felder sind erforderlich')
return false return false
} }
@ -167,16 +175,22 @@ export default function RegisterForm({
if (!validatePersonalForm()) return if (!validatePersonalForm()) return
setLoading(true) setLoading(true)
setError('')
try { try {
// TODO: Implement API call const res = await registerPersonalReferral({
console.log('Personal registration:', { ...personalForm, refToken }) refToken: refToken || '',
firstName: personalForm.firstName,
// Simulate API delay lastName: personalForm.lastName,
await new Promise(resolve => setTimeout(resolve, 1000)) email: personalForm.email,
password: personalForm.password,
// For now, just call onRegistered phone: personalForm.phoneNumber,
})
if (res.ok) {
onRegistered() onRegistered()
} else {
setError(res.message || 'Registrierung fehlgeschlagen. Bitte versuche es erneut.')
}
} catch (error) { } catch (error) {
setError('Registrierung fehlgeschlagen. Bitte versuche es erneut.') setError('Registrierung fehlgeschlagen. Bitte versuche es erneut.')
} finally { } finally {
@ -191,16 +205,23 @@ export default function RegisterForm({
if (!validateCompanyForm()) return if (!validateCompanyForm()) return
setLoading(true) setLoading(true)
setError('')
try { try {
// TODO: Implement API call const res = await registerCompanyReferral({
console.log('Company registration:', { ...companyForm, refToken }) refToken: refToken || '',
companyEmail: companyForm.companyEmail,
// Simulate API delay password: companyForm.password,
await new Promise(resolve => setTimeout(resolve, 1000)) companyName: companyForm.companyName,
companyPhone: companyForm.companyPhone,
// For now, just call onRegistered contactPersonName: companyForm.contactPersonName,
contactPersonPhone: companyForm.contactPersonPhone,
})
if (res.ok) {
onRegistered() onRegistered()
} else {
setError(res.message || 'Registrierung fehlgeschlagen. Bitte versuche es erneut.')
}
} catch (error) { } catch (error) {
setError('Registrierung fehlgeschlagen. Bitte versuche es erneut.') setError('Registrierung fehlgeschlagen. Bitte versuche es erneut.')
} finally { } finally {
@ -208,6 +229,11 @@ export default function RegisterForm({
} }
} }
// Surface hook error if present and no local error
useEffect(() => {
if (regError && !error) setError(regError)
}, [regError]) // eslint-disable-line react-hooks/exhaustive-deps
// Input change handlers // Input change handlers
const handlePersonalChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handlePersonalChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target const { name, value } = e.target
@ -375,7 +401,7 @@ export default function RegisterForm({
<div> <div>
<label htmlFor="phoneNumber" className="block text-sm font-medium text-[#0F172A] mb-2"> <label htmlFor="phoneNumber" className="block text-sm font-medium text-[#0F172A] mb-2">
Telefonnummer Telefonnummer *
</label> </label>
<input <input
type="tel" type="tel"
@ -385,6 +411,7 @@ export default function RegisterForm({
onChange={handlePersonalChange} onChange={handlePersonalChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary"
placeholder="+49 123 456 7890" placeholder="+49 123 456 7890"
required
/> />
</div> </div>
@ -522,7 +549,7 @@ export default function RegisterForm({
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<label htmlFor="companyPhone" className="block text-sm font-medium text-[#0F172A] mb-2"> <label htmlFor="companyPhone" className="block text-sm font-medium text-[#0F172A] mb-2">
Firmen-Telefon Firmen-Telefon *
</label> </label>
<input <input
type="tel" type="tel"
@ -532,12 +559,13 @@ export default function RegisterForm({
onChange={handleCompanyChange} onChange={handleCompanyChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary"
placeholder="+49 123 456 7890" placeholder="+49 123 456 7890"
required
/> />
</div> </div>
<div> <div>
<label htmlFor="contactPersonPhone" className="block text-sm font-medium text-[#0F172A] mb-2"> <label htmlFor="contactPersonPhone" className="block text-sm font-medium text-[#0F172A] mb-2">
Ansprechpartner-Telefon Ansprechpartner-Telefon *
</label> </label>
<input <input
type="tel" type="tel"
@ -547,6 +575,7 @@ export default function RegisterForm({
onChange={handleCompanyChange} onChange={handleCompanyChange}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary"
placeholder="+49 123 456 7890" placeholder="+49 123 456 7890"
required
/> />
</div> </div>
</div> </div>

View File

@ -0,0 +1,154 @@
'use client'
import { useState } from 'react'
export type PersonalReferralPayload = {
refToken: string
firstName: string
lastName: string
email: string
password: string
phone: string
lang?: 'de' | 'en'
}
export type CompanyReferralPayload = {
refToken: string
companyEmail: string
password: string
companyName: string
companyPhone: string
contactPersonName: string
contactPersonPhone: string
lang?: 'de' | 'en'
}
type RegisterResult<T = any> = {
ok: boolean
status: number
data?: T | null
message?: string
}
function detectLang(): 'de' | 'en' {
if (typeof navigator !== 'undefined') {
const n = navigator.language || ''
if (n.toLowerCase().startsWith('de')) return 'de'
}
return 'en'
}
function mapError(status: number): string {
switch (status) {
case 404:
return 'Referral token not found.'
case 410:
return 'Referral token expired or exhausted.'
case 403:
return 'Referral token is inactive.'
case 400:
return 'Invalid registration data or token validation failed.'
default:
return 'Registration failed. Please try again later.'
}
}
// NEW: read ?ref from the URL as a fallback
function getRefTokenFromUrl(): string | null {
if (typeof window === 'undefined') return null
try {
const u = new URL(window.location.href)
return u.searchParams.get('ref')
} catch {
return null
}
}
export function useRegister() {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string>('')
const base = process.env.NEXT_PUBLIC_API_BASE_URL || ''
const registerPersonalReferral = async (payload: PersonalReferralPayload): Promise<RegisterResult> => {
setError('')
setLoading(true)
// Resolve token: prefer payload, fallback to URL
const finalRefToken = (payload.refToken || '').trim() || getRefTokenFromUrl() || ''
const body = { ...payload, refToken: finalRefToken, lang: payload.lang || detectLang() }
// NOTE: align with other endpoints using /api prefix
const url = `${base}/api/register/personal-referral`
console.log('🌐 useRegister: POST personal-referral', { url, refToken: finalRefToken ? `${finalRefToken.slice(0, 6)}${finalRefToken.slice(-6)}` : null })
try {
const res = await fetch(url, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
const json = await res.json().catch(() => null)
console.log('📡 useRegister: personal status:', res.status, 'body:', json)
if (!res.ok) {
const msg = json?.message || mapError(res.status)
setError(msg)
return { ok: false, status: res.status, data: json, message: msg }
}
return { ok: true, status: res.status, data: json, message: json?.message }
} catch (e) {
console.error('❌ useRegister: personal error:', e)
const msg = 'Network error. Please try again later.'
setError(msg)
return { ok: false, status: 0, data: null, message: msg }
} finally {
setLoading(false)
}
}
const registerCompanyReferral = async (payload: CompanyReferralPayload): Promise<RegisterResult> => {
setError('')
setLoading(true)
// Resolve token: prefer payload, fallback to URL
const finalRefToken = (payload.refToken || '').trim() || getRefTokenFromUrl() || ''
const body = { ...payload, refToken: finalRefToken, lang: payload.lang || detectLang() }
// NOTE: align with other endpoints using /api prefix
const url = `${base}/api/register/company-referral`
console.log('🌐 useRegister: POST company-referral', { url, refToken: finalRefToken ? `${finalRefToken.slice(0, 6)}${finalRefToken.slice(-6)}` : null })
try {
const res = await fetch(url, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
const json = await res.json().catch(() => null)
console.log('📡 useRegister: company status:', res.status, 'body:', json)
if (!res.ok) {
const msg = json?.message || mapError(res.status)
setError(msg)
return { ok: false, status: res.status, data: json, message: msg }
}
return { ok: true, status: res.status, data: json, message: json?.message }
} catch (e) {
console.error('❌ useRegister: company error:', e)
const msg = 'Network error. Please try again later.'
setError(msg)
return { ok: false, status: 0, data: null, message: msg }
} finally {
setLoading(false)
}
}
return {
loading,
error,
setError,
registerPersonalReferral,
registerCompanyReferral,
}
}