feat(front): login form styled
This commit is contained in:
81
front/package-lock.json
generated
81
front/package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ApplicationLayout as default } from "@/modules/common/layouts/application.layout";
|
||||
export { ApplicationLayout as default } from "@/modules/common/layouts/application/application.layout";
|
||||
|
||||
@@ -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>
|
||||
<main
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
Main page
|
||||
<ThemeSwitcher />
|
||||
<SignInWidget />
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { RootLayout as default } from "@/modules/common/layouts/root.layout";
|
||||
export { RootLayout as default } from "@/modules/common/layouts/root/root.layout";
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { signIn, useSession } from "next-auth/react";
|
||||
|
||||
export const SignInWidget = () => {
|
||||
const { data } = useSession();
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const result = await signIn("credentials", {
|
||||
redirect: false,
|
||||
username: "dqnid",
|
||||
password: "1234",
|
||||
callbackUrl: "/",
|
||||
});
|
||||
|
||||
console.debug("Login result:", result);
|
||||
|
||||
if (result?.error === "CredentialsSignin") {
|
||||
return "Invalid credentials";
|
||||
}
|
||||
}}
|
||||
>
|
||||
LOGIN
|
||||
</button>
|
||||
{JSON.stringify(data)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
123
front/src/modules/auth/components/sign-in/sign-in.module.scss
Normal file
123
front/src/modules/auth/components/sign-in/sign-in.module.scss
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
front/src/modules/auth/components/sign-in/sign-in.widget.tsx
Normal file
82
front/src/modules/auth/components/sign-in/sign-in.widget.tsx
Normal file
@@ -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<SingInWidgetsProps> = ({
|
||||
afterSuccess,
|
||||
}) => {
|
||||
const { data } = useSession();
|
||||
|
||||
const [loginStatus, setLoginStatus] = useState<
|
||||
"idle" | "check" | "confirm" | "error"
|
||||
>("idle");
|
||||
|
||||
const submit = async (event: FormEvent<HTMLFormElement>) => {
|
||||
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 (
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles["heading"]}>Sign in</h1>
|
||||
<form className={styles.form} onSubmit={submit}>
|
||||
<div className={`${styles["form__group"]}`}>
|
||||
<label htmlFor="username" className={styles["form__label"]}>
|
||||
username
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="username"
|
||||
className={styles["form__input"]}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form__group"]}>
|
||||
<label htmlFor="password" className={styles["form__label"]}>
|
||||
password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="password"
|
||||
className={styles["form__input"]}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["form__actions"]}>
|
||||
<button type="submit" className={styles["submit__button"]}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
45
front/src/modules/common/hooks/api/useQuery.ts
Normal file
45
front/src/modules/common/hooks/api/useQuery.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { timedFetch } from "../../utils/timedFetch";
|
||||
|
||||
type QueryReturn<T> = {
|
||||
data?: T;
|
||||
status?: number;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
};
|
||||
|
||||
type QueryInput = {
|
||||
url: string;
|
||||
options: RequestInit;
|
||||
timeout: number;
|
||||
};
|
||||
|
||||
export function useQuery<DataType>({
|
||||
url,
|
||||
options,
|
||||
timeout,
|
||||
}: QueryInput): QueryReturn<DataType> {
|
||||
const [response, setResponse] = useState<{
|
||||
data: DataType;
|
||||
status: number;
|
||||
}>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setIsError(false);
|
||||
async () => {
|
||||
const _response = await timedFetch<DataType>(url, options, timeout);
|
||||
if (_response) {
|
||||
setResponse(_response);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
}, [url, options, timeout]);
|
||||
|
||||
return { ...response, isLoading, isError };
|
||||
}
|
||||
@@ -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<PropsWithChildren> = ({
|
||||
children,
|
||||
31
front/src/modules/common/utils/timedFetch.ts
Normal file
31
front/src/modules/common/utils/timedFetch.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
class HttpError extends Error {
|
||||
constructor(public response: Response) {
|
||||
super(`HTTP error ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function timedFetch<ResponseType = any>(
|
||||
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 };
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user