mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 20:41:16 +00:00
Merge pull request #5 from Since-3/feature/register-manager
added registriation of manager and business
This commit is contained in:
commit
4283f8a479
7 changed files with 706 additions and 55 deletions
|
|
@ -1,16 +1,24 @@
|
|||
import { LogoutButton } from "@/src/components/layout/LogoutButton";
|
||||
import { requireAuthWithData } from "@/src/lib/auth/getUser";
|
||||
import { requireAuthWithData, isManager, isUser } from "@/src/lib/auth/getUser";
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const userData = await requireAuthWithData();
|
||||
|
||||
// Get display name based on user type
|
||||
const displayName = isManager(userData)
|
||||
? `${userData.firstName} ${userData.lastName}`
|
||||
: userData.name || userData.email;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-4">Willkommen zurück!</h1>
|
||||
<p className="text-gray-600">
|
||||
Schön, dich wiederzusehen, {userData?.name || userData.email}
|
||||
</p>
|
||||
<p className="text-gray-600">Schön, dich wiederzusehen, {displayName}</p>
|
||||
{isManager(userData) && (
|
||||
<span className="inline-block mt-2 px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full">
|
||||
Manager
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
|
|
@ -19,10 +27,20 @@ export default async function DashboardPage() {
|
|||
<p className="text-gray-600 text-sm">Verwalte deine persönlichen Daten</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-2">Trainings</h2>
|
||||
<p className="text-gray-600 text-sm">Deine gebuchten Trainingseinheiten</p>
|
||||
</div>
|
||||
{isManager(userData) ? (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-2">Meine Businesses</h2>
|
||||
<p className="text-gray-600 text-sm">
|
||||
{userData.businesses.length}{" "}
|
||||
{userData.businesses.length === 1 ? "Business" : "Businesses"}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-2">Trainings</h2>
|
||||
<p className="text-gray-600 text-sm">Deine gebuchten Trainingseinheiten</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-2">Einstellungen</h2>
|
||||
|
|
@ -30,41 +48,93 @@ export default async function DashboardPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Benutzerdaten</h2>
|
||||
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Name</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData?.name || "—"}</dd>
|
||||
{isUser(userData) && (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Benutzerdaten</h2>
|
||||
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Name</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.name || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">E-Mail</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.email}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Geburtsdatum</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData.birthday ? new Date(userData.birthday).toLocaleDateString("de-DE") : "—"}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Wohnort</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData.plz && userData.city ? `${userData.plz} ${userData.city}` : "—"}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Geschlecht</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.gender || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Registriert seit</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData.createdAt
|
||||
? new Date(userData.createdAt).toLocaleDateString("de-DE")
|
||||
: "—"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isManager(userData) && (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Manager-Daten</h2>
|
||||
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Vorname</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.firstName}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Nachname</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.lastName}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">E-Mail</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.email}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Telefon</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.tel || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Registriert seit</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData.createdAt
|
||||
? new Date(userData.createdAt).toLocaleDateString("de-DE")
|
||||
: "—"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isManager(userData) && userData.businesses.length > 0 && (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Meine Businesses</h2>
|
||||
<div className="space-y-4">
|
||||
{userData.businesses.map((business) => (
|
||||
<div key={business.id} className="border-l-4 border-blue-500 pl-4 py-2">
|
||||
<h3 className="text-lg font-semibold text-gray-900">{business.name}</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">{business.address}</p>
|
||||
<p className="text-sm text-gray-600">{business.email}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">E-Mail</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData.email}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Geburtsdatum</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData?.birthday ? new Date(userData.birthday).toLocaleDateString("de-DE") : "—"}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Wohnort</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData?.plz && userData?.city ? `${userData.plz} ${userData.city}` : "—"}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Geschlecht</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">{userData?.gender || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">Registriert seit</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{userData?.createdAt ? new Date(userData.createdAt).toLocaleDateString("de-DE") : "—"}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LogoutButton />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
109
src/app/api/auth/register/admin/route.ts
Normal file
109
src/app/api/auth/register/admin/route.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// app/api/auth/register/admin/route.ts
|
||||
import { createClient } from "@/src/lib/supabase/server";
|
||||
import { prisma } from "@/src/lib/prisma";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
firstName,
|
||||
lastName,
|
||||
tel,
|
||||
plz,
|
||||
city,
|
||||
street,
|
||||
companyName,
|
||||
companyPlace,
|
||||
companyPLZ,
|
||||
companyStreet,
|
||||
companyMail,
|
||||
companyNumber,
|
||||
} = body;
|
||||
|
||||
// Validate required fields
|
||||
if (!email || !password || !firstName || !lastName || !companyName) {
|
||||
return NextResponse.json({ error: "Pflichtfelder fehlen" }, { status: 400 });
|
||||
}
|
||||
|
||||
const supabase = await createClient();
|
||||
|
||||
// Create Manager in Supabase Auth
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000"}/login`,
|
||||
data: {
|
||||
firstName,
|
||||
lastName,
|
||||
role: "manager",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error("❌ Supabase Auth Error:", error.message);
|
||||
return NextResponse.json({ error: error.message }, { status: 400 });
|
||||
}
|
||||
|
||||
const supabaseUser = data.user;
|
||||
if (!supabaseUser) {
|
||||
return NextResponse.json({ error: "Manager konnte nicht erstellt werden" }, { status: 500 });
|
||||
}
|
||||
|
||||
// Check if Manager already exists in DB
|
||||
const existingManager = await prisma.manager.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
|
||||
if (existingManager) {
|
||||
return NextResponse.json({ manager: existingManager }, { status: 200 });
|
||||
}
|
||||
|
||||
// Build business address
|
||||
const businessAddress = `${companyStreet}, ${companyPLZ} ${companyPlace}`;
|
||||
const managerAddress = plz && city && street ? `${street}, ${plz} ${city}` : "";
|
||||
|
||||
// Create Manager and Business in a transaction
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// Create Manager
|
||||
const newManager = await tx.manager.create({
|
||||
data: {
|
||||
id: supabaseUser.id,
|
||||
firstName,
|
||||
lastName,
|
||||
tel: tel || null,
|
||||
email,
|
||||
},
|
||||
});
|
||||
|
||||
// Create Business
|
||||
const newBusiness = await tx.business.create({
|
||||
data: {
|
||||
managerId: newManager.id,
|
||||
name: companyName,
|
||||
address: businessAddress,
|
||||
email: companyMail || email, // Use manager email if company email is not provided
|
||||
title: companyName, // Using company name as title
|
||||
},
|
||||
});
|
||||
|
||||
return { manager: newManager, business: newBusiness };
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
manager: result.manager,
|
||||
business: result.business,
|
||||
message: "Manager und Business erfolgreich erstellt",
|
||||
},
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error in /api/auth/register/admin:", error);
|
||||
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,8 @@ import { userLoginSchema } from "../modules/auth/validation/userLoginSchema";
|
|||
import { createClient } from "@/src/lib/supabase/client";
|
||||
import { useAuthStore } from "@/src/store/authStore";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { RegisterFormData } from "../types/auth";
|
||||
import { RegisterFormData, AdminRegisterFormData } from "../types/auth";
|
||||
import { adminRegisterSchema } from "../modules/auth/validation/adminRegisterSchema";
|
||||
|
||||
export const useAuth = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -112,10 +113,69 @@ export const useAuth = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const registerAdmin = async (formData: AdminRegisterFormData) => {
|
||||
setErrors({});
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
adminRegisterSchema.parse(formData);
|
||||
|
||||
const [firstName, ...lastNameParts] = formData.fullName.split(" ");
|
||||
const lastName = lastNameParts.join(" ") || firstName;
|
||||
|
||||
const res = await fetch("/api/auth/register/admin", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
firstName,
|
||||
lastName,
|
||||
tel: formData.tel,
|
||||
plz: formData.plz,
|
||||
city: formData.place,
|
||||
street: formData.street,
|
||||
companyName: formData.companyName,
|
||||
companyPlace: formData.companyPlace,
|
||||
companyPLZ: formData.companyPLZ,
|
||||
companyStreet: formData.companyStreet,
|
||||
companyMail: formData.companyMail,
|
||||
companyNumber: formData.companyNumber,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
setErrors({ email: data.error || "Registrierung fehlgeschlagen" });
|
||||
toast.error(data.error || "Registrierung fehlgeschlagen");
|
||||
return false;
|
||||
}
|
||||
|
||||
toast.success("Manager und Business erfolgreich registriert! Bitte bestätige deine E-Mail.");
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err instanceof ZodError) {
|
||||
const fieldErrors: Record<string, string> = {};
|
||||
err.issues.forEach((issue) => {
|
||||
const path = issue.path[0];
|
||||
if (typeof path === "string") fieldErrors[path] = issue.message;
|
||||
});
|
||||
setErrors(fieldErrors);
|
||||
return false;
|
||||
}
|
||||
toast.error("Ein unbekannter Fehler ist aufgetreten.");
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
errors,
|
||||
registerUser,
|
||||
registerAdmin,
|
||||
loginUser,
|
||||
setErrors,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createClient } from "@/src/lib/supabase/server";
|
|||
import { prisma } from "@/src/lib/prisma";
|
||||
import { cache } from "react";
|
||||
|
||||
// Define the user data type
|
||||
// Define the user data types
|
||||
export type UserData = {
|
||||
id: string;
|
||||
name: string | null;
|
||||
|
|
@ -15,11 +15,31 @@ export type UserData = {
|
|||
gender: string | null;
|
||||
pbSrc: string | null;
|
||||
createdAt: Date | null;
|
||||
role: "user";
|
||||
};
|
||||
|
||||
export type ManagerData = {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
tel: string | null;
|
||||
pbSrc: string | null;
|
||||
createdAt: Date | null;
|
||||
role: "manager";
|
||||
businesses: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
address: string;
|
||||
email: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type AuthUserData = UserData | ManagerData;
|
||||
|
||||
// Cache entry type
|
||||
type CacheEntry = {
|
||||
data: UserData;
|
||||
data: AuthUserData;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
|
|
@ -40,7 +60,7 @@ export const getUser = cache(async () => {
|
|||
});
|
||||
|
||||
// Cache user data from database with time-based cache
|
||||
export const getUserData = cache(async (): Promise<UserData | null> => {
|
||||
export const getUserData = cache(async (): Promise<AuthUserData | null> => {
|
||||
userDataCallCount++;
|
||||
console.log(`📊 [DB] getUserData called (count: ${userDataCallCount})`);
|
||||
|
||||
|
|
@ -60,6 +80,7 @@ export const getUserData = cache(async (): Promise<UserData | null> => {
|
|||
|
||||
console.log("🗄️ [DB] Fetching user data from Prisma for:", user.email);
|
||||
|
||||
// Try to find in User table first
|
||||
const userData = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: {
|
||||
|
|
@ -77,14 +98,47 @@ export const getUserData = cache(async (): Promise<UserData | null> => {
|
|||
},
|
||||
});
|
||||
|
||||
console.log("✅ [DB] User data fetched:", userData?.name);
|
||||
|
||||
// Store in cache
|
||||
if (userData) {
|
||||
userDataCache.set(user.id, { data: userData, timestamp: now });
|
||||
console.log("✅ [DB] User data fetched:", userData.name);
|
||||
const userWithRole: UserData = { ...userData, role: "user" };
|
||||
userDataCache.set(user.id, { data: userWithRole, timestamp: now });
|
||||
return userWithRole;
|
||||
}
|
||||
|
||||
return userData;
|
||||
// If not found in User table, try Manager table
|
||||
const managerData = await prisma.manager.findUnique({
|
||||
where: { id: user.id },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
email: true,
|
||||
tel: true,
|
||||
pbSrc: true,
|
||||
createdAt: true,
|
||||
businesses: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
address: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (managerData) {
|
||||
console.log(
|
||||
"✅ [DB] Manager data fetched:",
|
||||
`${managerData.firstName} ${managerData.lastName}`
|
||||
);
|
||||
const managerWithRole: ManagerData = { ...managerData, role: "manager" };
|
||||
userDataCache.set(user.id, { data: managerWithRole, timestamp: now });
|
||||
return managerWithRole;
|
||||
}
|
||||
|
||||
console.log("⚠️ [DB] No user or manager found for:", user.email);
|
||||
return null;
|
||||
});
|
||||
|
||||
export async function requireAuth() {
|
||||
|
|
@ -95,7 +149,7 @@ export async function requireAuth() {
|
|||
export async function requireAuthWithData() {
|
||||
const userData = await getUserData();
|
||||
if (!userData) {
|
||||
throw new Error("User not found");
|
||||
throw new Error("User or Manager not found");
|
||||
}
|
||||
return userData;
|
||||
}
|
||||
|
|
@ -107,3 +161,13 @@ export async function requireGuest() {
|
|||
redirect("/dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if user is a manager
|
||||
export function isManager(userData: AuthUserData): userData is ManagerData {
|
||||
return userData.role === "manager";
|
||||
}
|
||||
|
||||
// Helper function to check if user is a regular user
|
||||
export function isUser(userData: AuthUserData): userData is UserData {
|
||||
return userData.role === "user";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,292 @@
|
|||
"use client";
|
||||
import { useState } from "react";
|
||||
import AuthSidebarComponent from "../components/AuthSidebarComponent";
|
||||
import InputComponent from "@/src/components/layout/InputComponent";
|
||||
import { Button } from "@/src/components/ui/button";
|
||||
import { useAuthFormStore } from "../../store/authFormState";
|
||||
import { useAuth } from "@/src/hooks/useAuth";
|
||||
import { ZodError } from "zod";
|
||||
import {
|
||||
adminRegisterStep1Schema,
|
||||
adminRegisterStep2Schema,
|
||||
adminRegisterStep3Schema,
|
||||
} from "../../validation/adminRegisterSchema";
|
||||
import { Spinner } from "@/src/components/ui/spinner";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const RegisterAdminView = () => {
|
||||
return <div>Register Admin View</div>;
|
||||
const RegisterAdmin = () => {
|
||||
const [step, setStep] = useState(1);
|
||||
const { registerAdmin, loading, errors, setErrors } = useAuth();
|
||||
const router = useRouter();
|
||||
const formData = useAuthFormStore();
|
||||
|
||||
const handleChange = (field: string, value: string) => {
|
||||
formData.setField(field, value);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
setErrors({});
|
||||
try {
|
||||
if (step === 1) {
|
||||
adminRegisterStep1Schema.parse({
|
||||
fullName: formData.fullName,
|
||||
email: formData.email,
|
||||
tel: formData.tel,
|
||||
password: formData.password,
|
||||
passwordConfirm: formData.passwordConfirm,
|
||||
});
|
||||
setStep(2);
|
||||
} else if (step === 2) {
|
||||
adminRegisterStep2Schema.parse({
|
||||
plz: formData.plz,
|
||||
place: formData.place,
|
||||
street: formData.street,
|
||||
});
|
||||
setStep(3);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof ZodError) {
|
||||
const fieldErrors: Record<string, string> = {};
|
||||
err.issues.forEach((issue) => {
|
||||
const path = issue.path[0];
|
||||
if (typeof path === "string") {
|
||||
fieldErrors[path] = issue.message;
|
||||
}
|
||||
});
|
||||
setErrors(fieldErrors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegister = async () => {
|
||||
setErrors({});
|
||||
try {
|
||||
adminRegisterStep3Schema.parse({
|
||||
companyName: formData.companyName,
|
||||
companyPlace: formData.companyPlace,
|
||||
companyPLZ: formData.companyPLZ,
|
||||
companyStreet: formData.companyStreet,
|
||||
companyMail: formData.companyMail,
|
||||
companyNumber: formData.companyNumber,
|
||||
});
|
||||
|
||||
const success = await registerAdmin({
|
||||
fullName: formData.fullName,
|
||||
email: formData.email,
|
||||
tel: formData.tel,
|
||||
password: formData.password,
|
||||
passwordConfirm: formData.passwordConfirm,
|
||||
plz: formData.plz,
|
||||
place: formData.place,
|
||||
street: formData.street,
|
||||
companyName: formData.companyName,
|
||||
companyPlace: formData.companyPlace,
|
||||
companyPLZ: formData.companyPLZ,
|
||||
companyStreet: formData.companyStreet,
|
||||
companyMail: formData.companyMail,
|
||||
companyNumber: formData.companyNumber,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
formData.resetForm();
|
||||
router.push("/login");
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof ZodError) {
|
||||
const fieldErrors: Record<string, string> = {};
|
||||
err.issues.forEach((issue) => {
|
||||
const path = issue.path[0];
|
||||
if (typeof path === "string") {
|
||||
fieldErrors[path] = issue.message;
|
||||
}
|
||||
});
|
||||
setErrors(fieldErrors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen">
|
||||
<div className="hidden lg:flex lg:w-1/6 ">
|
||||
<div className="fixed p-4 left-0 top-0 h-screen w-1/2 z-10">
|
||||
<AuthSidebarComponent
|
||||
title="Werde zur besten Version deiner selbst"
|
||||
footer="info@since3.de"
|
||||
isSteps
|
||||
step={step}
|
||||
totalSteps={3}
|
||||
onStepChange={setStep}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-5/6 lg:ml-auto flex justify-center items-center min-h-screen px-4">
|
||||
<div className="w-full max-w-lg flex flex-col gap-4 z-20">
|
||||
<h1 className="text-2xl font-extrabold text-blue mb-8 mt-8 ">Sign up for free</h1>
|
||||
|
||||
{step === 1 && (
|
||||
<>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Vor- und Nachname"
|
||||
type="text"
|
||||
id="register-admin-fullName"
|
||||
value={formData.fullName}
|
||||
onChange={(e) => handleChange("fullName", e.target.value)}
|
||||
error={errors.fullName}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="E-Mail"
|
||||
type="email"
|
||||
id="register-admin-email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange("email", e.target.value)}
|
||||
error={errors.email}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Telefonnummer"
|
||||
type="text"
|
||||
id="register-admin-tel"
|
||||
value={formData.tel}
|
||||
onChange={(e) => handleChange("tel", e.target.value)}
|
||||
error={errors.tel}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Passwort"
|
||||
type="password"
|
||||
id="register-admin-password"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleChange("password", e.target.value)}
|
||||
error={errors.password}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Passwort bestätigen"
|
||||
type="password"
|
||||
id="register-admin-passwordConform"
|
||||
value={formData.passwordConfirm}
|
||||
onChange={(e) => handleChange("passwordConfirm", e.target.value)}
|
||||
error={errors.passwordConfirm}
|
||||
/>
|
||||
|
||||
<Button className="w-full mt-6" onClick={handleNext} disabled={loading}>
|
||||
{loading ? <Spinner /> : "Weiter"}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="PLZ"
|
||||
type="text"
|
||||
id="register-admin-plz"
|
||||
value={formData.plz}
|
||||
onChange={(e) => handleChange("plz", e.target.value)}
|
||||
error={errors.plz}
|
||||
/>
|
||||
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Wohnort"
|
||||
type="text"
|
||||
id="register-admin-place"
|
||||
value={formData.place}
|
||||
onChange={(e) => handleChange("place", e.target.value)}
|
||||
error={errors.place}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Straße und Hausnummer"
|
||||
type="text"
|
||||
id="register-admin-street"
|
||||
value={formData.street}
|
||||
onChange={(e) => handleChange("street", e.target.value)}
|
||||
error={errors.street}
|
||||
/>
|
||||
|
||||
<Button className="w-full mt-6" onClick={handleNext} disabled={loading}>
|
||||
{loading ? <Spinner /> : "Weiter"}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 3 && (
|
||||
<>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Unternehmensname"
|
||||
type="text"
|
||||
id="register-admin-companyName"
|
||||
value={formData.companyName}
|
||||
onChange={(e) => handleChange("companyName", e.target.value)}
|
||||
error={errors.companyName}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Unternehmenssitz"
|
||||
type="text"
|
||||
id="register-admin-companyPlace"
|
||||
value={formData.companyPlace}
|
||||
onChange={(e) => handleChange("companyPlace", e.target.value)}
|
||||
error={errors.companyPlace}
|
||||
/>
|
||||
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="PLZ"
|
||||
type="text"
|
||||
id="register-admin-companyPLZ"
|
||||
value={formData.companyPLZ}
|
||||
onChange={(e) => handleChange("companyPLZ", e.target.value)}
|
||||
error={errors.companyPLZ}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Straße und Hausnummer"
|
||||
type="text"
|
||||
id="register-admin-companyStreet"
|
||||
value={formData.companyStreet}
|
||||
onChange={(e) => handleChange("companyStreet", e.target.value)}
|
||||
error={errors.companyStreet}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Geschäftsmail (falls unterschiedlich)"
|
||||
type="email"
|
||||
id="register-admin-companyMail"
|
||||
value={formData.companyMail}
|
||||
onChange={(e) => handleChange("companyMail", e.target.value)}
|
||||
error={errors.companyMail}
|
||||
/>
|
||||
<InputComponent
|
||||
isLabel
|
||||
label="Geschäftsnummer (falls unterschiedlich)"
|
||||
type="text"
|
||||
id="register-admin-companyNumber"
|
||||
value={formData.companyNumber}
|
||||
onChange={(e) => handleChange("companyNumber", e.target.value)}
|
||||
error={errors.companyNumber}
|
||||
/>
|
||||
|
||||
<Button className="w-full mt-6" onClick={handleRegister} disabled={loading}>
|
||||
{loading ? <Spinner /> : "Anmelden"}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 items-center mt-8 text-sm">
|
||||
<p>Ich habe schon einen Account</p>
|
||||
<p className="text-yellow hover:underline cursor-pointer">Login</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterAdminView;
|
||||
export default RegisterAdmin;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import { z } from "zod";
|
||||
|
||||
// Step 1: Personal Info
|
||||
export const adminRegisterStep1Schema = z
|
||||
.object({
|
||||
fullName: z.string().min(2, "Name muss mindestens 2 Zeichen lang sein"),
|
||||
email: z.string().email("Bitte eine gültige E-Mail-Adresse eingeben"),
|
||||
tel: z.string().min(5, "Telefonnummer muss mindestens 5 Zeichen lang sein"),
|
||||
password: z.string().min(8, "Passwort muss mindestens 8 Zeichen lang sein"),
|
||||
passwordConfirm: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.passwordConfirm, {
|
||||
message: "Passwürter müssen übereinstimmen",
|
||||
path: ["passwordConfirm"],
|
||||
});
|
||||
|
||||
// Step 2: Address Info
|
||||
export const adminRegisterStep2Schema = z.object({
|
||||
plz: z.string().min(5, "PLZ muss 5 Zeichen lang sein").max(5, "PLZ muss 5 Zeichen lang sein"),
|
||||
place: z.string().min(2, "Wohnort muss mindestens 2 Zeichen lang sein"),
|
||||
street: z.string().min(3, "Straße und Hausnummer müssen mindestens 3 Zeichen lang sein"),
|
||||
});
|
||||
|
||||
// Step 3: Business Info
|
||||
export const adminRegisterStep3Schema = z.object({
|
||||
companyName: z.string().min(2, "Unternehmensname muss mindestens 2 Zeichen lang sein"),
|
||||
companyPlace: z.string().min(2, "Unternehmenssitz muss mindestens 2 Zeichen lang sein"),
|
||||
companyPLZ: z
|
||||
.string()
|
||||
.min(5, "PLZ muss 5 Zeichen lang sein")
|
||||
.max(5, "PLZ muss 5 Zeichen lang sein"),
|
||||
companyStreet: z.string().min(3, "Straße und Hausnummer müssen mindestens 3 Zeichen lang sein"),
|
||||
companyMail: z
|
||||
.string()
|
||||
.email("Bitte eine gültige E-Mail-Adresse eingeben")
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
companyNumber: z.string().optional().or(z.literal("")),
|
||||
});
|
||||
|
||||
// Full schema for complete validation
|
||||
export const adminRegisterSchema = adminRegisterStep1Schema
|
||||
.merge(adminRegisterStep2Schema)
|
||||
.merge(adminRegisterStep3Schema);
|
||||
|
||||
export type AdminRegisterFormData = z.infer<typeof adminRegisterSchema>;
|
||||
|
|
@ -10,6 +10,23 @@ export interface RegisterFormData {
|
|||
gender: string;
|
||||
}
|
||||
|
||||
export interface AdminRegisterFormData {
|
||||
fullName: string;
|
||||
email: string;
|
||||
tel: string;
|
||||
password: string;
|
||||
passwordConfirm: string;
|
||||
plz: string;
|
||||
place: string;
|
||||
street: string;
|
||||
companyName: string;
|
||||
companyPlace: string;
|
||||
companyPLZ: string;
|
||||
companyStreet: string;
|
||||
companyMail: string;
|
||||
companyNumber: string;
|
||||
}
|
||||
|
||||
export interface LoginFormData {
|
||||
email: string;
|
||||
password: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue