From 2e3ec9765d038a3418ce2c2195f5c15465c0bdf2 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 31 Mar 2025 19:46:26 +0000 Subject: [PATCH] Implement login form with improved UI components and error handling - Added new UI components: `Logo`, `Alert`, `Button`, `Input`, and `Card`. - Refactored `LoginForm` to utilize new components for better structure and styling. - Enhanced error handling in `LoginFormData` with specific error messages. - Updated styles for grid layout and various UI elements for a cohesive design. - Removed Bootstrap dependency in favor of custom styles for better control. --- bun.lockb | Bin 205245 -> 204521 bytes package.json | 1 - src/components/Logo.astro | 56 ++++ src/components/forms/login/LoginForm.astro | 86 +++--- .../forms/login/LoginFormData.astro | 89 ++++-- src/components/ui/Alert.astro | 15 + src/components/ui/Button.astro | 23 ++ src/components/ui/Card.astro | 30 ++ src/components/ui/Input.astro | 68 +++++ src/layouts/EmptyLayout.astro | 21 +- src/layouts/RootLayout.astro | 12 +- src/pages/login.astro | 19 +- src/styles/components/alerts.css | 24 ++ src/styles/components/buttons.css | 33 +++ src/styles/components/cards.css | 8 + src/styles/components/forms.css | 27 ++ src/styles/grid.css | 260 ++++++++++++++++++ src/styles/index.css | 79 ++++-- src/styles/utils.css | 89 ++++++ 19 files changed, 837 insertions(+), 103 deletions(-) create mode 100644 src/components/Logo.astro create mode 100644 src/components/ui/Alert.astro create mode 100644 src/components/ui/Button.astro create mode 100644 src/components/ui/Card.astro create mode 100644 src/components/ui/Input.astro create mode 100644 src/styles/components/alerts.css create mode 100644 src/styles/components/buttons.css create mode 100644 src/styles/components/cards.css create mode 100644 src/styles/components/forms.css create mode 100644 src/styles/grid.css create mode 100644 src/styles/utils.css diff --git a/bun.lockb b/bun.lockb index 2f4f8375815e0f8de0229b6c2a6900584a84fed0..5d2027e2547c1cf984f7beeae6b162307800ce40 100755 GIT binary patch delta 37725 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�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 hp*E}Jt)%IeXRc delta 38071 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 F{(n{nUR(eG diff --git a/package.json b/package.json index fb2cb2c..dc25952 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", "astro": "^5.5.5", - "bootstrap": "^5.3.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.5.0", diff --git a/src/components/Logo.astro b/src/components/Logo.astro new file mode 100644 index 0000000..81e839c --- /dev/null +++ b/src/components/Logo.astro @@ -0,0 +1,56 @@ +--- +interface Props { + class?: string; +} + +const { class: className = "" } = Astro.props; +--- + + + + diff --git a/src/components/forms/login/LoginForm.astro b/src/components/forms/login/LoginForm.astro index 4b7b8eb..02060b5 100644 --- a/src/components/forms/login/LoginForm.astro +++ b/src/components/forms/login/LoginForm.astro @@ -1,44 +1,56 @@ --- +import Alert from "../../ui/Alert.astro"; +import Logo from "../../Logo.astro"; +import Button from "../../ui/Button.astro"; +import Input from "../../ui/Input.astro"; interface Props { - error: string | null; + error?: string; + username?: string; + password?: string; } -const { error } = Astro.props as Props; +const { error, username = "", password = "" } = Astro.props; --- -
-
- { - error && ( -
- {error} -
- ) - } -
-
- - -
-
- - -
-
- -
-
+ +
+{ + error && ( + + {error} + + ) +} +
+
+ +
-
+
+ + +
+ + + + diff --git a/src/components/forms/login/LoginFormData.astro b/src/components/forms/login/LoginFormData.astro index 7a41a46..54ddb49 100644 --- a/src/components/forms/login/LoginFormData.astro +++ b/src/components/forms/login/LoginFormData.astro @@ -1,22 +1,36 @@ --- import LoginForm from "./LoginForm.astro"; -const req = Astro.request; -const isPost = req.method === "POST"; +type LoginError = + | "MISSING_FIELDS" + | "INVALID_CREDENTIALS" + | "SERVER_ERROR" + | "NOT_FOUND"; -let username = ""; -let password = ""; -let error = null; +const ERROR_MESSAGES: Record = { + MISSING_FIELDS: "Tous les champs sont requis", + INVALID_CREDENTIALS: "Identifiants invalides", + NOT_FOUND: "Compte non trouvé", + SERVER_ERROR: "Une erreur est survenue", +}; -if (isPost) { - const form = await req.formData(); - username = form.get("username")?.toString().trim() ?? ""; - password = form.get("password")?.toString().trim() ?? ""; +interface LoginResponse { + jwt: string; } -if (!username || !password) { - error = "Tous les champs sont requis"; -} else { +interface LoginResult { + error?: LoginError; + jwt?: string; +} + +async function handleLogin( + username: string, + password: string, +): Promise { + if (!username || !password) { + return { error: "MISSING_FIELDS" }; + } + try { const res = await fetch(`${import.meta.env.API_URL}/accounts/login`, { method: "POST", @@ -27,23 +41,44 @@ if (!username || !password) { }); if (!res.ok) { - if (res.status === 401) { - error = "Identifiants invalides"; - } else { - error = "Une erreur est survenue"; - } - } else { - const data = await res.json(); - Astro.cookies.set("jwt", data.jwt, { - path: "/", - secure: true, - httpOnly: true, - }); - - return Astro.redirect("/"); + return { + error: + res.status === 401 + ? "INVALID_CREDENTIALS" + : res.status === 404 + ? "NOT_FOUND" + : "SERVER_ERROR", + }; } + + const data = (await res.json()) as LoginResponse; + return { jwt: data.jwt }; } catch (err) { - error = "Une erreur est survenue"; + return { error: "SERVER_ERROR" }; + } +} + +const req = Astro.request; +let error: string | undefined; + +if (req.method === "POST") { + const form = await req.formData(); + const username = form.get("username")?.toString().trim() ?? ""; + const password = form.get("password")?.toString().trim() ?? ""; + + const { error: loginError, jwt } = await handleLogin(username, password); + + if (loginError) { + error = ERROR_MESSAGES[loginError]; + } else if (jwt) { + Astro.cookies.set("jwt", jwt, { + path: "/", + secure: true, + httpOnly: true, + sameSite: "strict", + }); + + return Astro.redirect("/"); } } --- diff --git a/src/components/ui/Alert.astro b/src/components/ui/Alert.astro new file mode 100644 index 0000000..56c37a8 --- /dev/null +++ b/src/components/ui/Alert.astro @@ -0,0 +1,15 @@ +--- +interface Props { + type?: "error" | "warning" | "success"; + class?: string; +} + +const { type = "error", class: className = "" } = Astro.props; + +const typeClass = `message-${type}`; +--- + +
+ +
+ diff --git a/src/components/ui/Button.astro b/src/components/ui/Button.astro new file mode 100644 index 0000000..9464004 --- /dev/null +++ b/src/components/ui/Button.astro @@ -0,0 +1,23 @@ +--- +interface Props { + type?: "button" | "submit" | "reset"; + variant?: "default" | "primary" | "danger" | "warning"; + class?: string; + fullWidth?: boolean; +} + +const { + type = "button", + variant = "default", + class: className = "", + fullWidth = false, +} = Astro.props; + +const variantClass = variant !== "default" ? `btn-${variant}` : ""; +const widthClass = fullWidth ? "w-100" : ""; +--- + + + diff --git a/src/components/ui/Card.astro b/src/components/ui/Card.astro new file mode 100644 index 0000000..b994c18 --- /dev/null +++ b/src/components/ui/Card.astro @@ -0,0 +1,30 @@ +--- +interface Props { + class?: string; + padding?: "sm" | "md" | "lg"; +} + +const { class: className = "", padding = "md" } = Astro.props; + +const paddingMap = { + sm: "1rem", + md: "1.5rem", + lg: "2rem", +}; +--- + +
+ +
+ + + diff --git a/src/components/ui/Input.astro b/src/components/ui/Input.astro new file mode 100644 index 0000000..cdfc125 --- /dev/null +++ b/src/components/ui/Input.astro @@ -0,0 +1,68 @@ +--- +interface Props { + label?: string; + type?: "text" | "password" | "email" | "number"; + name: string; + id?: string; + placeholder?: string; + value?: string | number; + required?: boolean; + error?: string; + class?: string; +} + +const { + label, + type = "text", + name, + id = name, + placeholder = "", + value = "", + required = false, + error, + class: className = "", +} = Astro.props; +--- + +
+ { + label && ( + + ) + } + + {error &&
{error}
} +
+ + diff --git a/src/layouts/EmptyLayout.astro b/src/layouts/EmptyLayout.astro index 286079e..f1fe919 100644 --- a/src/layouts/EmptyLayout.astro +++ b/src/layouts/EmptyLayout.astro @@ -9,14 +9,17 @@ const { title } = Astro.props; --- -
-
-
- -
-
+
+
+ + diff --git a/src/layouts/RootLayout.astro b/src/layouts/RootLayout.astro index 4a08673..b98f4d4 100644 --- a/src/layouts/RootLayout.astro +++ b/src/layouts/RootLayout.astro @@ -11,8 +11,14 @@ interface Props { const { title } = Astro.props; -import "bootstrap/dist/css/bootstrap.min.css"; +// Import des styles import "../styles/index.css"; +import "../styles/grid.css"; +import "../styles/components/buttons.css"; +import "../styles/components/cards.css"; +import "../styles/components/forms.css"; +import "../styles/components/alerts.css"; +import "../styles/utils.css"; --- @@ -39,7 +45,3 @@ import "../styles/index.css"; height: 100%; } - - diff --git a/src/pages/login.astro b/src/pages/login.astro index 1362bea..d5e0c2b 100644 --- a/src/pages/login.astro +++ b/src/pages/login.astro @@ -1,8 +1,23 @@ --- import LoginFormData from "../components/forms/login/LoginFormData.astro"; import EmptyLayout from "../layouts/EmptyLayout.astro"; +import Card from "../components/ui/Card.astro"; --- - - + +
+
+
+ + + +
+
+
+ + diff --git a/src/styles/components/alerts.css b/src/styles/components/alerts.css new file mode 100644 index 0000000..bab4e08 --- /dev/null +++ b/src/styles/components/alerts.css @@ -0,0 +1,24 @@ +/* Styles des messages d'alerte */ +.message { + padding: 1rem; + border-radius: var(--radius-md); + margin-bottom: 1rem; +} + +.message-error { + background: rgba(255, 107, 107, 0.1); + border: 1px solid var(--error-color); + color: var(--error-color); +} + +.message-warning { + background: rgba(255, 217, 61, 0.1); + border: 1px solid var(--warning-color); + color: var(--warning-color); +} + +.message-success { + background: rgba(107, 203, 119, 0.1); + border: 1px solid var(--success-color); + color: var(--success-color); +} diff --git a/src/styles/components/buttons.css b/src/styles/components/buttons.css new file mode 100644 index 0000000..d89c8fe --- /dev/null +++ b/src/styles/components/buttons.css @@ -0,0 +1,33 @@ +/* Styles des boutons */ +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: var(--radius-md); + font-weight: 500; + cursor: pointer; + transition: all var(--transition-speed); + background: var(--background-card); + color: var(--text-secondary); + backdrop-filter: var(--glass-blur); +} + +.btn:hover { + opacity: 0.75; +} + +.btn-primary { + background: var(--primary-gradient); + color: var(--background-dark); + font-weight: 600; +} + +.btn-danger { + background: var(--error-color); + color: var(--text-primary); +} + +.btn-warning { + background: var(--warning-color); + color: var(--background-dark); + font-weight: 600; +} diff --git a/src/styles/components/cards.css b/src/styles/components/cards.css new file mode 100644 index 0000000..696eece --- /dev/null +++ b/src/styles/components/cards.css @@ -0,0 +1,8 @@ +/* Styles des cartes */ +.card { + background: var(--background-card); + border-radius: var(--radius-lg); + padding: 1.5rem; + backdrop-filter: var(--glass-blur); + border: 1px solid rgba(255, 255, 255, 0.1); +} diff --git a/src/styles/components/forms.css b/src/styles/components/forms.css new file mode 100644 index 0000000..ee06ead --- /dev/null +++ b/src/styles/components/forms.css @@ -0,0 +1,27 @@ +/* Styles des formulaires */ +.input { + padding: 0.75rem 1rem; + background: var(--background-card); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-md); + color: var(--text-primary); + backdrop-filter: var(--glass-blur); + width: 100%; + transition: all var(--transition-speed); +} + +.input:focus { + outline: none; + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(134, 227, 206, 0.2); +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-label { + display: block; + margin-bottom: 0.5rem; + color: var(--text-secondary); +} diff --git a/src/styles/grid.css b/src/styles/grid.css new file mode 100644 index 0000000..706e45a --- /dev/null +++ b/src/styles/grid.css @@ -0,0 +1,260 @@ +/* Système de grille */ +.container { + width: 100%; + margin-right: auto; + margin-left: auto; + padding-right: var(--container-padding); + padding-left: var(--container-padding); +} + +@media (min-width: 640px) { + .container { + max-width: var(--breakpoint-sm); + } +} +@media (min-width: 768px) { + .container { + max-width: var(--breakpoint-md); + } +} +@media (min-width: 1024px) { + .container { + max-width: var(--breakpoint-lg); + } +} +@media (min-width: 1280px) { + .container { + max-width: var(--breakpoint-xl); + } +} + +.row { + display: flex; + flex-wrap: wrap; + margin-right: calc(-1 * var(--grid-gutter)); + margin-left: calc(-1 * var(--grid-gutter)); +} + +[class^="col-"] { + position: relative; + width: 100%; + padding-right: var(--grid-gutter); + padding-left: var(--grid-gutter); +} + +/* Colonnes */ +.col { + flex: 1 0 0%; +} +.col-auto { + flex: 0 0 auto; + width: auto; +} + +/* Small (sm) */ +@media (min-width: 640px) { + .col-sm-1 { + flex: 0 0 var(--col-1); + max-width: var(--col-1); + } + .col-sm-2 { + flex: 0 0 var(--col-2); + max-width: var(--col-2); + } + .col-sm-3 { + flex: 0 0 var(--col-3); + max-width: var(--col-3); + } + .col-sm-4 { + flex: 0 0 var(--col-4); + max-width: var(--col-4); + } + .col-sm-5 { + flex: 0 0 var(--col-5); + max-width: var(--col-5); + } + .col-sm-6 { + flex: 0 0 var(--col-6); + max-width: var(--col-6); + } + .col-sm-7 { + flex: 0 0 var(--col-7); + max-width: var(--col-7); + } + .col-sm-8 { + flex: 0 0 var(--col-8); + max-width: var(--col-8); + } + .col-sm-9 { + flex: 0 0 var(--col-9); + max-width: var(--col-9); + } + .col-sm-10 { + flex: 0 0 var(--col-10); + max-width: var(--col-10); + } + .col-sm-11 { + flex: 0 0 var(--col-11); + max-width: var(--col-11); + } + .col-sm-12 { + flex: 0 0 var(--col-12); + max-width: var(--col-12); + } +} + +/* Medium (md) */ +@media (min-width: 768px) { + .col-md-1 { + flex: 0 0 var(--col-1); + max-width: var(--col-1); + } + .col-md-2 { + flex: 0 0 var(--col-2); + max-width: var(--col-2); + } + .col-md-3 { + flex: 0 0 var(--col-3); + max-width: var(--col-3); + } + .col-md-4 { + flex: 0 0 var(--col-4); + max-width: var(--col-4); + } + .col-md-5 { + flex: 0 0 var(--col-5); + max-width: var(--col-5); + } + .col-md-6 { + flex: 0 0 var(--col-6); + max-width: var(--col-6); + } + .col-md-7 { + flex: 0 0 var(--col-7); + max-width: var(--col-7); + } + .col-md-8 { + flex: 0 0 var(--col-8); + max-width: var(--col-8); + } + .col-md-9 { + flex: 0 0 var(--col-9); + max-width: var(--col-9); + } + .col-md-10 { + flex: 0 0 var(--col-10); + max-width: var(--col-10); + } + .col-md-11 { + flex: 0 0 var(--col-11); + max-width: var(--col-11); + } + .col-md-12 { + flex: 0 0 var(--col-12); + max-width: var(--col-12); + } +} + +/* Large (lg) */ +@media (min-width: 1024px) { + .col-lg-1 { + flex: 0 0 var(--col-1); + max-width: var(--col-1); + } + .col-lg-2 { + flex: 0 0 var(--col-2); + max-width: var(--col-2); + } + .col-lg-3 { + flex: 0 0 var(--col-3); + max-width: var(--col-3); + } + .col-lg-4 { + flex: 0 0 var(--col-4); + max-width: var(--col-4); + } + .col-lg-5 { + flex: 0 0 var(--col-5); + max-width: var(--col-5); + } + .col-lg-6 { + flex: 0 0 var(--col-6); + max-width: var(--col-6); + } + .col-lg-7 { + flex: 0 0 var(--col-7); + max-width: var(--col-7); + } + .col-lg-8 { + flex: 0 0 var(--col-8); + max-width: var(--col-8); + } + .col-lg-9 { + flex: 0 0 var(--col-9); + max-width: var(--col-9); + } + .col-lg-10 { + flex: 0 0 var(--col-10); + max-width: var(--col-10); + } + .col-lg-11 { + flex: 0 0 var(--col-11); + max-width: var(--col-11); + } + .col-lg-12 { + flex: 0 0 var(--col-12); + max-width: var(--col-12); + } +} + +/* Extra Large (xl) */ +@media (min-width: 1280px) { + .col-xl-1 { + flex: 0 0 var(--col-1); + max-width: var(--col-1); + } + .col-xl-2 { + flex: 0 0 var(--col-2); + max-width: var(--col-2); + } + .col-xl-3 { + flex: 0 0 var(--col-3); + max-width: var(--col-3); + } + .col-xl-4 { + flex: 0 0 var(--col-4); + max-width: var(--col-4); + } + .col-xl-5 { + flex: 0 0 var(--col-5); + max-width: var(--col-5); + } + .col-xl-6 { + flex: 0 0 var(--col-6); + max-width: var(--col-6); + } + .col-xl-7 { + flex: 0 0 var(--col-7); + max-width: var(--col-7); + } + .col-xl-8 { + flex: 0 0 var(--col-8); + max-width: var(--col-8); + } + .col-xl-9 { + flex: 0 0 var(--col-9); + max-width: var(--col-9); + } + .col-xl-10 { + flex: 0 0 var(--col-10); + max-width: var(--col-10); + } + .col-xl-11 { + flex: 0 0 var(--col-11); + max-width: var(--col-11); + } + .col-xl-12 { + flex: 0 0 var(--col-12); + max-width: var(--col-12); + } +} diff --git a/src/styles/index.css b/src/styles/index.css index 217912a..dd54de2 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -1,27 +1,62 @@ -.cardshadow { - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.4); - transition: box-shadow 0.2s ease-in-out; +/* Variables globales */ +:root { + /* Couleurs principales */ + --primary-gradient: linear-gradient(135deg, #86e3ce 0%, #d6a4e5 100%); + --background-dark: #1a1b1e; + --background-card: rgba(255, 255, 255, 0.05); + --text-primary: #ffffff; + --text-secondary: rgba(255, 255, 255, 0.7); + --accent-color: #86e3ce; + --error-color: #ff6b6b; + --warning-color: #ffd93d; + --success-color: #6bcb77; + + /* Effets */ + --glass-blur: blur(10px); + --card-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37); + --transition-speed: 0.3s; + + /* Border radius */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 16px; + + /* Grid */ + --grid-gutter: 1rem; + --container-padding: 1rem; + + /* Breakpoints */ + --breakpoint-sm: 640px; + --breakpoint-md: 768px; + --breakpoint-lg: 1024px; + --breakpoint-xl: 1280px; + + /* Column widths */ + --col-1: 8.333333%; + --col-2: 16.666667%; + --col-3: 25%; + --col-4: 33.333333%; + --col-5: 41.666667%; + --col-6: 50%; + --col-7: 58.333333%; + --col-8: 66.666667%; + --col-9: 75%; + --col-10: 83.333333%; + --col-11: 91.666667%; + --col-12: 100%; } -.cardshadow:hover { - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.8); +/* Reset et styles de base */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; } -.movieitem { - opacity: 0; - background-color: rgba(0, 0, 0, 0.75); - color: rgba(255, 255, 255, 1); -} - -.movieitem:hover { - animation: 0.2s ease-in-out forwards moviehover; -} - -@keyframes moviehover { - from { - opacity: 0; - } - to { - opacity: 1; - } +body { + background-color: var(--background-dark); + color: var(--text-primary); + font-family: "Inter", system-ui, sans-serif; + line-height: 1.5; + min-height: 100vh; } diff --git a/src/styles/utils.css b/src/styles/utils.css new file mode 100644 index 0000000..fea2f90 --- /dev/null +++ b/src/styles/utils.css @@ -0,0 +1,89 @@ +/* Classes utilitaires */ + +/* Flex */ +.d-flex { + display: flex; +} +.flex-column { + flex-direction: column; +} +.justify-content-center { + justify-content: center; +} +.align-items-center { + align-items: center; +} + +/* Text */ +.text-center { + text-align: center; +} + +/* Dimensions */ +.w-100 { + width: 100%; +} +.h-100 { + height: 100%; +} + +/* Marges */ +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-3 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-4 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.my-5 { + margin-top: 2rem; + margin-bottom: 2rem; +} + +/* Séparateur */ +hr { + border: none; + height: 1px; + margin: 2rem 0; + background: linear-gradient( + 90deg, + transparent, + var(--text-secondary), + transparent + ); + opacity: 0.2; +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-in { + animation: fadeIn 0.5s ease-out; +}