feat(front): routes secured under jwt session + small user component done
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
NEXTAUTH_URL=http://localhost:3016
|
||||
# GEN AUTH_SECRET: $ openssl rand -base64 32
|
||||
NEXTAUTH_SECRET=6lHRWUvCBtqlgTWc6aFn6s6PudYjuN6oUY+RrcEntTU=
|
||||
|
||||
@@ -9,6 +9,16 @@ const nextConfig = {
|
||||
prependData: `@import "@/styles/variables";`,
|
||||
includePaths: [path.join("@", "styles")],
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "picsum.photos",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ApplicationLayout as default } from "@/modules/common/layouts/application/application.layout";
|
||||
export { HomeLayout as default } from "@/modules/common/layouts/home";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SignInWidget } from "@/modules/auth/components/sign-in/sign-in.widget";
|
||||
import ThemeSwitcher from "@/modules/common/theme-switcher";
|
||||
|
||||
export default function Home() {
|
||||
@@ -14,7 +13,6 @@ export default function Home() {
|
||||
>
|
||||
Main page
|
||||
<ThemeSwitcher />
|
||||
<SignInWidget />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
1
front/src/app/auth/log-in/page.tsx
Normal file
1
front/src/app/auth/log-in/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { LogInView as default } from "@/modules/auth/views/log-in/log-in.view";
|
||||
17
front/src/middleware.ts
Normal file
17
front/src/middleware.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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) return true;
|
||||
const pathname = req.nextUrl.pathname;
|
||||
return (
|
||||
pathname.startsWith("/_next/") ||
|
||||
pathname.startsWith("/favicon.ico") ||
|
||||
pathname.startsWith("/assets/")
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
.container {
|
||||
max-width: 20em;
|
||||
height: fit-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
import styles from "./sign-in.module.scss";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import styles from "./log-in.module.scss";
|
||||
|
||||
import { signIn, useSession } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
type SingInWidgetsProps = {
|
||||
type LogInWidgetsProps = {
|
||||
afterSuccess?: Function;
|
||||
};
|
||||
|
||||
export const SignInWidget: React.FC<SingInWidgetsProps> = ({
|
||||
afterSuccess,
|
||||
}) => {
|
||||
export const LogInWidget: React.FC<LogInWidgetsProps> = ({ afterSuccess }) => {
|
||||
const { data } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
const [loginStatus, setLoginStatus] = useState<
|
||||
"idle" | "check" | "confirm" | "error"
|
||||
@@ -41,9 +41,15 @@ export const SignInWidget: React.FC<SingInWidgetsProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (loginStatus === "confirm") {
|
||||
router.push("/");
|
||||
}
|
||||
}, [loginStatus]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles["heading"]}>Sign in</h1>
|
||||
<h1 className={styles["heading"]}>Log in</h1>
|
||||
<form className={styles.form} onSubmit={submit}>
|
||||
<div className={`${styles["form__group"]}`}>
|
||||
<label htmlFor="username" className={styles["form__label"]}>
|
||||
1
front/src/modules/auth/components/user-dropdown/index.ts
Normal file
1
front/src/modules/auth/components/user-dropdown/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { UserDropdown } from "./user-dropdown.component";
|
||||
@@ -0,0 +1,14 @@
|
||||
import { UserFrame } from "../user-frame";
|
||||
import styles from "./user-dropdown.module.scss";
|
||||
|
||||
export const UserDropdown = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles["avatar__container"]}>
|
||||
<UserFrame />
|
||||
</div>
|
||||
<div></div>
|
||||
<button>Log out</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
.container {
|
||||
.avatar__container {
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
1
front/src/modules/auth/components/user-frame/index.ts
Normal file
1
front/src/modules/auth/components/user-frame/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { UserFrame } from "./user-frame.component";
|
||||
@@ -0,0 +1,39 @@
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "../../configs/auth.options";
|
||||
import Image from "next/image";
|
||||
|
||||
import styles from "./user-frame.module.scss";
|
||||
|
||||
export async function UserFrame() {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
return (
|
||||
<div className={styles.frame}>
|
||||
<span className={styles.initials}>
|
||||
{getInitialsFromName(session?.user?.name || "")}
|
||||
</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";
|
||||
|
||||
// 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);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.frame {
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
border: 1px solid $color-grey-medium;
|
||||
box-sizing: border-box;
|
||||
|
||||
& > span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
font-size: inherit;
|
||||
text-transform: uppercase;
|
||||
color: $color-grey-medium;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export const authOptions: AuthOptions = {
|
||||
strategy: "jwt",
|
||||
},
|
||||
pages: {
|
||||
signIn: "/auth/sign-in",
|
||||
signIn: "/auth/log-in",
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
@@ -50,7 +50,7 @@ export const authOptions: AuthOptions = {
|
||||
const user: User = {
|
||||
id: token_payload.username,
|
||||
roles: token_payload.roles,
|
||||
image: "https://randomuser.me/api/portraits/women/92.jpg",
|
||||
image: "https://picsum.photos/200/300",
|
||||
name: token_payload.username,
|
||||
apiSession: {
|
||||
accessToken: response_body.access_token,
|
||||
|
||||
7
front/src/modules/auth/views/log-in/log-in.module.scss
Normal file
7
front/src/modules/auth/views/log-in/log-in.module.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
12
front/src/modules/auth/views/log-in/log-in.view.tsx
Normal file
12
front/src/modules/auth/views/log-in/log-in.view.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LogInWidget } from "../../components/log-in/log-in.widget";
|
||||
import styles from "./log-in.module.scss";
|
||||
|
||||
type LogInWidgetsProps = {};
|
||||
|
||||
export const LogInView: React.FC<LogInWidgetsProps> = ({}) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LogInWidget />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { UserDropdown } from "@/modules/auth/components/user-dropdown";
|
||||
|
||||
export const Header = () => {
|
||||
return <UserDropdown />;
|
||||
};
|
||||
1
front/src/modules/common/components/header/index.ts
Normal file
1
front/src/modules/common/components/header/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Header } from "./header.component";
|
||||
@@ -1,8 +0,0 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
import { ApplicationProvider } from "../../providers/application.provider";
|
||||
|
||||
export const ApplicationLayout: React.FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
return <ApplicationProvider>{children}</ApplicationProvider>;
|
||||
};
|
||||
13
front/src/modules/common/layouts/home/home.layout.tsx
Normal file
13
front/src/modules/common/layouts/home/home.layout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
import styles from "./home.module.scss";
|
||||
import { Header } from "../../components/header";
|
||||
|
||||
export const HomeLayout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Header />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
1
front/src/modules/common/layouts/home/home.module.scss
Normal file
1
front/src/modules/common/layouts/home/home.module.scss
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
front/src/modules/common/layouts/home/index.ts
Normal file
1
front/src/modules/common/layouts/home/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { HomeLayout } from "./home.layout";
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import "@/styles/main.scss";
|
||||
import { ApplicationProvider } from "../../providers/application.provider";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Personal finance - Home",
|
||||
description: "Manage your money blablabla",
|
||||
title: "Full stack archetype",
|
||||
description:
|
||||
"This is a full stack archetype supported in NextJS, NestJS and mysql",
|
||||
};
|
||||
|
||||
export function RootLayout({
|
||||
@@ -13,7 +15,9 @@ export function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
<body>
|
||||
<ApplicationProvider>{children}</ApplicationProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user