feat(back+front): user roles expanded + frontend styling
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
NEXTAUTH_URL=http://localhost:3016
|
||||
NEXT_PUBLIC_URL=http://localhost:3016
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3016
|
||||
|
||||
# GEN AUTH_SECRET: $ openssl rand -base64 32
|
||||
NEXTAUTH_SECRET=6lHRWUvCBtqlgTWc6aFn6s6PudYjuN6oUY+RrcEntTU=
|
||||
NEXTAUTH_URL=http://localhost:3016
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import ThemeSwitcher from "@/modules/common/theme-switcher";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main
|
||||
@@ -12,7 +10,6 @@ export default function Home() {
|
||||
}}
|
||||
>
|
||||
Main page
|
||||
<ThemeSwitcher />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
border-radius: 0.4rem;
|
||||
background-image: linear-gradient(
|
||||
16deg,
|
||||
rgba($color-primary-light-rgb, 0.3),
|
||||
rgba($color-primary-light-rgb, 0.2)
|
||||
rgba($color-primary-90-rgb, 0.3),
|
||||
rgba($color-primary-90-rgb, 0.2)
|
||||
);
|
||||
|
||||
.heading {
|
||||
@@ -21,7 +21,7 @@
|
||||
text-align: center;
|
||||
background-color: #e5e5f7;
|
||||
opacity: 0.8;
|
||||
text-shadow: rgba($color-grey-dark, 0.3);
|
||||
text-shadow: rgba($color-grey-10, 0.3);
|
||||
|
||||
padding: 2rem 4rem 2rem;
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
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-grey-30 0 60deg,
|
||||
$color-grey-90 0 120deg,
|
||||
$color-white 0 180deg
|
||||
);
|
||||
background-size: var(--s) calc(var(--s) * 0.577);
|
||||
@@ -61,7 +61,7 @@
|
||||
font-size: inherit;
|
||||
margin-left: 0.8em;
|
||||
|
||||
color: $color-grey-dark;
|
||||
color: $color-grey-10;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
color: $color-foreground;
|
||||
|
||||
background: rgba($color-primary-light-rgb, 0.5);
|
||||
background: rgba($color-primary-90-rgb, 0.5);
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-radius: 2px;
|
||||
@@ -79,7 +79,7 @@
|
||||
transition: all 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
color: $color-grey-dark;
|
||||
color: $color-grey-10;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
|
||||
export { SignInButton as default } from "./sign-in-button.component";
|
||||
@@ -0,0 +1,10 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
.button {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
|
||||
export { SignOutButton as default } from "./sign-out-button.component";
|
||||
@@ -0,0 +1,10 @@
|
||||
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()}>
|
||||
Sign out
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
.button {
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
export { UserDropdown } from "./user-dropdown.component";
|
||||
export { UserDropdown as default } from "./user-dropdown.component";
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { UserFrame } from "../user-frame";
|
||||
import styles from "./user-dropdown.module.scss";
|
||||
import UserPanel from "../user-panel";
|
||||
import { UserFrame } from "../user-frame";
|
||||
|
||||
export const UserDropdown = () => {
|
||||
export const UserDropdown = async () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles["avatar__container"]}>
|
||||
<UserFrame />
|
||||
</div>
|
||||
<div></div>
|
||||
<button>Log out</button>
|
||||
<div className={styles["dropdown__container"]}>
|
||||
<UserPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
.container {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
.avatar__container {
|
||||
width: 6rem;
|
||||
height: 6rem;
|
||||
|
||||
font-size: 3rem;
|
||||
font-size: 3.4rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown__container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
&:hover > .dropdown__container {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "../../configs/auth.options";
|
||||
import Image from "next/image";
|
||||
|
||||
import styles from "./user-frame.module.scss";
|
||||
import { getServerSession } from "next-auth";
|
||||
import SignInButton from "../sign-in-button";
|
||||
|
||||
export async function UserFrame() {
|
||||
const session = await getServerSession(authOptions);
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session) {
|
||||
return <SignInButton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.frame}>
|
||||
<span className={styles.initials}>
|
||||
{getInitialsFromName(session?.user?.name || "")}
|
||||
{getInitialsFromName(session?.user?.name || "00")}
|
||||
</span>
|
||||
<Image
|
||||
className={styles["profile__picture"]}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
border: 1px solid $color-grey-medium;
|
||||
border: 1px solid $color-grey-30;
|
||||
box-sizing: border-box;
|
||||
|
||||
& > span {
|
||||
@@ -18,6 +18,6 @@
|
||||
|
||||
font-size: inherit;
|
||||
text-transform: uppercase;
|
||||
color: $color-grey-medium;
|
||||
color: $color-grey-30;
|
||||
}
|
||||
}
|
||||
|
||||
3
front/src/modules/auth/components/user-panel/index.ts
Normal file
3
front/src/modules/auth/components/user-panel/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
|
||||
export { UserPanel as default } from "./user-panel.component";
|
||||
@@ -0,0 +1,21 @@
|
||||
import styles from "./user-panel.module.scss";
|
||||
import SignOutButton from "../sign-out-button";
|
||||
import { useSession } from "next-auth/react";
|
||||
import ThemeSwitcher from "@/modules/common/components/theme-switcher";
|
||||
|
||||
export function UserPanel() {
|
||||
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 className={styles["role__chip"]}>{role}</li>
|
||||
))}
|
||||
</ul>
|
||||
<ThemeSwitcher />
|
||||
<SignOutButton />
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
.frame {
|
||||
list-style: none;
|
||||
min-width: 15rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.8rem 0.6rem;
|
||||
|
||||
padding: 1.2rem 1.8rem;
|
||||
|
||||
border-radius: 6px;
|
||||
background-color: $color-foreground;
|
||||
color: $color-background;
|
||||
|
||||
& .user {
|
||||
font-size: 1.6rem;
|
||||
|
||||
&__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: $color-grey-90;
|
||||
color: $color-grey-10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export const authOptions: AuthOptions = {
|
||||
},
|
||||
pages: {
|
||||
signIn: "/auth/log-in",
|
||||
signOut: "/",
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
@@ -39,6 +40,7 @@ export const authOptions: AuthOptions = {
|
||||
sub: string;
|
||||
username: string;
|
||||
roles: Role[];
|
||||
picture: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
};
|
||||
@@ -50,7 +52,7 @@ export const authOptions: AuthOptions = {
|
||||
const user: User = {
|
||||
id: token_payload.username,
|
||||
roles: token_payload.roles,
|
||||
image: "https://picsum.photos/200/300",
|
||||
image: token_payload.picture,
|
||||
name: token_payload.username,
|
||||
apiSession: {
|
||||
accessToken: response_body.access_token,
|
||||
|
||||
3
front/src/modules/auth/types/next-auth.d.ts
vendored
3
front/src/modules/auth/types/next-auth.d.ts
vendored
@@ -2,7 +2,7 @@ import NextAuth, { DefaultSession } from "next-auth";
|
||||
import { JWT, DefaultJWT } from "next-auth/jwt";
|
||||
|
||||
declare module "next-auth" {
|
||||
type Role = "user" | "admin";
|
||||
type Role = "user" | "manager" | "admin";
|
||||
interface ApiSession {
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
@@ -16,6 +16,7 @@ declare module "next-auth" {
|
||||
}
|
||||
|
||||
interface Session extends DefaultSession {
|
||||
user?: Omit<User, "apiSession">;
|
||||
apiSession?: ApiSession;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { UserDropdown } from "@/modules/auth/components/user-dropdown";
|
||||
|
||||
export const Header = () => {
|
||||
return <UserDropdown />;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { Header } from "./header.component";
|
||||
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
|
||||
export { NavbarHeader as default } from "./navbar-header.component";
|
||||
@@ -0,0 +1,18 @@
|
||||
import Link from "next/link";
|
||||
import styles from "./navbar-header.module.scss";
|
||||
|
||||
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}>
|
||||
LoGo
|
||||
</Link>
|
||||
<nav>{props.children}</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
.header {
|
||||
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.4rem 1.8rem;
|
||||
background-color: $color-white;
|
||||
box-shadow: 0 1px 3px rgba($color-grey-10-rgb, 0.3);
|
||||
color: $color-background;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-size: 2.6rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
import styles from "./home.module.scss";
|
||||
import { Header } from "../../components/header";
|
||||
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}>
|
||||
<Header />
|
||||
<NavbarHeader>
|
||||
<UserDropdown />
|
||||
</NavbarHeader>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
|
||||
.container {
|
||||
background-color: $color-background;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ $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-background: var(--color-grey-90);
|
||||
$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);
|
||||
$color-grey-90: var(--color-grey-90);
|
||||
$color-grey-60: var(--color-grey-60);
|
||||
$color-grey-30: var(--color-grey-30);
|
||||
$color-grey-10: var(--color-grey-10);
|
||||
$color-grey-10-rgb: var(--color-grey-10-rgb);
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
--color-white: #fafafa;
|
||||
--color-black: #101010;
|
||||
|
||||
--color-grey-dark: #495057;
|
||||
--color-grey-medium: #adb5bd;
|
||||
--color-grey-light: #dee2e6;
|
||||
--color-grey-10: #171717;
|
||||
--color-grey-30: #242424;
|
||||
--color-grey-60: #363636;
|
||||
--color-grey-90: #bbbbbb;
|
||||
--color-grey-90-rgb: 153, 153, 153;
|
||||
--color-grey-10-rgb: 23, 23, 23;
|
||||
|
||||
--color-primary: #9c89b8;
|
||||
--color-primary-light: #b8bedd;
|
||||
@@ -24,13 +27,14 @@
|
||||
--color-grey-dark: #dee2e6;
|
||||
--color-grey-medium: #adb5bd;
|
||||
--color-grey-light: #495057;
|
||||
--color-grey-dark-rgb: 222, 226, 230;
|
||||
|
||||
--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;
|
||||
--color-grey-10: #bbbbbb;
|
||||
--color-grey-30: #363636;
|
||||
--color-grey-60: #242424;
|
||||
--color-grey-90: #171717;
|
||||
--color-grey-90-rgb: 23, 23, 23;
|
||||
--color-grey-10-rgb: 153, 153, 153;
|
||||
}
|
||||
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user