feat: Integrate AWS Amplify authentication, update API queries, and enhance sidebar with user details

This commit is contained in:
2024-11-19 12:04:32 +02:00
parent a06a190574
commit 782af69049
10 changed files with 4062 additions and 52 deletions

26
package-lock.json generated
View File

@@ -1,26 +0,0 @@
{
"name": "tasker",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"@types/lodash": "^4.17.13"
}
},
"node_modules/@types/lodash": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
"dev": true
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
}
}

View File

@@ -1,8 +0,0 @@
{
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"@types/lodash": "^4.17.13"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,13 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@aws-amplify/ui-react": "^6.6.0",
"@emotion/react": "^11.13.3", "@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",
"@mui/material": "^6.1.6", "@mui/material": "^6.1.6",
"@mui/x-data-grid": "^7.22.0", "@mui/x-data-grid": "^7.22.0",
"@reduxjs/toolkit": "^2.3.0", "@reduxjs/toolkit": "^2.3.0",
"aws-amplify": "^6.8.2",
"axios": "^1.7.7", "axios": "^1.7.7",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",

View File

@@ -0,0 +1,64 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from "react";
import { Authenticator } from "@aws-amplify/ui-react";
import { Amplify } from "aws-amplify";
import "@aws-amplify/ui-react/styles.css";
Amplify.configure({
Auth: {
Cognito: {
userPoolId: process.env.NEXT_PUBLIC_COGNITO_USER_POOL_ID || "",
userPoolClientId:
process.env.NEXT_PUBLIC_COGNITO_USER_POOL_CLIENT_ID || "",
},
},
});
const formFields = {
signUp: {
username: {
order: 1,
placeholder: "Choose a username",
label: "Username",
inputProps: { required: true },
},
email: {
order: 2,
placeholder: "Enter your email address",
label: "Email",
inputProps: { type: "email", required: true },
},
password: {
order: 3,
placeholder: "Enter your password",
label: "Password",
inputProps: { type: "password", required: true },
},
confirm_password: {
order: 4,
placeholder: "Confirm your password",
label: "Confirm Password",
inputProps: { type: "password", required: true },
},
},
};
const AuthProvider = ({ children }: any) => {
return (
<div>
<Authenticator formFields={formFields}>
{({ user }: any) =>
user ? (
<div>{children}</div>
) : (
<div>
<h1>Please sign in below:</h1>
</div>
)
}
</Authenticator>
</div>
);
};
export default AuthProvider;

View File

@@ -1,8 +1,11 @@
import React from "react"; import React from "react";
import { Menu, Moon, Search, Settings, Sun } from "lucide-react"; import { Menu, Moon, Search, Settings, Sun, User } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import { useAppDispatch, useAppSelector } from "@/app/redux"; import { useAppDispatch, useAppSelector } from "@/app/redux";
import { setIsDarkMode, setIsSidebarCollapsed } from "@/state"; import { setIsDarkMode, setIsSidebarCollapsed } from "@/state";
import { useGetAuthUserQuery } from "@/state/api";
import { signOut } from "aws-amplify/auth";
const Navbar = () => { const Navbar = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -11,6 +14,18 @@ const Navbar = () => {
); );
const isDarkMode = useAppSelector((state) => state.global.isDarkMode); const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const { data: currentUser } = useGetAuthUserQuery({});
const handleSignOut = async () => {
try {
await signOut();
} catch (error) {
console.error("Error signing out: ", error);
}
};
if (!currentUser) return null;
const currentUserDetails = currentUser?.userDetails;
return ( return (
<div className="flex items-center justify-between bg-white px-4 py-3 dark:bg-black"> <div className="flex items-center justify-between bg-white px-4 py-3 dark:bg-black">
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
@@ -57,7 +72,31 @@ const Navbar = () => {
> >
<Settings className="h-6 w-6 cursor-pointer dark:text-white" /> <Settings className="h-6 w-6 cursor-pointer dark:text-white" />
</Link> </Link>
<div className="ml-2 mr-2 hidden min-h-[2em] w-[0.1rem] bg-gray-200 md:inline-block"></div> <div className="ml-2 mr-5 hidden min-h-[2em] w-[0.1rem] bg-gray-200 md:inline-block"></div>
<div className="hidden items-center justify-between md:flex">
<div className="align-center flex h-9 w-9 justify-center">
{!!currentUserDetails?.profilePictureUrl ? (
<Image
src={`/${currentUserDetails?.profilePictureUrl}`}
alt={currentUserDetails?.username || "User Profile Picture"}
width={100}
height={50}
className="h-full rounded-full object-cover"
/>
) : (
<User className="h-6 w-6 cursor-pointer self-center rounded-full dark:text-white" />
)}
</div>
<span className="mx-3 text-gray-800 dark:text-white">
{currentUserDetails?.username}
</span>
<button
className="hidden rounded bg-blue-400 px-4 py-2 text-xs font-bold text-white hover:bg-blue-500 md:block"
onClick={handleSignOut}
>
Sign out
</button>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -25,7 +25,8 @@ import { usePathname } from "next/navigation";
import { setIsSidebarCollapsed } from "@/state"; import { setIsSidebarCollapsed } from "@/state";
import { useAppDispatch, useAppSelector } from "@/app/redux"; import { useAppDispatch, useAppSelector } from "@/app/redux";
import Link from "next/link"; import Link from "next/link";
import { useGetProjectsQuery } from "@/state/api"; import { useGetAuthUserQuery, useGetProjectsQuery } from "@/state/api";
import { signOut } from "aws-amplify/auth";
const Sidebar = () => { const Sidebar = () => {
const [showProjects, setShowProjects] = React.useState(true); const [showProjects, setShowProjects] = React.useState(true);
@@ -37,6 +38,17 @@ const Sidebar = () => {
(state) => state.global.isSidebarCollapsed, (state) => state.global.isSidebarCollapsed,
); );
const { data: currentUser } = useGetAuthUserQuery({});
const handleSignOut = async () => {
try {
await signOut();
} catch (error) {
console.error("Error signing out: ", error);
}
};
if (!currentUser) return null;
const currentUserDetails = currentUser?.userDetails;
return ( return (
<div <div
className={`fixed z-40 flex h-full w-64 flex-col justify-between overflow-y-auto bg-white shadow-xl transition-all duration-300 dark:bg-black ${isSidebarCollapsed ? "hidden w-0" : "w-64"}`} className={`fixed z-40 flex h-full w-64 flex-col justify-between overflow-y-auto bg-white shadow-xl transition-all duration-300 dark:bg-black ${isSidebarCollapsed ? "hidden w-0" : "w-64"}`}
@@ -141,6 +153,32 @@ const Sidebar = () => {
</> </>
)} )}
</div> </div>
<div className="z-10 mt-32 flex w-full flex-col items-center gap-4 bg-white px-8 py-4 dark:bg-black md:hidden">
<div className="flex w-full items-center">
<div className="align-center flex h-9 w-9 justify-center">
{!!currentUserDetails?.profilePictureUrl ? (
<Image
src={`/${currentUserDetails?.profilePictureUrl}`}
alt={currentUserDetails?.username || "User Profile Picture"}
width={100}
height={50}
className="h-full rounded-full object-cover"
/>
) : (
<User className="h-6 w-6 cursor-pointer self-center rounded-full dark:text-white" />
)}
</div>
<span className="mx-3 text-gray-800 dark:text-white">
{currentUserDetails?.username}
</span>
<button
className="self-start rounded bg-blue-400 px-4 py-2 text-xs font-bold text-white hover:bg-blue-500 md:block"
onClick={handleSignOut}
>
Sign out
</button>
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -4,6 +4,7 @@ import React, { useEffect } from "react";
import Navbar from "@/app/components/Navbar"; import Navbar from "@/app/components/Navbar";
import Sidebar from "@/app/components/Sidebar"; import Sidebar from "@/app/components/Sidebar";
import StoreProvider, { useAppSelector } from "./redux"; import StoreProvider, { useAppSelector } from "./redux";
import AuthProvider from "./authProvider";
const DashboardLayout = ({ children }: { children: React.ReactNode }) => { const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
const isSidebarCollapsed = useAppSelector( const isSidebarCollapsed = useAppSelector(
@@ -35,7 +36,9 @@ const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
const DashboardWrapper = ({ children }: { children: React.ReactNode }) => { const DashboardWrapper = ({ children }: { children: React.ReactNode }) => {
return ( return (
<StoreProvider> <StoreProvider>
<DashboardLayout>{children}</DashboardLayout> <AuthProvider>
<DashboardLayout>{children}</DashboardLayout>
</AuthProvider>
</StoreProvider> </StoreProvider>
); );
}; };

View File

@@ -5,7 +5,12 @@ import Header from "@/app/components/Header";
import ModalNewTask from "@/app/components/ModalNewTask"; import ModalNewTask from "@/app/components/ModalNewTask";
import TaskCard from "@/app/components/TaskCard"; import TaskCard from "@/app/components/TaskCard";
import { dataGridSxStyles } from "@/lib/utils"; import { dataGridSxStyles } from "@/lib/utils";
import { Priority, Task, useGetTasksByUserQuery } from "@/state/api"; import {
Priority,
Task,
useGetAuthUserQuery,
useGetTasksByUserQuery,
} from "@/state/api";
import { DataGrid, GridColDef } from "@mui/x-data-grid"; import { DataGrid, GridColDef } from "@mui/x-data-grid";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -72,9 +77,8 @@ const ReusablePriorityPage = ({ priority }: Props) => {
const [view, setView] = useState("list"); const [view, setView] = useState("list");
const [isModalNewTaskOpen, setIsModalNewTaskOpen] = useState(false); const [isModalNewTaskOpen, setIsModalNewTaskOpen] = useState(false);
// const { data: currentUser } = useGetAuthUserQuery({}); const { data: currentUser } = useGetAuthUserQuery({});
// const userId = currentUser?.userDetails?.userId ?? null; const userId = currentUser?.userDetails?.userId ?? null;
const userId = 1;
const { const {
data: tasks, data: tasks,
isLoading, isLoading,
@@ -83,8 +87,6 @@ const ReusablePriorityPage = ({ priority }: Props) => {
skip: userId === null, skip: userId === null,
}); });
console.log(tasks);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode); const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const filteredTasks = tasks?.filter( const filteredTasks = tasks?.filter(

View File

@@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { fetchAuthSession, getCurrentUser } from "aws-amplify/auth";
export interface Project { export interface Project {
id: number; id: number;
@@ -76,10 +78,36 @@ export interface Team {
export const api = createApi({ export const api = createApi({
baseQuery: fetchBaseQuery({ baseQuery: fetchBaseQuery({
baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL, baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
prepareHeaders: async (headers) => {
const session = await fetchAuthSession();
const { accessToken } = session.tokens ?? {};
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
}
return headers;
},
}), }),
reducerPath: "api", reducerPath: "api",
tagTypes: ["Projects", "Tasks", "Users", "Teams"], tagTypes: ["Projects", "Tasks", "Users", "Teams"],
endpoints: (build) => ({ endpoints: (build) => ({
getAuthUser: build.query({
queryFn: async (_, _queryApi, _extraoptions, fetchWithBQ) => {
try {
const user = await getCurrentUser();
const session = await fetchAuthSession();
if (!session) throw new Error("No session found");
const { userSub } = session;
const userDetailsResponse = await fetchWithBQ(`users/${userSub}`);
const userDetails = userDetailsResponse.data as User;
return { data: { user, userSub, userDetails } };
} catch (error: any) {
return { error: error.message || "Could not fetch user data" };
}
},
}),
getProjects: build.query<Project[], void>({ getProjects: build.query<Project[], void>({
query: () => "projects", query: () => "projects",
providesTags: ["Projects"], providesTags: ["Projects"],
@@ -139,6 +167,7 @@ export const api = createApi({
}); });
export const { export const {
useGetAuthUserQuery,
useGetProjectsQuery, useGetProjectsQuery,
useCreateProjectMutation, useCreateProjectMutation,
useGetTasksQuery, useGetTasksQuery,