feat: reworked registration/sign-in, auth is now on homescreen and includes an explanation text. Reworked onboarding to only include a participation checkbox for the raffle.
This commit is contained in:
@@ -57,13 +57,10 @@ export function AuthForm() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center py-8 px-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="w-full">
|
||||
{/* Card */}
|
||||
<div className="bg-white rounded-lg shadow-lg overflow-hidden border border-gray-200">
|
||||
{/* Header */}
|
||||
<div className="bg-gradient-to-r from-blue-600 to-blue-700 px-6 py-8">
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-gray-200">
|
||||
@@ -200,7 +197,7 @@ export function AuthForm() {
|
||||
<p className="text-center text-sm text-gray-600 mt-6">
|
||||
{mode === 'signin' ? (
|
||||
<>
|
||||
Sie haben noch kein Konto?{' '}
|
||||
Du hast noch kein Konto?{' '}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -214,7 +211,7 @@ export function AuthForm() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Sie haben bereits ein Konto?{' '}
|
||||
Du hast bereits ein Konto?{' '}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
||||
@@ -25,68 +25,65 @@ export function Header() {
|
||||
<div className="flex justify-between items-center py-5">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="font-bold hover:text-blue-100 transition-colors text-lg sm:text-2xl">
|
||||
<span className="hidden sm:inline">TTS Dialektannotations-Plattform</span>
|
||||
<span className="sm:hidden">Dialektannotation</span>
|
||||
<span className="hidden sm:inline">TTS Dialektannotation</span>
|
||||
<span className="sm:hidden">TTS Dialektannotation</span>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-6">
|
||||
{loading ? (
|
||||
<span className="text-sm text-blue-100">Laden...</span>
|
||||
) : session ? (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-blue-100 uppercase tracking-wide">Willkommen zurück</p>
|
||||
<p className="text-sm font-semibold">{session.user?.email}</p>
|
||||
</div>
|
||||
{(session.user as any)?.admin && (
|
||||
<Link
|
||||
href="/admin"
|
||||
className="px-4 py-2 bg-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 text-white font-semibold rounded-lg transition-all duration-200 transform hover:scale-105"
|
||||
{(loading || session) && (
|
||||
<nav className="hidden md:flex items-center gap-6">
|
||||
{loading ? (
|
||||
<span className="text-sm text-blue-100">Laden...</span>
|
||||
) : session && (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-blue-100 uppercase tracking-wide">Willkommen zurück</p>
|
||||
<p className="text-sm font-semibold">{session.user?.email}</p>
|
||||
</div>
|
||||
{(session.user as any)?.admin && (
|
||||
<Link
|
||||
href="/admin"
|
||||
className="px-4 py-2 bg-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 text-white font-semibold rounded-lg transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
Admin
|
||||
</Link>
|
||||
)}
|
||||
<button
|
||||
onClick={handleSignOut}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-700 active:bg-red-800 text-white font-semibold rounded-lg transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
Admin
|
||||
</Link>
|
||||
)}
|
||||
<button
|
||||
onClick={handleSignOut}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-700 active:bg-red-800 text-white font-semibold rounded-lg transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
href="/user/sign-in"
|
||||
className="px-5 py-2 bg-green-500 hover:bg-green-600 active:bg-green-700 text-white font-semibold rounded-lg transition-all duration-200 transform hover:scale-105"
|
||||
>
|
||||
Anmelden
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="md:hidden p-2"
|
||||
title={mobileMenuOpen ? 'Menü schließen' : 'Menü öffnen'}
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{mobileMenuOpen ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
{(loading || session) && (
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="md:hidden p-2"
|
||||
title={mobileMenuOpen ? 'Menü schließen' : 'Menü öffnen'}
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{mobileMenuOpen ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
) : (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
)}
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Fullscreen Menu */}
|
||||
{mobileMenuOpen && (
|
||||
{mobileMenuOpen && session && (
|
||||
<div className="md:hidden fixed inset-0 top-16 bg-blue-700 z-40 flex flex-col p-4 space-y-4">
|
||||
{loading ? (
|
||||
<span className="text-sm text-blue-100">Laden...</span>
|
||||
) : session ? (
|
||||
) : (
|
||||
<>
|
||||
<div className="text-center py-4 border-b border-blue-600">
|
||||
<p className="text-xs text-blue-100 uppercase tracking-wide">Willkommen zurück</p>
|
||||
@@ -111,14 +108,6 @@ export function Header() {
|
||||
Abmelden
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
href="/user/sign-in"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="px-4 py-3 bg-green-500 hover:bg-green-600 text-white font-semibold rounded-lg transition-colors text-center"
|
||||
>
|
||||
Anmelden
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DIALECT_LABELS_WITHOUT_DE } from '@/lib/dialects';
|
||||
interface OnboardingFormViewProps {
|
||||
experimentId: number;
|
||||
onBack: () => void;
|
||||
userEmail: string;
|
||||
}
|
||||
|
||||
interface ResidenceEntry {
|
||||
@@ -17,7 +18,7 @@ interface ResidenceEntry {
|
||||
|
||||
interface FormData {
|
||||
age?: string;
|
||||
email?: string;
|
||||
participateInRaffle?: boolean;
|
||||
residences?: ResidenceEntry[];
|
||||
dialectQualities?: Record<string, string>;
|
||||
listeningExperience?: string;
|
||||
@@ -26,7 +27,7 @@ interface FormData {
|
||||
|
||||
type Step = 1 | 2 | 3;
|
||||
|
||||
export default function OnboardingFormView({ experimentId, onBack }: OnboardingFormViewProps) {
|
||||
export default function OnboardingFormView({ experimentId, onBack, userEmail }: OnboardingFormViewProps) {
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [currentStep, setCurrentStep] = useState<Step>(1);
|
||||
@@ -37,7 +38,7 @@ export default function OnboardingFormView({ experimentId, onBack }: OnboardingF
|
||||
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
age: '',
|
||||
email: '',
|
||||
participateInRaffle: false,
|
||||
residences: [{ region: '', years: '' }],
|
||||
dialectQualities: initializeDialectQualities()
|
||||
});
|
||||
@@ -50,7 +51,7 @@ export default function OnboardingFormView({ experimentId, onBack }: OnboardingF
|
||||
});
|
||||
}, [currentStep]);
|
||||
|
||||
const handleChange = (field: keyof FormData, value: string) => {
|
||||
const handleChange = (field: keyof FormData, value: string | boolean) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
@@ -223,18 +224,23 @@ export default function OnboardingFormView({ experimentId, onBack }: OnboardingF
|
||||
</div>
|
||||
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<label htmlFor="email" className="block text-s font-medium text-gray-900 mb-1">
|
||||
E-Mail-Adresse (optional)
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 mb-2">Wird nur für die Gutschein-Verlosung verwendet.</p>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
placeholder="beispiel@email.ch"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="participateInRaffle"
|
||||
checked={formData.participateInRaffle ?? false}
|
||||
onChange={(e) => handleChange('participateInRaffle', e.target.checked ? true : false)}
|
||||
className="mt-1 w-5 h-5 cursor-pointer accent-blue-600"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<label htmlFor="participateInRaffle" className="block text-s font-medium text-gray-900 mb-2 cursor-pointer">
|
||||
Ich möchte an der Gutscheinverlosung teilnehmen
|
||||
</label>
|
||||
<p className="text-sm text-gray-600 mb-2">
|
||||
Wir werden deine E-Mail-Adresse<strong>{userEmail && ` (${userEmail})`}</strong> verwenden, um dich im Falle eines Gewinns zu kontaktieren.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import { getOnboardingAnswers } from '@/app/actions/onboarding';
|
||||
import OnboardingInfoView from './OnboardingInfoView';
|
||||
import OnboardingFormView from './OnboardingFormView';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({
|
||||
@@ -21,6 +22,9 @@ export default function OnboardingPhase({ experimentId }: OnboardingPhaseProps)
|
||||
const [hasExistingAnswers, setHasExistingAnswers] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [userEmail, setUserEmail] = useState<string>('');
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
@@ -32,6 +36,11 @@ export default function OnboardingPhase({ experimentId }: OnboardingPhaseProps)
|
||||
setHasExistingAnswers(true);
|
||||
}
|
||||
}
|
||||
// Get user email for raffle participation
|
||||
const session = await authClient.getSession();
|
||||
if (session && session.data && session.data.user && session.data.user.email) {
|
||||
setUserEmail(session.data.user.email);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
@@ -69,5 +78,5 @@ export default function OnboardingPhase({ experimentId }: OnboardingPhaseProps)
|
||||
}
|
||||
|
||||
|
||||
return <OnboardingFormView experimentId={experimentId} onBack={() => { scrollToTop(); setShowInfoPage(true); }} />;
|
||||
return <OnboardingFormView experimentId={experimentId} userEmail={userEmail} onBack={() => { scrollToTop(); setShowInfoPage(true); }} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user