Add login and signup form
This commit is contained in:
parent
f62175e490
commit
0bb4963897
8 changed files with 943 additions and 452 deletions
|
|
@ -43,6 +43,8 @@
|
|||
"dependencies": {
|
||||
"@react-stately/calendar": "^3.7.1",
|
||||
"@tailwindcss/vite": "^4.0.14",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"react-stately": "^3.36.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ importers:
|
|||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.14
|
||||
version: 4.0.14(vite@6.2.2(jiti@2.4.2)(lightningcss@1.29.2))
|
||||
'@types/react-router-dom':
|
||||
specifier: ^5.3.3
|
||||
version: 5.3.3
|
||||
react-router-dom:
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react-stately:
|
||||
specifier: ^3.36.1
|
||||
version: 3.36.1(react@19.0.0)
|
||||
|
|
@ -1259,12 +1265,18 @@ packages:
|
|||
'@types/babel__traverse@7.20.6':
|
||||
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
|
||||
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
'@types/gensync@1.0.4':
|
||||
resolution: {integrity: sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==}
|
||||
|
||||
'@types/history@4.7.11':
|
||||
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
|
||||
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
|
|
@ -1273,6 +1285,12 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': ^19.0.0
|
||||
|
||||
'@types/react-router-dom@5.3.3':
|
||||
resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
|
||||
|
||||
'@types/react-router@5.1.20':
|
||||
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
|
||||
|
||||
'@types/react@19.0.10':
|
||||
resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==}
|
||||
|
||||
|
|
@ -1458,6 +1476,10 @@ packages:
|
|||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -1980,6 +2002,23 @@ packages:
|
|||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-router-dom@7.3.0:
|
||||
resolution: {integrity: sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
react-router@7.3.0:
|
||||
resolution: {integrity: sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-stately@3.36.1:
|
||||
resolution: {integrity: sha512-H9kiGAylNec/iE5qk7qQLV1cvtSAIVq3mgt87zx2EA+f+/sYy2oBtchFPaDiBf/m7xMEKf0Fr9zSLU6G99xQ8g==}
|
||||
peerDependencies:
|
||||
|
|
@ -2034,6 +2073,9 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-cookie-parser@2.7.1:
|
||||
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -2111,6 +2153,9 @@ packages:
|
|||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
turbo-stream@2.4.0:
|
||||
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -3750,16 +3795,31 @@ snapshots:
|
|||
dependencies:
|
||||
'@babel/types': 7.26.8
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/gensync@1.0.4': {}
|
||||
|
||||
'@types/history@4.7.11': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/react-dom@19.0.4(@types/react@19.0.10)':
|
||||
dependencies:
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@types/react-router-dom@5.3.3':
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 19.0.10
|
||||
'@types/react-router': 5.1.20
|
||||
|
||||
'@types/react-router@5.1.20':
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 19.0.10
|
||||
|
||||
'@types/react@19.0.10':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
|
@ -3969,6 +4029,8 @@ snapshots:
|
|||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cookie@1.0.2: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
|
@ -4478,6 +4540,22 @@ snapshots:
|
|||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react-router-dom@7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
react-router: 7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
|
||||
react-router@7.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@types/cookie': 0.6.0
|
||||
cookie: 1.0.2
|
||||
react: 19.0.0
|
||||
set-cookie-parser: 2.7.1
|
||||
turbo-stream: 2.4.0
|
||||
optionalDependencies:
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-stately@3.36.1(react@19.0.0):
|
||||
dependencies:
|
||||
'@react-stately/calendar': 3.7.1(react@19.0.0)
|
||||
|
|
@ -4560,6 +4638,8 @@ snapshots:
|
|||
|
||||
semver@7.7.1: {}
|
||||
|
||||
set-cookie-parser@2.7.1: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
|
@ -4616,6 +4696,8 @@ snapshots:
|
|||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
turbo-stream@2.4.0: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
|
|
|||
809
ui/src/App.tsx
809
ui/src/App.tsx
|
|
@ -2,6 +2,9 @@ import { Button } from "./ui-components/button";
|
|||
import { useState, useEffect } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Header } from "./ui-components/header";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import { LoginPage } from "./pages/login";
|
||||
import { SignUpPage } from "./pages/signup";
|
||||
|
||||
type Theme = "dark" | "light" | "system";
|
||||
|
||||
|
|
@ -24,403 +27,419 @@ export const App = () => {
|
|||
}, [theme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"min-h-screen bg-gradient-to-br from-emerald-100 via-green-100 to-white",
|
||||
"dark:bg-gradient-to-br dark:from-[#0a1f0a] dark:via-[#051505] dark:to-black"
|
||||
)}
|
||||
>
|
||||
<Header theme={theme} onThemeChange={setTheme} />
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
<main className="text-center">
|
||||
<h2 className="text-5xl font-bold text-slate-900 dark:text-white mb-6">
|
||||
Maîtrisez vos dépenses de chantier
|
||||
</h2>
|
||||
<p className="text-xl text-slate-700 dark:text-white mb-12 max-w-2xl mx-auto">
|
||||
XTablo aide les entreprises du BTP à suivre, analyser et optimiser
|
||||
leurs dépenses de projet. Obtenez des insights en temps réel sur vos
|
||||
coûts et prenez de meilleures décisions financières. Mise en place
|
||||
en moins de 48h.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
className={twMerge(
|
||||
"inline-flex items-center gap-2 bg-blue-950 px-8 py-4 rounded-full",
|
||||
"text-white font-semibold hover:bg-blue-900 transition-colors",
|
||||
"shadow-lg hover:shadow-xl shadow-blue-950/20"
|
||||
)}
|
||||
>
|
||||
Démarrer gratuitement
|
||||
</Button>
|
||||
|
||||
<div className="mt-20">
|
||||
<div
|
||||
className={twMerge(
|
||||
"bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl p-8",
|
||||
"shadow-xl border border-emerald-200 dark:border-slate-700/50"
|
||||
)}
|
||||
>
|
||||
<div className="aspect-video rounded-lg bg-emerald-100 dark:bg-slate-700/50 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="features" className="mt-24">
|
||||
<h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-12">
|
||||
Fait confiance par les entreprises du BTP
|
||||
</h3>
|
||||
<div className="flex overflow-x-hidden gap-12 pb-4 -mx-4 px-4 scrollbar-hide">
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-none w-[300px] bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-xl p-6",
|
||||
"hover:bg-emerald-100/50 dark:hover:bg-slate-800/70 transition",
|
||||
"border border-emerald-200 dark:border-slate-700/50 flex flex-col animate-slide"
|
||||
)}
|
||||
>
|
||||
<p className="text-slate-700 dark:text-white mb-4 flex-grow">
|
||||
"XTablo a révolutionné notre gestion des dépenses. Nous
|
||||
pouvons maintenant suivre chaque euro en temps réel et prendre
|
||||
de meilleures décisions pour nos projets de construction."
|
||||
</p>
|
||||
<div className="flex items-center mt-auto">
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30" />
|
||||
<div className="ml-3">
|
||||
<p className="text-slate-900 dark:text-white font-medium">
|
||||
Michel Dubois
|
||||
</p>
|
||||
<p className="text-slate-600 dark:text-white/80 text-sm">
|
||||
Chef de Projet
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-none w-[300px] bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-xl p-6",
|
||||
"hover:bg-emerald-100/50 dark:hover:bg-slate-800/70 transition",
|
||||
"border border-emerald-200 dark:border-slate-700/50 flex flex-col animate-slide"
|
||||
)}
|
||||
>
|
||||
<p className="text-slate-700 dark:text-white mb-4 flex-grow">
|
||||
"Les fonctionnalités de suivi des dépenses sont exactement ce
|
||||
dont nous avions besoin. Cela nous a permis de réduire nos
|
||||
coûts de 15% et d'améliorer significativement nos marges."
|
||||
</p>
|
||||
<div className="flex items-center mt-auto">
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30" />
|
||||
<div className="ml-3">
|
||||
<p className="text-slate-900 dark:text-white font-medium">
|
||||
Sophie Martin
|
||||
</p>
|
||||
<p className="text-slate-600 dark:text-white/80 text-sm">
|
||||
Directrice Financière
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-none w-[300px] bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-xl p-6",
|
||||
"hover:bg-emerald-100/50 dark:hover:bg-slate-800/70 transition",
|
||||
"border border-emerald-200 dark:border-slate-700/50 flex flex-col animate-slide"
|
||||
)}
|
||||
>
|
||||
<p className="text-slate-700 dark:text-white mb-4 flex-grow">
|
||||
"La gestion des dépenses sur plusieurs chantiers n'a jamais
|
||||
été aussi simple. XTablo nous donne une visibilité totale sur
|
||||
nos coûts."
|
||||
</p>
|
||||
<div className="flex items-center mt-auto">
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30" />
|
||||
<div className="ml-3">
|
||||
<p className="text-slate-900 dark:text-white font-medium">
|
||||
David Laurent
|
||||
</p>
|
||||
<p className="text-slate-600 dark:text-white/80 text-sm">
|
||||
Chef de Chantier
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="pricing" className="mt-24">
|
||||
<h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-12">
|
||||
Des tarifs adaptés à vos besoins
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||
<div
|
||||
className={twMerge(
|
||||
"bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl p-8",
|
||||
"border border-emerald-200 dark:border-white/30 hover:border-emerald-300 dark:hover:border-white/50",
|
||||
"transition-all duration-300 hover:scale-[1.02] hover:shadow-2xl",
|
||||
"hover:shadow-emerald-200/20 dark:hover:shadow-emerald-900/20 flex flex-col"
|
||||
)}
|
||||
>
|
||||
<h4 className="text-2xl font-bold text-slate-900 dark:text-white mb-4">
|
||||
Starter
|
||||
</h4>
|
||||
<div className="mb-6">
|
||||
<span className="text-4xl font-bold text-slate-900 dark:text-white">
|
||||
12€
|
||||
</span>
|
||||
<span className="text-slate-600 dark:text-white/80">
|
||||
/mois
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Jusqu'à 5 chantiers
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Suivi des dépenses en temps réel
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Rapports mensuels
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Support par email
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
className={twMerge(
|
||||
"w-full px-6 py-3 bg-blue-950 rounded-full text-white font-semibold",
|
||||
"hover:bg-blue-900 transition-all duration-300 hover:scale-[1.02]",
|
||||
"hover:shadow-lg hover:shadow-xl shadow-blue-950/20 mt-auto"
|
||||
)}
|
||||
>
|
||||
Commencer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl p-8",
|
||||
"border border-emerald-200 dark:border-white/50 relative flex flex-col",
|
||||
"transition-all duration-300 hover:scale-[1.02] hover:shadow-2xl",
|
||||
"hover:shadow-emerald-200/20 dark:hover:shadow-emerald-900/20"
|
||||
)}
|
||||
>
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 transition-all duration-300 group-hover:scale-110">
|
||||
<span className="bg-blue-950 text-white px-4 py-1 rounded-full text-sm font-semibold">
|
||||
Plus populaire
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="text-2xl font-bold text-slate-900 dark:text-white mb-4">
|
||||
Pro
|
||||
</h4>
|
||||
<div className="mb-6">
|
||||
<span className="text-4xl font-bold text-slate-900 dark:text-white">
|
||||
25€
|
||||
</span>
|
||||
<span className="text-slate-600 dark:text-white/80">
|
||||
/mois
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Chantiers illimités
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Suivi des dépenses en temps réel
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Rapports personnalisés
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Support prioritaire
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
API disponible
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
className={twMerge(
|
||||
"w-full px-6 py-3 bg-blue-950 rounded-full text-white font-semibold",
|
||||
"hover:bg-blue-900 transition-all duration-300 hover:scale-[1.02]",
|
||||
"hover:shadow-lg hover:shadow-xl shadow-blue-950/20 mt-auto"
|
||||
)}
|
||||
>
|
||||
Commencer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact" className="mt-24 text-center">
|
||||
<h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-6">
|
||||
Prêt à optimiser vos dépenses de projet ?
|
||||
</h3>
|
||||
<p className="text-xl text-slate-700 dark:text-white mb-12 max-w-2xl mx-auto">
|
||||
Commencez votre essai gratuit de 14 jours aujourd'hui. Aucune
|
||||
carte bancaire requise.
|
||||
</p>
|
||||
<div className="flex justify-center gap-6">
|
||||
<button
|
||||
className={twMerge(
|
||||
"px-8 py-3 bg-blue-950 rounded-full text-white font-semibold",
|
||||
"hover:bg-blue-900 transition shadow-lg hover:shadow-xl shadow-blue-950/20"
|
||||
)}
|
||||
>
|
||||
Essai gratuit
|
||||
</button>
|
||||
<button
|
||||
className={twMerge(
|
||||
"px-8 py-3 border border-blue-400/30 rounded-full",
|
||||
"text-slate-900 dark:text-white font-semibold hover:bg-blue-950/30 transition"
|
||||
)}
|
||||
>
|
||||
Contacter les ventes
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<footer
|
||||
<Router>
|
||||
<div
|
||||
className={twMerge(
|
||||
"mt-24 py-8 border-t border-slate-200 dark:border-slate-800"
|
||||
"min-h-screen bg-gradient-to-br from-emerald-100 via-green-100 to-white",
|
||||
"dark:bg-gradient-to-br dark:from-[#0a1f0a] dark:via-[#051505] dark:to-black"
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<p className="text-slate-600 dark:text-slate-400 text-sm">
|
||||
© {new Date().getFullYear()} XTablo. Tous droits réservés.
|
||||
</p>
|
||||
<p className="text-slate-500 dark:text-slate-500 text-xs mt-2">
|
||||
XTablo est une marque déposée. Les logos et noms de marques sont des
|
||||
marques déposées de leurs propriétaires respectifs.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<style>
|
||||
{`
|
||||
@keyframes slide {
|
||||
0% { transform: translateX(-100vw); }
|
||||
100% { transform: translateX(100vw); }
|
||||
}
|
||||
.animate-slide {
|
||||
animation: slide 24s linear infinite;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<>
|
||||
<Header theme={theme} onThemeChange={setTheme} />
|
||||
<div className="container mx-auto px-4 py-16">
|
||||
<main className="text-center">
|
||||
<h2 className="text-5xl font-bold text-slate-900 dark:text-white mb-6">
|
||||
Maîtrisez vos dépenses de chantier
|
||||
</h2>
|
||||
<p className="text-xl text-slate-700 dark:text-white mb-12 max-w-2xl mx-auto">
|
||||
XTablo aide les entreprises du BTP à suivre, analyser et
|
||||
optimiser leurs dépenses de projet. Obtenez des insights
|
||||
en temps réel sur vos coûts et prenez de meilleures
|
||||
décisions financières. Mise en place en moins de 48h.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
className={twMerge(
|
||||
"inline-flex items-center gap-2 bg-emerald-700 px-8 py-4 rounded-full",
|
||||
"text-white font-semibold hover:bg-emerald-600 transition-colors",
|
||||
"shadow-lg hover:shadow-xl shadow-emerald-700/20"
|
||||
)}
|
||||
>
|
||||
Démarrer gratuitement
|
||||
</Button>
|
||||
|
||||
<div className="mt-20">
|
||||
<div
|
||||
className={twMerge(
|
||||
"bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl p-8",
|
||||
"shadow-xl border border-emerald-200 dark:border-slate-700/50"
|
||||
)}
|
||||
>
|
||||
<div className="aspect-video rounded-lg bg-emerald-100 dark:bg-slate-700/50 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="features" className="mt-24">
|
||||
<h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-12">
|
||||
Fait confiance par les entreprises du BTP
|
||||
</h3>
|
||||
<div className="flex overflow-x-hidden gap-12 pb-4 -mx-4 px-4 scrollbar-hide">
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-none w-[300px] bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-xl p-6",
|
||||
"hover:bg-emerald-100/50 dark:hover:bg-slate-800/70 transition",
|
||||
"border border-emerald-200 dark:border-slate-700/50 flex flex-col animate-slide"
|
||||
)}
|
||||
>
|
||||
<p className="text-slate-700 dark:text-white mb-4 flex-grow">
|
||||
"XTablo a révolutionné notre gestion des dépenses.
|
||||
Nous pouvons maintenant suivre chaque euro en temps
|
||||
réel et prendre de meilleures décisions pour nos
|
||||
projets de construction."
|
||||
</p>
|
||||
<div className="flex items-center mt-auto">
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30" />
|
||||
<div className="ml-3">
|
||||
<p className="text-slate-900 dark:text-white font-medium">
|
||||
Michel Dubois
|
||||
</p>
|
||||
<p className="text-slate-600 dark:text-white/80 text-sm">
|
||||
Chef de Projet
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-none w-[300px] bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-xl p-6",
|
||||
"hover:bg-emerald-100/50 dark:hover:bg-slate-800/70 transition",
|
||||
"border border-emerald-200 dark:border-slate-700/50 flex flex-col animate-slide"
|
||||
)}
|
||||
>
|
||||
<p className="text-slate-700 dark:text-white mb-4 flex-grow">
|
||||
"Les fonctionnalités de suivi des dépenses sont
|
||||
exactement ce dont nous avions besoin. Cela nous a
|
||||
permis de réduire nos coûts de 15% et d'améliorer
|
||||
significativement nos marges."
|
||||
</p>
|
||||
<div className="flex items-center mt-auto">
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30" />
|
||||
<div className="ml-3">
|
||||
<p className="text-slate-900 dark:text-white font-medium">
|
||||
Sophie Martin
|
||||
</p>
|
||||
<p className="text-slate-600 dark:text-white/80 text-sm">
|
||||
Directrice Financière
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex-none w-[300px] bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-xl p-6",
|
||||
"hover:bg-emerald-100/50 dark:hover:bg-slate-800/70 transition",
|
||||
"border border-emerald-200 dark:border-slate-700/50 flex flex-col animate-slide"
|
||||
)}
|
||||
>
|
||||
<p className="text-slate-700 dark:text-white mb-4 flex-grow">
|
||||
"La gestion des dépenses sur plusieurs chantiers n'a
|
||||
jamais été aussi simple. XTablo nous donne une
|
||||
visibilité totale sur nos coûts."
|
||||
</p>
|
||||
<div className="flex items-center mt-auto">
|
||||
<div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30" />
|
||||
<div className="ml-3">
|
||||
<p className="text-slate-900 dark:text-white font-medium">
|
||||
David Laurent
|
||||
</p>
|
||||
<p className="text-slate-600 dark:text-white/80 text-sm">
|
||||
Chef de Chantier
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="pricing" className="mt-24">
|
||||
<h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-12">
|
||||
Des tarifs adaptés à vos besoins
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
|
||||
<div
|
||||
className={twMerge(
|
||||
"bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl p-8",
|
||||
"border border-emerald-200 dark:border-white/30 hover:border-emerald-300 dark:hover:border-white/50",
|
||||
"transition-all duration-300 hover:scale-[1.02] hover:shadow-2xl",
|
||||
"hover:shadow-emerald-200/20 dark:hover:shadow-emerald-900/20 flex flex-col"
|
||||
)}
|
||||
>
|
||||
<h4 className="text-2xl font-bold text-slate-900 dark:text-white mb-4">
|
||||
Starter
|
||||
</h4>
|
||||
<div className="mb-6">
|
||||
<span className="text-4xl font-bold text-slate-900 dark:text-white">
|
||||
12€
|
||||
</span>
|
||||
<span className="text-slate-600 dark:text-white/80">
|
||||
/mois
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Jusqu'à 5 chantiers
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Suivi des dépenses en temps réel
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Rapports mensuels
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Support par email
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
className={twMerge(
|
||||
"w-full px-6 py-3 bg-emerald-700 rounded-full text-white font-semibold",
|
||||
"hover:bg-emerald-600 transition-all duration-300 hover:scale-[1.02]",
|
||||
"hover:shadow-lg hover:shadow-xl shadow-emerald-700/20 mt-auto"
|
||||
)}
|
||||
>
|
||||
Commencer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl p-8",
|
||||
"border border-emerald-200 dark:border-white/50 relative flex flex-col",
|
||||
"transition-all duration-300 hover:scale-[1.02] hover:shadow-2xl",
|
||||
"hover:shadow-emerald-200/20 dark:hover:shadow-emerald-900/20"
|
||||
)}
|
||||
>
|
||||
<div className="absolute -top-4 left-1/2 transform -translate-x-1/2 transition-all duration-300 group-hover:scale-110">
|
||||
<span className="bg-emerald-700 text-white px-4 py-1 rounded-full text-sm font-semibold">
|
||||
Plus populaire
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="text-2xl font-bold text-slate-900 dark:text-white mb-4">
|
||||
Pro
|
||||
</h4>
|
||||
<div className="mb-6">
|
||||
<span className="text-4xl font-bold text-slate-900 dark:text-white">
|
||||
25€
|
||||
</span>
|
||||
<span className="text-slate-600 dark:text-white/80">
|
||||
/mois
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-4 mb-8 flex-grow">
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Chantiers illimités
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Suivi des dépenses en temps réel
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Rapports personnalisés
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Support prioritaire
|
||||
</li>
|
||||
<li className="flex items-center text-slate-700 dark:text-white">
|
||||
<svg
|
||||
className="w-5 h-5 text-slate-700 dark:text-white mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
API disponible
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
className={twMerge(
|
||||
"w-full px-6 py-3 bg-emerald-700 rounded-full text-white font-semibold",
|
||||
"hover:bg-emerald-600 transition-all duration-300 hover:scale-[1.02]",
|
||||
"hover:shadow-lg hover:shadow-xl shadow-emerald-700/20 mt-auto"
|
||||
)}
|
||||
>
|
||||
Commencer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact" className="mt-24 text-center">
|
||||
<h3 className="text-3xl font-bold text-slate-900 dark:text-white mb-6">
|
||||
Prêt à optimiser vos dépenses de projet ?
|
||||
</h3>
|
||||
<p className="text-xl text-slate-700 dark:text-white mb-12 max-w-2xl mx-auto">
|
||||
Commencez votre essai gratuit de 14 jours aujourd'hui.
|
||||
Aucune carte bancaire requise.
|
||||
</p>
|
||||
<div className="flex justify-center gap-6">
|
||||
<button
|
||||
className={twMerge(
|
||||
"px-8 py-3 bg-emerald-700 rounded-full text-white font-semibold",
|
||||
"hover:bg-emerald-600 transition shadow-lg hover:shadow-xl shadow-emerald-700/20"
|
||||
)}
|
||||
>
|
||||
Essai gratuit
|
||||
</button>
|
||||
<button
|
||||
className={twMerge(
|
||||
"px-8 py-3 border border-emerald-400/30 rounded-full",
|
||||
"text-slate-900 dark:text-white font-semibold hover:bg-emerald-700/30 transition"
|
||||
)}
|
||||
>
|
||||
Contacter les ventes
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<footer
|
||||
className={twMerge(
|
||||
"mt-24 py-8 border-t border-slate-200 dark:border-slate-800"
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto px-4 text-center">
|
||||
<p className="text-slate-600 dark:text-slate-400 text-sm">
|
||||
© {new Date().getFullYear()} XTablo. Tous droits réservés.
|
||||
</p>
|
||||
<p className="text-slate-500 dark:text-slate-500 text-xs mt-2">
|
||||
XTablo est une marque déposée. Les logos et noms de
|
||||
marques sont des marques déposées de leurs propriétaires
|
||||
respectifs.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/signup" element={<SignUpPage />} />
|
||||
</Routes>
|
||||
<style>
|
||||
{`
|
||||
@keyframes slide {
|
||||
0% { transform: translateX(-100vw); }
|
||||
100% { transform: translateX(100vw); }
|
||||
}
|
||||
.animate-slide {
|
||||
animation: slide 24s linear infinite;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
157
ui/src/pages/login.tsx
Normal file
157
ui/src/pages/login.tsx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { Button } from "../ui-components/button";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-emerald-100 via-green-100 to-white dark:bg-gradient-to-br dark:from-[#0a1f0a] dark:via-[#051505] dark:to-black">
|
||||
<div
|
||||
className={twMerge(
|
||||
"w-full max-w-md p-8 bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl",
|
||||
"border border-emerald-200 dark:border-emerald-900/30",
|
||||
"shadow-xl"
|
||||
)}
|
||||
>
|
||||
<h1 className="text-3xl font-bold text-slate-900 dark:text-white mb-8 text-center">
|
||||
Connexion
|
||||
</h1>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
className={twMerge(
|
||||
"w-full bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"border border-slate-200 dark:border-slate-700",
|
||||
"hover:bg-slate-50 dark:hover:bg-slate-700/50",
|
||||
"flex items-center justify-center gap-2"
|
||||
)}
|
||||
>
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Continuer avec Google
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className={twMerge(
|
||||
"w-full bg-black text-white",
|
||||
"hover:bg-slate-900",
|
||||
"flex items-center justify-center gap-2"
|
||||
)}
|
||||
>
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 2C6.477 2 2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.879V14.89h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.989C18.343 21.129 22 16.99 22 12c0-5.523-4.477-10-10-10z"
|
||||
/>
|
||||
</svg>
|
||||
Continuer avec Apple
|
||||
</Button>
|
||||
|
||||
<div className="relative my-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-slate-200 dark:border-slate-700"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white dark:bg-slate-800 text-slate-500">
|
||||
Ou continuer avec
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Mot de passe
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="remember"
|
||||
className="h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-slate-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="remember"
|
||||
className="ml-2 block text-sm text-slate-700 dark:text-slate-300"
|
||||
>
|
||||
Se souvenir de moi
|
||||
</label>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
className="text-sm text-emerald-600 hover:text-emerald-500"
|
||||
>
|
||||
Mot de passe oublié ?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={twMerge(
|
||||
"w-full bg-emerald-700 text-white",
|
||||
"hover:bg-emerald-600"
|
||||
)}
|
||||
>
|
||||
Se connecter
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<p className="mt-6 text-center text-sm text-slate-600 dark:text-slate-400">
|
||||
Pas encore de compte ?{" "}
|
||||
<a
|
||||
href="/signup"
|
||||
className="text-emerald-600 hover:text-emerald-500 font-medium"
|
||||
>
|
||||
S'inscrire
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
229
ui/src/pages/signup.tsx
Normal file
229
ui/src/pages/signup.tsx
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import { Button } from "../ui-components/button";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function SignUpPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-emerald-100 via-green-100 to-white dark:bg-gradient-to-br dark:from-[#0a1f0a] dark:via-[#051505] dark:to-black">
|
||||
<div
|
||||
className={twMerge(
|
||||
"w-full max-w-md p-8 bg-white dark:bg-slate-800/50 backdrop-blur-lg rounded-2xl",
|
||||
"border border-emerald-200 dark:border-emerald-900/30",
|
||||
"shadow-xl"
|
||||
)}
|
||||
>
|
||||
<h1 className="text-3xl font-bold text-slate-900 dark:text-white mb-8 text-center">
|
||||
Créer un compte
|
||||
</h1>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
className={twMerge(
|
||||
"w-full bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"border border-slate-200 dark:border-slate-700",
|
||||
"hover:bg-slate-50 dark:hover:bg-slate-700/50",
|
||||
"flex items-center justify-center gap-2"
|
||||
)}
|
||||
>
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Continuer avec Google
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className={twMerge(
|
||||
"w-full bg-black text-white",
|
||||
"hover:bg-slate-900",
|
||||
"flex items-center justify-center gap-2"
|
||||
)}
|
||||
>
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 2C6.477 2 2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.879V14.89h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.989C18.343 21.129 22 16.99 22 12c0-5.523-4.477-10-10-10z"
|
||||
/>
|
||||
</svg>
|
||||
Continuer avec Apple
|
||||
</Button>
|
||||
|
||||
<div className="relative my-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-slate-200 dark:border-slate-700"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white dark:bg-slate-800 text-slate-500">
|
||||
Ou continuer avec
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="firstName"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Prénom
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="lastName"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Nom
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="company"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Nom de l'entreprise
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Email professionnel
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Mot de passe
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="confirmPassword"
|
||||
className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1"
|
||||
>
|
||||
Confirmer le mot de passe
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
className={twMerge(
|
||||
"w-full px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-700",
|
||||
"bg-white dark:bg-slate-800 text-slate-900 dark:text-white",
|
||||
"focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="terms"
|
||||
className="mt-1 h-4 w-4 text-emerald-600 focus:ring-emerald-500 border-slate-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="ml-2 block text-sm text-slate-700 dark:text-slate-300"
|
||||
>
|
||||
J'accepte les{" "}
|
||||
<a href="#" className="text-emerald-600 hover:text-emerald-500">
|
||||
conditions d'utilisation
|
||||
</a>{" "}
|
||||
et la{" "}
|
||||
<a href="#" className="text-emerald-600 hover:text-emerald-500">
|
||||
politique de confidentialité
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={twMerge(
|
||||
"w-full bg-emerald-700 text-white",
|
||||
"hover:bg-emerald-600"
|
||||
)}
|
||||
>
|
||||
Créer mon compte
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<p className="mt-6 text-center text-sm text-slate-600 dark:text-slate-400">
|
||||
Déjà un compte ?{" "}
|
||||
<a
|
||||
href="/login"
|
||||
className="text-emerald-600 hover:text-emerald-500 font-medium"
|
||||
>
|
||||
Se connecter
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import logo from "../assets/icon.jpg";
|
|||
import { useState } from "react";
|
||||
import { ConnectionForm } from "./connection-form";
|
||||
import { SignUpForm } from "./signup-form";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
interface HeaderProps {
|
||||
theme: "dark" | "light" | "system";
|
||||
|
|
@ -14,24 +15,25 @@ interface HeaderProps {
|
|||
export function Header({ theme, onThemeChange }: HeaderProps) {
|
||||
const [showConnectionForm, setShowConnectionForm] = useState(false);
|
||||
const [showSignUpForm, setShowSignUpForm] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="sticky top-0 z-50 w-full border-b border-slate-200 dark:border-slate-800 bg-white/80 dark:bg-slate-900/80 backdrop-blur-lg">
|
||||
<header className="sticky top-0 z-50 w-full border-b border-emerald-200 dark:border-emerald-900/30 bg-emerald-100/90 dark:bg-[#0a1f0a]/90 backdrop-blur-lg">
|
||||
<div className="container mx-auto px-4">
|
||||
<nav className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<img src={logo} alt="Logo XTablo" className="w-8 h-8" />
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
XTablo
|
||||
</h1>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="hidden md:flex items-center gap-6">
|
||||
<a
|
||||
href="#features"
|
||||
className={twMerge(
|
||||
"text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white",
|
||||
"text-slate-600 dark:text-slate-300 hover:text-emerald-700 dark:hover:text-emerald-400",
|
||||
"transition-colors"
|
||||
)}
|
||||
>
|
||||
|
|
@ -40,7 +42,7 @@ export function Header({ theme, onThemeChange }: HeaderProps) {
|
|||
<a
|
||||
href="#pricing"
|
||||
className={twMerge(
|
||||
"text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white",
|
||||
"text-slate-600 dark:text-slate-300 hover:text-emerald-700 dark:hover:text-emerald-400",
|
||||
"transition-colors"
|
||||
)}
|
||||
>
|
||||
|
|
@ -49,7 +51,7 @@ export function Header({ theme, onThemeChange }: HeaderProps) {
|
|||
<a
|
||||
href="#contact"
|
||||
className={twMerge(
|
||||
"text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white",
|
||||
"text-slate-600 dark:text-slate-300 hover:text-emerald-700 dark:hover:text-emerald-400",
|
||||
"transition-colors"
|
||||
)}
|
||||
>
|
||||
|
|
@ -59,19 +61,19 @@ export function Header({ theme, onThemeChange }: HeaderProps) {
|
|||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={() => setShowConnectionForm(true)}
|
||||
onPress={() => navigate("/login")}
|
||||
className={twMerge(
|
||||
"text-slate-900 dark:text-white hover:text-slate-700 dark:hover:text-white/80",
|
||||
"border-slate-300 dark:border-white/30 hover:border-slate-400 dark:hover:border-white/50",
|
||||
"text-slate-900 dark:text-white hover:text-emerald-700 dark:hover:text-emerald-400",
|
||||
"border-emerald-300 dark:border-emerald-700/30 hover:border-emerald-400 dark:hover:border-emerald-600/50",
|
||||
"transition"
|
||||
)}
|
||||
>
|
||||
Connexion
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => setShowSignUpForm(true)}
|
||||
onPress={() => navigate("/signup")}
|
||||
className={twMerge(
|
||||
"bg-blue-950 text-white hover:bg-blue-900",
|
||||
"bg-emerald-700 text-white hover:bg-emerald-600",
|
||||
"transition-colors"
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import {
|
||||
composeRenderProps,
|
||||
Group,
|
||||
|
|
@ -7,9 +7,9 @@ import {
|
|||
Switch as RACSwitch,
|
||||
SwitchProps as RACSwitchProps,
|
||||
SwitchRenderProps,
|
||||
} from 'react-aria-components';
|
||||
import { groupBox, composeTailwindRenderProps } from './utils';
|
||||
import { DescriptionProvider, DescriptionContext, LabeledGroup } from './field';
|
||||
} from "react-aria-components";
|
||||
import { groupBox, composeTailwindRenderProps } from "./utils";
|
||||
import { DescriptionProvider, DescriptionContext, LabeledGroup } from "./field";
|
||||
|
||||
export function SwitchGroup(props: GroupProps) {
|
||||
return (
|
||||
|
|
@ -25,16 +25,16 @@ export function SwitchGroup(props: GroupProps) {
|
|||
export function Switches({
|
||||
className,
|
||||
...props
|
||||
}: React.JSX.IntrinsicElements['div']) {
|
||||
}: React.JSX.IntrinsicElements["div"]) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
data-ui="box"
|
||||
className={twMerge(
|
||||
'flex flex-col',
|
||||
"flex flex-col",
|
||||
// When any switch item has description, apply all `font-medium` to all switch item labels
|
||||
'has-data-[ui=description]:[&_label]:font-medium',
|
||||
className,
|
||||
"has-data-[ui=description]:[&_label]:font-medium",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
@ -43,19 +43,19 @@ export function Switches({
|
|||
export function SwitchField({
|
||||
className,
|
||||
...props
|
||||
}: React.JSX.IntrinsicElements['div']) {
|
||||
}: React.JSX.IntrinsicElements["div"]) {
|
||||
return (
|
||||
<DescriptionProvider>
|
||||
<div
|
||||
{...props}
|
||||
data-ui="field"
|
||||
className={twMerge(
|
||||
'group flex flex-col gap-y-1',
|
||||
'has-[label[data-label-placement=start]]:[&_[data-ui=description]:not([class*=pe-])]:pe-[calc(theme(width.8)+16px)]',
|
||||
'has-[label[data-label-placement=end]]:[&_[data-ui=description]:not([class*=ps-])]:ps-[calc(theme(width.8)+12px)]',
|
||||
'has-data-[ui=description]:[&_label]:font-medium',
|
||||
'has-[label[data-disabled]]:**:data-[ui=description]:opacity-50',
|
||||
className,
|
||||
"group flex flex-col gap-y-1",
|
||||
"has-[label[data-label-placement=start]]:[&_[data-ui=description]:not([class*=pe-])]:pe-[calc(theme(width.8)+16px)]",
|
||||
"has-[label[data-label-placement=end]]:[&_[data-ui=description]:not([class*=ps-])]:ps-[calc(theme(width.8)+12px)]",
|
||||
"has-data-[ui=description]:[&_label]:font-medium",
|
||||
"has-[label[data-disabled]]:**:data-[ui=description]:opacity-50",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
</DescriptionProvider>
|
||||
|
|
@ -63,13 +63,13 @@ export function SwitchField({
|
|||
}
|
||||
|
||||
interface SwitchProps extends RACSwitchProps {
|
||||
labelPlacement?: 'start' | 'end';
|
||||
size?: 'lg';
|
||||
labelPlacement?: "start" | "end";
|
||||
size?: "lg";
|
||||
render?: never;
|
||||
}
|
||||
|
||||
export interface CustomRenderSwitchProps
|
||||
extends Omit<RACSwitchProps, 'children'> {
|
||||
extends Omit<RACSwitchProps, "children"> {
|
||||
render: React.ReactElement | ((props: SwitchRenderProps) => React.ReactNode);
|
||||
children?: never;
|
||||
size?: never;
|
||||
|
|
@ -85,15 +85,15 @@ export function Switch(props: SwitchProps | CustomRenderSwitchProps) {
|
|||
return (
|
||||
<RACSwitch
|
||||
{...restProps}
|
||||
aria-describedby={descriptionContext?.['aria-describedby']}
|
||||
aria-describedby={descriptionContext?.["aria-describedby"]}
|
||||
className={composeRenderProps(
|
||||
props.className,
|
||||
(className, { isDisabled }) =>
|
||||
twMerge(
|
||||
'group text-base/6 sm:text-sm/6',
|
||||
isDisabled && 'opacity-50',
|
||||
className,
|
||||
),
|
||||
"group text-base/6 sm:text-sm/6",
|
||||
isDisabled && "opacity-50",
|
||||
className
|
||||
)
|
||||
)}
|
||||
>
|
||||
{render}
|
||||
|
|
@ -101,53 +101,53 @@ export function Switch(props: SwitchProps | CustomRenderSwitchProps) {
|
|||
);
|
||||
}
|
||||
|
||||
const { labelPlacement = 'end', size, children, ...restProps } = props;
|
||||
const { labelPlacement = "end", size, children, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<RACSwitch
|
||||
{...restProps}
|
||||
aria-describedby={descriptionContext?.['aria-describedby']}
|
||||
aria-describedby={descriptionContext?.["aria-describedby"]}
|
||||
data-label-placement={labelPlacement}
|
||||
className={composeRenderProps(
|
||||
props.className,
|
||||
(className, { isDisabled }) =>
|
||||
twMerge(
|
||||
'group flex items-center text-base/6 sm:text-sm/6',
|
||||
labelPlacement === 'start' && 'flex-row-reverse justify-between',
|
||||
isDisabled && 'opacity-50',
|
||||
className,
|
||||
),
|
||||
"group flex items-center text-base/6 sm:text-sm/6",
|
||||
labelPlacement === "start" && "flex-row-reverse justify-between",
|
||||
isDisabled && "opacity-50",
|
||||
className
|
||||
)
|
||||
)}
|
||||
>
|
||||
{(renderProps) => (
|
||||
<>
|
||||
<div
|
||||
className={twMerge(
|
||||
'dark:border-input flex h-6 w-11 shrink-0 cursor-default items-center rounded-full border border-zinc-200 bg-zinc-200 p-px dark:bg-transparent',
|
||||
size !== 'lg' && 'sm:h-5 sm:w-8',
|
||||
labelPlacement === 'end' ? 'me-3' : 'ms-3',
|
||||
renderProps.isReadOnly && 'opacity-50',
|
||||
"dark:border-input flex h-6 w-11 shrink-0 cursor-default items-center rounded-full border border-zinc-200 bg-zinc-200 p-px dark:bg-transparent",
|
||||
size !== "lg" && "sm:h-5 sm:w-8",
|
||||
labelPlacement === "end" ? "me-3" : "ms-3",
|
||||
renderProps.isReadOnly && "opacity-50",
|
||||
renderProps.isSelected &&
|
||||
'border-accent bg-accent dark:bg-accent dark:border-accent',
|
||||
renderProps.isDisabled && 'bg-gray-200 dark:bg-zinc-700',
|
||||
"border-accent bg-accent dark:bg-accent dark:border-accent",
|
||||
renderProps.isDisabled && "bg-gray-200 dark:bg-zinc-700",
|
||||
renderProps.isFocusVisible &&
|
||||
'outline-ring outline outline-2 outline-offset-2',
|
||||
"outline-ring outline outline-offset-2"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
data-ui="handle"
|
||||
className={twMerge(
|
||||
'size-5',
|
||||
size !== 'lg' && 'sm:size-4',
|
||||
'rounded-full bg-white shadow transition-all ease-in-out',
|
||||
"size-5",
|
||||
size !== "lg" && "sm:size-4",
|
||||
"rounded-full bg-white shadow transition-all ease-in-out",
|
||||
renderProps.isSelected && [
|
||||
'translate-x-5 bg-[lch(from_var(--color-accent)_calc((49.44_-_l)_*_infinity)_0_0)] rtl:-translate-x-5',
|
||||
size !== 'lg' && 'sm:translate-x-3 sm:rtl:-translate-x-3',
|
||||
],
|
||||
"translate-x-5 bg-[lch(from_var(--color-accent)_calc((49.44_-_l)_*_infinity)_0_0)] rtl:-translate-x-5",
|
||||
size !== "lg" && "sm:translate-x-3 sm:rtl:-translate-x-3",
|
||||
]
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{typeof children === 'function' ? children(renderProps) : children}
|
||||
{typeof children === "function" ? children(renderProps) : children}
|
||||
</>
|
||||
)}
|
||||
</RACSwitch>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue