├── .dockerignore ├── .github └── workflows │ ├── cd_frontend.yml │ └── cd_ws.yml ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── apps ├── collabydraw │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── actions │ │ ├── auth.ts │ │ ├── room.ts │ │ └── shape.ts │ ├── app │ │ ├── (auth-layout) │ │ │ ├── auth │ │ │ │ ├── signin │ │ │ │ │ └── page.tsx │ │ │ │ └── signup │ │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── (main) │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── (site) │ │ │ └── about │ │ │ │ └── page.tsx │ │ ├── api │ │ │ └── auth │ │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── provider.tsx │ ├── canvas-engine │ │ ├── CanvasEngine.ts │ │ ├── MessageQueue.ts │ │ └── SelectionController.ts │ ├── components.json │ ├── components │ │ ├── AppMenuButton.tsx │ │ ├── AppSidebar.tsx │ │ ├── Branding.tsx │ │ ├── CollabAuthPrompt.tsx │ │ ├── CollaborationButton.tsx │ │ ├── CollaborationToolbar.tsx │ │ ├── Collabydraw-TextEditorContainer.tsx │ │ ├── CreateRoomDialog.tsx │ │ ├── EncryptedWidget.tsx │ │ ├── Footer.tsx │ │ ├── ItemLabel.tsx │ │ ├── MobileCommandBar.tsx │ │ ├── RoomSharingDialog.tsx │ │ ├── ScreenLoading.tsx │ │ ├── SideToolbar.tsx │ │ ├── SignOutButton.tsx │ │ ├── SignupWelcomeButton.tsx │ │ ├── StyleConfigurator.tsx │ │ ├── SvgIcons.tsx │ │ ├── ToolButton.tsx │ │ ├── ToolSelector.tsx │ │ ├── UserMenu.tsx │ │ ├── UserRoomsList.tsx │ │ ├── UserRoomsListDialog.tsx │ │ ├── ZoomControl.tsx │ │ ├── auth │ │ │ ├── signin-form.tsx │ │ │ └── signup-form.tsx │ │ ├── canvas │ │ │ └── CanvasBoard.tsx │ │ ├── clear-canvas-dialog.tsx │ │ ├── color-board.tsx │ │ ├── color-picker.tsx │ │ ├── drawing-sidebar.tsx │ │ ├── drawing-toolbar.tsx │ │ ├── eraser-cursor.tsx │ │ ├── theme-provider.tsx │ │ ├── ui │ │ │ ├── alert-dialog.tsx │ │ │ ├── avatar.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ └── tooltip.tsx │ │ └── welcome-screen.tsx │ ├── config │ │ ├── canvasTypeMappings.tsx │ │ └── constants.ts │ ├── content │ │ └── detailsContent.ts │ ├── eslint.config.mjs │ ├── global.d.ts │ ├── hooks │ │ ├── use-mobile.tsx │ │ └── useMediaQuery.ts │ ├── lib │ │ └── utils.ts │ ├── middleware.ts │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── bg-transparent-checkerboard.png │ │ ├── brand │ │ │ ├── CollabyDraw1.png │ │ │ ├── CollabyDraw2.png │ │ │ ├── CollabyDraw3.png │ │ │ ├── CollabyDraw4.png │ │ │ ├── CollabyDraw5.png │ │ │ ├── CollabyDrawLogo.png │ │ │ ├── CollabyDraw_Signin.png │ │ │ ├── CollabyDraw_Signup.png │ │ │ └── favicon.png │ │ ├── checkerboard-dark.png │ │ ├── file.svg │ │ ├── fonts │ │ │ └── Collabyfont-Regular.woff2 │ │ ├── globe.svg │ │ ├── google_symbol.png │ │ ├── next.svg │ │ ├── vercel.svg │ │ └── window.svg │ ├── shape-render │ │ ├── RenderElements.ts │ │ └── roundRect.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── types │ │ ├── Tools.tsx │ │ ├── canvas.ts │ │ ├── colors.ts │ │ ├── next-auth.d.ts │ │ └── utility-types.ts │ └── utils │ │ ├── auth.ts │ │ ├── crypto.ts │ │ ├── getClientColor.ts │ │ ├── getStreamKey.ts │ │ ├── index.ts │ │ ├── metadata.ts │ │ ├── roomParams.ts │ │ ├── svgIcons.tsx │ │ └── textUtils.ts └── ws │ ├── .env.example │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── docker-compose.yml ├── docker ├── Dockerfile.frontend └── Dockerfile.websocket ├── package.json ├── packages ├── common │ ├── package.json │ ├── src │ │ └── types.ts │ └── tsconfig.json ├── db │ ├── .env.example │ ├── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── prisma │ │ ├── migrations │ │ │ ├── 20250213082803_initial_setup │ │ │ │ └── migration.sql │ │ │ ├── 20250216062805_added_next_auth │ │ │ │ └── migration.sql │ │ │ ├── 20250216141735_redesign_for_auth │ │ │ │ └── migration.sql │ │ │ ├── 20250316165315_add_shape_table │ │ │ │ └── migration.sql │ │ │ ├── 20250322022212_remove_unused_tables_including_chat │ │ │ │ └── migration.sql │ │ │ ├── 20250330142503_remove_slug_from_room_table │ │ │ │ └── migration.sql │ │ │ ├── 20250330151218_remove_slug_from_room │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── eslint-config │ ├── README.md │ ├── base.js │ ├── next.js │ ├── package.json │ └── react-internal.js ├── typescript-config │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ ├── button.tsx │ ├── card.tsx │ ├── code.tsx │ └── input.tsx │ ├── tsconfig.json │ └── turbo │ └── generators │ ├── config.ts │ └── templates │ └── component.hbs ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.github/workflows/cd_frontend.yml: -------------------------------------------------------------------------------- 1 | name: Deploy the frontend 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - 'apps/collabydraw/**' 7 | - 'packages/**' 8 | - '.github/workflows/cd_frontend.yml' 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout the code 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 2 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v3 20 | 21 | - name: Docker login 22 | uses: docker/login-action@v3 23 | with: 24 | username: ${{ secrets.DOCKER_USERNAME }} 25 | password: ${{ secrets.DOCKER_PASSWORD }} 26 | 27 | - name: Verify secrets 28 | run: | 29 | if [ -z "${{ secrets.DATABASE_URL }}" ]; then 30 | echo "Error: DATABASE_URL secret is not set" 31 | exit 1 32 | fi 33 | if [ -z "${{ secrets.JWT_SECRET }}" ]; then 34 | echo "Error: JWT_SECRET secret is not set" 35 | exit 1 36 | fi 37 | $secrets_valid 38 | 39 | - name: Build and push Frontend 40 | uses: docker/build-push-action@v5 41 | with: 42 | context: . 43 | file: ./docker/Dockerfile.frontend 44 | provenance: false 45 | build-args: | 46 | DATABASE_URL=${{ secrets.DATABASE_URL }} 47 | JWT_SECRET=${{ secrets.JWT_SECRET }} 48 | WEBSOCKET_URL=${{ secrets.WEBSOCKET_URL }} 49 | push: true 50 | tags: | 51 | coderomm/collabydraw:latest 52 | coderomm/collabydraw:${{ github.sha }} 53 | 54 | - name: Check SSH key presence (for debug) 55 | run: | 56 | echo "${{ secrets.SSH_PRIVATE_KEY }}" | head -n 15 57 | 58 | # - name: Deploy to VM 59 | # uses: appleboy/ssh-action@v1.1.0 60 | # with: 61 | # host: ${{ secrets.VM_HOST }} 62 | # username: ${{ secrets.VM_USERNAME }} 63 | # key: ${{ secrets.SSH_PRIVATE_KEY }} 64 | # script: | 65 | # docker pull coderomm/collabydraw:${{ github.sha }} 66 | # docker stop collabydraw-frontend || true 67 | # docker rm collabydraw-frontend || true 68 | # docker run -d \ 69 | # --name collabydraw-frontend \ 70 | # --restart always \ 71 | # -p 3000:3000 \ 72 | # -e DATABASE_URL=${{ secrets.DATABASE_URL }} \ 73 | # -e JWT_SECRET=${{ secrets.JWT_SECRET }} \ 74 | # -e WEBSOCKET_URL=${{ secrets.WEBSOCKET_URL }} \ 75 | # coderomm/collabydraw:${{ github.sha }} 76 | 77 | -------------------------------------------------------------------------------- /.github/workflows/cd_ws.yml: -------------------------------------------------------------------------------- 1 | name: Deploy the websocket server 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - 'apps/ws/**' 7 | - 'packages/**' 8 | - '.github/workflows/cd_ws.yml' 9 | 10 | jobs: 11 | build-and-deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout the code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v3 19 | 20 | - name: Docker login 21 | uses: docker/login-action@v2 22 | with: 23 | username: ${{ secrets.DOCKER_USERNAME }} 24 | password: ${{ secrets.DOCKER_PASSWORD }} 25 | 26 | - name: Build and push 27 | uses: docker/build-push-action@v4 28 | with: 29 | context: . 30 | file: ./docker/Dockerfile.websocket 31 | provenance: false 32 | push: true 33 | tags: | 34 | coderomm/collabydraw-websocket:latest 35 | coderomm/collabydraw-websocket:${{ github.sha }} 36 | 37 | - name: Check SSH key presence (for debug) 38 | run: | 39 | echo "${{ secrets.SSH_PRIVATE_KEY }}" | head -n 15 40 | 41 | # - name: Deploy to VM 42 | # uses: appleboy/ssh-action@v1.1.0 43 | # with: 44 | # host: ${{ secrets.VM_HOST }} 45 | # username: ${{ secrets.VM_USERNAME }} 46 | # key: ${{ secrets.SSH_PRIVATE_KEY }} 47 | # script: | 48 | # docker pull coderomm/collabydraw-websocket:${{ github.sha }} 49 | # docker stop collabydraw-websocket || true 50 | # docker rm collabydraw-websocket || true 51 | # docker run -d \ 52 | # --name collabydraw-websocket \ 53 | # --restart always \ 54 | # -p 8080:8080 \ 55 | # -e DATABASE_URL=${{ secrets.DATABASE_URL }} \ 56 | # coderomm/collabydraw-websocket:${{ github.sha }} 57 | -------------------------------------------------------------------------------- /.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 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | 40 | # docker 41 | docker 42 | .vercel 43 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderomm/CollabyDraw/02dc5c424a79afb930864e8e8eab23d01dc3e8a6/.npmrc -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CollabyDraw Custom License 2 | Copyright (c) 2025 Om Sharma 3 | 4 | Permission is granted to anyone to view, use, and learn from this source code for **personal and educational purposes only**. 5 | 6 | You are **not allowed** to: 7 | - Use this code, in whole or in part, for any **commercial** purpose. 8 | - Sell, sublicense, or distribute this project or its derivatives. 9 | - Remove or alter any original author credit. 10 | - Claim authorship or ownership of the code or project. 11 | - Re-upload, fork, or host this code as your own work. 12 | - Deploy this app as a hosted service (SaaS) without explicit written permission. 13 | - Use the name “CollabyDraw” or any branding assets without permission. 14 | 15 | You **must**: 16 | - Credit Om Sharma if you publicly showcase or share code derived from this project. 17 | 18 | Violation of any of these terms will be considered a breach of license and may lead to legal action. 19 | 20 | For commercial use, hosting, or licensing inquiries, contact: omsharma.dev@gmail.com -------------------------------------------------------------------------------- /apps/collabydraw/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL='postgresql://postgres:your_secure_password@localhost:5432/CollabyDraw' 2 | NEXT_PUBLIC_BASE_URL="http://localhost:3000" 3 | NEXT_PUBLIC_WS_URL="ws://localhost:8080" 4 | JWT_SECRET=your_secret_here -------------------------------------------------------------------------------- /apps/collabydraw/.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.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env 35 | .env.local 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | -------------------------------------------------------------------------------- /apps/collabydraw/README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/user-attachments/assets/63df71e4-11fb-43c1-abb2-eaa31c670a9d) 2 | 3 | ![image](https://github.com/user-attachments/assets/348e548a-cce9-49cd-b6c4-a234fe6fd294) 4 | 5 | ![image](https://github.com/user-attachments/assets/55e13e49-9443-459b-bd68-d651839c24d8) 6 | 7 | ![image](https://github.com/user-attachments/assets/313ed11f-5c18-4b7b-983b-b33b8d370fe7) 8 | 9 | 10 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 11 | 12 | ## Getting Started 13 | 14 | First, run the development server: 15 | 16 | ```bash 17 | npm run dev 18 | # or 19 | yarn dev 20 | # or 21 | pnpm dev 22 | # or 23 | bun dev 24 | ``` 25 | 26 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 27 | 28 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 29 | 30 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 31 | 32 | ## Learn More 33 | 34 | To learn more about Next.js, take a look at the following resources: 35 | 36 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 37 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 38 | 39 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 40 | 41 | ## Deploy on Vercel 42 | 43 | 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. 44 | 45 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 46 | -------------------------------------------------------------------------------- /apps/collabydraw/actions/auth.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { SignupSchema } from '@repo/common/types'; 4 | import client from '@repo/db/client'; 5 | import bcrypt from "bcrypt"; 6 | import { z } from 'zod'; 7 | 8 | export async function signUp(values: z.infer) { 9 | const validatedFields = SignupSchema.safeParse(values); 10 | 11 | if (!validatedFields.success) { 12 | return { error: "Invalid fields." }; 13 | } 14 | 15 | const { name, email, password } = validatedFields.data; 16 | 17 | const existingUser = await client.user.findFirst({ 18 | where: { 19 | OR: [{ email }, { name }] 20 | } 21 | }); 22 | 23 | if (existingUser) { 24 | return { error: "User already exists." }; 25 | } 26 | 27 | const hashedPassword = await bcrypt.hash(password, 10); 28 | 29 | try { 30 | await client.user.create({ 31 | data: { 32 | name, 33 | email, 34 | password: hashedPassword, 35 | }, 36 | }); 37 | 38 | return { success: true }; 39 | } catch (error) { 40 | const errorMessage = error instanceof Error ? error.message : 'Some Brutal Error'; 41 | console.error("Error: ", errorMessage) 42 | return { error: "Error creating user." }; 43 | } 44 | } -------------------------------------------------------------------------------- /apps/collabydraw/actions/room.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { z } from "zod"; 4 | import client from "@repo/db/client"; 5 | import { getServerSession } from "next-auth"; 6 | import { authOptions } from "@/utils/auth"; 7 | import { cookies } from "next/headers"; 8 | 9 | export async function joinRoom(data: { id: string }) { 10 | try { 11 | const room = await client.room.findUnique({ 12 | where: { id: data.id }, 13 | }); 14 | 15 | if (!room) { 16 | return { success: false, error: "Room not found" }; 17 | } 18 | 19 | const session = await getServerSession(authOptions); 20 | const cookieToken = session?.accessToken; 21 | 22 | if (!cookieToken) { 23 | console.error("session.accessToken not found in join room server action"); 24 | return; 25 | } 26 | 27 | (await cookies()).set("accessToken", cookieToken, { 28 | maxAge: 60 * 60 * 24 * 7, 29 | httpOnly: true, 30 | path: "/", 31 | sameSite: "lax", 32 | secure: false, 33 | }); 34 | 35 | return { 36 | success: true, 37 | room: room, 38 | }; 39 | } catch (error) { 40 | if (error instanceof z.ZodError) { 41 | return { success: false, error: "Invalid room code format" }; 42 | } 43 | console.error("Failed to join room:", error); 44 | return { success: false, error: "Failed to join room" }; 45 | } 46 | } 47 | 48 | export async function createRoom() { 49 | try { 50 | const session = await getServerSession(authOptions); 51 | const user = session?.user; 52 | 53 | if (!user || !user.id) { 54 | return { success: false, error: "User not found" }; 55 | } 56 | 57 | const room = await client.room.create({ 58 | data: { 59 | adminId: user.id, 60 | }, 61 | }); 62 | 63 | return { 64 | success: true, 65 | room, 66 | }; 67 | } catch (error) { 68 | if (error instanceof z.ZodError) { 69 | return { 70 | success: false, 71 | error: "Invalid room name format", 72 | errorMessage: error.message, 73 | }; 74 | } 75 | console.error("Failed to create room:", error); 76 | return { success: false, error: "Failed to create room" }; 77 | } 78 | } 79 | 80 | export async function getRoom(data: { id: string }) { 81 | try { 82 | const room = await client.room.findUnique({ 83 | where: { id: data.id }, 84 | include: { Shape: true }, 85 | }); 86 | 87 | if (!room) { 88 | return { success: false, error: "Room not found" }; 89 | } 90 | 91 | const session = await getServerSession(authOptions); 92 | const cookieToken = session?.accessToken; 93 | 94 | if (!cookieToken) { 95 | console.error("session.accessToken not found in join room server action"); 96 | return; 97 | } 98 | 99 | (await cookies()).set("accessToken", cookieToken, { 100 | maxAge: 60 * 60 * 24 * 7, 101 | httpOnly: true, 102 | path: "/", 103 | sameSite: "lax", 104 | secure: false, 105 | }); 106 | 107 | return { 108 | success: true, 109 | room: room, 110 | }; 111 | } catch (error) { 112 | if (error instanceof z.ZodError) { 113 | return { success: false, error: "Invalid room code format" }; 114 | } 115 | console.error("Failed to join room:", error); 116 | return { success: false, error: "Failed to join room" }; 117 | } 118 | } 119 | 120 | export async function deleteRoom(data: { id: string }) { 121 | try { 122 | const session = await getServerSession(authOptions); 123 | 124 | if (!session || !session.user || !session.user.id) { 125 | return { success: false, error: "Authentication required" }; 126 | } 127 | 128 | const room = await client.room.findUnique({ 129 | where: { id: data.id }, 130 | include: { admin: true }, 131 | }); 132 | 133 | if (!room) { 134 | return { success: false, error: "Room not found" }; 135 | } 136 | 137 | if (room.adminId !== session.user.id) { 138 | return { 139 | success: false, 140 | error: "Unauthorized: Only the room creator can delete this room", 141 | }; 142 | } 143 | 144 | await client.shape.deleteMany({ 145 | where: { roomId: room.id }, 146 | }); 147 | 148 | await client.room.delete({ 149 | where: { id: room.id }, 150 | }); 151 | 152 | return { success: true, message: "Room deleted successfully" }; 153 | } catch (error) { 154 | if (error instanceof z.ZodError) { 155 | return { success: false, error: "Invalid room name format" }; 156 | } 157 | console.error("Failed to delete room:", error); 158 | return { success: false, error: "Failed to delete room" }; 159 | } 160 | } 161 | 162 | export async function getUserRooms() { 163 | try { 164 | const session = await getServerSession(authOptions); 165 | const user = session?.user; 166 | 167 | if (!user || !user.id) { 168 | return { success: false, error: "User not authenticated" }; 169 | } 170 | 171 | const rooms = await client.room.findMany({ 172 | where: { adminId: user.id }, 173 | select: { 174 | id: true, 175 | createdAt: true, 176 | updatedAt: true, 177 | }, 178 | }); 179 | 180 | return { success: true, rooms }; 181 | } catch (error) { 182 | console.error("Failed to fetch user rooms:", error); 183 | return { success: false, error: "Failed to fetch user rooms" }; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /apps/collabydraw/actions/shape.ts: -------------------------------------------------------------------------------- 1 | // "use server"; 2 | 3 | // import { z } from "zod"; 4 | // import client from "@repo/db/client"; 5 | // import { JoinRoomSchema } from "@repo/common/types"; 6 | // import { getServerSession } from "next-auth"; 7 | // import { authOptions } from "@/utils/auth"; 8 | // import { Shape } from "@/types/canvas"; 9 | 10 | // export async function getShapes(data: { roomName: string }) { 11 | // try { 12 | // const validatedRoomName = JoinRoomSchema.parse(data); 13 | 14 | // const room = await client.room.findUnique({ 15 | // where: { slug: validatedRoomName.roomName }, 16 | // }); 17 | 18 | // if (!room || !room.id) { 19 | // return { success: false, error: "Room not found" }; 20 | // } 21 | 22 | // const shapesResponse = await client.shape.findMany({ 23 | // where: { roomId: room.id }, 24 | // }); 25 | 26 | // if (!shapesResponse.length) { 27 | // return { success: true, shapes: [] }; 28 | // } 29 | 30 | // const shapes: Shape[] = shapesResponse.map((x) => JSON.parse(x.message)); 31 | 32 | // return { success: true, shapes }; 33 | // } catch (error) { 34 | // if (error instanceof z.ZodError) { 35 | // return { success: false, error: "Invalid room code format" }; 36 | // } 37 | // console.error("Failed to get shapes:", error); 38 | // return { success: false, error: "Failed to get shapes" }; 39 | // } 40 | // } 41 | 42 | // export async function clearAllShapes(data: { roomName: string }) { 43 | // try { 44 | // const session = await getServerSession(authOptions); 45 | 46 | // if (!session || !session.user || !session.user.email) { 47 | // return { success: false, error: "Authentication required" }; 48 | // } 49 | 50 | // const userEmail = session.user.email; 51 | // const validatedRoomName = JoinRoomSchema.parse(data); 52 | 53 | // const room = await client.room.findUnique({ 54 | // where: { slug: validatedRoomName.roomName }, 55 | // include: { admin: true }, 56 | // }); 57 | 58 | // if (!room || !room.id) { 59 | // return { success: false, error: "Room not found" }; 60 | // } 61 | 62 | // if (room.admin.email !== userEmail) { 63 | // return { 64 | // success: false, 65 | // error: "Unauthorized: Only the room creator can clear chats", 66 | // }; 67 | // } 68 | 69 | // const result = await client.shape.deleteMany({ 70 | // where: { roomId: room.id }, 71 | // }); 72 | 73 | // return { 74 | // success: true, 75 | // count: result.count, 76 | // }; 77 | // } catch (error) { 78 | // if (error instanceof z.ZodError) { 79 | // return { success: false, error: "Invalid room code format" }; 80 | // } 81 | // console.error("Failed to clear shapes:", error); 82 | // return { success: false, error: "Failed to clear shapes" }; 83 | // } 84 | // } 85 | -------------------------------------------------------------------------------- /apps/collabydraw/app/(auth-layout)/auth/signin/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignInForm } from "@/components/auth/signin-form"; 2 | import ScreenLoading from "@/components/ScreenLoading"; 3 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; 4 | import Link from "next/link"; 5 | import { Suspense } from "react"; 6 | 7 | export default function SignInPage() { 8 | return ( 9 | 10 | 11 | Hi there! 12 | Enter your email to sign in to your account 13 | 14 | 15 | }> 16 | 17 | 18 | 19 | 20 |
21 |
22 | or 23 |
24 |
25 |
26 | Don't have an account? Sign Up 27 |
28 |
29 | Back to Home 30 |
31 |
32 |
33 | ); 34 | } -------------------------------------------------------------------------------- /apps/collabydraw/app/(auth-layout)/auth/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUpForm } from "@/components/auth/signup-form"; 2 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; 3 | import Link from "next/link"; 4 | 5 | export default function SignUpPage() { 6 | return ( 7 | 8 | 9 | Create an account 10 | Enter your email below to create your account 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | or 19 |
20 |
21 |
22 | Already have an account? Sign In 23 |
24 |
25 | Back to Home 26 |
27 |
28 |
29 | ); 30 | } -------------------------------------------------------------------------------- /apps/collabydraw/app/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | export default async function MainLayout({ children }: { children: React.ReactNode; }) { 2 | return ( 3 | <> 4 |
{children}
5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /apps/collabydraw/app/(main)/loading.tsx: -------------------------------------------------------------------------------- 1 | import ScreenLoading from "@/components/ScreenLoading"; 2 | 3 | export default function Loading() { 4 | return ( 5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /apps/collabydraw/app/(main)/page.tsx: -------------------------------------------------------------------------------- 1 | import CanvasBoard from "@/components/canvas/CanvasBoard"; 2 | 3 | export default async function Home() { 4 | return ( 5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /apps/collabydraw/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import { authOptions } from "@/utils/auth"; 3 | 4 | const handler = NextAuth(authOptions); 5 | 6 | export { handler as GET, handler as POST }; -------------------------------------------------------------------------------- /apps/collabydraw/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderomm/CollabyDraw/02dc5c424a79afb930864e8e8eab23d01dc3e8a6/apps/collabydraw/app/favicon.ico -------------------------------------------------------------------------------- /apps/collabydraw/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Assistant, Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | import Provider from "./provider"; 5 | import { Toaster } from "sonner"; 6 | import { ThemeProvider } from "@/components/theme-provider"; 7 | import { baseMetadata, jsonLdSchema } from "@/utils/metadata"; 8 | import Script from "next/script"; 9 | import { Analytics } from "@vercel/analytics/react" 10 | 11 | const geistSans = Geist({ 12 | variable: "--font-geist-sans", 13 | subsets: ["latin"], 14 | weight: ["300", "400", "600", "700"], 15 | display: "swap", 16 | }); 17 | 18 | const geistMono = Geist_Mono({ 19 | variable: "--font-geist-mono", 20 | subsets: ["latin"], 21 | weight: ["300", "400", "600", "700"], 22 | display: "swap", 23 | }); 24 | 25 | const assistant = Assistant({ 26 | variable: "--font-assistant", 27 | subsets: ["latin"], 28 | weight: ["300", "400", "600", "700"], 29 | display: "swap", 30 | }); 31 | 32 | export const metadata: Metadata = baseMetadata; 33 | 34 | export default function RootLayout({ 35 | children, 36 | }: Readonly<{ 37 | children: React.ReactNode; 38 | }>) { 39 | return ( 40 | 41 | 42 |