Initial front end
This commit is contained in:
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
**/.next/
|
||||||
|
**/out/
|
||||||
|
|
||||||
|
|
||||||
|
# misc
|
||||||
|
**/.DS_Store
|
||||||
|
**/*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
**/npm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for commiting if needed)
|
||||||
|
**/.env*
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
**/*.tsbuildinfo
|
||||||
|
**/next-env.d.ts
|
||||||
|
|
||||||
|
# images
|
||||||
|
**/public/**
|
||||||
3
tasker-client/.eslintrc.json
Normal file
3
tasker-client/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||||
|
}
|
||||||
3
tasker-client/.prettierrc
Normal file
3
tasker-client/.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
||||||
36
tasker-client/README.md
Normal file
36
tasker-client/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
7
tasker-client/next.config.ts
Normal file
7
tasker-client/next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
6725
tasker-client/package-lock.json
generated
Normal file
6725
tasker-client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
tasker-client/package.json
Normal file
47
tasker-client/package.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "tasker-client",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
|
"@emotion/styled": "^11.13.0",
|
||||||
|
"@mui/material": "^6.1.6",
|
||||||
|
"@mui/x-data-grid": "^7.22.0",
|
||||||
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"gantt-task-react": "^0.3.9",
|
||||||
|
"lucide-react": "^0.454.0",
|
||||||
|
"next": "15.0.2",
|
||||||
|
"numeral": "^2.0.6",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-redux": "^9.1.2",
|
||||||
|
"recharts": "^2.13.1",
|
||||||
|
"redux-persist": "^6.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/numeral": "^2.0.5",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "15.0.2",
|
||||||
|
"postcss": "^8",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||||
|
"tailwind-merge": "^2.5.4",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tasker-client/postcss.config.mjs
Normal file
8
tasker-client/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
66
tasker-client/src/app/(components)/Navbar/index.tsx
Normal file
66
tasker-client/src/app/(components)/Navbar/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Menu, Moon, Search, Settings, Sun } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useAppDispatch, useAppSelector } from "@/app/redux";
|
||||||
|
import { setIsDarkMode, setIsSidebarCollapsed } from "@/state";
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSidebarCollapsed = useAppSelector(
|
||||||
|
(state) => state.global.isSidebarCollapsed,
|
||||||
|
);
|
||||||
|
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between bg-white px-4 py-3 dark:bg-black">
|
||||||
|
<div className="flex items-center gap-8">
|
||||||
|
{!isSidebarCollapsed ? null : (
|
||||||
|
<button
|
||||||
|
onClick={() => dispatch(setIsSidebarCollapsed(!isSidebarCollapsed))}
|
||||||
|
>
|
||||||
|
<Menu className="h-8 w-8 dark:text-white" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<div className="relative flex h-min w-[200px]">
|
||||||
|
<Search className="absolute left-[6px] top-1/2 mr-2 h-5 w-5 -translate-y-1/2 transform cursor-pointer dark:text-white" />
|
||||||
|
<input
|
||||||
|
className="w-full rounded border-none bg-gray-100 p-2 pl-8 placeholder-gray-500 focus:border-transparent focus:outline-none dark:bg-gray-700 dark:text-white dark:placeholder-white"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Icons */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<button
|
||||||
|
onClick={() => dispatch(setIsDarkMode(!isDarkMode))}
|
||||||
|
className={
|
||||||
|
isDarkMode
|
||||||
|
? "rounded p-2 dark:hover:bg-gray-700"
|
||||||
|
: "rounded p-2 hover:bg-gray-100"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isDarkMode ? (
|
||||||
|
<Sun className="e-6 h-6 cursor-pointer dark:text-white" />
|
||||||
|
) : (
|
||||||
|
<Moon className="e-6 h-6 cursor-pointer dark:text-white" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<Link
|
||||||
|
href="settings"
|
||||||
|
className={
|
||||||
|
isDarkMode
|
||||||
|
? "h-min w-min rounded p-2 dark:hover:bg-gray-700"
|
||||||
|
: "h-min w-min rounded p-2 hover:bg-gray-100"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Settings className="h-6 w-6 cursor-pointer dark:text-white" />
|
||||||
|
</Link>
|
||||||
|
<div className="ml-2 mr-2 hidden min-h-[2em] w-[0.1rem] bg-gray-200 md:inline-block"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
162
tasker-client/src/app/(components)/Sidebar/index.tsx
Normal file
162
tasker-client/src/app/(components)/Sidebar/index.tsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
"use client";
|
||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
AlertOctagon,
|
||||||
|
AlertTriangle,
|
||||||
|
Briefcase,
|
||||||
|
ChevronDownCircleIcon,
|
||||||
|
ChevronDownSquareIcon,
|
||||||
|
ChevronUpCircleIcon,
|
||||||
|
ChevronUpSquareIcon,
|
||||||
|
Home,
|
||||||
|
Layers3,
|
||||||
|
LockIcon,
|
||||||
|
LucideIcon,
|
||||||
|
Search,
|
||||||
|
Settings,
|
||||||
|
ShieldAlert,
|
||||||
|
User,
|
||||||
|
Users,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { setIsSidebarCollapsed } from "@/state";
|
||||||
|
import { useAppDispatch, useAppSelector } from "@/app/redux";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
const [showProjects, setShowProjects] = React.useState(true);
|
||||||
|
const [showPriority, setShowPriority] = React.useState(true);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isSidebarCollapsed = useAppSelector(
|
||||||
|
(state) => state.global.isSidebarCollapsed,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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"}`}
|
||||||
|
>
|
||||||
|
<div className="flex h-full w-full flex-col justify-start">
|
||||||
|
<div className="z-50 flex min-h-[56px] w-64 items-center justify-between bg-white px-6 pt-3 dark:bg-black">
|
||||||
|
<div className="text-xl font-bold text-gray-800 dark:text-white">
|
||||||
|
Tasker.IO
|
||||||
|
</div>
|
||||||
|
{isSidebarCollapsed ? null : (
|
||||||
|
<button
|
||||||
|
className="py-3"
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(setIsSidebarCollapsed(!isSidebarCollapsed))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<X className="h-6 w-6 text-gray-800 hover:text-gray-500 dark:text-white" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-5 border-y-[1.5px] border-gray-200 px-8 py-4 dark:border-gray-700">
|
||||||
|
<Image src="/logo.png" alt="logo" width={40} height={40} />
|
||||||
|
<div>
|
||||||
|
<h3 className="text-md font-bold tracking-widest dark:text-gray-200">
|
||||||
|
Tasker.IO
|
||||||
|
</h3>
|
||||||
|
<div className="mt-1 flex items-start gap-2">
|
||||||
|
<LockIcon className="mt-[0.1rem] h-3 w-3 text-gray-500 dark:text-gray-400" />
|
||||||
|
<p className="text-xs text-gray-500">Private</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav className="z-10 w-full">
|
||||||
|
<SidebarLink icon={Home} label="Home" href="/" />
|
||||||
|
<SidebarLink icon={Briefcase} label="Timeline" href="/timeline" />
|
||||||
|
<SidebarLink icon={Search} label="Search" href="/search" />
|
||||||
|
<SidebarLink icon={Settings} label="Settings" href="/settings" />
|
||||||
|
<SidebarLink icon={User} label="Users" href="/users" />
|
||||||
|
<SidebarLink icon={Users} label="Teams" href="/teams" />
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setShowProjects((prev) => !prev)}
|
||||||
|
className="flex w-full items-center justify-between px-8 py-3 text-gray-500"
|
||||||
|
>
|
||||||
|
<span className="">Projects</span>
|
||||||
|
{showProjects ? (
|
||||||
|
<ChevronDownCircleIcon className="h-5 w-5" />
|
||||||
|
) : (
|
||||||
|
<ChevronUpCircleIcon className="h-5 w-5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setShowPriority((prev) => !prev)}
|
||||||
|
className="flex w-full items-center justify-between px-8 py-3 text-gray-500"
|
||||||
|
>
|
||||||
|
<span className="">Priority</span>
|
||||||
|
{showPriority ? (
|
||||||
|
<ChevronDownSquareIcon className="h-5 w-5" />
|
||||||
|
) : (
|
||||||
|
<ChevronUpSquareIcon className="h-5 w-5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
{showPriority && (
|
||||||
|
<>
|
||||||
|
<SidebarLink
|
||||||
|
icon={AlertCircle}
|
||||||
|
label="Urgent"
|
||||||
|
href="/priority/urgent"
|
||||||
|
/>
|
||||||
|
<SidebarLink
|
||||||
|
icon={ShieldAlert}
|
||||||
|
label="High"
|
||||||
|
href="/priority/high"
|
||||||
|
/>
|
||||||
|
<SidebarLink
|
||||||
|
icon={AlertTriangle}
|
||||||
|
label="Medium"
|
||||||
|
href="/priority/medium"
|
||||||
|
/>
|
||||||
|
<SidebarLink icon={AlertOctagon} label="Low" href="/priority/low" />
|
||||||
|
<SidebarLink
|
||||||
|
icon={Layers3}
|
||||||
|
label="Backlog"
|
||||||
|
href="/priority/backlog"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SidebarLinkProps {
|
||||||
|
href: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarLink = ({ href, icon: Icon, label }: SidebarLinkProps) => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const isActive = pathname === href || (pathname === "/" && href === "/home");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href={href} className="w-full">
|
||||||
|
<div
|
||||||
|
className={`relative flex cursor-pointer items-center gap-3 transition-colors hover:bg-gray-100 dark:bg-black dark:hover:bg-gray-700 ${
|
||||||
|
isActive ? "bg-gray-100 text-white dark:bg-gray-600" : ""
|
||||||
|
} justify-start px-8 py-3`}
|
||||||
|
>
|
||||||
|
{isActive && (
|
||||||
|
<div className="absolute left-0 top-0 h-[100%] w-[5px] bg-blue-200" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Icon className="h-6 w-6 text-gray-800 dark:text-gray-100" />
|
||||||
|
<span className={`font-medium text-gray-800 dark:text-gray-100`}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
43
tasker-client/src/app/dashboardWrapper.tsx
Normal file
43
tasker-client/src/app/dashboardWrapper.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import Navbar from "@/app/(components)/Navbar";
|
||||||
|
import Sidebar from "@/app/(components)/Sidebar";
|
||||||
|
import StoreProvider, { useAppSelector } from "./redux";
|
||||||
|
|
||||||
|
const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const isSidebarCollapsed = useAppSelector(
|
||||||
|
(state) => state.global.isSidebarCollapsed,
|
||||||
|
);
|
||||||
|
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDarkMode) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen w-full bg-gray-50 text-gray-900">
|
||||||
|
<Sidebar />
|
||||||
|
<main
|
||||||
|
className={`flex w-full flex-col bg-gray-50 dark:bg-dark-bg ${isSidebarCollapsed ? "" : "md:pl-64"}`}
|
||||||
|
>
|
||||||
|
<Navbar />
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboardWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<StoreProvider>
|
||||||
|
<DashboardLayout>{children}</DashboardLayout>
|
||||||
|
</StoreProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardWrapper;
|
||||||
BIN
tasker-client/src/app/favicon.ico
Normal file
BIN
tasker-client/src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
tasker-client/src/app/fonts/GeistMonoVF.woff
Normal file
BIN
tasker-client/src/app/fonts/GeistMonoVF.woff
Normal file
Binary file not shown.
BIN
tasker-client/src/app/fonts/GeistVF.woff
Normal file
BIN
tasker-client/src/app/fonts/GeistVF.woff
Normal file
Binary file not shown.
20
tasker-client/src/app/globals.css
Normal file
20
tasker-client/src/app/globals.css
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#root,
|
||||||
|
.app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
@apply text-sm;
|
||||||
|
@apply bg-gray-500;
|
||||||
|
@apply text-gray-900;
|
||||||
|
}
|
||||||
24
tasker-client/src/app/layout.tsx
Normal file
24
tasker-client/src/app/layout.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
import DashboardWrapper from "./dashboardWrapper";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Create Next App",
|
||||||
|
description: "Generated by create next app",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
<DashboardWrapper>{children}</DashboardWrapper>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
tasker-client/src/app/page.tsx
Normal file
7
tasker-client/src/app/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col items-center justify-center h-screen">
|
||||||
|
New
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
100
tasker-client/src/app/redux.tsx
Normal file
100
tasker-client/src/app/redux.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
"use client";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||||
|
import {
|
||||||
|
TypedUseSelectorHook,
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
Provider,
|
||||||
|
} from "react-redux";
|
||||||
|
import globalReducer from "@/state";
|
||||||
|
import { api } from "@/state/api";
|
||||||
|
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||||
|
|
||||||
|
import {
|
||||||
|
persistStore,
|
||||||
|
persistReducer,
|
||||||
|
FLUSH,
|
||||||
|
REHYDRATE,
|
||||||
|
PAUSE,
|
||||||
|
PERSIST,
|
||||||
|
PURGE,
|
||||||
|
REGISTER,
|
||||||
|
} from "redux-persist";
|
||||||
|
import { PersistGate } from "redux-persist/integration/react";
|
||||||
|
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
|
||||||
|
|
||||||
|
/* REDUX PERSISTENCE */
|
||||||
|
const createNoopStorage = () => {
|
||||||
|
return {
|
||||||
|
getItem(_key: any) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
setItem(_key: any, value: any) {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
removeItem(_key: any) {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage =
|
||||||
|
typeof window === "undefined"
|
||||||
|
? createNoopStorage()
|
||||||
|
: createWebStorage("local");
|
||||||
|
|
||||||
|
const persistConfig = {
|
||||||
|
key: "root",
|
||||||
|
storage,
|
||||||
|
whitelist: ["global"],
|
||||||
|
};
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
global: globalReducer,
|
||||||
|
[api.reducerPath]: api.reducer,
|
||||||
|
});
|
||||||
|
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||||
|
|
||||||
|
/* REDUX STORE */
|
||||||
|
export const makeStore = () => {
|
||||||
|
return configureStore({
|
||||||
|
reducer: persistedReducer,
|
||||||
|
middleware: (getDefault) =>
|
||||||
|
getDefault({
|
||||||
|
serializableCheck: {
|
||||||
|
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
||||||
|
},
|
||||||
|
}).concat(api.middleware),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* REDUX TYPES */
|
||||||
|
export type AppStore = ReturnType<typeof makeStore>;
|
||||||
|
export type RootState = ReturnType<AppStore["getState"]>;
|
||||||
|
export type AppDispatch = AppStore["dispatch"];
|
||||||
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
|
|
||||||
|
/* PROVIDER */
|
||||||
|
export default function StoreProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const storeRef = useRef<AppStore>();
|
||||||
|
if (!storeRef.current) {
|
||||||
|
storeRef.current = makeStore();
|
||||||
|
setupListeners(storeRef.current.dispatch);
|
||||||
|
}
|
||||||
|
const persistor = persistStore(storeRef.current);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider store={storeRef.current}>
|
||||||
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
|
{children}
|
||||||
|
</PersistGate>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
tasker-client/src/state/api.ts
Normal file
10
tasker-client/src/state/api.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
|
||||||
|
|
||||||
|
export const api = createApi({
|
||||||
|
baseQuery: fetchBaseQuery({ baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL }),
|
||||||
|
reducerPath: "api",
|
||||||
|
tagTypes: ["Task"],
|
||||||
|
endpoints: (builder) => ({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {} = api;
|
||||||
27
tasker-client/src/state/index.tsx
Normal file
27
tasker-client/src/state/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
export interface initialStateType {
|
||||||
|
isSidebarCollapsed: boolean;
|
||||||
|
isDarkMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
isSidebarCollapsed: false,
|
||||||
|
isDarkMode: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const globalSlice = createSlice({
|
||||||
|
name: "global",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setIsSidebarCollapsed: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isSidebarCollapsed = action.payload;
|
||||||
|
},
|
||||||
|
setIsDarkMode: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isDarkMode = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setIsSidebarCollapsed, setIsDarkMode } = globalSlice.actions;
|
||||||
|
export default globalSlice.reducer;
|
||||||
44
tasker-client/tailwind.config.ts
Normal file
44
tasker-client/tailwind.config.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
darkMode: "class",
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
white: "#ffffff",
|
||||||
|
grey: {
|
||||||
|
100: "#f3f4f6",
|
||||||
|
200: "#e5e7eb",
|
||||||
|
300: "#d1d5db",
|
||||||
|
400: "#9ca3af",
|
||||||
|
500: "#6b7280",
|
||||||
|
600: "#4b5563",
|
||||||
|
700: "#374151",
|
||||||
|
800: "#1f2937",
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
100: "#dbeafe",
|
||||||
|
200: "#bfdbfe",
|
||||||
|
300: "#93c5fd",
|
||||||
|
400: "#60a5fa",
|
||||||
|
500: "#3b82f6",
|
||||||
|
600: "#2563eb",
|
||||||
|
700: "#1d4ed8",
|
||||||
|
800: "#1e40af",
|
||||||
|
},
|
||||||
|
"dark-bg": "#101214",
|
||||||
|
"dark-secondary": "#1e2029",
|
||||||
|
"dark-tertiary": "#25262d",
|
||||||
|
"blue-primary": "#3b82f6",
|
||||||
|
"stroke-dark": "#2d3135",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
27
tasker-client/tsconfig.json
Normal file
27
tasker-client/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"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/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user