├── app
├── favicon.ico
├── utils
│ ├── formatDate.js
│ ├── connect.ts
│ ├── menu.js
│ └── Icons.js
├── page.tsx
├── signin
│ └── page.tsx
├── signup
│ └── page.tsx
├── important
│ └── page.tsx
├── incomplete
│ └── page.tsx
├── completed
│ └── page.tsx
├── providers
│ ├── GlobalStyleProvider.tsx
│ └── ContextProvider.tsx
├── api
│ └── tasks
│ │ ├── [id]
│ │ └── route.ts
│ │ └── route.ts
├── globals.css
├── Components
│ ├── Modals
│ │ ├── Modal.tsx
│ │ └── CreateContent.tsx
│ ├── Button
│ │ └── Button.tsx
│ ├── TaskItem
│ │ └── TaskItem.tsx
│ ├── Tasks
│ │ └── Tasks.tsx
│ └── Sidebar
│ │ └── Sidebar.tsx
├── layout.tsx
└── context
│ ├── globalProvider.js
│ └── themes.js
├── public
├── avatar1.png
├── vercel.svg
└── next.svg
├── postcss.config.js
├── middleware.ts
├── next.config.js
├── .gitignore
├── tailwind.config.ts
├── prisma
└── schema.prisma
├── tsconfig.json
├── package.json
└── README.md
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maclinz/todoapp_fullstack/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/public/avatar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maclinz/todoapp_fullstack/HEAD/public/avatar1.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/app/utils/formatDate.js:
--------------------------------------------------------------------------------
1 | const moment = require("moment/moment");
2 |
3 | const formatDate = (date) => {
4 | return moment(date).format("DD/MM/YYYY");
5 | };
6 |
7 | export default formatDate;
8 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { authMiddleware } from "@clerk/nextjs";
2 |
3 | export default authMiddleware({});
4 |
5 | export const config = {
6 | matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
7 | };
8 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: ["localhost", "https://img.clerk.com", "img.clerk.com"],
5 | },
6 | };
7 |
8 | module.exports = nextConfig;
9 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Tasks from "./Components/Tasks/Tasks";
3 | import { useGlobalState } from "./context/globalProvider";
4 |
5 | export default function Home() {
6 | const { tasks } = useGlobalState();
7 |
8 | return ;
9 | }
10 |
--------------------------------------------------------------------------------
/app/signin/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { SignIn } from "@clerk/nextjs";
3 | import React from "react";
4 |
5 | function page() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default page;
14 |
--------------------------------------------------------------------------------
/app/signup/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { SignUp } from "@clerk/nextjs";
3 | import React from "react";
4 |
5 | function page() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default page;
14 |
--------------------------------------------------------------------------------
/app/important/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { useGlobalState } from "../context/globalProvider";
4 | import Tasks from "../Components/Tasks/Tasks";
5 |
6 | function page() {
7 | const { importantTasks } = useGlobalState();
8 | return ;
9 | }
10 |
11 | export default page;
12 |
--------------------------------------------------------------------------------
/app/incomplete/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { useGlobalState } from "../context/globalProvider";
4 | import Tasks from "../Components/Tasks/Tasks";
5 |
6 | function page() {
7 | const { incompleteTasks } = useGlobalState();
8 | return ;
9 | }
10 |
11 | export default page;
12 |
--------------------------------------------------------------------------------
/app/completed/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { useGlobalState } from "../context/globalProvider";
4 | import Tasks from "../Components/Tasks/Tasks";
5 |
6 | function page() {
7 | const { completedTasks } = useGlobalState();
8 |
9 | return ;
10 | }
11 |
12 | export default page;
13 |
--------------------------------------------------------------------------------
/app/utils/connect.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 | let prisma: PrismaClient;
4 |
5 | if (process.env.NODE_ENV === "production") {
6 | prisma = new PrismaClient();
7 | } else {
8 | // @ts-ignore
9 | if (!global.prisma) {
10 | // @ts-ignore
11 | global.prisma = new PrismaClient();
12 | }
13 | // @ts-ignore
14 | prisma = global.prisma;
15 | }
16 |
17 | export default prisma;
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .env
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/app/utils/menu.js:
--------------------------------------------------------------------------------
1 | import { list, check, todo, home } from "./Icons";
2 |
3 | const menu = [
4 | {
5 | id: 1,
6 | title: "All Tasks",
7 | icon: home,
8 | link: "/",
9 | },
10 | {
11 | id: 2,
12 | title: "Important!",
13 | icon: list,
14 | link: "/important",
15 | },
16 | {
17 | id: 3,
18 | title: "Completed!",
19 | icon: check,
20 | link: "/completed",
21 | },
22 | {
23 | id: 4,
24 | title: "Do It Now",
25 | icon: todo,
26 | link: "/incomplete",
27 | },
28 | ];
29 |
30 | export default menu;
31 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | generator client {
5 | provider = "prisma-client-js"
6 | }
7 |
8 | datasource db {
9 | provider = "mongodb"
10 | url = env("DATABASE_URL")
11 | }
12 |
13 |
14 | model Task{
15 | id String @id @default(cuid()) @map("_id")
16 | title String
17 | description String?
18 | date String
19 | isCompleted Boolean @default(false)
20 | isImportant Boolean @default(false)
21 |
22 | createdAt DateTime @default(now()) @map("created_at")
23 | updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
24 | userId String
25 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/app/providers/GlobalStyleProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import styled from "styled-components";
4 |
5 | interface Props {
6 | children: React.ReactNode;
7 | }
8 |
9 | function GlobalStyleProvider({ children }: Props) {
10 | return {children};
11 | }
12 |
13 | const GlobalStyles = styled.div`
14 | padding: 2.5rem;
15 | display: flex;
16 | gap: 2.5rem;
17 | height: 100%;
18 | transition: all 0.3s ease-in-out;
19 |
20 | @media screen and (max-width: 768px) {
21 | padding: 1rem;
22 | gap: 1rem;
23 | }
24 |
25 | .grid {
26 | display: grid;
27 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
28 | gap: 1.5rem;
29 | }
30 | `;
31 |
32 | export default GlobalStyleProvider;
33 |
--------------------------------------------------------------------------------
/app/api/tasks/[id]/route.ts:
--------------------------------------------------------------------------------
1 | import prisma from "@/app/utils/connect";
2 | import { auth } from "@clerk/nextjs";
3 | import { NextResponse } from "next/server";
4 |
5 | export async function DELETE(
6 | req: Request,
7 | { params }: { params: { id: string } }
8 | ) {
9 | try {
10 | const { userId } = auth();
11 | const { id } = params;
12 |
13 | if (!userId) {
14 | return new NextResponse("Unauthorized", { status: 401 });
15 | }
16 |
17 | const task = await prisma.task.delete({
18 | where: {
19 | id,
20 | },
21 | });
22 |
23 | return NextResponse.json(task);
24 | } catch (error) {
25 | console.log("ERROR DELETING TASK: ", error);
26 | return NextResponse.json({ error: "Error deleting task", status: 500 });
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/providers/ContextProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import { GlobalProvider } from "../context/globalProvider";
4 | import { Toaster } from "react-hot-toast";
5 |
6 | interface Props {
7 | children: React.ReactNode;
8 | }
9 |
10 | function ContextProvider({ children }: Props) {
11 | const [isReady, setIsReady] = React.useState(false);
12 |
13 | React.useEffect(() => {
14 | setTimeout(() => {
15 | setIsReady(true);
16 | }, 250);
17 | }, []);
18 |
19 | if (!isReady) {
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | return (
28 |
29 |
30 | {children}
31 |
32 | );
33 | }
34 |
35 | export default ContextProvider;
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mytasks",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "postinstall": "npx prisma generate"
11 | },
12 | "dependencies": {
13 | "@clerk/nextjs": "^4.25.6",
14 | "@next-auth/prisma-adapter": "^1.0.7",
15 | "@prisma/client": "^5.4.2",
16 | "axios": "^1.5.1",
17 | "moment": "^2.29.4",
18 | "next": "13.5.6",
19 | "nextjs-toploader": "^1.5.3",
20 | "prisma": "^5.4.2",
21 | "react": "^18",
22 | "react-dom": "^18",
23 | "react-hot-toast": "^2.4.1",
24 | "styled-components": "^6.1.0"
25 | },
26 | "devDependencies": {
27 | "@types/node": "^20",
28 | "@types/react": "^18",
29 | "@types/react-dom": "^18",
30 | "autoprefixer": "^10",
31 | "postcss": "^8",
32 | "tailwindcss": "^3",
33 | "typescript": "^5"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | background-color: #181818;
21 | font-size: 17px;
22 | color: #fff;
23 |
24 | height: 100vh;
25 | }
26 |
27 | input,
28 | textarea,
29 | button {
30 | border: none;
31 | outline: none;
32 | background: transparent;
33 | }
34 |
35 | input[type="date"]::-webkit-calendar-picker-indicator {
36 | filter: invert(1);
37 | font-size: 1.5rem;
38 | cursor: pointer;
39 | }
40 |
41 | input[type="checkbox"] {
42 | width: 1.5rem;
43 | height: 1.5rem;
44 | cursor: pointer;
45 | }
46 |
47 | .loader {
48 | width: 48px;
49 | height: 48px;
50 | border: 5px solid #27ae60;
51 | border-bottom-color: transparent;
52 | border-radius: 50%;
53 | display: inline-block;
54 | box-sizing: border-box;
55 | animation: rotation 1s linear infinite;
56 | }
57 |
58 | @keyframes rotation {
59 | 0% {
60 | transform: rotate(0deg);
61 | }
62 | 100% {
63 | transform: rotate(360deg);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | 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.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/app/Components/Modals/Modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useGlobalState } from "@/app/context/globalProvider";
3 | import React from "react";
4 | import styled from "styled-components";
5 |
6 | interface Props {
7 | content: React.ReactNode;
8 | }
9 |
10 | function Modal({ content }: Props) {
11 | const { closeModal } = useGlobalState();
12 |
13 | const { theme } = useGlobalState();
14 | return (
15 |
16 |
17 | {content}
18 |
19 | );
20 | }
21 |
22 | const ModalStyled = styled.div`
23 | position: fixed;
24 | top: 0;
25 | left: 0;
26 | width: 100%;
27 | height: 100vh;
28 | z-index: 100;
29 |
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 |
34 | .modal-overlay {
35 | position: absolute;
36 | top: 0;
37 | left: 0;
38 | width: 100%;
39 | height: 100vh;
40 | background-color: rgba(0, 0, 0, 0.45);
41 | filter: blur(4px);
42 | }
43 |
44 | .modal-content {
45 | margin: 0 1rem;
46 |
47 | padding: 2rem;
48 | position: relative;
49 | max-width: 630px;
50 | width: 100%;
51 | z-index: 100;
52 |
53 | border-radius: 1rem;
54 | background-color: ${(props) => props.theme.colorBg2};
55 | box-shadow: 0 0 1rem rgba(0, 0, 0, 0.3);
56 | border-radius: ${(props) => props.theme.borderRadiusMd2};
57 |
58 | @media screen and (max-width: 450px) {
59 | font-size: 90%;
60 | }
61 | }
62 | `;
63 |
64 | export default Modal;
65 |
--------------------------------------------------------------------------------
/app/utils/Icons.js:
--------------------------------------------------------------------------------
1 | export const bars = ;
2 | export const arrowLeft = ;
3 |
4 | export const add = ;
5 | export const moon = ;
6 | export const search = ;
7 | export const bell = ;
8 | export const home = ;
9 | export const list = ;
10 | export const check = ;
11 | export const help = ;
12 | export const gear = ;
13 | export const login = ;
14 | export const logout = ;
15 | export const user = ;
16 | export const join = ;
17 | export const heart = ;
18 | export const edit = ;
19 | export const users = ;
20 | export const todo = ;
21 | export const down = ;
22 | export const trash = ;
23 | export const admin = ;
24 | export const linked = ;
25 | export const mailIcon = ;
26 | export const plus = ;
27 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Nunito } from "next/font/google";
3 | import { ClerkProvider, auth } from "@clerk/nextjs";
4 |
5 | import "./globals.css";
6 | import Sidebar from "./Components/Sidebar/Sidebar";
7 | import GlobalStyleProvider from "./providers/GlobalStyleProvider";
8 | import ContextProvider from "./providers/ContextProvider";
9 | import NextTopLoader from "nextjs-toploader";
10 |
11 | const nunito = Nunito({
12 | weight: ["400", "500", "600", "700", "800"],
13 | subsets: ["latin"],
14 | });
15 |
16 | export const metadata: Metadata = {
17 | title: "Create Next App",
18 | description: "Generated by create next app",
19 | };
20 |
21 | export default function RootLayout({
22 | children,
23 | }: {
24 | children: React.ReactNode;
25 | }) {
26 | const { userId } = auth();
27 |
28 | return (
29 |
30 |
31 |
32 |
39 |
40 |
41 |
46 |
47 |
48 | {userId && }
49 | {children}
50 |
51 |
52 |
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/app/Components/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useGlobalState } from "@/app/context/globalProvider";
3 |
4 | import React from "react";
5 | import styled from "styled-components";
6 |
7 | interface Props {
8 | icon?: React.ReactNode;
9 | name?: string;
10 | background?: string;
11 | padding?: string;
12 | borderRad?: string;
13 | fw?: string;
14 | fs?: string;
15 | click?: () => void;
16 | type?: "submit" | "button" | "reset" | undefined;
17 | border?: string;
18 | color?: string;
19 | }
20 |
21 | function Button({
22 | icon,
23 | name,
24 | background,
25 | padding,
26 | borderRad,
27 | fw,
28 | fs,
29 | click,
30 | type,
31 | border,
32 | color,
33 | }: Props) {
34 | const { theme } = useGlobalState();
35 |
36 | return (
37 |
51 | {icon && icon}
52 | {name}
53 |
54 | );
55 | }
56 |
57 | const ButtonStyled = styled.button`
58 | position: relative;
59 | display: flex;
60 | align-items: center;
61 | color: ${(props) => props.theme.colorGrey2};
62 | z-index: 5;
63 | cursor: pointer;
64 |
65 | transition: all 0.55s ease-in-out;
66 |
67 | i {
68 | margin-right: 1rem;
69 | color: ${(props) => props.theme.colorGrey2};
70 | font-size: 1.5rem;
71 | transition: all 0.55s ease-in-out;
72 | }
73 |
74 | &:hover {
75 | color: ${(props) => props.theme.colorGrey0};
76 | i {
77 | color: ${(props) => props.theme.colorGrey0};
78 | }
79 | }
80 | `;
81 |
82 | export default Button;
83 |
--------------------------------------------------------------------------------
/app/api/tasks/route.ts:
--------------------------------------------------------------------------------
1 | import prisma from "@/app/utils/connect";
2 | import { auth } from "@clerk/nextjs";
3 | import { NextResponse } from "next/server";
4 |
5 | export async function POST(req: Request) {
6 | try {
7 | const { userId } = auth();
8 |
9 | if (!userId) {
10 | return NextResponse.json({ error: "Unauthorized", status: 401 });
11 | }
12 |
13 | const { title, description, date, completed, important } = await req.json();
14 |
15 | if (!title || !description || !date) {
16 | return NextResponse.json({
17 | error: "Missing required fields",
18 | status: 400,
19 | });
20 | }
21 |
22 | if (title.length < 3) {
23 | return NextResponse.json({
24 | error: "Title must be at least 3 characters long",
25 | status: 400,
26 | });
27 | }
28 |
29 | const task = await prisma.task.create({
30 | data: {
31 | title,
32 | description,
33 | date,
34 | isCompleted: completed,
35 | isImportant: important,
36 | userId,
37 | },
38 | });
39 |
40 | return NextResponse.json(task);
41 | } catch (error) {
42 | console.log("ERROR CREATING TASK: ", error);
43 | return NextResponse.json({ error: "Error creating task", status: 500 });
44 | }
45 | }
46 |
47 | export async function GET(req: Request) {
48 | try {
49 | const { userId } = auth();
50 |
51 | if (!userId) {
52 | return NextResponse.json({ error: "Unauthorized", status: 401 });
53 | }
54 |
55 | const tasks = await prisma.task.findMany({
56 | where: {
57 | userId,
58 | },
59 | });
60 |
61 | return NextResponse.json(tasks);
62 | } catch (error) {
63 | console.log("ERROR GETTING TASKS: ", error);
64 | return NextResponse.json({ error: "Error updating task", status: 500 });
65 | }
66 | }
67 |
68 | export async function PUT(req: Request) {
69 | try {
70 | const { userId } = auth();
71 | const { isCompleted, id } = await req.json();
72 |
73 | if (!userId) {
74 | return NextResponse.json({ error: "Unauthorized", status: 401 });
75 | }
76 |
77 | const task = await prisma.task.update({
78 | where: {
79 | id,
80 | },
81 | data: {
82 | isCompleted,
83 | },
84 | });
85 |
86 | return NextResponse.json(task);
87 | } catch (error) {
88 | console.log("ERROR UPDATING TASK: ", error);
89 | return NextResponse.json({ error: "Error deleting task", status: 500 });
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/Components/TaskItem/TaskItem.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useGlobalState } from "@/app/context/globalProvider";
3 | import { edit, trash } from "@/app/utils/Icons";
4 | import React from "react";
5 | import styled from "styled-components";
6 | import formatDate from "@/app/utils/formatDate";
7 |
8 | interface Props {
9 | title: string;
10 | description: string;
11 | date: string;
12 | isCompleted: boolean;
13 | id: string;
14 | }
15 |
16 | function TaskItem({ title, description, date, isCompleted, id }: Props) {
17 | const { theme, deleteTask, updateTask } = useGlobalState();
18 | return (
19 |
20 | {title}
21 | {description}
22 | {formatDate(date)}
23 |
24 | {isCompleted ? (
25 |
38 | ) : (
39 |
52 | )}
53 |
54 |
62 |
63 |
64 | );
65 | }
66 |
67 | const TaskItemStyled = styled.div`
68 | padding: 1.2rem 1rem;
69 | border-radius: 1rem;
70 | background-color: ${(props) => props.theme.borderColor2};
71 | box-shadow: ${(props) => props.theme.shadow7};
72 | border: 2px solid ${(props) => props.theme.borderColor2};
73 |
74 | height: 16rem;
75 | display: flex;
76 | flex-direction: column;
77 | gap: 0.5rem;
78 |
79 | .date {
80 | margin-top: auto;
81 | }
82 |
83 | > h1 {
84 | font-size: 1.5rem;
85 | font-weight: 600;
86 | }
87 |
88 | .task-footer {
89 | display: flex;
90 | align-items: center;
91 | gap: 1.2rem;
92 |
93 | button {
94 | border: none;
95 | outline: none;
96 | cursor: pointer;
97 |
98 | i {
99 | font-size: 1.4rem;
100 | color: ${(props) => props.theme.colorGrey2};
101 | }
102 | }
103 |
104 | .edit {
105 | margin-left: auto;
106 | }
107 |
108 | .completed,
109 | .incomplete {
110 | display: inline-block;
111 | padding: 0.4rem 1rem;
112 | background: ${(props) => props.theme.colorDanger};
113 | border-radius: 30px;
114 | }
115 |
116 | .completed {
117 | background: ${(props) => props.theme.colorGreenDark} !important;
118 | }
119 | }
120 | `;
121 |
122 | export default TaskItem;
123 |
--------------------------------------------------------------------------------
/app/context/globalProvider.js:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { createContext, useState, useContext } from "react";
3 | import themes from "./themes";
4 | import axios from "axios";
5 | import toast from "react-hot-toast";
6 | import { useUser } from "@clerk/nextjs";
7 |
8 | export const GlobalContext = createContext();
9 | export const GlobalUpdateContext = createContext();
10 |
11 | export const GlobalProvider = ({ children }) => {
12 | const { user } = useUser();
13 |
14 | const [selectedTheme, setSelectedTheme] = useState(0);
15 | const [isLoading, setIsLoading] = useState(false);
16 | const [modal, setModal] = useState(false);
17 | const [collapsed, setCollapsed] = useState(false);
18 |
19 | const [tasks, setTasks] = useState([]);
20 |
21 | const theme = themes[selectedTheme];
22 |
23 | const openModal = () => {
24 | setModal(true);
25 | };
26 |
27 | const closeModal = () => {
28 | setModal(false);
29 | };
30 |
31 | const collapseMenu = () => {
32 | setCollapsed(!collapsed);
33 | };
34 |
35 | const allTasks = async () => {
36 | setIsLoading(true);
37 | try {
38 | const res = await axios.get("/api/tasks");
39 |
40 | const sorted = res.data.sort((a, b) => {
41 | return (
42 | new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
43 | );
44 | });
45 |
46 | setTasks(sorted);
47 |
48 | setIsLoading(false);
49 | } catch (error) {
50 | console.log(error);
51 | }
52 | };
53 |
54 | const deleteTask = async (id) => {
55 | try {
56 | const res = await axios.delete(`/api/tasks/${id}`);
57 | toast.success("Task deleted");
58 |
59 | allTasks();
60 | } catch (error) {
61 | console.log(error);
62 | toast.error("Something went wrong");
63 | }
64 | };
65 |
66 | const updateTask = async (task) => {
67 | try {
68 | const res = await axios.put(`/api/tasks`, task);
69 |
70 | toast.success("Task updated");
71 |
72 | allTasks();
73 | } catch (error) {
74 | console.log(error);
75 | toast.error("Something went wrong");
76 | }
77 | };
78 |
79 | const completedTasks = tasks.filter((task) => task.isCompleted === true);
80 | const importantTasks = tasks.filter((task) => task.isImportant === true);
81 | const incompleteTasks = tasks.filter((task) => task.isCompleted === false);
82 |
83 | React.useEffect(() => {
84 | if (user) allTasks();
85 | }, [user]);
86 |
87 | return (
88 |
106 |
107 | {children}
108 |
109 |
110 | );
111 | };
112 |
113 | export const useGlobalState = () => useContext(GlobalContext);
114 | export const useGlobalUpdate = () => useContext(GlobalUpdateContext);
115 |
--------------------------------------------------------------------------------
/app/Components/Tasks/Tasks.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useGlobalState } from "@/app/context/globalProvider";
3 | import React from "react";
4 | import styled from "styled-components";
5 | import CreateContent from "../Modals/CreateContent";
6 | import TaskItem from "../TaskItem/TaskItem";
7 | import { add, plus } from "@/app/utils/Icons";
8 | import Modal from "../Modals/Modal";
9 |
10 | interface Props {
11 | title: string;
12 | tasks: any[];
13 | }
14 |
15 | function Tasks({ title, tasks }: Props) {
16 | const { theme, isLoading, openModal, modal } = useGlobalState();
17 |
18 | return (
19 |
20 | {modal && } />}
21 | {title}
22 |
23 |
26 |
27 |
28 | {tasks.map((task) => (
29 |
37 | ))}
38 |
42 |
43 |
44 | );
45 | }
46 |
47 | const TaskStyled = styled.main`
48 | position: relative;
49 | padding: 2rem;
50 | width: 100%;
51 | background-color: ${(props) => props.theme.colorBg2};
52 | border: 2px solid ${(props) => props.theme.borderColor2};
53 | border-radius: 1rem;
54 | height: 100%;
55 |
56 | overflow-y: auto;
57 |
58 | &::-webkit-scrollbar {
59 | width: 0.5rem;
60 | }
61 |
62 | .btn-rounded {
63 | position: fixed;
64 | top: 4.9rem;
65 | right: 5.1rem;
66 | width: 3rem;
67 | height: 3rem;
68 | border-radius: 50%;
69 |
70 | background-color: ${(props) => props.theme.colorBg};
71 | border: 2px solid ${(props) => props.theme.borderColor2};
72 | box-shadow: 0 3px 15px rgba(0, 0, 0, 0.3);
73 | color: ${(props) => props.theme.colorGrey2};
74 | font-size: 1.4rem;
75 |
76 | display: flex;
77 | align-items: center;
78 | justify-content: center;
79 |
80 | @media screen and (max-width: 768px) {
81 | top: 3rem;
82 | right: 3.5rem;
83 | }
84 | }
85 |
86 | .tasks {
87 | margin: 2rem 0;
88 | }
89 |
90 | > h1 {
91 | font-size: clamp(1.5rem, 2vw, 2rem);
92 | font-weight: 800;
93 | position: relative;
94 |
95 | &::after {
96 | content: "";
97 | position: absolute;
98 | bottom: -0.5rem;
99 | left: 0;
100 | width: 3rem;
101 | height: 0.2rem;
102 | background-color: ${(props) => props.theme.colorPrimaryGreen};
103 | border-radius: 0.5rem;
104 | }
105 | }
106 |
107 | .create-task {
108 | display: flex;
109 | align-items: center;
110 | justify-content: center;
111 | gap: 0.5rem;
112 |
113 | height: 16rem;
114 | color: ${(props) => props.theme.colorGrey2};
115 | font-weight: 600;
116 | cursor: pointer;
117 | border-radius: 1rem;
118 | border: 3px dashed ${(props) => props.theme.colorGrey5};
119 | transition: all 0.3s ease;
120 |
121 | i {
122 | font-size: 1.5rem;
123 | margin-right: 0.2rem;
124 | }
125 |
126 | &:hover {
127 | background-color: ${(props) => props.theme.colorGrey5};
128 | color: ${(props) => props.theme.colorGrey0};
129 | }
130 | }
131 | `;
132 |
133 | export default Tasks;
134 |
--------------------------------------------------------------------------------
/app/context/themes.js:
--------------------------------------------------------------------------------
1 | const themes = [
2 | {
3 | name: "default",
4 | colorBg: "#252525",
5 | colorBg2: "#212121",
6 | colorBg3: "#181818",
7 | colorBg4: "#1A1A1A",
8 | colorButton: "#3A3B3C",
9 | colorDanger: "#fe6854",
10 | colorFontPrimary: "#e5e7eb",
11 | colorTextSecondary: "#B0B3B8",
12 | colorTextPrimary: "#FFFFFF",
13 | colorTextLight: "#f8f8f8",
14 | colorbackground: "#FBFBFD",
15 | colorGradient: "linear-gradient(110.42deg, #CF57A3 29.2%, #4731B6 63.56%)",
16 | colorGreenDark: "#27AE60",
17 | colorGreenLight: "#dbe1e8",
18 | activeNavLink: "rgba(249,249,249, 0.08)",
19 | activeNavLinkHover: "rgba(249,249,249, 0.03)",
20 | colorPrimary: "#7263F3",
21 | colorPrimary2: "#705DF2",
22 | colorGreyDark: "#131313",
23 | colorGrey0: "#f8f8f8",
24 | colorGrey1: "#dbe1e8",
25 | colorGrey2: "#b2becd",
26 | colorGrey3: "#6c7983",
27 | colorGrey4: "#454e56",
28 | colorGrey5: "#2a2e35",
29 | colorGrey6: "#12181b",
30 | colorWhite: "#fff",
31 | colorPrimaryGreen: "#6FCF97",
32 | borderColor: "rgba(249,249,249, 0.08)",
33 | borderColor2: "rgba(249,249,249, 0.08)",
34 | shadow7: "1px 7px 12px rgba(8, 18, 69, 0.1)",
35 | sidebarWidth: "15rem",
36 | sidebarCollapsed: "5.4rem",
37 | fH4: "19px",
38 | fontSmall: "14px",
39 | fontSmall2: "15px",
40 | gridGap: "2rem",
41 | padLRSm: "0 2rem",
42 | colorIcons: "rgba(249,249,249, 0.35)",
43 | colorIcons2: "rgba(249,249,249, 0.75)",
44 | colorIcons3: "rgba(249,249,249, 0.08)",
45 | colorIcons4: "rgba(0,0,0, 0.3)",
46 | marLRSm: "0 1rem",
47 | },
48 | {
49 | name: "light",
50 | colorBg: "#fff",
51 | colorBg2: "#F9F9F9",
52 | colorBg3: "#EDEDED",
53 | colorBg4: "#1A1A1A",
54 | colorButton: "#3A3B3C",
55 | colorDanger: "#fe6854",
56 | colorTextSecondary: "#B0B3B8",
57 | colorFontPrimary: "#6c7983",
58 | colorTextPrimary: "#FFFFFF",
59 | colorTextLight: "#f8f8f8",
60 | colorbackground: "#FBFBFD",
61 | colorGradient: "linear-gradient(110.42deg, #CF57A3 29.2%, #4731B6 63.56%)",
62 | colorGreenDark: "#27AE60",
63 | colorGreenLight: "#dbe1e8",
64 | activeNavLink: "rgba(230,230,230, .87)",
65 | activeNavLinkHover: "#C5C5C5",
66 | colorPrimary: "#7263F3",
67 | colorPrimary2: "#705DF2",
68 | colorGreyDark: "#131313",
69 | colorGrey0: "#f8f8f8",
70 | colorGrey1: "#dbe1e8",
71 | colorGrey2: "#b2becd",
72 | colorGrey3: "#6c7983",
73 | colorGrey4: "#454e56",
74 | colorGrey5: "#2a2e35",
75 | colorGrey6: "#12181b",
76 | colorWhite: "#fff",
77 | buttonGradient1:
78 | "linear-gradient(110.42deg, rgba(107, 190, 146, 0.7) 29.2%, rgba(245, 102, 146, 0.7) 63.56%)",
79 | buttonGradient2:
80 | "linear-gradient(110.42deg, rgba(25, 151, 222, 0.7) 29.2%, rgba(168, 85, 247, 0.7) 63.56%)",
81 | buttonGradient3:
82 | "linear-gradient(110.42deg, rgba(25, 151, 222, 0.7) 29.2%, rgba(168, 85, 247, 0.7) 63.56%)",
83 | buttonGradient4:
84 | "linear-gradient(110.42deg, rgba(168, 85, 247, 0.7) 29.2%, rgba(245, 102, 146, 0.7) 63.56%)",
85 | buttonGradient5:
86 | "linear-gradient(110.42deg, rgba(25, 151, 222, 0.7) 29.2%, rgba(168, 85, 247, 0.7) 63.56%)",
87 | buttonGradient6:
88 | "linear-gradient(110.42deg, rgba(25, 151, 222, 0.7) 29.2%, rgba(168, 85, 247, 0.7) 63.56%)",
89 | buttonGradient7:
90 | "linear-gradient(110.42deg, rgba(41, 25, 222, 0.7) 29.2%, rgba(235, 87, 87, 0.7) 63.56%)",
91 | buttonGradient8:
92 | "linear-gradient(110.42deg, rgba(25, 151, 222, 0.7) 29.2%, rgba(168, 85, 247, 0.7) 63.56%)",
93 | buttonGradient9:
94 | "linear-gradient(110.42deg, rgba(226, 195, 33, 0.7) 29.2%, rgba(247, 104, 85, 0.7) 63.56%)",
95 | buttonGradient10:
96 | "linear-gradient(110.42deg, rgba(235, 87, 87, 0.7) 29.2%, rgba(189, 68, 166, 0.7) 53.82%, rgba(247, 85, 143, 0.1) 63.56%)",
97 | buttonGradient11:
98 | "linear-gradient(110.42deg, rgba(25, 151, 222, 0.7) 29.2%, rgba(168, 85, 247, 0.7) 63.56%)",
99 | buttonGradient12:
100 | "linear-gradient(110.42deg, rgba(226, 195, 33, 0.7) 29.2%, rgba(247, 104, 85, 0.7) 63.56%)",
101 | buttonGradient13:
102 | "linear-gradient(110.42deg, rgba(226, 195, 33, 0.7) 29.2%, rgba(99, 3, 255, 0.7) 63.56%)",
103 | buttonGradient14:
104 | "linear-gradient(110.42deg, rgba(41, 25, 222, 0.7) 29.2%, rgba(235, 87, 87, 0.7) 63.56%)",
105 | borderRadiusMd: "12px",
106 | borderRadiusMd2: "15px",
107 | borderRadiusSm: "8px",
108 | borderColor: "rgba(249,249,249, 0.08)",
109 | borderColor2: "rgba(249,249,249, 0.08)",
110 | shadow1: "4px 4px 84px rgba(16, 10, 86, 0.04)",
111 | shadow2: "0px 48px 77px rgba(8, 18, 69, 0.07)",
112 | shadow3: "0 14px 40px rgb(0 0 0 / 25%)",
113 | shadow7: "0px 48px 77px rgba(8, 18, 69, 0.16)",
114 | shadow5: "0px 4px 0px rgba(249,249,249, 0.35)",
115 | shadow6:
116 | "0px 6px 20px rgba(0, 0, 0, 0.15), 0px -3px 20px rgba(0, 0, 0, 0.1)",
117 | sidebarWidth: "15rem",
118 | sidebarCollapsed: "5.4rem",
119 | fH4: "19px",
120 | fontSmall: "14px",
121 | fontSmall2: "15px",
122 | gridGap: "2rem",
123 | padLRSm: "0 2rem",
124 | colorIcons: "#999999",
125 | colorIcons2: "#181818",
126 | colorIcons3: "rgba(249,249,249, 0.08)",
127 | colorIcons4: "rgba(0,0,0, 0.3)",
128 | marLRSm: "0 1rem",
129 | },
130 | ];
131 | export default themes;
132 |
--------------------------------------------------------------------------------
/app/Components/Modals/CreateContent.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useGlobalState } from "@/app/context/globalProvider";
3 | import axios from "axios";
4 | import React, { useState } from "react";
5 | import toast from "react-hot-toast";
6 | import styled from "styled-components";
7 | import Button from "../Button/Button";
8 | import { add, plus } from "@/app/utils/Icons";
9 |
10 | function CreateContent() {
11 | const [title, setTitle] = useState("");
12 | const [description, setDescription] = useState("");
13 | const [date, setDate] = useState("");
14 | const [completed, setCompleted] = useState(false);
15 | const [important, setImportant] = useState(false);
16 |
17 | const { theme, allTasks, closeModal } = useGlobalState();
18 |
19 | const handleChange = (name: string) => (e: any) => {
20 | switch (name) {
21 | case "title":
22 | setTitle(e.target.value);
23 | break;
24 | case "description":
25 | setDescription(e.target.value);
26 | break;
27 | case "date":
28 | setDate(e.target.value);
29 | break;
30 | case "completed":
31 | setCompleted(e.target.checked);
32 | break;
33 | case "important":
34 | setImportant(e.target.checked);
35 | break;
36 | default:
37 | break;
38 | }
39 | };
40 |
41 | const handleSubmit = async (e: any) => {
42 | e.preventDefault();
43 |
44 | const task = {
45 | title,
46 | description,
47 | date,
48 | completed,
49 | important,
50 | };
51 |
52 | try {
53 | const res = await axios.post("/api/tasks", task);
54 |
55 | if (res.data.error) {
56 | toast.error(res.data.error);
57 | }
58 |
59 | if (!res.data.error) {
60 | toast.success("Task created successfully.");
61 | allTasks();
62 | closeModal();
63 | }
64 | } catch (error) {
65 | toast.error("Something went wrong.");
66 | console.log(error);
67 | }
68 | };
69 |
70 | return (
71 |
72 | Create a Task
73 |
74 |
75 |
83 |
84 |
85 |
86 |
94 |
95 |
96 |
97 |
104 |
105 |
106 |
107 |
114 |
115 |
116 |
117 |
124 |
125 |
126 |
127 |
137 |
138 |
139 | );
140 | }
141 |
142 | const CreateContentStyled = styled.form`
143 | > h1 {
144 | font-size: clamp(1.2rem, 5vw, 1.6rem);
145 | font-weight: 600;
146 | }
147 |
148 | color: ${(props) => props.theme.colorGrey1};
149 |
150 | .input-control {
151 | position: relative;
152 | margin: 1.6rem 0;
153 | font-weight: 500;
154 |
155 | @media screen and (max-width: 450px) {
156 | margin: 1rem 0;
157 | }
158 |
159 | label {
160 | margin-bottom: 0.5rem;
161 | display: inline-block;
162 | font-size: clamp(0.9rem, 5vw, 1.2rem);
163 |
164 | span {
165 | color: ${(props) => props.theme.colorGrey3};
166 | }
167 | }
168 |
169 | input,
170 | textarea {
171 | width: 100%;
172 | padding: 1rem;
173 |
174 | resize: none;
175 | background-color: ${(props) => props.theme.colorGreyDark};
176 | color: ${(props) => props.theme.colorGrey2};
177 | border-radius: 0.5rem;
178 | }
179 | }
180 |
181 | .submit-btn button {
182 | transition: all 0.35s ease-in-out;
183 |
184 | @media screen and (max-width: 500px) {
185 | font-size: 0.9rem !important;
186 | padding: 0.6rem 1rem !important;
187 |
188 | i {
189 | font-size: 1.2rem !important;
190 | margin-right: 0.5rem !important;
191 | }
192 | }
193 |
194 | i {
195 | color: ${(props) => props.theme.colorGrey0};
196 | }
197 |
198 | &:hover {
199 | background: ${(props) => props.theme.colorPrimaryGreen} !important;
200 | color: ${(props) => props.theme.colorWhite} !important;
201 | }
202 | }
203 |
204 | .toggler {
205 | display: flex;
206 | align-items: center;
207 | justify-content: space-between;
208 |
209 | cursor: pointer;
210 |
211 | label {
212 | flex: 1;
213 | }
214 |
215 | input {
216 | width: initial;
217 | }
218 | }
219 | `;
220 |
221 | export default CreateContent;
222 |
--------------------------------------------------------------------------------
/app/Components/Sidebar/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React from "react";
3 | import styled from "styled-components";
4 | import { useGlobalState } from "@/app/context/globalProvider";
5 | import Image from "next/image";
6 |
7 | import menu from "@/app/utils/menu";
8 | import Link from "next/link";
9 | import { usePathname, useRouter } from "next/navigation";
10 | import Button from "../Button/Button";
11 | import { arrowLeft, bars, logout } from "@/app/utils/Icons";
12 | import { UserButton, useClerk, useUser } from "@clerk/nextjs";
13 |
14 | function Sidebar() {
15 | const { theme, collapsed, collapseMenu } = useGlobalState();
16 | const { signOut } = useClerk();
17 |
18 | const { user } = useUser();
19 |
20 | const { firstName, lastName, imageUrl } = user || {
21 | firstName: "",
22 | lastName: "",
23 | imageUrl: "",
24 | };
25 |
26 | const router = useRouter();
27 | const pathname = usePathname();
28 |
29 | const handleClick = (link: string) => {
30 | router.push(link);
31 | };
32 |
33 | return (
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {firstName} {lastName}
48 |
49 |
50 |
51 | {menu.map((item) => {
52 | const link = item.link;
53 | return (
54 | - {
58 | handleClick(link);
59 | }}
60 | >
61 | {item.icon}
62 | {item.title}
63 |
64 | );
65 | })}
66 |
67 |
68 |
81 |
82 | );
83 | }
84 |
85 | const SidebarStyled = styled.nav<{ collapsed: boolean }>`
86 | position: relative;
87 | width: ${(props) => props.theme.sidebarWidth};
88 | background-color: ${(props) => props.theme.colorBg2};
89 | border: 2px solid ${(props) => props.theme.borderColor2};
90 | border-radius: 1rem;
91 |
92 | display: flex;
93 | flex-direction: column;
94 | justify-content: space-between;
95 |
96 | color: ${(props) => props.theme.colorGrey3};
97 |
98 | @media screen and (max-width: 768px) {
99 | position: fixed;
100 | height: calc(100vh - 2rem);
101 | z-index: 100;
102 |
103 | transition: all 0.3s cubic-bezier(0.53, 0.21, 0, 1);
104 | transform: ${(props) =>
105 | props.collapsed ? "translateX(-107%)" : "translateX(0)"};
106 |
107 | .toggle-nav {
108 | display: block !important;
109 | }
110 | }
111 |
112 | .toggle-nav {
113 | display: none;
114 | padding: 0.8rem 0.9rem;
115 | position: absolute;
116 | right: -69px;
117 | top: 1.8rem;
118 |
119 | border-top-right-radius: 1rem;
120 | border-bottom-right-radius: 1rem;
121 |
122 | background-color: ${(props) => props.theme.colorBg2};
123 | border-right: 2px solid ${(props) => props.theme.borderColor2};
124 | border-top: 2px solid ${(props) => props.theme.borderColor2};
125 | border-bottom: 2px solid ${(props) => props.theme.borderColor2};
126 | }
127 |
128 | .user-btn {
129 | .cl-rootBox {
130 | width: 100%;
131 | height: 100%;
132 |
133 | .cl-userButtonBox {
134 | width: 100%;
135 | height: 100%;
136 |
137 | .cl-userButtonTrigger {
138 | width: 100%;
139 | height: 100%;
140 | opacity: 0;
141 | }
142 | }
143 | }
144 | }
145 |
146 | .profile {
147 | margin: 1.5rem;
148 | padding: 1rem 0.8rem;
149 | position: relative;
150 |
151 | border-radius: 1rem;
152 | cursor: pointer;
153 |
154 | font-weight: 500;
155 | color: ${(props) => props.theme.colorGrey0};
156 |
157 | display: flex;
158 | align-items: center;
159 |
160 | .profile-overlay {
161 | position: absolute;
162 | top: 0;
163 | left: 0;
164 | width: 100%;
165 | height: 100%;
166 | backdrop-filter: blur(10px);
167 | z-index: 0;
168 | background: ${(props) => props.theme.colorBg3};
169 | transition: all 0.55s linear;
170 | border-radius: 1rem;
171 | border: 2px solid ${(props) => props.theme.borderColor2};
172 |
173 | opacity: 0.2;
174 | }
175 |
176 | h1 {
177 | font-size: 1.2rem;
178 | display: flex;
179 | flex-direction: column;
180 |
181 | line-height: 1.4rem;
182 | }
183 |
184 | .image,
185 | h1 {
186 | position: relative;
187 | z-index: 1;
188 | }
189 |
190 | .image {
191 | flex-shrink: 0;
192 | display: inline-block;
193 | overflow: hidden;
194 | transition: all 0.5s ease;
195 | border-radius: 100%;
196 |
197 | width: 70px;
198 | height: 70px;
199 |
200 | img {
201 | border-radius: 100%;
202 | transition: all 0.5s ease;
203 | }
204 | }
205 |
206 | > h1 {
207 | margin-left: 0.8rem;
208 | font-size: clamp(1.2rem, 4vw, 1.4rem);
209 | line-height: 100%;
210 | }
211 |
212 | &:hover {
213 | .profile-overlay {
214 | opacity: 1;
215 | border: 2px solid ${(props) => props.theme.borderColor2};
216 | }
217 |
218 | img {
219 | transform: scale(1.1);
220 | }
221 | }
222 | }
223 |
224 | .nav-item {
225 | position: relative;
226 | padding: 0.8rem 1rem 0.9rem 2.1rem;
227 | margin: 0.3rem 0;
228 |
229 | display: grid;
230 | grid-template-columns: 40px 1fr;
231 | cursor: pointer;
232 | align-items: center;
233 |
234 | &::after {
235 | position: absolute;
236 | content: "";
237 | left: 0;
238 | top: 0;
239 | width: 0;
240 | height: 100%;
241 | background-color: ${(props) => props.theme.activeNavLinkHover};
242 | z-index: 1;
243 | transition: all 0.3s ease-in-out;
244 | }
245 |
246 | &::before {
247 | position: absolute;
248 | content: "";
249 | right: 0;
250 | top: 0;
251 | width: 0%;
252 | height: 100%;
253 | background-color: ${(props) => props.theme.colorGreenDark};
254 |
255 | border-bottom-left-radius: 5px;
256 | border-top-left-radius: 5px;
257 | }
258 |
259 | a {
260 | font-weight: 500;
261 | transition: all 0.3s ease-in-out;
262 | z-index: 2;
263 | line-height: 0;
264 | }
265 |
266 | i {
267 | display: flex;
268 | align-items: center;
269 | color: ${(props) => props.theme.colorIcons};
270 | }
271 |
272 | &:hover {
273 | &::after {
274 | width: 100%;
275 | }
276 | }
277 | }
278 |
279 | .active {
280 | background-color: ${(props) => props.theme.activeNavLink};
281 |
282 | i,
283 | a {
284 | color: ${(props) => props.theme.colorIcons2};
285 | }
286 | }
287 |
288 | .active::before {
289 | width: 0.3rem;
290 | }
291 |
292 | > button {
293 | margin: 1.5rem;
294 | }
295 | `;
296 |
297 | export default Sidebar;
298 |
--------------------------------------------------------------------------------