├── 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 |
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 | profile 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 | --------------------------------------------------------------------------------