add images and redesign hompeage into sections, fix the dark color
Build & Deploy / Build & Push Docker Image (push) Failing after 48s
Build & Deploy / Deploy to VPS (push) Has been skipped
Test / API Unit Tests (push) Failing after 5m2s
Test / Marketplace Unit Tests (push) Failing after 4m55s
Test / Admin Unit Tests (push) Successful in 9m37s
Test / Dashboard Unit Tests (push) Successful in 9m37s
Test / API Integration Tests (push) Successful in 9m54s

This commit is contained in:
root
2026-06-26 21:01:02 -04:00
parent d7fb7b7a7b
commit 35172ab46b
102 changed files with 1106 additions and 280 deletions
@@ -1,17 +1,46 @@
"use client";
import Image from "next/image";
import { useState } from "react";
import { useSearchParams } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { apiFetch } from "@/lib/api";
import PublicShell from "@/components/layout/PublicShell";
import { useDashboardI18n } from "@/components/I18nProvider";
import { marketplaceUrl } from "@/lib/urls";
export default function SignUpForm() {
const { language, setLanguage } = useDashboardI18n();
const DASHBOARD_LOGO_SRC = "/dashboard/rentaldrivego.png";
function notifyParent(message: Record<string, unknown>) {
if (typeof window === "undefined" || window.parent === window) return;
window.parent.postMessage(message, "*");
}
function withAuthQuery(
path: string,
params: { embedded: boolean; language: "en" | "fr" | "ar"; theme: string },
) {
const search = new URLSearchParams({
lang: params.language,
theme: params.theme,
});
if (params.embedded) search.set("embedded", "1");
return `${path}?${search.toString()}`;
}
export default function SignUpForm({
embedded = false,
}: {
embedded?: boolean;
}) {
const { language, theme, setLanguage, setTheme } = useDashboardI18n();
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();
const requestedLanguage = searchParams.get("lang");
const requestedTheme = searchParams.get("theme");
const initializedFromQuery = useRef(false);
const initialLanguage =
requestedLanguage === "en" ||
requestedLanguage === "fr" ||
@@ -29,9 +58,79 @@ export default function SignUpForm() {
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
useEffect(() => {
if (initializedFromQuery.current) return;
if (
(requestedLanguage === "en" ||
requestedLanguage === "fr" ||
requestedLanguage === "ar") &&
requestedLanguage !== language
) {
setLanguage(requestedLanguage);
setPreferredLanguage(requestedLanguage);
}
if (
(requestedTheme === "light" || requestedTheme === "dark") &&
requestedTheme !== theme
) {
setTheme(requestedTheme);
}
initializedFromQuery.current = true;
}, [
language,
requestedLanguage,
requestedTheme,
setLanguage,
setTheme,
theme,
]);
useEffect(() => {
if (!initializedFromQuery.current) return;
if (language !== preferredLanguage) setPreferredLanguage(language);
}, [language, preferredLanguage]);
useEffect(() => {
if (!initializedFromQuery.current) return;
const params = new URLSearchParams(searchParams.toString());
let changed = false;
if (params.get("lang") !== preferredLanguage) {
params.set("lang", preferredLanguage);
changed = true;
}
if (params.get("theme") !== theme) {
params.set("theme", theme);
changed = true;
}
if (embedded && params.get("embedded") !== "1") {
params.set("embedded", "1");
changed = true;
}
if (!changed) return;
const nextQuery = params.toString();
router.replace(nextQuery ? `${pathname}?${nextQuery}` : pathname, {
scroll: false,
});
}, [embedded, pathname, preferredLanguage, router, searchParams, theme]);
useEffect(() => {
const currentPath =
window.location.pathname + (window.location.search || "");
notifyParent({ type: "rentaldrivego:embedded-path", path: currentPath });
}, [pathname, searchParams]);
const dict = {
en: {
title: "Create your workspace",
title: "Create account",
subtitle: "Enter your email and password to get started.",
email: "Email",
emailPlaceholder: "you@company.com",
@@ -39,7 +138,7 @@ export default function SignUpForm() {
passwordPlaceholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
passwordHint: "Minimum 8 characters",
languageLabel: "Preferred language",
create: "Create workspace",
create: "Create account",
creating: "Creating\u2026",
alreadyHave: "Already have an account?",
signIn: "Sign in",
@@ -51,7 +150,7 @@ export default function SignUpForm() {
successSignIn: "Go to sign in",
},
fr: {
title: "Cr\u00e9ez votre espace",
title: "Cr\u00e9er un compte",
subtitle: "Saisissez votre email et mot de passe pour commencer.",
email: "Email",
emailPlaceholder: "vous@entreprise.com",
@@ -59,7 +158,7 @@ export default function SignUpForm() {
passwordPlaceholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
passwordHint: "Minimum 8 caract\u00e8res",
languageLabel: "Langue pr\u00e9f\u00e9r\u00e9e",
create: "Cr\u00e9er mon espace",
create: "Cr\u00e9er un compte",
creating: "Cr\u00e9ation\u2026",
alreadyHave: "Vous avez d\u00e9j\u00e0 un compte ?",
signIn: "Se connecter",
@@ -72,7 +171,7 @@ export default function SignUpForm() {
},
ar: {
title:
"\u0623\u0646\u0634\u0626 \u0645\u0633\u0627\u062d\u0629 \u0639\u0645\u0644\u0643",
"\u0623\u0646\u0634\u0626 \u062d\u0633\u0627\u0628\u064b\u0627",
subtitle:
"\u0623\u062f\u062e\u0644 \u0628\u0631\u064a\u062f\u0643 \u0627\u0644\u0625\u0644\u0643\u062a\u0631\u0648\u0646\u064a \u0648\u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631 \u0644\u0644\u0628\u062f\u0621.",
email:
@@ -85,7 +184,7 @@ export default function SignUpForm() {
languageLabel:
"\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u0645\u0641\u0636\u0644\u0629",
create:
"\u0623\u0646\u0634\u0626 \u0627\u0644\u0645\u0633\u0627\u062d\u0629",
"\u0623\u0646\u0634\u0626 \u062d\u0633\u0627\u0628\u064b\u0627",
creating:
"\u062c\u0627\u0631\u064d \u0627\u0644\u0625\u0646\u0634\u0627\u0621\u2026",
alreadyHave:
@@ -105,6 +204,12 @@ export default function SignUpForm() {
},
}[preferredLanguage];
const signInHref = withAuthQuery("/sign-in", {
embedded,
language: preferredLanguage,
theme,
});
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
@@ -135,17 +240,18 @@ export default function SignUpForm() {
if (success) {
return (
<PublicShell hideHeaderActions>
<main className="flex flex-1 items-center justify-center px-4 py-16">
<PublicShell embedded={embedded} hideFooter hideHeaderActions>
<main className="flex flex-1 items-center justify-center px-4 py-12">
<div className="w-full max-w-md text-center">
<div className="flex justify-center mb-6">
<Image
src="/dashboard/rentalcardrive.png"
src={DASHBOARD_LOGO_SRC}
alt="RentalDriveGo"
width={80}
height={80}
width={104}
height={104}
priority
className="h-20 w-20 rounded-[1.5rem] border border-stone-200/80 bg-white/85 p-1.5 shadow-sm dark:border-blue-800 dark:bg-blue-950/80"
unoptimized
className="h-24 w-24 rounded-[1.75rem] border border-stone-200/80 bg-white/85 p-1.5 shadow-sm transition-colors dark:border-blue-800 dark:bg-blue-950/80"
/>
</div>
<div className="rounded-[2rem] border border-stone-200/80 bg-white/82 p-8 shadow-[0_30px_80px_rgba(28,25,23,0.08)] backdrop-blur dark:border-blue-900 dark:bg-blue-950/78">
@@ -156,7 +262,8 @@ export default function SignUpForm() {
{dict.successMessage.replace("{email}", email)}
</p>
<a
href="/sign-in"
href={signInHref}
target={embedded ? "_self" : undefined}
className="mt-6 inline-flex justify-center rounded-full bg-orange-600 px-6 py-3 text-sm font-semibold text-white transition hover:bg-orange-700 dark:bg-orange-500 dark:hover:bg-orange-400"
>
{dict.successSignIn}
@@ -169,23 +276,24 @@ export default function SignUpForm() {
}
return (
<PublicShell hideHeaderActions>
<main className="flex flex-1 items-center justify-center px-4 py-16">
<PublicShell embedded={embedded} hideFooter hideHeaderActions>
<main className="flex flex-1 items-center justify-center px-4 py-12">
<div className="w-full max-w-md">
<div className="mb-8 text-center">
<div className="flex justify-center">
<a href={marketplaceUrl} target="_top">
<Image
src="/dashboard/rentalcardrive.png"
src={DASHBOARD_LOGO_SRC}
alt="RentalDriveGo"
width={80}
height={80}
width={104}
height={104}
priority
className="h-20 w-20 rounded-[1.5rem] border border-stone-200/80 bg-white/85 p-1.5 shadow-sm dark:border-blue-800 dark:bg-blue-950/80"
unoptimized
className="h-24 w-24 rounded-[1.75rem] border border-stone-200/80 bg-white/85 p-1.5 shadow-sm transition-colors dark:border-blue-800 dark:bg-blue-950/80"
/>
</a>
</div>
<h1 className="mt-4 text-2xl font-black tracking-[-0.03em] text-blue-950 dark:text-stone-50">
<h1 className="mt-3 text-3xl font-black tracking-tight text-stone-900 dark:text-stone-100">
{dict.title}
</h1>
<p className="mt-2 text-sm text-stone-600 dark:text-stone-300">
@@ -281,32 +389,6 @@ export default function SignUpForm() {
</p>
</div>
<div>
<span className="mb-1.5 block text-sm font-medium text-stone-700 dark:text-stone-200">
{dict.languageLabel} <span className="text-red-500">*</span>
</span>
<div className="grid grid-cols-3 gap-2">
{(["en", "fr", "ar"] as const).map((lang) => (
<button
key={lang}
type="button"
onClick={() => setPreferredLanguage(lang)}
className={`rounded-2xl border py-3 text-sm font-semibold transition ${
preferredLanguage === lang
? "border-blue-900 bg-blue-900 text-white dark:bg-orange-500 dark:border-orange-400"
: "border-stone-200 bg-white text-stone-600 hover:border-stone-300 hover:bg-stone-50 dark:border-blue-800 dark:bg-blue-950/50 dark:text-stone-300 dark:hover:border-blue-600"
}`}
>
{lang === "en"
? "English"
: lang === "fr"
? "Fran\u00e7ais"
: "\u0627\u0644\u0639\u0631\u0628\u064a\u0629"}
</button>
))}
</div>
</div>
<button
type="submit"
disabled={loading}
@@ -319,7 +401,7 @@ export default function SignUpForm() {
<p className="mt-6 text-center text-sm text-stone-500 dark:text-stone-400">
{dict.alreadyHave}{" "}
<a
href="/sign-in"
href={signInHref}
className="font-semibold text-orange-700 hover:text-orange-800 dark:text-orange-300 dark:hover:text-orange-200"
>
{dict.signIn}