feat: Integrate AWS Amplify authentication, update API queries, and enhance sidebar with user details
This commit is contained in:
26
package-lock.json
generated
26
package-lock.json
generated
@@ -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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": "^4.17.21"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/lodash": "^4.17.13"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3883
tasker-client/package-lock.json
generated
3883
tasker-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
64
tasker-client/src/app/authProvider.tsx
Normal file
64
tasker-client/src/app/authProvider.tsx
Normal 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;
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user