fix the booking and contract
Build & Push / Pipeline Tests (push) Failing after 1m31s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 55s
Test / API Unit Tests (push) Successful in 1m8s
Test / Homepage Unit Tests (push) Successful in 47s
Test / Carplace Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Failing after 46s
Test / API Integration Tests (push) Successful in 1m7s

This commit is contained in:
root
2026-07-27 23:04:26 -04:00
parent 8046a1d447
commit 5649ab02c7
40 changed files with 1699 additions and 1066 deletions
+267
View File
@@ -0,0 +1,267 @@
#!/usr/bin/env python3
"""Generate an A4 French/Arabic vehicle rental contract as a print-ready PDF.
Usage:
python generate_rental_contract.py
Edit the CONTRACT dictionary below, or import generate_contract() in your app.
Requires: weasyprint (pip install weasyprint)
"""
from __future__ import annotations
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any
from html import escape
from weasyprint import HTML
OUTPUT_DIR = Path(__file__).resolve().parent
CONTRACT: dict[str, Any] = {
"company_name": "CAR LOCATION",
"company_address": "Adresse de la société, Midelt, Maroc",
"delivery_place": "Midelt",
"return_place": "Midelt",
"issued_at": "Midelt le 06/06/2021 11:35",
"driver": {
"first_name": "Prénom",
"last_name": "Nom",
"birth_date": "01/01/1990",
"nationality": "Marocain",
"address": "Adresse du client",
"phone": "+212 6 00 00 00 00",
"cin": "AB123456",
"passport": "",
"license": "P123456",
"license_issued": "09/03/2004",
},
"second_driver": None,
"vehicle": {
"make_model": "Dacia Dokker",
"registration": "12345-A-6",
"departure": "06/06/2021 11:35",
"return": "08/06/2021 11:35",
"duration": "2 jour(s)",
"fuel_type": "Diesel",
"extension": False,
"extension_return": "",
"spare_wheel": True,
"radio_cd": False,
"fuel_level": "1/4", # 0, 1/4, 1/2, 3/4 or 1
},
"notes": "",
}
def h(value: Any) -> str:
return escape("" if value is None else str(value))
def check(value: bool) -> str:
return "" if value else ""
def field(label_fr: str, label_ar: str, value: Any) -> str:
return (
'<div class="field">'
f'<span class="label-fr">{h(label_fr)}</span>'
f'<span class="value">{h(value)}</span>'
f'<span class="label-ar" dir="rtl">{h(label_ar)}</span>'
'</div>'
)
def driver_block(driver: dict[str, Any] | None, title: str) -> str:
if not driver:
return f'''<section class="box driver-box">
<div class="section-title">{h(title)}</div>
<div class="empty-driver">Prénom: ***** &nbsp;&nbsp; AUCUN &nbsp;&nbsp; ***** <span dir="rtl">لا أحد</span><br>
Nom: *****<br>Date de naissance: *****<br>N° de C.I.N.: *****<br>N° de Permis: *****<br>Délivrée le: *****</div>
</section>'''
return f'''<section class="box driver-box">
<div class="section-title">{h(title)}</div>
<div class="fields">
{field("Prénom:", "الاسم", driver.get("first_name"))}
{field("Nom:", "النسب", driver.get("last_name"))}
{field("Date de naissance:", "تاريخ الازدياد", driver.get("birth_date"))}
{field("Nationalité:", "الجنسية", driver.get("nationality"))}
{field("Adresse:", "العنوان", driver.get("address"))}
{field("N° téléphone:", "رقم الهاتف", driver.get("phone"))}
{field("N° de C.I.N.:", "رقم البطاقة الوطنية", driver.get("cin"))}
{field("N° de Passeport:", "رقم جواز السفر", driver.get("passport"))}
{field("N° de Permis:", "رقم رخصة السياقة", driver.get("license"))}
{field("Délivrée le:", "سلمت بتاريخ", driver.get("license_issued"))}
</div>
</section>'''
def vehicle_diagram() -> str:
return '''
<svg class="vehicle-diagram" viewBox="0 0 560 250" xmlns="http://www.w3.org/2000/svg" aria-label="Schéma état du véhicule">
<g fill="none" stroke="#222" stroke-width="3">
<!-- top view -->
<rect x="185" y="78" width="190" height="94" rx="35"/>
<path d="M210 92 Q280 55 350 92 M210 158 Q280 195 350 158"/>
<path d="M232 80 L230 170 M328 80 L330 170"/>
<rect x="150" y="92" width="38" height="65" rx="8"/>
<rect x="372" y="92" width="38" height="65" rx="8"/>
<!-- front/back -->
<path d="M55 52 Q105 16 155 52 L166 91 L45 91 Z"/>
<circle cx="66" cy="90" r="13"/><circle cx="145" cy="90" r="13"/>
<path d="M405 52 Q455 16 505 52 L516 91 L395 91 Z"/>
<circle cx="416" cy="90" r="13"/><circle cx="495" cy="90" r="13"/>
<!-- side views -->
<path d="M38 210 L78 175 L160 169 L206 210 Z"/>
<path d="M85 177 L107 145 L154 145 L180 173"/>
<circle cx="78" cy="210" r="18"/><circle cx="170" cy="210" r="18"/>
<path d="M354 210 L394 175 L476 169 L522 210 Z"/>
<path d="M401 177 L423 145 L470 145 L496 173"/>
<circle cx="394" cy="210" r="18"/><circle cx="486" cy="210" r="18"/>
</g>
<g font-family="DejaVu Sans" font-size="13" fill="#222">
<text x="6" y="56">R: Rayures</text><text x="6" y="83">B: Bosses</text>
<text x="6" y="110">E: Éclats</text><text x="6" y="137">C: Cassures</text>
</g>
</svg>'''
def render_html(data: dict[str, Any]) -> str:
v = data["vehicle"]
levels = ["0", "1/4", "1/2", "3/4", "1"]
fuel_marks = "".join(
f'<div class="fuel-option"><span>{x}</span><b>{"X" if v.get("fuel_level") == x else ""}</b></div>'
for x in levels
)
extension_yes = bool(v.get("extension"))
return f'''<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Contrat de location</title>
<style>
@page {{ size: A4; margin: 8mm; }}
* {{ box-sizing: border-box; }}
body {{ margin: 0; font-family: "DejaVu Sans", Arial, sans-serif; color: #111; font-size: 9.2pt; }}
.page {{ width: 100%; }}
.header {{ display:grid; grid-template-columns: 31% 69%; align-items:start; margin-bottom:4px; }}
.logo {{ height:70px; display:flex; align-items:center; justify-content:center; font-size:19pt; font-weight:700; font-style:italic; letter-spacing:1px; }}
.logo::before, .logo::after {{ content:""; display:block; width:45px; border-top:3px solid #111; margin:0 8px; }}
h1 {{ margin: 0; text-align:center; font-family: Georgia, serif; font-size:28pt; text-decoration: underline; font-style:italic; }}
.issued {{ text-align:center; font-weight:700; margin-top:10px; }}
table {{ border-collapse: collapse; width:100%; }}
.places td {{ border:1.3px solid #111; padding:6px; font-weight:700; text-align:center; }}
.columns {{ display:grid; grid-template-columns: 52% 48%; gap:4px; margin-top:4px; }}
.box {{ border:1.3px solid #111; margin-bottom:4px; break-inside:avoid; }}
.section-title {{ text-align:center; font-weight:700; font-size:12pt; padding:4px; border-bottom:1.3px solid #111; background:#e5e5e5; font-family:Georgia, serif; }}
.fields {{ padding:6px 7px 5px; }}
.field {{ display:grid; grid-template-columns: 34% 43% 23%; min-height:24px; align-items:center; gap:3px; }}
.label-fr, .label-ar {{ font-weight:600; }}
.label-ar {{ text-align:right; font-family:"Noto Sans Arabic", "DejaVu Sans", sans-serif; }}
.value {{ min-height:18px; border-bottom:1px dotted #777; padding:1px 4px; font-weight:700; }}
.empty-driver {{ padding:10px; line-height:1.75; min-height:160px; }}
.vehicle-details {{ padding:7px; }}
.vehicle-details .row {{ display:grid; grid-template-columns:42% 58%; min-height:28px; align-items:center; }}
.vehicle-details .row span:first-child {{ font-weight:700; }}
.vehicle-details .row span:last-child {{ font-weight:700; border-bottom:1px dotted #777; }}
.equipment {{ display:grid; grid-template-columns:1fr 1fr; gap:4px; }}
.mini {{ border:1.3px solid #111; text-align:center; }}
.mini-title {{ background:#e5e5e5; font-weight:700; padding:5px; border-bottom:1.3px solid #111; }}
.mini-value {{ padding:7px; font-size:11pt; }}
.fuel {{ border:1.3px solid #111; margin:4px 0; display:grid; grid-template-columns:30% 70%; }}
.fuel-label {{ background:#e5e5e5; font-weight:700; display:flex; align-items:center; justify-content:center; border-right:1.3px solid #111; }}
.fuel-options {{ display:grid; grid-template-columns:repeat(5,1fr); padding:4px 5px; }}
.fuel-option {{ text-align:center; border-bottom:1px solid #111; min-height:34px; }}
.fuel-option span {{ display:block; font-weight:700; }}
.fuel-option b {{ display:block; font-size:13pt; line-height:14px; }}
.diagram-box {{ border:1.3px solid #111; }}
.vehicle-diagram {{ width:100%; height:200px; display:block; padding:2px; }}
.notice {{ border:1.3px solid #111; background:#d8d8d8; text-align:center; font-weight:700; text-decoration:underline; font-size:11pt; padding:8px 20px; margin:4px 0; }}
.signatures {{ display:grid; grid-template-columns:1fr 1fr; border:1.3px solid #111; min-height:124px; }}
.signature {{ padding:8px; text-align:center; font-weight:700; text-decoration:underline; }}
.signature + .signature {{ border-left:1.3px solid #111; }}
.signature-space {{ height:80px; margin-top:6px; }}
.footer-logo {{ margin:13px auto 5px; text-align:center; font-size:15pt; font-weight:700; font-style:italic; }}
.footer {{ border-top:1px solid #777; padding-top:6px; font-weight:600; }}
.notes {{ white-space:pre-wrap; }}
</style>
</head>
<body>
<div class="page">
<header class="header">
<div class="logo">{h(data['company_name'])}</div>
<div><h1>Contrat de location</h1><div class="issued">{h(data['issued_at'])}</div></div>
</header>
<table class="places"><tr>
<td>Lieu de livraison : &nbsp;&nbsp; {h(data['delivery_place'])}</td>
<td>Lieu de reprise : &nbsp;&nbsp; {h(data['return_place'])}</td>
</tr></table>
<main class="columns">
<div>
{driver_block(data.get('driver'), 'Conducteur I')}
{driver_block(data.get('second_driver'), 'Conducteur II')}
<div class="notice">Le client est seul responsable des délits, contraventions et infractions au code de la route.</div>
</div>
<div>
<section class="box">
<div class="section-title">Véhicule</div>
<div class="vehicle-details">
<div class="row"><span>Marque :</span><span>{h(v.get('make_model'))}</span></div>
<div class="row"><span>Matricule :</span><span>{h(v.get('registration'))}</span></div>
<div class="row"><span>Date de départ :</span><span>{h(v.get('departure'))}</span></div>
<div class="row"><span>Date de retour :</span><span>{h(v.get('return'))}</span></div>
<div class="row"><span>Durée de location :</span><span>{h(v.get('duration'))}</span></div>
<div class="row"><span>Carburant :</span><span>{h(v.get('fuel_type'))}</span></div>
<div class="row"><span>Prolongation :</span><span>{check(extension_yes)} OUI &nbsp;&nbsp; {check(not extension_yes)} NON</span></div>
<div class="row"><span>Retour en cas de prolongation :</span><span>{h(v.get('extension_return'))}</span></div>
</div>
</section>
<div class="equipment">
<div class="mini"><div class="mini-title">Roue de secours</div><div class="mini-value">{check(bool(v.get('spare_wheel')))} OUI &nbsp; {check(not bool(v.get('spare_wheel')))} NON</div></div>
<div class="mini"><div class="mini-title">Poste radio et CD</div><div class="mini-value">{check(bool(v.get('radio_cd')))} OUI &nbsp; {check(not bool(v.get('radio_cd')))} NON</div></div>
</div>
<div class="fuel"><div class="fuel-label">Niveau carburant</div><div class="fuel-options">{fuel_marks}</div></div>
<section class="diagram-box">
<div class="section-title">État général à la réception du client</div>
{vehicle_diagram()}
</section>
</div>
</main>
<section class="signatures">
<div class="signature">Signature du client : lu et approuvé des conditions<div class="signature-space"></div></div>
<div class="signature">Cachet et signature de la société<div class="signature-space"></div></div>
</section>
<div class="footer-logo">{h(data['company_name'])}</div>
<footer class="footer">Adresse : {h(data['company_address'])}<div class="notes">{h(data.get('notes'))}</div></footer>
</div>
</body>
</html>'''
def generate_contract(data: dict[str, Any], output_pdf: str | Path, output_html: str | Path | None = None) -> Path:
"""Render contract data to a print-ready PDF and optionally save the HTML preview."""
output_pdf = Path(output_pdf)
output_pdf.parent.mkdir(parents=True, exist_ok=True)
html = render_html(data)
if output_html:
output_html = Path(output_html)
output_html.write_text(html, encoding="utf-8")
HTML(string=html, base_url=str(OUTPUT_DIR)).write_pdf(str(output_pdf))
return output_pdf
if __name__ == "__main__":
pdf = generate_contract(
CONTRACT,
OUTPUT_DIR / "rental_contract.pdf",
OUTPUT_DIR / "rental_contract_preview.html",
)
print(f"Generated: {pdf}")
Binary file not shown.
+154
View File
@@ -0,0 +1,154 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Contrat de location</title>
<style>
@page { size: A4; margin: 8mm; }
* { box-sizing: border-box; }
body { margin: 0; font-family: "DejaVu Sans", Arial, sans-serif; color: #111; font-size: 9.2pt; }
.page { width: 100%; }
.header { display:grid; grid-template-columns: 31% 69%; align-items:start; margin-bottom:4px; }
.logo { height:70px; display:flex; align-items:center; justify-content:center; font-size:19pt; font-weight:700; font-style:italic; letter-spacing:1px; }
.logo::before, .logo::after { content:""; display:block; width:45px; border-top:3px solid #111; margin:0 8px; }
h1 { margin: 0; text-align:center; font-family: Georgia, serif; font-size:28pt; text-decoration: underline; font-style:italic; }
.issued { text-align:center; font-weight:700; margin-top:10px; }
table { border-collapse: collapse; width:100%; }
.places td { border:1.3px solid #111; padding:6px; font-weight:700; text-align:center; }
.columns { display:grid; grid-template-columns: 52% 48%; gap:4px; margin-top:4px; }
.box { border:1.3px solid #111; margin-bottom:4px; break-inside:avoid; }
.section-title { text-align:center; font-weight:700; font-size:12pt; padding:4px; border-bottom:1.3px solid #111; background:#e5e5e5; font-family:Georgia, serif; }
.fields { padding:6px 7px 5px; }
.field { display:grid; grid-template-columns: 34% 43% 23%; min-height:24px; align-items:center; gap:3px; }
.label-fr, .label-ar { font-weight:600; }
.label-ar { text-align:right; font-family:"Noto Sans Arabic", "DejaVu Sans", sans-serif; }
.value { min-height:18px; border-bottom:1px dotted #777; padding:1px 4px; font-weight:700; }
.empty-driver { padding:10px; line-height:1.75; min-height:160px; }
.vehicle-details { padding:7px; }
.vehicle-details .row { display:grid; grid-template-columns:42% 58%; min-height:28px; align-items:center; }
.vehicle-details .row span:first-child { font-weight:700; }
.vehicle-details .row span:last-child { font-weight:700; border-bottom:1px dotted #777; }
.equipment { display:grid; grid-template-columns:1fr 1fr; gap:4px; }
.mini { border:1.3px solid #111; text-align:center; }
.mini-title { background:#e5e5e5; font-weight:700; padding:5px; border-bottom:1.3px solid #111; }
.mini-value { padding:7px; font-size:11pt; }
.fuel { border:1.3px solid #111; margin:4px 0; display:grid; grid-template-columns:30% 70%; }
.fuel-label { background:#e5e5e5; font-weight:700; display:flex; align-items:center; justify-content:center; border-right:1.3px solid #111; }
.fuel-options { display:grid; grid-template-columns:repeat(5,1fr); padding:4px 5px; }
.fuel-option { text-align:center; border-bottom:1px solid #111; min-height:34px; }
.fuel-option span { display:block; font-weight:700; }
.fuel-option b { display:block; font-size:13pt; line-height:14px; }
.diagram-box { border:1.3px solid #111; }
.vehicle-diagram { width:100%; height:200px; display:block; padding:2px; }
.notice { border:1.3px solid #111; background:#d8d8d8; text-align:center; font-weight:700; text-decoration:underline; font-size:11pt; padding:8px 20px; margin:4px 0; }
.signatures { display:grid; grid-template-columns:1fr 1fr; border:1.3px solid #111; min-height:124px; }
.signature { padding:8px; text-align:center; font-weight:700; text-decoration:underline; }
.signature + .signature { border-left:1.3px solid #111; }
.signature-space { height:80px; margin-top:6px; }
.footer-logo { margin:13px auto 5px; text-align:center; font-size:15pt; font-weight:700; font-style:italic; }
.footer { border-top:1px solid #777; padding-top:6px; font-weight:600; }
.notes { white-space:pre-wrap; }
</style>
</head>
<body>
<div class="page">
<header class="header">
<div class="logo">CAR LOCATION</div>
<div><h1>Contrat de location</h1><div class="issued">Midelt le 06/06/2021 11:35</div></div>
</header>
<table class="places"><tr>
<td>Lieu de livraison : &nbsp;&nbsp; Midelt</td>
<td>Lieu de reprise : &nbsp;&nbsp; Midelt</td>
</tr></table>
<main class="columns">
<div>
<section class="box driver-box">
<div class="section-title">Conducteur I</div>
<div class="fields">
<div class="field"><span class="label-fr">Prénom:</span><span class="value">Prénom</span><span class="label-ar" dir="rtl">الاسم</span></div>
<div class="field"><span class="label-fr">Nom:</span><span class="value">Nom</span><span class="label-ar" dir="rtl">النسب</span></div>
<div class="field"><span class="label-fr">Date de naissance:</span><span class="value">01/01/1990</span><span class="label-ar" dir="rtl">تاريخ الازدياد</span></div>
<div class="field"><span class="label-fr">Nationalité:</span><span class="value">Marocain</span><span class="label-ar" dir="rtl">الجنسية</span></div>
<div class="field"><span class="label-fr">Adresse:</span><span class="value">Adresse du client</span><span class="label-ar" dir="rtl">العنوان</span></div>
<div class="field"><span class="label-fr">N° téléphone:</span><span class="value">+212 6 00 00 00 00</span><span class="label-ar" dir="rtl">رقم الهاتف</span></div>
<div class="field"><span class="label-fr">N° de C.I.N.:</span><span class="value">AB123456</span><span class="label-ar" dir="rtl">رقم البطاقة الوطنية</span></div>
<div class="field"><span class="label-fr">N° de Passeport:</span><span class="value"></span><span class="label-ar" dir="rtl">رقم جواز السفر</span></div>
<div class="field"><span class="label-fr">N° de Permis:</span><span class="value">P123456</span><span class="label-ar" dir="rtl">رقم رخصة السياقة</span></div>
<div class="field"><span class="label-fr">Délivrée le:</span><span class="value">09/03/2004</span><span class="label-ar" dir="rtl">سلمت بتاريخ</span></div>
</div>
</section>
<section class="box driver-box">
<div class="section-title">Conducteur II</div>
<div class="empty-driver">Prénom: ***** &nbsp;&nbsp; AUCUN &nbsp;&nbsp; ***** <span dir="rtl">لا أحد</span><br>
Nom: *****<br>Date de naissance: *****<br>N° de C.I.N.: *****<br>N° de Permis: *****<br>Délivrée le: *****</div>
</section>
<div class="notice">Le client est seul responsable des délits, contraventions et infractions au code de la route.</div>
</div>
<div>
<section class="box">
<div class="section-title">Véhicule</div>
<div class="vehicle-details">
<div class="row"><span>Marque :</span><span>Dacia Dokker</span></div>
<div class="row"><span>Matricule :</span><span>12345-A-6</span></div>
<div class="row"><span>Date de départ :</span><span>06/06/2021 11:35</span></div>
<div class="row"><span>Date de retour :</span><span>08/06/2021 11:35</span></div>
<div class="row"><span>Durée de location :</span><span>2 jour(s)</span></div>
<div class="row"><span>Carburant :</span><span>Diesel</span></div>
<div class="row"><span>Prolongation :</span><span>☐ OUI &nbsp;&nbsp; ☒ NON</span></div>
<div class="row"><span>Retour en cas de prolongation :</span><span></span></div>
</div>
</section>
<div class="equipment">
<div class="mini"><div class="mini-title">Roue de secours</div><div class="mini-value">☒ OUI &nbsp; ☐ NON</div></div>
<div class="mini"><div class="mini-title">Poste radio et CD</div><div class="mini-value">☐ OUI &nbsp; ☒ NON</div></div>
</div>
<div class="fuel"><div class="fuel-label">Niveau carburant</div><div class="fuel-options"><div class="fuel-option"><span>0</span><b></b></div><div class="fuel-option"><span>1/4</span><b>X</b></div><div class="fuel-option"><span>1/2</span><b></b></div><div class="fuel-option"><span>3/4</span><b></b></div><div class="fuel-option"><span>1</span><b></b></div></div></div>
<section class="diagram-box">
<div class="section-title">État général à la réception du client</div>
<svg class="vehicle-diagram" viewBox="0 0 560 250" xmlns="http://www.w3.org/2000/svg" aria-label="Schéma état du véhicule">
<g fill="none" stroke="#222" stroke-width="3">
<!-- top view -->
<rect x="185" y="78" width="190" height="94" rx="35"/>
<path d="M210 92 Q280 55 350 92 M210 158 Q280 195 350 158"/>
<path d="M232 80 L230 170 M328 80 L330 170"/>
<rect x="150" y="92" width="38" height="65" rx="8"/>
<rect x="372" y="92" width="38" height="65" rx="8"/>
<!-- front/back -->
<path d="M55 52 Q105 16 155 52 L166 91 L45 91 Z"/>
<circle cx="66" cy="90" r="13"/><circle cx="145" cy="90" r="13"/>
<path d="M405 52 Q455 16 505 52 L516 91 L395 91 Z"/>
<circle cx="416" cy="90" r="13"/><circle cx="495" cy="90" r="13"/>
<!-- side views -->
<path d="M38 210 L78 175 L160 169 L206 210 Z"/>
<path d="M85 177 L107 145 L154 145 L180 173"/>
<circle cx="78" cy="210" r="18"/><circle cx="170" cy="210" r="18"/>
<path d="M354 210 L394 175 L476 169 L522 210 Z"/>
<path d="M401 177 L423 145 L470 145 L496 173"/>
<circle cx="394" cy="210" r="18"/><circle cx="486" cy="210" r="18"/>
</g>
<g font-family="DejaVu Sans" font-size="13" fill="#222">
<text x="6" y="56">R: Rayures</text><text x="6" y="83">B: Bosses</text>
<text x="6" y="110">E: Éclats</text><text x="6" y="137">C: Cassures</text>
</g>
</svg>
</section>
</div>
</main>
<section class="signatures">
<div class="signature">Signature du client : lu et approuvé des conditions<div class="signature-space"></div></div>
<div class="signature">Cachet et signature de la société<div class="signature-space"></div></div>
</section>
<div class="footer-logo">CAR LOCATION</div>
<footer class="footer">Adresse : Adresse de la société, Midelt, Maroc<div class="notes"></div></footer>
</div>
</body>
</html>
+1 -1
View File
@@ -206,7 +206,7 @@ POST /api/v1/auth/company/signup
### Sign in
Navigate to `/sign-in` (redirects to `/dashboard/sign-in`). Enter the owner email and the password set during sign-up.
Navigate to `/en/light/sign-in`. Enter the owner email and the password set during sign-up.
### Onboarding
+2 -1
View File
@@ -80,7 +80,8 @@ Base path:
### Public/auth routes
- `/dashboard/sign-in`
- `/en/light/sign-in`
- `/dashboard/sign-in` (legacy redirect)
- `/dashboard/sign-up`
- `/dashboard/forgot-password`
- `/dashboard/reset-password`