From 231db52718841076d9f580ff2e7de38b7e8040e6 Mon Sep 17 00:00:00 2001 From: dqnid Date: Mon, 22 Jul 2024 22:51:14 +0200 Subject: [PATCH] feat(front): login form styled --- front/package-lock.json | 81 +++++++++--- front/package.json | 9 +- front/src/app/(home)/layout.tsx | 2 +- front/src/app/(home)/page.tsx | 12 +- front/src/app/layout.tsx | 2 +- .../auth/components/sign-in.widget.tsx | 30 ----- .../components/sign-in/sign-in.module.scss | 123 ++++++++++++++++++ .../components/sign-in/sign-in.widget.tsx | 82 ++++++++++++ .../src/modules/auth/configs/auth.options.ts | 3 +- .../src/modules/common/hooks/api/useQuery.ts | 45 +++++++ .../{ => application}/application.layout.tsx | 2 +- .../common/layouts/{ => root}/root.layout.tsx | 0 .../common/providers/api-context.provider.tsx | 0 front/src/modules/common/utils/timedFetch.ts | 31 +++++ front/src/styles/_variables.scss | 12 +- front/src/styles/main.scss | 25 ++++ 16 files changed, 401 insertions(+), 58 deletions(-) delete mode 100644 front/src/modules/auth/components/sign-in.widget.tsx create mode 100644 front/src/modules/auth/components/sign-in/sign-in.module.scss create mode 100644 front/src/modules/auth/components/sign-in/sign-in.widget.tsx create mode 100644 front/src/modules/common/hooks/api/useQuery.ts rename front/src/modules/common/layouts/{ => application}/application.layout.tsx (72%) rename front/src/modules/common/layouts/{ => root}/root.layout.tsx (100%) create mode 100644 front/src/modules/common/providers/api-context.provider.tsx create mode 100644 front/src/modules/common/utils/timedFetch.ts diff --git a/front/package-lock.json b/front/package-lock.json index 44f97d7..7780ac3 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -10,9 +10,9 @@ "dependencies": { "next": "14.2.3", "next-auth": "^4.24.7", + "postcss": "^8.4.39", "react": "^18", - "react-dom": "^18", - "sass": "^1.77.4" + "react-dom": "^18" }, "devDependencies": { "@types/node": "^20", @@ -20,6 +20,7 @@ "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.2.3", + "sass": "^1.77.8", "typescript": "^5" } }, @@ -628,6 +629,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -868,6 +870,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "devOptional": true, "engines": { "node": ">=8" }, @@ -889,6 +892,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "devOptional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -936,9 +940,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001625", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz", - "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==", + "version": "1.0.30001643", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", + "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", "funding": [ { "type": "opencollective", @@ -952,7 +956,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", @@ -974,6 +979,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "devOptional": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -997,6 +1003,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1887,6 +1894,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1965,6 +1973,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2283,7 +2292,8 @@ "node_modules/immutable": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==" + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", + "devOptional": true }, "node_modules/import-fresh": { "version": "3.3.0", @@ -2388,6 +2398,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -2469,6 +2480,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -2513,6 +2525,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -2548,6 +2561,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -3057,10 +3071,39 @@ } } }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -3376,6 +3419,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -3393,9 +3437,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "funding": [ { "type": "opencollective", @@ -3410,10 +3454,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3529,6 +3574,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -3721,9 +3767,11 @@ } }, "node_modules/sass": { - "version": "1.77.4", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.4.tgz", - "integrity": "sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "devOptional": true, + "license": "MIT", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -4115,6 +4163,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, diff --git a/front/package.json b/front/package.json index a03e3fb..7791ad5 100644 --- a/front/package.json +++ b/front/package.json @@ -3,17 +3,17 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 3016", "build": "next build", - "start": "next start", + "start": "next start -p 3016", "lint": "next lint" }, "dependencies": { "next": "14.2.3", "next-auth": "^4.24.7", + "postcss": "^8.4.39", "react": "^18", - "react-dom": "^18", - "sass": "^1.77.4" + "react-dom": "^18" }, "devDependencies": { "@types/node": "^20", @@ -21,6 +21,7 @@ "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "14.2.3", + "sass": "^1.77.8", "typescript": "^5" } } diff --git a/front/src/app/(home)/layout.tsx b/front/src/app/(home)/layout.tsx index 7db596a..ea1c4bc 100644 --- a/front/src/app/(home)/layout.tsx +++ b/front/src/app/(home)/layout.tsx @@ -1 +1 @@ -export { ApplicationLayout as default } from "@/modules/common/layouts/application.layout"; +export { ApplicationLayout as default } from "@/modules/common/layouts/application/application.layout"; diff --git a/front/src/app/(home)/page.tsx b/front/src/app/(home)/page.tsx index e6a1544..a060dd5 100644 --- a/front/src/app/(home)/page.tsx +++ b/front/src/app/(home)/page.tsx @@ -1,9 +1,17 @@ -import { SignInWidget } from "@/modules/auth/components/sign-in.widget"; +import { SignInWidget } from "@/modules/auth/components/sign-in/sign-in.widget"; import ThemeSwitcher from "@/modules/common/theme-switcher"; export default function Home() { return ( -
+
Main page diff --git a/front/src/app/layout.tsx b/front/src/app/layout.tsx index ddb8b6a..e7cfa46 100644 --- a/front/src/app/layout.tsx +++ b/front/src/app/layout.tsx @@ -1 +1 @@ -export { RootLayout as default } from "@/modules/common/layouts/root.layout"; +export { RootLayout as default } from "@/modules/common/layouts/root/root.layout"; diff --git a/front/src/modules/auth/components/sign-in.widget.tsx b/front/src/modules/auth/components/sign-in.widget.tsx deleted file mode 100644 index 7610cf4..0000000 --- a/front/src/modules/auth/components/sign-in.widget.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -import { signIn, useSession } from "next-auth/react"; - -export const SignInWidget = () => { - const { data } = useSession(); - return ( -
- - {JSON.stringify(data)} -
- ); -}; diff --git a/front/src/modules/auth/components/sign-in/sign-in.module.scss b/front/src/modules/auth/components/sign-in/sign-in.module.scss new file mode 100644 index 0000000..c95014b --- /dev/null +++ b/front/src/modules/auth/components/sign-in/sign-in.module.scss @@ -0,0 +1,123 @@ +.container { + max-width: 20em; + + display: flex; + flex-direction: column; + gap: 1em; + + font-size: 1.6rem; + overflow: hidden; + + border-radius: 0.4rem; + background-image: linear-gradient( + 16deg, + rgba($color-primary-light-rgb, 0.3), + rgba($color-primary-light-rgb, 0.2) + ); + + .heading { + font-size: 3.4rem; + text-align: center; + background-color: #e5e5f7; + opacity: 0.8; + text-shadow: rgba($color-grey-dark, 0.3); + + padding: 2rem 4rem 2rem; + + --s: 80px; + background: + repeating-conic-gradient( + from 30deg, + #0000 0 120deg, + $color-white 0 180deg + ) + calc(0.5 * var(--s)) calc(0.5 * var(--s) * 0.577), + repeating-conic-gradient( + from 30deg, + $color-grey-medium 0 60deg, + $color-grey-light 0 120deg, + $color-white 0 180deg + ); + background-size: var(--s) calc(var(--s) * 0.577); + } + + .form { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5em; + + padding: 1.5em 4rem 2.5em; + + &__group { + display: flex; + flex-direction: column; + font-size: 1.6rem; + gap: 0.2em; + } + + &__label { + font-size: inherit; + margin-left: 0.8em; + + color: $color-grey-dark; + transition: all 0.3s; + } + + &__input { + font-size: inherit; + padding: 0.8em; + + color: $color-foreground; + + background: rgba($color-primary-light-rgb, 0.5); + border: none; + border-bottom: 2px solid transparent; + border-radius: 2px; + + transition: all 0.2s; + + &::placeholder { + color: $color-grey-dark; + } + + &:focus { + outline: none; + border-bottom: 2px solid $color-primary; + box-shadow: 0 1px 4px rgba($color-primary-dark-rgb, 0.3); + } + } + + &__group:has(.form__input:placeholder-shown) .form__label { + opacity: 0; + transform: translateY(1rem); + } + + &__actions { + width: fit-content; + margin-top: 1em; + + & .submit__button { + font-size: 2rem; + padding: 0.5em 1em; + border-radius: 2px; + + border: none; + background-color: $color-primary; + color: $color-foreground; + + transition: all 0.2s; + + &:hover { + cursor: pointer; + transform: translateY(-2px); + box-shadow: 0 2px 6px rgba($color-primary-dark-rgb, 0.4); + } + + &:active { + transform: translateY(0); + } + } + } + } +} diff --git a/front/src/modules/auth/components/sign-in/sign-in.widget.tsx b/front/src/modules/auth/components/sign-in/sign-in.widget.tsx new file mode 100644 index 0000000..8047d15 --- /dev/null +++ b/front/src/modules/auth/components/sign-in/sign-in.widget.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { FormEvent, useState } from "react"; +import styles from "./sign-in.module.scss"; + +import { signIn, useSession } from "next-auth/react"; + +type SingInWidgetsProps = { + afterSuccess?: Function; +}; + +export const SignInWidget: React.FC = ({ + afterSuccess, +}) => { + const { data } = useSession(); + + const [loginStatus, setLoginStatus] = useState< + "idle" | "check" | "confirm" | "error" + >("idle"); + + const submit = async (event: FormEvent) => { + event.preventDefault(); + setLoginStatus("check"); + const username = ( + event.currentTarget.elements.namedItem("username") as HTMLInputElement + ).value; + const password = ( + event.currentTarget.elements.namedItem("password") as HTMLInputElement + ).value; + + const result = await signIn("credentials", { + redirect: false, + username: username, + password: password, + }); + + if (!result?.ok) { + setLoginStatus("error"); + } else { + setLoginStatus("confirm"); + } + }; + + return ( +
+

Sign in

+
+
+ + +
+
+ + +
+
+ +
+
+
+ ); +}; diff --git a/front/src/modules/auth/configs/auth.options.ts b/front/src/modules/auth/configs/auth.options.ts index 7700ba5..b98cb0b 100644 --- a/front/src/modules/auth/configs/auth.options.ts +++ b/front/src/modules/auth/configs/auth.options.ts @@ -16,7 +16,6 @@ export const authOptions: AuthOptions = { password: { label: "Password", type: "password" }, }, async authorize(credentials, req) { - console.log("__credentials", credentials); const user: User = { id: credentials?.password ?? "asdf", role: "admin", @@ -26,7 +25,7 @@ export const authOptions: AuthOptions = { accessToken: credentials?.password ?? "asdf", }, }; - return user; + return credentials?.password === "secure-password" ? user : null; }, }), ], diff --git a/front/src/modules/common/hooks/api/useQuery.ts b/front/src/modules/common/hooks/api/useQuery.ts new file mode 100644 index 0000000..14a9185 --- /dev/null +++ b/front/src/modules/common/hooks/api/useQuery.ts @@ -0,0 +1,45 @@ +import { useEffect, useState } from "react"; +import { timedFetch } from "../../utils/timedFetch"; + +type QueryReturn = { + data?: T; + status?: number; + isLoading: boolean; + isError: boolean; +}; + +type QueryInput = { + url: string; + options: RequestInit; + timeout: number; +}; + +export function useQuery({ + url, + options, + timeout, +}: QueryInput): QueryReturn { + const [response, setResponse] = useState<{ + data: DataType; + status: number; + }>(); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + + useEffect(() => { + setIsLoading(true); + setIsError(false); + async () => { + const _response = await timedFetch(url, options, timeout); + if (_response) { + setResponse(_response); + setIsLoading(false); + } else { + setIsLoading(false); + setIsError(true); + } + }; + }, [url, options, timeout]); + + return { ...response, isLoading, isError }; +} diff --git a/front/src/modules/common/layouts/application.layout.tsx b/front/src/modules/common/layouts/application/application.layout.tsx similarity index 72% rename from front/src/modules/common/layouts/application.layout.tsx rename to front/src/modules/common/layouts/application/application.layout.tsx index fb729b9..18e9e91 100644 --- a/front/src/modules/common/layouts/application.layout.tsx +++ b/front/src/modules/common/layouts/application/application.layout.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren } from "react"; -import { ApplicationProvider } from "../providers/application.provider"; +import { ApplicationProvider } from "../../providers/application.provider"; export const ApplicationLayout: React.FC = ({ children, diff --git a/front/src/modules/common/layouts/root.layout.tsx b/front/src/modules/common/layouts/root/root.layout.tsx similarity index 100% rename from front/src/modules/common/layouts/root.layout.tsx rename to front/src/modules/common/layouts/root/root.layout.tsx diff --git a/front/src/modules/common/providers/api-context.provider.tsx b/front/src/modules/common/providers/api-context.provider.tsx new file mode 100644 index 0000000..e69de29 diff --git a/front/src/modules/common/utils/timedFetch.ts b/front/src/modules/common/utils/timedFetch.ts new file mode 100644 index 0000000..264afd1 --- /dev/null +++ b/front/src/modules/common/utils/timedFetch.ts @@ -0,0 +1,31 @@ +class HttpError extends Error { + constructor(public response: Response) { + super(`HTTP error ${response.status}`); + } +} + +async function timedFetch( + url: string, + options: RequestInit = {}, + timeout: number = 5000, +) { + try { + const result = await fetch(url, { + signal: AbortSignal.timeout(timeout), + ...options, + }); + if (!result.ok) { + throw new HttpError(result); + } + return { + data: (await result.json()) as ResponseType, + status: result.status, + }; + } catch (error) { + if ((error as Error).name === "AbortError") { + console.log(`Fetch aborted by timeout (${timeout}ms)`); + } + } +} + +export { timedFetch }; diff --git a/front/src/styles/_variables.scss b/front/src/styles/_variables.scss index 0656473..95bd6b3 100644 --- a/front/src/styles/_variables.scss +++ b/front/src/styles/_variables.scss @@ -1,8 +1,18 @@ $default-font-size: 1.6rem; -$color-primary: #ff7730; +$color-primary: var(--color-primary); +$color-primary-light: var(--color-primary-light); +$color-primary-dark: var(--color-primary-dark); +$color-primary-rgb: var(--color-primary-rgb); +$color-primary-light-rgb: var(--color-primary-light-rgb); +$color-primary-dark-rgb: var(--color-primary-dark-rgb); + $color-secondary: #ff7730; $color-background: var(--color-white); $color-foreground: var(--color-black); $color-white: var(--color-white); + +$color-grey-light: var(--color-grey-light); +$color-grey-medium: var(--color-grey-medium); +$color-grey-dark: var(--color-grey-dark); diff --git a/front/src/styles/main.scss b/front/src/styles/main.scss index ff7f2c3..b53bf52 100644 --- a/front/src/styles/main.scss +++ b/front/src/styles/main.scss @@ -1,12 +1,36 @@ +@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); + // For variable definition :root { --color-white: #fafafa; --color-black: #101010; + + --color-grey-dark: #495057; + --color-grey-medium: #adb5bd; + --color-grey-light: #dee2e6; + + --color-primary: #9c89b8; + --color-primary-light: #b8bedd; + --color-primary-dark: #56577d; + --color-primary-rgb: 156, 137, 184; + --color-primary-light-rgb: 184, 190, 221; + --color-primary-dark-rgb: 86, 87, 125; } [data-theme="dark"] { --color-white: #101010; --color-black: #fafafa; + + --color-grey-dark: #dee2e6; + --color-grey-medium: #adb5bd; + --color-grey-light: #495057; + + --color-primary: #56577d; + --color-primary-light: #b8bedd; + --color-primary-dark: #9c89b8; + --color-primary-rgb: 86, 87, 125; + --color-primary-light-rgb: 184, 190, 221; + --color-primary-dark-rgb: 156, 137, 184; } *, @@ -18,6 +42,7 @@ } html { + font-family: "Roboto", sans-serif; max-width: 100vw; overflow-x: hidden;