Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions public/locales/de/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -2204,6 +2204,40 @@
"description": "Ein Administrator prüft deine Anfrage, dieser Organisation beizutreten. Nach der Freigabe erhältst du Zugriff."
}
},
"volunteerRegistration": {
"title": "Ehrenamtlich werden",
"subtitle": "Erstellen Sie ein Konto, um sich ehrenamtlich für Geflüchtete in Berlin zu engagieren.",
"next": "Weiter",
"alreadyUser": "Bereits registriert?",
"loginLink": "In Ihrem Konto anmelden",
"steps": {
"account": {
"title": "Ihr Konto",
"description": "Geben Sie Ihre persönlichen Daten ein, um ein Login zu erstellen."
}
},
"fields": {
"firstName": "Vorname",
"lastName": "Nachname",
"email": "E-Mail-Adresse",
"password": "Passwort",
"confirmPassword": "Passwort bestätigen",
"phone": "Telefonnummer",
"consent": {
"header": "Hiermit stimme ich zu:",
"and": "und"
}
},
"errors": {
"passwordTooShort": "Das Passwort muss mindestens 8 Zeichen lang sein",
"passwordMismatch": "Die Passwörter stimmen nicht überein",
"phoneTooShort": "Die Telefonnummer muss mindestens 7 Zeichen lang sein"
},
"checkEmail": {
"title": "Prüfen Sie Ihre E-Mail",
"description": "Wir haben einen Bestätigungslink an Ihre E-Mail-Adresse gesendet. Klicken Sie darauf, um die Einrichtung Ihres Kontos fortzusetzen."
}
},
"formInput": {
"showPassword": "Passwort anzeigen",
"hidePassword": "Passwort verbergen"
Expand Down
34 changes: 34 additions & 0 deletions public/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,40 @@
"description": "An administrator will review your request to join this organisation. You'll get access once it's approved."
}
},
"volunteerRegistration": {
"title": "Become a volunteer",
"subtitle": "Create an account to start volunteering with refugees in Berlin.",
"next": "Next",
"alreadyUser": "Already registered?",
"loginLink": "Login to your account",
"steps": {
"account": {
"title": "Your account",
"description": "Enter your personal details to create a login."
}
},
"fields": {
"firstName": "First name",
"lastName": "Last name",
"email": "Email address",
"password": "Password",
"confirmPassword": "Confirm password",
"phone": "Phone number",
"consent": {
"header": "I hereby agree to the:",
"and": "and"
}
},
"errors": {
"passwordTooShort": "Password must be at least 8 characters",
"passwordMismatch": "Passwords do not match",
"phoneTooShort": "Phone number must be at least 7 characters"
},
"checkEmail": {
"title": "Check your email",
"description": "We sent a confirmation link to your email address. Click it to continue setting up your account."
}
},
"formInput": {
"showPassword": "Show password",
"hidePassword": "Hide password"
Expand Down
13 changes: 13 additions & 0 deletions src/app/[lang]/register/volunteer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";
import { VolunteerRegistration } from "@/components/VolunteerRegistration";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

export default function VolunteerRegistrationPage() {
return (
<QueryClientProvider client={queryClient}>
<VolunteerRegistration />
</QueryClientProvider>
);
}
12 changes: 1 addition & 11 deletions src/components/AgentRegistration/AgentRegistration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { UserRole } from "need4deed-sdk";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { validateRACEmail } from "@/components/forms/validators";
import { validateStep } from "./helpers";
import { AccountStep } from "./steps/AccountStep";
Expand All @@ -20,25 +19,16 @@ import {
ExistingUserWrapper,
PageSubtitle,
PageTitle,
PageWrapper,
SuccessText,
SuccessTitle,
SuccessWrapper,
Wrapper,
} from "./styled";
import { AgentRegistrationData, defaultAgentRegistrationData } from "./types";
import Link from "next/link";

const PENDING_ROLE_COOKIE = "n4d_pending_role=agent; path=/; max-age=86400; SameSite=Lax; Secure";

// Wrapper's own min-height: 100vh would double up with PageLayout's flex: 1
// container (which already fills the viewport minus header/footer), adding
// a spurious extra viewport of empty space. Override it only here — the
// other consumer of Wrapper (ProfileCompletion) isn't rendered inside PageLayout.
const PageWrapper = styled(Wrapper)`
min-height: 0;
flex: 1;
`;

export function AgentRegistration() {
const { t, i18n } = useTranslation();
const router = useRouter();
Expand Down
8 changes: 4 additions & 4 deletions src/components/AgentRegistration/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export function validateStep(
step: number,
data: AgentRegistrationData,
t: (k: string) => string,
namespace: string = "agentRegistration",
): Partial<Record<keyof AgentRegistrationData, string>> {
const errors: Partial<Record<keyof AgentRegistrationData, string>> = {};
const required = t("form.error.required");
Expand All @@ -14,12 +15,11 @@ export function validateStep(
if (!data.email.trim()) errors.email = required;
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) errors.email = t("form.error.email");
if (!data.password) errors.password = required;
else if (data.password.length < 8) errors.password = t("agentRegistration.errors.passwordTooShort");
else if (data.password.length < 8) errors.password = t(`${namespace}.errors.passwordTooShort`);
if (!data.confirmPassword) errors.confirmPassword = required;
else if (data.password !== data.confirmPassword)
errors.confirmPassword = t("agentRegistration.errors.passwordMismatch");
else if (data.password !== data.confirmPassword) errors.confirmPassword = t(`${namespace}.errors.passwordMismatch`);
if (!data.phone.trim()) errors.phone = required;
else if (data.phone.trim().length < 7) errors.phone = t("agentRegistration.errors.phoneTooShort");
else if (data.phone.trim().length < 7) errors.phone = t(`${namespace}.errors.phoneTooShort`);
if (!data.consent) errors.consent = required;
}

Expand Down
35 changes: 18 additions & 17 deletions src/components/AgentRegistration/steps/AccountStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,76 +10,77 @@ type Props = {
data: AgentRegistrationData;
onChange: (fields: Partial<AgentRegistrationData>) => void;
errors: Partial<Record<keyof AgentRegistrationData, string>>;
namespace?: string;
};

export function AccountStep({ data, onChange, errors }: Props) {
export function AccountStep({ data, onChange, errors, namespace = "agentRegistration" }: Props) {
const { t } = useTranslation();

return (
<div>
<StepTitle>{t("agentRegistration.steps.account.title")}</StepTitle>
<StepDescription>{t("agentRegistration.steps.account.description")}</StepDescription>
<StepTitle>{t(`${namespace}.steps.account.title`)}</StepTitle>
<StepDescription>{t(`${namespace}.steps.account.description`)}</StepDescription>

<FieldWrapper>
<FieldLabel>{t("agentRegistration.fields.firstName")}</FieldLabel>
<FieldLabel>{t(`${namespace}.fields.firstName`)}</FieldLabel>
<FormInput
value={data.firstName}
onInputChange={(v) => onChange({ firstName: v })}
placeHolder={t("agentRegistration.fields.firstName")}
placeHolder={t(`${namespace}.fields.firstName`)}
errors={errors.firstName ? [errors.firstName] : []}
/>
</FieldWrapper>

<FieldWrapper>
<FieldLabel>{t("agentRegistration.fields.lastName")}</FieldLabel>
<FieldLabel>{t(`${namespace}.fields.lastName`)}</FieldLabel>
<FormInput
value={data.lastName}
onInputChange={(v) => onChange({ lastName: v })}
placeHolder={t("agentRegistration.fields.lastName")}
placeHolder={t(`${namespace}.fields.lastName`)}
errors={errors.lastName ? [errors.lastName] : []}
/>
</FieldWrapper>

<FieldWrapper>
<FieldLabel>{t("agentRegistration.fields.email")}</FieldLabel>
<FieldLabel>{t(`${namespace}.fields.email`)}</FieldLabel>
<FormInput
type="email"
value={data.email}
onInputChange={(v) => onChange({ email: v })}
placeHolder={t("agentRegistration.fields.email")}
placeHolder={t(`${namespace}.fields.email`)}
errors={errors.email ? [errors.email] : []}
/>
</FieldWrapper>

<FieldWrapper>
<FieldLabel>{t("agentRegistration.fields.password")}</FieldLabel>
<FieldLabel>{t(`${namespace}.fields.password`)}</FieldLabel>
<FormInput
type="password"
value={data.password}
onInputChange={(v) => onChange({ password: v })}
placeHolder={t("agentRegistration.fields.password")}
placeHolder={t(`${namespace}.fields.password`)}
errors={errors.password ? [errors.password] : []}
/>
</FieldWrapper>

<FieldWrapper>
<FieldLabel>{t("agentRegistration.fields.confirmPassword")}</FieldLabel>
<FieldLabel>{t(`${namespace}.fields.confirmPassword`)}</FieldLabel>
<FormInput
type="password"
value={data.confirmPassword}
onInputChange={(v) => onChange({ confirmPassword: v })}
placeHolder={t("agentRegistration.fields.confirmPassword")}
placeHolder={t(`${namespace}.fields.confirmPassword`)}
errors={errors.confirmPassword ? [errors.confirmPassword] : []}
/>
</FieldWrapper>

<FieldWrapper>
<FieldLabel>{t("agentRegistration.fields.phone")}</FieldLabel>
<FieldLabel>{t(`${namespace}.fields.phone`)}</FieldLabel>
<FormInput
type="tel"
value={data.phone}
onInputChange={(v) => onChange({ phone: v })}
placeHolder={t("agentRegistration.fields.phone")}
placeHolder={t(`${namespace}.fields.phone`)}
errors={errors.phone ? [errors.phone] : []}
/>
</FieldWrapper>
Expand All @@ -93,10 +94,10 @@ export function AccountStep({ data, onChange, errors }: Props) {
onChange={(e) => onChange({ consent: e.target.checked })}
/>
<span>
{t("agentRegistration.fields.consent.header")}{" "}
{t(`${namespace}.fields.consent.header`)}{" "}
<a href={`/${Subpage.DATA_PRIVACY}`}>{t("homepage.footer.legal.dataPrivacy")}</a>,{" "}
<a href={`/${Subpage.RAC_GUIDELINES}`}>{t("homepage.footer.legal.guidelines")}</a>{" "}
{t("agentRegistration.fields.consent.and")}{" "}
{t(`${namespace}.fields.consent.and`)}{" "}
<a href={`/${Subpage.AGREEMENT}`}>{t("homepage.footer.legal.agreement")}</a>{" "}
</span>
</FieldConsent>
Expand Down
10 changes: 10 additions & 0 deletions src/components/AgentRegistration/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export const Card = styled.div`
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.08);
`;

// Wrapper's own min-height: 100vh would double up with PageLayout's flex: 1
// container (which already fills the viewport minus header/footer), adding
// a spurious extra viewport of empty space. Used by AgentRegistration and
// VolunteerRegistration, both rendered inside PageLayout — not by
// ProfileCompletion, which isn't.
export const PageWrapper = styled(Wrapper)`
min-height: 0;
flex: 1;
`;

export const PageTitle = styled.h1`
font-size: 1.625rem;
font-weight: 700;
Expand Down
5 changes: 5 additions & 0 deletions src/components/AgentRegistration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export const defaultAgentRegistrationData: AgentRegistrationData = {
consent: false,
};

// The account step (name/email/password/phone/consent) is identical for the
// agent and volunteer registration flows, only the translation copy differs.
export type AccountRegistrationData = AgentRegistrationData;
export const defaultAccountRegistrationData = defaultAgentRegistrationData;

export const TOTAL_STEPS = 1;
export const TOTAL_COMPLETION_STEPS = 3;

Expand Down
Loading
Loading