refactor(lint): code styled with prettier
This commit is contained in:
@@ -24,8 +24,8 @@ This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-opti
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
|
||||
@@ -2,23 +2,23 @@ import path from "path";
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
/**
|
||||
* https://nextjs.org/docs/app/building-your-application/styling/sass
|
||||
*/
|
||||
sassOptions: {
|
||||
// optional: prependData: `@import "@/styles/variables";`,
|
||||
includePaths: [path.join("@", "styles")],
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "picsum.photos",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
/**
|
||||
* https://nextjs.org/docs/app/building-your-application/styling/sass
|
||||
*/
|
||||
sassOptions: {
|
||||
// optional: prependData: `@import "@/styles/variables";`,
|
||||
includePaths: [path.join("@", "styles")],
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "picsum.photos",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<main
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
Main page
|
||||
</main>
|
||||
);
|
||||
return (
|
||||
<main
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
Main page
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,38 +2,38 @@ import { withAuth } from "next-auth/middleware";
|
||||
import { authOptions } from "./modules/auth/configs/auth.options";
|
||||
|
||||
export default withAuth({
|
||||
pages: authOptions.pages,
|
||||
callbacks: {
|
||||
authorized({ req, token }) {
|
||||
if (token && token.apiSession.exp * 1000 > Date.now()) {
|
||||
return true;
|
||||
}
|
||||
const pathname = req.nextUrl.pathname;
|
||||
return (
|
||||
pathname.startsWith("/_next/") ||
|
||||
pathname.startsWith("/favicon.ico") ||
|
||||
pathname.startsWith("/assets/")
|
||||
);
|
||||
pages: authOptions.pages,
|
||||
callbacks: {
|
||||
authorized({ req, token }) {
|
||||
if (token && token.apiSession.exp * 1000 > Date.now()) {
|
||||
return true;
|
||||
}
|
||||
const pathname = req.nextUrl.pathname;
|
||||
return (
|
||||
pathname.startsWith("/_next/") ||
|
||||
pathname.startsWith("/favicon.ico") ||
|
||||
pathname.startsWith("/assets/")
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const value = {
|
||||
token: {
|
||||
name: "dqnid",
|
||||
picture: "https://picsum.photos/200/300",
|
||||
sub: "dqnid",
|
||||
user: {
|
||||
id: "dqnid",
|
||||
roles: ["user", "manager", "admin"],
|
||||
image: "https://picsum.photos/200/300",
|
||||
name: "dqnid",
|
||||
token: {
|
||||
name: "dqnid",
|
||||
picture: "https://picsum.photos/200/300",
|
||||
sub: "dqnid",
|
||||
user: {
|
||||
id: "dqnid",
|
||||
roles: ["user", "manager", "admin"],
|
||||
image: "https://picsum.photos/200/300",
|
||||
name: "dqnid",
|
||||
},
|
||||
apiSession: {
|
||||
exp: 1725398177,
|
||||
},
|
||||
iat: 1725394577,
|
||||
exp: 1727986577,
|
||||
jti: "3203d3c7-dc27-4599-b37e-16737b3a6674",
|
||||
},
|
||||
apiSession: {
|
||||
exp: 1725398177,
|
||||
},
|
||||
iat: 1725394577,
|
||||
exp: 1727986577,
|
||||
jti: "3203d3c7-dc27-4599-b37e-16737b3a6674",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,179 +1,179 @@
|
||||
.container {
|
||||
max-width: 50em;
|
||||
height: fit-content;
|
||||
max-width: 50em;
|
||||
height: fit-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
font-size: 1.4rem;
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 0.4rem;
|
||||
border: 2px solid rgba(var(--color-grey-90-rgb), 0.4);
|
||||
background-color: var(--color-white);
|
||||
|
||||
.side__illustration {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
max-width: 16em;
|
||||
box-sizing: content-box;
|
||||
opacity: 0.8;
|
||||
flex-direction: row;
|
||||
|
||||
text-shadow: rgba(var(--color-grey-10), 0.3);
|
||||
padding: 2rem 2rem;
|
||||
font-size: 1.4rem;
|
||||
overflow: hidden;
|
||||
|
||||
--s: 100px;
|
||||
--c1: rgba(var(--color-grey-70-rgb), 0.4);
|
||||
--c2: var(--color-white);
|
||||
border-radius: 0.4rem;
|
||||
border: 2px solid rgba(var(--color-grey-90-rgb), 0.4);
|
||||
background-color: var(--color-white);
|
||||
|
||||
--_g: #0000 90deg, var(--c1) 0;
|
||||
background: conic-gradient(from 90deg at 2px 2px, var(--_g)),
|
||||
conic-gradient(from 90deg at 1px 1px, var(--_g)), var(--c2);
|
||||
background-size:
|
||||
var(--s) var(--s),
|
||||
calc(var(--s) / 5) calc(var(--s) / 5);
|
||||
background-position: -2px -2px;
|
||||
.side__illustration {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
max-width: 16em;
|
||||
box-sizing: content-box;
|
||||
opacity: 0.8;
|
||||
|
||||
& > h1 {
|
||||
font-size: 3.4rem;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
text-shadow: rgba(var(--color-grey-10), 0.3);
|
||||
padding: 2rem 2rem;
|
||||
|
||||
padding-right: 2rem;
|
||||
--s: 100px;
|
||||
--c1: rgba(var(--color-grey-70-rgb), 0.4);
|
||||
--c2: var(--color-white);
|
||||
|
||||
& > span {
|
||||
position: relative;
|
||||
--_g: #0000 90deg, var(--c1) 0;
|
||||
background: conic-gradient(from 90deg at 2px 2px, var(--_g)),
|
||||
conic-gradient(from 90deg at 1px 1px, var(--_g)), var(--c2);
|
||||
background-size:
|
||||
var(--s) var(--s),
|
||||
calc(var(--s) / 5) calc(var(--s) / 5);
|
||||
background-position: -2px -2px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 40%;
|
||||
width: 110%;
|
||||
top: 70%;
|
||||
left: -5%;
|
||||
& > h1 {
|
||||
font-size: 3.4rem;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
|
||||
z-index: -1;
|
||||
padding-right: 2rem;
|
||||
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
& > span {
|
||||
position: relative;
|
||||
|
||||
& > h4 {
|
||||
font-size: 1.4rem;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 40%;
|
||||
width: 110%;
|
||||
top: 70%;
|
||||
left: -5%;
|
||||
|
||||
text-align: right;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
z-index: -1;
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
padding: 3.5em 2.5em;
|
||||
|
||||
background: rgba(var(--color-grey-90-rgb), 0.4);
|
||||
|
||||
&__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-left: 0.8em;
|
||||
|
||||
color: rgba(var(--color-grey-60-rgb), 0.8);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
&__input {
|
||||
padding: 0.8em;
|
||||
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--color-grey-70);
|
||||
border-radius: 2px;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
opacity: 1;
|
||||
color: rgba(var(--color-grey-60-rgb), 0.8);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-bottom: 2px solid var(--color-primary);
|
||||
box-shadow: 0 1px 4px rgba(var(--color-primary-dark-rgb), 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&__group:has(.form__input:placeholder-shown) .form__label {
|
||||
opacity: 0;
|
||||
transform: translateY(1rem);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
& .submit__button {
|
||||
all: unset;
|
||||
width: fit-content;
|
||||
font-size: 1.4rem;
|
||||
text-transform: uppercase;
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 2px;
|
||||
|
||||
align-self: center;
|
||||
|
||||
border: none;
|
||||
color: var(--color-black);
|
||||
border: 2px solid var(--color-primary);
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-white);
|
||||
background-color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 6px rgba(var(--color-primary-dark-rgb), 0.4);
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
& > h4 {
|
||||
font-size: 1.4rem;
|
||||
|
||||
text-align: right;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
opacity: 0;
|
||||
height: 2.5rem;
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
|
||||
align-self: flex-start;
|
||||
padding: 3.5em 2.5em;
|
||||
|
||||
transition: opacity 0.3s ease-in;
|
||||
background: rgba(var(--color-grey-90-rgb), 0.4);
|
||||
|
||||
&--wrong {
|
||||
color: var(--color-error);
|
||||
}
|
||||
&__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-left: 0.8em;
|
||||
|
||||
color: rgba(var(--color-grey-60-rgb), 0.8);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
&__input {
|
||||
padding: 0.8em;
|
||||
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--color-grey-70);
|
||||
border-radius: 2px;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
opacity: 1;
|
||||
color: rgba(var(--color-grey-60-rgb), 0.8);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-bottom: 2px solid var(--color-primary);
|
||||
box-shadow: 0 1px 4px rgba(var(--color-primary-dark-rgb), 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&__group:has(.form__input:placeholder-shown) .form__label {
|
||||
opacity: 0;
|
||||
transform: translateY(1rem);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
& .submit__button {
|
||||
all: unset;
|
||||
width: fit-content;
|
||||
font-size: 1.4rem;
|
||||
text-transform: uppercase;
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 2px;
|
||||
|
||||
align-self: center;
|
||||
|
||||
border: none;
|
||||
color: var(--color-black);
|
||||
border: 2px solid var(--color-primary);
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-white);
|
||||
background-color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 6px rgba(var(--color-primary-dark-rgb), 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
opacity: 0;
|
||||
height: 2.5rem;
|
||||
|
||||
align-self: flex-start;
|
||||
|
||||
transition: opacity 0.3s ease-in;
|
||||
|
||||
&--wrong {
|
||||
color: var(--color-error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.form--error {
|
||||
.form__input {
|
||||
border-bottom: 2px solid var(--color-error);
|
||||
}
|
||||
.form__input {
|
||||
border-bottom: 2px solid var(--color-error);
|
||||
}
|
||||
|
||||
.form__message {
|
||||
opacity: 1;
|
||||
}
|
||||
.form__message {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,101 +7,101 @@ import { signIn, useSession } from "next-auth/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
type LogInWidgetsProps = {
|
||||
afterSuccess?: Function;
|
||||
afterSuccess?: Function;
|
||||
};
|
||||
|
||||
export const LogInWidget: React.FC<LogInWidgetsProps> = ({ afterSuccess }) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const callbackUrl = searchParams.get("callbackUrl");
|
||||
const callbackUrl = searchParams.get("callbackUrl");
|
||||
|
||||
const [loginStatus, setLoginStatus] = useState<
|
||||
"idle" | "check" | "confirm" | "error"
|
||||
>("idle");
|
||||
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 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,
|
||||
});
|
||||
const result = await signIn("credentials", {
|
||||
redirect: false,
|
||||
username: username,
|
||||
password: password,
|
||||
});
|
||||
|
||||
if (!result?.ok) {
|
||||
setLoginStatus("error");
|
||||
} else {
|
||||
setLoginStatus("confirm");
|
||||
}
|
||||
};
|
||||
if (!result?.ok) {
|
||||
setLoginStatus("error");
|
||||
} else {
|
||||
setLoginStatus("confirm");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (loginStatus === "confirm") {
|
||||
router.push(callbackUrl ?? "/");
|
||||
}
|
||||
}, [loginStatus]);
|
||||
useEffect(() => {
|
||||
if (loginStatus === "confirm") {
|
||||
router.push(callbackUrl ?? "/");
|
||||
}
|
||||
}, [loginStatus]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles["side__illustration"]}>
|
||||
<h1>
|
||||
Welcome to this <span>page!</span>
|
||||
</h1>
|
||||
<h4>Use your credentials to log-in</h4>
|
||||
</div>
|
||||
<form
|
||||
className={`${styles.form} ${loginStatus === "error" && styles["form--error"]} ${loginStatus === "check" && styles["form--loading"]}`}
|
||||
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
|
||||
/>
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles["side__illustration"]}>
|
||||
<h1>
|
||||
Welcome to this <span>page!</span>
|
||||
</h1>
|
||||
<h4>Use your credentials to log-in</h4>
|
||||
</div>
|
||||
<form
|
||||
className={`${styles.form} ${loginStatus === "error" && styles["form--error"]} ${loginStatus === "check" && styles["form--loading"]}`}
|
||||
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"]}>
|
||||
<div className={styles["form__message"]}>
|
||||
{loginStatus === "error" ? (
|
||||
<p className={styles["form__message--wrong"]}>
|
||||
Wrong credentials, try again
|
||||
</p>
|
||||
) : (
|
||||
<>Loading...</>
|
||||
)}
|
||||
</div>
|
||||
<button type="submit" className={styles["submit__button"]}>
|
||||
Log-in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</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"]}>
|
||||
<div className={styles["form__message"]}>
|
||||
{loginStatus === "error" ? (
|
||||
<p className={styles["form__message--wrong"]}>
|
||||
Wrong credentials, try again
|
||||
</p>
|
||||
) : (
|
||||
<>Loading...</>
|
||||
)}
|
||||
</div>
|
||||
<button type="submit" className={styles["submit__button"]}>
|
||||
Log-in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,9 +2,9 @@ import { signIn } from "next-auth/react";
|
||||
import styles from "./sign-in-button.module.scss";
|
||||
|
||||
export const SignInButton = () => {
|
||||
return (
|
||||
<button className={styles.button} onClick={() => void signIn()}>
|
||||
Sign in
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button className={styles.button} onClick={() => void signIn()}>
|
||||
Sign in
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,12 @@ import { signOut } from "next-auth/react";
|
||||
import styles from "./sign-out-button.module.scss";
|
||||
|
||||
export const SignOutButton = () => {
|
||||
return (
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={() => void signOut({ callbackUrl: "/" })}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={() => void signOut({ callbackUrl: "/" })}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,14 +3,14 @@ import UserPanel from "../user-panel";
|
||||
import { UserFrame } from "../user-frame";
|
||||
|
||||
export const UserDropdown = async () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles["avatar__container"]}>
|
||||
<UserFrame />
|
||||
</div>
|
||||
<div className={styles["dropdown__container"]}>
|
||||
<UserPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles["avatar__container"]}>
|
||||
<UserFrame />
|
||||
</div>
|
||||
<div className={styles["dropdown__container"]}>
|
||||
<UserPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
.container {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
.avatar__container {
|
||||
height: var(--header-item-height, 4rem);
|
||||
width: var(--header-item-height, 4rem);
|
||||
.avatar__container {
|
||||
height: var(--header-item-height, 4rem);
|
||||
width: var(--header-item-height, 4rem);
|
||||
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
font-size: 3.2rem;
|
||||
}
|
||||
|
||||
.dropdown__container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
.dropdown__container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
|
||||
transition: all 0.2s;
|
||||
}
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
&:hover > .dropdown__container {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
&:hover > .dropdown__container {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,39 +5,39 @@ import { getServerSession } from "next-auth";
|
||||
import SignInButton from "../sign-in-button";
|
||||
|
||||
export async function UserFrame() {
|
||||
const session = await getServerSession();
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session) {
|
||||
return <SignInButton />;
|
||||
}
|
||||
if (!session) {
|
||||
return <SignInButton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.frame}>
|
||||
<span className={styles.initials}>
|
||||
{getInitialsFromName(session?.user?.name || "00")}
|
||||
</span>
|
||||
<Image
|
||||
className={styles["profile__picture"]}
|
||||
src={session?.user?.image || ""}
|
||||
alt="user profile picture"
|
||||
fill={true}
|
||||
sizes="(max-width: 768px) 10rem, 6rem"
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={styles.frame}>
|
||||
<span className={styles.initials}>
|
||||
{getInitialsFromName(session?.user?.name || "00")}
|
||||
</span>
|
||||
<Image
|
||||
className={styles["profile__picture"]}
|
||||
src={session?.user?.image || ""}
|
||||
alt="user profile picture"
|
||||
fill={true}
|
||||
sizes="(max-width: 768px) 10rem, 6rem"
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getInitialsFromName = (name: string): string => {
|
||||
if (name.length < 1) return "00";
|
||||
if (name.length < 1) return "00";
|
||||
|
||||
// TODO: this can be done in one line probably
|
||||
const words = name.split(" ");
|
||||
let initials = "";
|
||||
words.forEach((word) => (initials += word[0]));
|
||||
if (initials.length < 2) {
|
||||
initials += name[1];
|
||||
}
|
||||
// TODO: this can be done in one line probably
|
||||
const words = name.split(" ");
|
||||
let initials = "";
|
||||
words.forEach((word) => (initials += word[0]));
|
||||
if (initials.length < 2) {
|
||||
initials += name[1];
|
||||
}
|
||||
|
||||
return initials.slice(0, 2);
|
||||
return initials.slice(0, 2);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
.frame {
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
box-shadow: 1px 1px 3px var(--color-grey-30);
|
||||
box-sizing: border-box;
|
||||
|
||||
& > span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-grey-30);
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
box-shadow: 1px 1px 3px var(--color-grey-30);
|
||||
box-sizing: border-box;
|
||||
|
||||
& > span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
font-size: inherit;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-grey-30);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,20 @@ import { useSession } from "next-auth/react";
|
||||
import ThemeSwitcher from "@/modules/common/components/theme-switcher";
|
||||
|
||||
export function UserPanel() {
|
||||
const session = useSession();
|
||||
const session = useSession();
|
||||
|
||||
return (
|
||||
<ul className={styles.frame}>
|
||||
<li className={styles["user__name"]}>{session.data?.user?.name}</li>
|
||||
<ul className={styles["user__roles"]}>
|
||||
{session.data?.user?.roles.map((role) => (
|
||||
<li key={role} className={styles["role__chip"]}>
|
||||
{role}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ThemeSwitcher />
|
||||
<SignOutButton />
|
||||
</ul>
|
||||
);
|
||||
return (
|
||||
<ul className={styles.frame}>
|
||||
<li className={styles["user__name"]}>{session.data?.user?.name}</li>
|
||||
<ul className={styles["user__roles"]}>
|
||||
{session.data?.user?.roles.map((role) => (
|
||||
<li key={role} className={styles["role__chip"]}>
|
||||
{role}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ThemeSwitcher />
|
||||
<SignOutButton />
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
.frame {
|
||||
list-style: none;
|
||||
min-width: 15rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.8rem 0.6rem;
|
||||
list-style: none;
|
||||
min-width: 15rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.8rem 0.6rem;
|
||||
|
||||
padding: 1.2rem 1.8rem;
|
||||
padding: 1.2rem 1.8rem;
|
||||
|
||||
border-radius: 6px;
|
||||
background-color: var(--color-white);
|
||||
box-shadow: 0 2px 3px rgba(var(--color-grey-10-rgb), 0.2);
|
||||
color: var(--color-background);
|
||||
border-radius: 6px;
|
||||
background-color: var(--color-white);
|
||||
box-shadow: 0 2px 3px rgba(var(--color-grey-10-rgb), 0.2);
|
||||
color: var(--color-background);
|
||||
|
||||
z-index: 10;
|
||||
z-index: 10;
|
||||
|
||||
& .user {
|
||||
font-size: 1.6rem;
|
||||
& .user {
|
||||
font-size: 1.6rem;
|
||||
|
||||
&__name {
|
||||
&__name {
|
||||
}
|
||||
|
||||
&__roles {
|
||||
list-style: none;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
|
||||
& .role__chip {
|
||||
padding: 0.4rem 0.6rem;
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-grey-90);
|
||||
color: var(--color-grey-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__roles {
|
||||
list-style: none;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
|
||||
& .role__chip {
|
||||
padding: 0.4rem 0.6rem;
|
||||
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-grey-90);
|
||||
color: var(--color-grey-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,88 +2,92 @@ import { AuthOptions, Role, User } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
|
||||
export const authOptions: AuthOptions = {
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
pages: {
|
||||
signIn: "/auth/log-in",
|
||||
signOut: "/",
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: "Sign in",
|
||||
credentials: {
|
||||
username: { label: "Username", type: "text", placeholder: "yourself" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
const response = await fetch(
|
||||
process.env.NEXT_PUBLIC_AUTH_URL + "/auth/login",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
pages: {
|
||||
signIn: "/auth/log-in",
|
||||
signOut: "/",
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: "Sign in",
|
||||
credentials: {
|
||||
username: {
|
||||
label: "Username",
|
||||
type: "text",
|
||||
placeholder: "yourself",
|
||||
},
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: credentials?.username,
|
||||
password: credentials?.password,
|
||||
}),
|
||||
},
|
||||
);
|
||||
async authorize(credentials, req) {
|
||||
const response = await fetch(
|
||||
process.env.NEXT_PUBLIC_AUTH_URL + "/auth/login",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: credentials?.username,
|
||||
password: credentials?.password,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
type LoginResponse = {
|
||||
access_token: string;
|
||||
};
|
||||
type LoginResponse = {
|
||||
access_token: string;
|
||||
};
|
||||
|
||||
if (response.status < 200 || response.status > 399) return null;
|
||||
if (response.status < 200 || response.status > 399) return null;
|
||||
|
||||
const response_body = (await response.json()) as LoginResponse;
|
||||
const response_body = (await response.json()) as LoginResponse;
|
||||
|
||||
type TokenPayload = {
|
||||
sub: string;
|
||||
username: string;
|
||||
roles: Role[];
|
||||
picture: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
};
|
||||
type TokenPayload = {
|
||||
sub: string;
|
||||
username: string;
|
||||
roles: Role[];
|
||||
picture: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
};
|
||||
|
||||
const token_payload = JSON.parse(
|
||||
atob(response_body.access_token.split(".")[1]),
|
||||
) as TokenPayload;
|
||||
const token_payload = JSON.parse(
|
||||
atob(response_body.access_token.split(".")[1]),
|
||||
) as TokenPayload;
|
||||
|
||||
const user: User = {
|
||||
id: token_payload.username,
|
||||
roles: token_payload.roles,
|
||||
image: token_payload.picture,
|
||||
name: token_payload.username,
|
||||
apiSession: {
|
||||
exp: token_payload.exp,
|
||||
accessToken: response_body.access_token,
|
||||
},
|
||||
};
|
||||
const user: User = {
|
||||
id: token_payload.username,
|
||||
roles: token_payload.roles,
|
||||
image: token_payload.picture,
|
||||
name: token_payload.username,
|
||||
apiSession: {
|
||||
exp: token_payload.exp,
|
||||
accessToken: response_body.access_token,
|
||||
},
|
||||
};
|
||||
|
||||
return user;
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
// Persist the OAuth access_token to the token right after signin
|
||||
if (user) {
|
||||
const { apiSession, ...cleanedUser } = user;
|
||||
token.user = cleanedUser;
|
||||
token.apiSession = apiSession;
|
||||
}
|
||||
return token;
|
||||
return user;
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
// Persist the OAuth access_token to the token right after signin
|
||||
if (user) {
|
||||
const { apiSession, ...cleanedUser } = user;
|
||||
token.user = cleanedUser;
|
||||
token.apiSession = apiSession;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
// Send properties to the client, like an access_token from a provider.
|
||||
if (token?.user && session) {
|
||||
session.apiSession = token.apiSession;
|
||||
session.user = token.user;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
async session({ session, token }) {
|
||||
// Send properties to the client, like an access_token from a provider.
|
||||
if (token?.user && session) {
|
||||
session.apiSession = token.apiSession;
|
||||
session.user = token.user;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,5 +4,5 @@ import { SessionProvider } from "next-auth/react";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
return <SessionProvider>{children}</SessionProvider>;
|
||||
};
|
||||
|
||||
42
front/src/modules/auth/types/next-auth.d.ts
vendored
42
front/src/modules/auth/types/next-auth.d.ts
vendored
@@ -2,29 +2,29 @@ import NextAuth, { DefaultSession } from "next-auth";
|
||||
import { JWT, DefaultJWT } from "next-auth/jwt";
|
||||
|
||||
declare module "next-auth" {
|
||||
type Role = "user" | "manager" | "admin";
|
||||
interface ApiSession {
|
||||
exp?: number;
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
}
|
||||
interface User {
|
||||
id: string;
|
||||
roles: Role[];
|
||||
image?: string;
|
||||
name?: string;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
type Role = "user" | "manager" | "admin";
|
||||
interface ApiSession {
|
||||
exp?: number;
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
}
|
||||
interface User {
|
||||
id: string;
|
||||
roles: Role[];
|
||||
image?: string;
|
||||
name?: string;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
|
||||
interface Session extends DefaultSession {
|
||||
user?: Omit<User, "apiSession">;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
interface Session extends DefaultSession {
|
||||
user?: Omit<User, "apiSession">;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
user?: Omit<User, "apiSession">;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
interface JWT {
|
||||
user?: Omit<User, "apiSession">;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
min-height: 100vh;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import styles from "./log-in.module.scss";
|
||||
type LogInWidgetsProps = {};
|
||||
|
||||
export const LogInView: React.FC<LogInWidgetsProps> = ({}) => {
|
||||
return (
|
||||
<Suspense>
|
||||
<div className={styles.container}>
|
||||
<LogInWidget />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
return (
|
||||
<Suspense>
|
||||
<div className={styles.container}>
|
||||
<LogInWidget />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,13 +7,13 @@ import { PropsWithChildren } from "react";
|
||||
* Receives the user component as a children in order to fetch it from the server and allow quick session information fetch
|
||||
* */
|
||||
export const NavbarHeader = (props: PropsWithChildren) => {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Link href={"/"} className={styles.logo}>
|
||||
<img src="/main-logo.svg" alt="Main logo" />
|
||||
<h1>Your brand</h1>
|
||||
</Link>
|
||||
<nav>{props.children}</nav>
|
||||
</header>
|
||||
);
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Link href={"/"} className={styles.logo}>
|
||||
<img src="/main-logo.svg" alt="Main logo" />
|
||||
<h1>Your brand</h1>
|
||||
</Link>
|
||||
<nav>{props.children}</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 1.2rem;
|
||||
left: 0;
|
||||
position: sticky;
|
||||
top: 1.2rem;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
margin: 1.2rem;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
|
||||
padding: 0.6rem 2rem;
|
||||
background-color: var(--color-white);
|
||||
box-shadow: 0 1px 3px rgba(var(--color-grey-10-rgb), 0.3);
|
||||
color: var(--color-background);
|
||||
|
||||
--header-item-height: 4.5rem;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
font-size: 2.6rem;
|
||||
font-weight: bold;
|
||||
margin: 1.2rem;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
|
||||
height: var(--header-item-height);
|
||||
width: auto;
|
||||
padding: 0.6rem 2rem;
|
||||
background-color: var(--color-white);
|
||||
box-shadow: 0 1px 3px rgba(var(--color-grey-10-rgb), 0.3);
|
||||
color: var(--color-background);
|
||||
|
||||
& > img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
--header-item-height: 4.5rem;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
|
||||
font-size: 2.6rem;
|
||||
font-weight: bold;
|
||||
|
||||
height: var(--header-item-height);
|
||||
width: auto;
|
||||
|
||||
& > img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
& > h1 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
& > h1 {
|
||||
font-size: 1.4rem;
|
||||
& > nav {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
& > nav {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ import React from "react";
|
||||
|
||||
// TODO: make it consistent and link current data-theme with checked state fo the checkbox, maybe with a react state
|
||||
export const ThemeSwitcher = () => {
|
||||
const changeHandler = () => {
|
||||
const isDarkTheme =
|
||||
document.documentElement.getAttribute("data-theme") === "dark";
|
||||
document.documentElement.setAttribute(
|
||||
"data-theme",
|
||||
isDarkTheme ? "light" : "dark",
|
||||
);
|
||||
};
|
||||
const changeHandler = () => {
|
||||
const isDarkTheme =
|
||||
document.documentElement.getAttribute("data-theme") === "dark";
|
||||
document.documentElement.setAttribute(
|
||||
"data-theme",
|
||||
isDarkTheme ? "light" : "dark",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<label className="switch">
|
||||
<input type="checkbox" onClick={changeHandler} />
|
||||
<span className="slider round"></span>
|
||||
</label>
|
||||
);
|
||||
return (
|
||||
<label className="switch">
|
||||
<input type="checkbox" onClick={changeHandler} />
|
||||
<span className="slider round"></span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,51 +3,51 @@ import { timedFetch } from "../../utils/timedFetch";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
type QueryReturn<T> = {
|
||||
data?: T;
|
||||
status?: number;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
data?: T;
|
||||
status?: number;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
};
|
||||
|
||||
type QueryInput = {
|
||||
url: string;
|
||||
options: RequestInit;
|
||||
timeout?: number;
|
||||
url: string;
|
||||
options: RequestInit;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export function useQuery<DataType>({
|
||||
url,
|
||||
options,
|
||||
timeout,
|
||||
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);
|
||||
const [response, setResponse] = useState<{
|
||||
data: DataType;
|
||||
status: number;
|
||||
}>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
|
||||
const session = useSession();
|
||||
const token = session.data?.apiSession?.accessToken;
|
||||
if (token) {
|
||||
options.headers = { ...options.headers, Authorization: "Bearer " + token };
|
||||
}
|
||||
const session = useSession();
|
||||
const token = session.data?.apiSession?.accessToken;
|
||||
if (token) {
|
||||
options.headers = { ...options.headers, Authorization: "Bearer " + token };
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setIsError(false);
|
||||
(async () => {
|
||||
if (session.status !== "loading") {
|
||||
const _response = await timedFetch<DataType>(url, options, timeout);
|
||||
if (_response) {
|
||||
setResponse(_response);
|
||||
} else {
|
||||
setIsError(true);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [url, options, timeout, session.status]);
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setIsError(false);
|
||||
(async () => {
|
||||
if (session.status !== "loading") {
|
||||
const _response = await timedFetch<DataType>(url, options, timeout);
|
||||
if (_response) {
|
||||
setResponse(_response);
|
||||
} else {
|
||||
setIsError(true);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [url, options, timeout, session.status]);
|
||||
|
||||
return { ...response, isLoading, isError };
|
||||
return { ...response, isLoading, isError };
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import NavbarHeader from "../../components/navbar-header";
|
||||
import UserDropdown from "@/modules/auth/components/user-dropdown";
|
||||
|
||||
export const HomeLayout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<NavbarHeader>
|
||||
<UserDropdown />
|
||||
</NavbarHeader>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<NavbarHeader>
|
||||
<UserDropdown />
|
||||
</NavbarHeader>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
@@ -3,21 +3,21 @@ import "@/styles/main.scss";
|
||||
import { ApplicationProvider } from "../../providers/application.provider";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Full stack archetype",
|
||||
description:
|
||||
"This is a full stack archetype supported in NextJS, NestJS and mysql",
|
||||
title: "Full stack archetype",
|
||||
description:
|
||||
"This is a full stack archetype supported in NextJS, NestJS and mysql",
|
||||
};
|
||||
|
||||
export function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<ApplicationProvider>{children}</ApplicationProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<ApplicationProvider>{children}</ApplicationProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import { AuthProvider } from "@/modules/auth/providers/auth.provider";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
export const ApplicationProvider: React.FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
return <AuthProvider>{children}</AuthProvider>;
|
||||
export const ApplicationProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return <AuthProvider>{children}</AuthProvider>;
|
||||
};
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
class HttpError extends Error {
|
||||
constructor(public response: Response) {
|
||||
super(`HTTP error ${response.status}`);
|
||||
}
|
||||
constructor(public response: Response) {
|
||||
super(`HTTP error ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function timedFetch<ResponseType = any>(
|
||||
url: string,
|
||||
options: RequestInit = {},
|
||||
timeout: number = 5000,
|
||||
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);
|
||||
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)`);
|
||||
}
|
||||
}
|
||||
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 };
|
||||
|
||||
@@ -3,44 +3,44 @@ import styles from "./example-detail.module.scss";
|
||||
import Image from "next/image";
|
||||
|
||||
type ExampleDetailProps = {
|
||||
exampleId: number;
|
||||
exampleId: number;
|
||||
};
|
||||
|
||||
type ExampleDetailType = {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
image: string;
|
||||
created_at: string;
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
image: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export const ExampleDetail: React.FC<ExampleDetailProps> = ({ exampleId }) => {
|
||||
const result = useQuery<ExampleDetailType>({
|
||||
url: `${process.env.NEXT_PUBLIC_BACKEND_URL}/example/${exampleId}`,
|
||||
options: { headers: {} },
|
||||
timeout: 4000,
|
||||
});
|
||||
const result = useQuery<ExampleDetailType>({
|
||||
url: `${process.env.NEXT_PUBLIC_BACKEND_URL}/example/${exampleId}`,
|
||||
options: { headers: {} },
|
||||
timeout: 4000,
|
||||
});
|
||||
|
||||
if (result.isLoading) {
|
||||
return <>Loading...</>;
|
||||
}
|
||||
if (result.isLoading) {
|
||||
return <>Loading...</>;
|
||||
}
|
||||
|
||||
if (result.isError) {
|
||||
return <>Error</>;
|
||||
}
|
||||
if (result.isError) {
|
||||
return <>Error</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="example-detail" className={styles.container}>
|
||||
<div className={styles["example__card"]}>
|
||||
<Image
|
||||
src={result.data?.image ?? ""}
|
||||
alt="picture"
|
||||
width={130}
|
||||
height={130}
|
||||
/>
|
||||
<h3>{result.data?.name}</h3>
|
||||
<p>{result.data?.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div data-testid="example-detail" className={styles.container}>
|
||||
<div className={styles["example__card"]}>
|
||||
<Image
|
||||
src={result.data?.image ?? ""}
|
||||
alt="picture"
|
||||
width={130}
|
||||
height={130}
|
||||
/>
|
||||
<h3>{result.data?.name}</h3>
|
||||
<p>{result.data?.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
& .example__card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
|
||||
max-width: 250px;
|
||||
& .example__card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
|
||||
padding: 2.5rem;
|
||||
max-width: 250px;
|
||||
|
||||
border-radius: 4px;
|
||||
padding: 2.5rem;
|
||||
|
||||
background-color: var(--color-grey-90);
|
||||
box-shadow: 0px 2px 5px rgba(var(--color-black-rgb), 0.2);
|
||||
border-radius: 4px;
|
||||
|
||||
& img {
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 2px 5px rgba(var(--color-black-rgb), 0.2);
|
||||
background-color: var(--color-grey-90);
|
||||
box-shadow: 0px 2px 5px rgba(var(--color-black-rgb), 0.2);
|
||||
|
||||
& img {
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 2px 5px rgba(var(--color-black-rgb), 0.2);
|
||||
}
|
||||
|
||||
& h3 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
& p {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
& h3 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
& p {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
export { ExampleDetail } from "./example-detail.component";
|
||||
|
||||
@@ -6,52 +6,52 @@ import Link from "next/link";
|
||||
type ExampleListProps = {};
|
||||
|
||||
type ExampleListType = {
|
||||
results: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
image: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
count: number;
|
||||
results: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
image: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
count: number;
|
||||
};
|
||||
|
||||
export const ExampleList: React.FC<ExampleListProps> = ({}) => {
|
||||
const result = useQuery<ExampleListType>({
|
||||
url: process.env.NEXT_PUBLIC_BACKEND_URL + "/example",
|
||||
options: { headers: {} },
|
||||
timeout: 4000,
|
||||
});
|
||||
const result = useQuery<ExampleListType>({
|
||||
url: process.env.NEXT_PUBLIC_BACKEND_URL + "/example",
|
||||
options: { headers: {} },
|
||||
timeout: 4000,
|
||||
});
|
||||
|
||||
if (result.isLoading) {
|
||||
return <>Loading...</>;
|
||||
}
|
||||
if (result.isLoading) {
|
||||
return <>Loading...</>;
|
||||
}
|
||||
|
||||
if (result.isError) {
|
||||
return <>Error</>;
|
||||
}
|
||||
if (result.isError) {
|
||||
return <>Error</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="example-list" className={styles.container}>
|
||||
<ul>
|
||||
{result.data?.results?.map((example) => {
|
||||
return (
|
||||
<Link href={`/examples/${example.id}`} key={example.id}>
|
||||
<li key={example.id}>
|
||||
<Image
|
||||
src={example.image}
|
||||
alt="picture"
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
<span>{example.name}</span>
|
||||
<span>{example.description}</span>
|
||||
</li>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div>This could be the pagination...</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div data-testid="example-list" className={styles.container}>
|
||||
<ul>
|
||||
{result.data?.results?.map((example) => {
|
||||
return (
|
||||
<Link href={`/examples/${example.id}`} key={example.id}>
|
||||
<li key={example.id}>
|
||||
<Image
|
||||
src={example.image}
|
||||
alt="picture"
|
||||
width={30}
|
||||
height={30}
|
||||
/>
|
||||
<span>{example.name}</span>
|
||||
<span>{example.description}</span>
|
||||
</li>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div>This could be the pagination...</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 1.2rem;
|
||||
padding-top: 0;
|
||||
gap: 1rem;
|
||||
|
||||
& ul {
|
||||
flex: 1;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 0.8rem;
|
||||
background-color: var(--color-grey-90);
|
||||
border-radius: 0.8rem;
|
||||
flex: 1;
|
||||
padding: 1.2rem;
|
||||
padding-top: 0;
|
||||
gap: 1rem;
|
||||
|
||||
& a > li {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
padding: 0.3rem 1.4rem;
|
||||
width: 100%;
|
||||
background-color: var(--color-white);
|
||||
border-radius: 0.8rem;
|
||||
& ul {
|
||||
flex: 1;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 0.8rem;
|
||||
background-color: var(--color-grey-90);
|
||||
border-radius: 0.8rem;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-light);
|
||||
box-shadow: 0 2px 4px rgba(var(--color-black-rgb), 0.2);
|
||||
}
|
||||
& a > li {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
padding: 0.3rem 1.4rem;
|
||||
width: 100%;
|
||||
background-color: var(--color-white);
|
||||
border-radius: 0.8rem;
|
||||
|
||||
& > img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-primary-light);
|
||||
box-shadow: 0 2px 4px rgba(var(--color-black-rgb), 0.2);
|
||||
}
|
||||
|
||||
& > img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
export { ExampleList } from "./example-list.component";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -2,18 +2,16 @@ import { ExampleDetail } from "../../components/example-detail";
|
||||
import styles from "./example-detail.module.scss";
|
||||
|
||||
type ExampleDetailViewProps = {
|
||||
// query parameters
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
// url parameters
|
||||
params: { exampleId: string };
|
||||
// query parameters
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
// url parameters
|
||||
params: { exampleId: string };
|
||||
};
|
||||
|
||||
export const ExampleDetailView: React.FC<ExampleDetailViewProps> = ({
|
||||
params,
|
||||
}) => {
|
||||
return (
|
||||
<div data-testid="example-detail-view" className={styles.container}>
|
||||
<ExampleDetail exampleId={parseInt(params.exampleId)} />
|
||||
</div>
|
||||
);
|
||||
export const ExampleDetailView: React.FC<ExampleDetailViewProps> = ({ params }) => {
|
||||
return (
|
||||
<div data-testid="example-detail-view" className={styles.container}>
|
||||
<ExampleDetail exampleId={parseInt(params.exampleId)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { ExampleList } from "../../components/example-list";
|
||||
type ExampleListProps = {};
|
||||
|
||||
export const ExampleListView: React.FC<ExampleListProps> = ({}) => {
|
||||
return (
|
||||
<div data-testid="{example-list}" className={styles.container}>
|
||||
<ExampleList />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div data-testid="{example-list}" className={styles.container}>
|
||||
<ExampleList />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,103 +2,103 @@
|
||||
|
||||
// For variable definition
|
||||
:root {
|
||||
--color-white: #fafafa;
|
||||
--color-black: #101010;
|
||||
--color-white: #fafafa;
|
||||
--color-black: #101010;
|
||||
|
||||
--color-grey-10: #171717;
|
||||
--color-grey-30: #242424;
|
||||
--color-grey-60: #363636;
|
||||
--color-grey-70: #bbbbbb;
|
||||
--color-grey-90: #e9ecef;
|
||||
--color-grey-10: #171717;
|
||||
--color-grey-30: #242424;
|
||||
--color-grey-60: #363636;
|
||||
--color-grey-70: #bbbbbb;
|
||||
--color-grey-90: #e9ecef;
|
||||
|
||||
--color-primary: #274c77;
|
||||
--color-primary-light: #a3cef1;
|
||||
--color-primary-dark: #052f5f;
|
||||
--color-primary: #274c77;
|
||||
--color-primary-light: #a3cef1;
|
||||
--color-primary-dark: #052f5f;
|
||||
|
||||
--color-error: #e63946;
|
||||
--color-error: #e63946;
|
||||
|
||||
// -------- RGB variants -------- //
|
||||
--color-white-rgb: 250, 250, 250;
|
||||
--color-black-rgb: 16, 16, 16;
|
||||
// -------- RGB variants -------- //
|
||||
--color-white-rgb: 250, 250, 250;
|
||||
--color-black-rgb: 16, 16, 16;
|
||||
|
||||
--color-grey-10-rgb: 23, 23, 23;
|
||||
--color-grey-30-rgb: 36, 36, 36;
|
||||
--color-grey-60-rgb: 54, 54, 54;
|
||||
--color-grey-70-rgb: 187, 187, 187;
|
||||
--color-grey-90-rgb: 233, 236, 239;
|
||||
--color-grey-10-rgb: 23, 23, 23;
|
||||
--color-grey-30-rgb: 36, 36, 36;
|
||||
--color-grey-60-rgb: 54, 54, 54;
|
||||
--color-grey-70-rgb: 187, 187, 187;
|
||||
--color-grey-90-rgb: 233, 236, 239;
|
||||
|
||||
--color-primary-rgb: 39, 76, 119;
|
||||
--color-primary-light-rgb: 163, 206, 241;
|
||||
--color-primary-dark-rgb: 5, 47, 95;
|
||||
--color-primary-rgb: 39, 76, 119;
|
||||
--color-primary-light-rgb: 163, 206, 241;
|
||||
--color-primary-dark-rgb: 5, 47, 95;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-white: #101010;
|
||||
--color-black: #fafafa;
|
||||
--color-white: #101010;
|
||||
--color-black: #fafafa;
|
||||
|
||||
--color-grey-10: #e9ecef;
|
||||
--color-grey-30: #bbbbbb;
|
||||
--color-grey-70: #242424;
|
||||
--color-grey-60: #363636;
|
||||
--color-grey-90: #171717;
|
||||
--color-grey-10: #e9ecef;
|
||||
--color-grey-30: #bbbbbb;
|
||||
--color-grey-70: #242424;
|
||||
--color-grey-60: #363636;
|
||||
--color-grey-90: #171717;
|
||||
|
||||
--color-primary: #274c77;
|
||||
--color-primary-light: #052f5f;
|
||||
--color-primary-dark: #a3cef1;
|
||||
--color-primary: #274c77;
|
||||
--color-primary-light: #052f5f;
|
||||
--color-primary-dark: #a3cef1;
|
||||
|
||||
// -------- RGB variants -------- //
|
||||
--color-white-rgb: 16, 16, 16;
|
||||
--color-black-rgb: 250, 250, 250;
|
||||
// -------- RGB variants -------- //
|
||||
--color-white-rgb: 16, 16, 16;
|
||||
--color-black-rgb: 250, 250, 250;
|
||||
|
||||
--color-grey-10-rgb: 233, 236, 239;
|
||||
--color-grey-30-rgb: 187, 187, 187;
|
||||
--color-grey-60-rgb: 54, 54, 54;
|
||||
--color-grey-70-rgb: 36, 36, 36;
|
||||
--color-grey-90-rgb: 23, 23, 23;
|
||||
--color-grey-10-rgb: 233, 236, 239;
|
||||
--color-grey-30-rgb: 187, 187, 187;
|
||||
--color-grey-60-rgb: 54, 54, 54;
|
||||
--color-grey-70-rgb: 36, 36, 36;
|
||||
--color-grey-90-rgb: 23, 23, 23;
|
||||
|
||||
--color-primary-rgb: 39, 76, 119;
|
||||
--color-primary-light-rgb: 5, 47, 95;
|
||||
--color-primary-dark-rgb: 163, 206, 241;
|
||||
--color-primary-rgb: 39, 76, 119;
|
||||
--color-primary-light-rgb: 5, 47, 95;
|
||||
--color-primary-dark-rgb: 163, 206, 241;
|
||||
}
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: "Roboto", sans-serif;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
font-family: "Roboto", sans-serif;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
|
||||
font-size: 62.5%; // 1rem = 10px; 10px/16px = 62.5%
|
||||
@media only screen and (min-width: 112.5em) {
|
||||
font-size: 75%;
|
||||
}
|
||||
@media only screen and (max-width: 75em) {
|
||||
font-size: 56.25%;
|
||||
}
|
||||
@media only screen and (max-width: 56.25em) {
|
||||
font-size: 50%;
|
||||
}
|
||||
font-size: 62.5%; // 1rem = 10px; 10px/16px = 62.5%
|
||||
@media only screen and (min-width: 112.5em) {
|
||||
font-size: 75%;
|
||||
}
|
||||
@media only screen and (max-width: 75em) {
|
||||
font-size: 56.25%;
|
||||
}
|
||||
@media only screen and (max-width: 56.25em) {
|
||||
font-size: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-black);
|
||||
background-color: var(--color-white);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"typeRoots": ["./src/modules/auth/types"]
|
||||
},
|
||||
"typeRoots": ["./src/modules/auth/types"]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"src/modules/**/*.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"src/modules/**/*.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user