From 4f374a21338e81d2d78d742d60e7db52f7afd3c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:22:44 +0000 Subject: [PATCH 1/7] Initial plan From 422d8767b0e0cde7a087e70032411dbc3ca255e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:30:39 +0000 Subject: [PATCH 2/7] Add environment variable to limit drawings per user Co-authored-by: notkainoa <123281048+notkainoa@users.noreply.github.com> --- .env.example | 12 ++++++++++++ README.md | 11 +++++++++++ src/components/SearchCommand.tsx | 8 +++++++- src/components/Sidebar.tsx | 24 ++++++++++++++++++------ src/db/draw.ts | 28 ++++++++++++++++++++++++++++ src/views/Pages.tsx | 12 +++++++++--- 6 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a8358a5 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Supabase Configuration +VITE_SUPABASE_URL=your_supabase_url +VITE_SUPABASE_ANON_KEY=your_supabase_anon_key + +# Sentry Configuration (Optional) +VITE_SENTRY_DSN=your_sentry_dsn + +# Drawing Limit (Optional) +# Set the maximum number of drawings a user can create +# If not set, users can create unlimited drawings +# Example: VITE_MAX_DRAWINGS_PER_USER=10 +# VITE_MAX_DRAWINGS_PER_USER= diff --git a/README.md b/README.md index 7180344..a5b7a5c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Draw is a wrapper around Excalidraw, integrated with Supabase to save and sync y * Cloud Sync: Uses Supabase for authentication and storage, ensuring secure access and synchronization of your drawings. + Folders: Organize drawings into folders. + Sidebar: Navigate and manage folders and drawings from a sleek sidebar. ++ Drawing Limit: Optional environment variable to limit the number of drawings per user. - Clunky UI: Removed clunky, unnecessary ui components from the UI. ``` @@ -31,6 +32,16 @@ We have set up a one-click deploy to Vercel. [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/KainoaNewton/draw) +### Drawing Limit (Optional) + +You can optionally limit the number of drawings a user can create by setting the `VITE_MAX_DRAWINGS_PER_USER` environment variable in your Vercel deployment (or any other hosting platform). + +For example, to limit users to 10 drawings: +- In Vercel: Go to your project settings → Environment Variables → Add `VITE_MAX_DRAWINGS_PER_USER` with value `10` +- In `.env` file: Add `VITE_MAX_DRAWINGS_PER_USER=10` + +If this environment variable is not set, users can create unlimited drawings (default behavior). + If you want to deploy using Docker, you can use the provided docker-compose file, using the instruction in the [Docker](https://github.com/kainoanewton/draw/blob/main/docs/docker.md) section. If you'd like to build the app yourself, run: diff --git a/src/components/SearchCommand.tsx b/src/components/SearchCommand.tsx index 82eb544..3762fc9 100644 --- a/src/components/SearchCommand.tsx +++ b/src/components/SearchCommand.tsx @@ -91,7 +91,13 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { const response = await createNewPage(undefined, targetFolderId); if (response.error) { - toast.error("Failed to create page"); + if (response.error.code === 'DrawingLimitReached') { + toast.error("Drawing limit reached", { + description: response.error.message, + }); + } else { + toast.error("Failed to create page"); + } return; } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 0ba20e8..45134a3 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -492,9 +492,15 @@ export default function Sidebar({ className }: SidebarProps) { toast("Successfully created a new page!"); } if (data.error) { - toast("An error occurred", { - description: `Error: ${data.error.message}`, - }); + if (data.error.code === 'DrawingLimitReached') { + toast.error("Drawing limit reached", { + description: data.error.message, + }); + } else { + toast("An error occurred", { + description: `Error: ${data.error.message}`, + }); + } } } @@ -523,9 +529,15 @@ export default function Sidebar({ className }: SidebarProps) { toast("Successfully created a new drawing!"); } if (data.error) { - toast("An error occurred", { - description: `Error: ${data.error.message}`, - }); + if (data.error.code === 'DrawingLimitReached') { + toast.error("Drawing limit reached", { + description: data.error.message, + }); + } else { + toast("An error occurred", { + description: `Error: ${data.error.message}`, + }); + } } } diff --git a/src/db/draw.ts b/src/db/draw.ts index 42a8f79..cb23fdc 100644 --- a/src/db/draw.ts +++ b/src/db/draw.ts @@ -46,6 +46,34 @@ export async function createNewPage( ): Promise { const { data: profile, error: profileError } = await supabase.auth.getUser(); if (profile) { + // Check drawing limit if environment variable is set + const maxDrawings = import.meta.env.VITE_MAX_DRAWINGS_PER_USER; + if (maxDrawings) { + const limit = parseInt(maxDrawings, 10); + if (!isNaN(limit)) { + // Count existing non-deleted drawings for this user + const { count, error: countError } = await supabase + .from(DB_NAME) + .select('*', { count: 'exact', head: true }) + .eq("user_id", profile.user?.id) + .eq("is_deleted", false); + + if (countError) { + return { data: null, error: countError }; + } + + if (count !== null && count >= limit) { + // Create a custom error to indicate limit reached + const limitError = new AuthError( + `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'}. Please delete some drawings to create new ones.`, + 403, + 'DrawingLimitReached' + ); + return { data: null, error: limitError }; + } + } + } + const { data, error } = await supabase .from(DB_NAME) .insert({ diff --git a/src/views/Pages.tsx b/src/views/Pages.tsx index 701a91e..3d8a303 100644 --- a/src/views/Pages.tsx +++ b/src/views/Pages.tsx @@ -225,9 +225,15 @@ export default function Pages() { navigate({ to: "/page/$id", params: { id: data.data[0].page_id } }); } if (data.error) { - toast("An error occurred", { - description: `Error: ${data.error.message}`, - }); + if (data.error.code === 'DrawingLimitReached') { + toast.error("Drawing limit reached", { + description: data.error.message, + }); + } else { + toast("An error occurred", { + description: `Error: ${data.error.message}`, + }); + } } } From 3b53b68d33a89a938b61dd6f3c63a9790deabe45 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:33:26 +0000 Subject: [PATCH 3/7] Address code review feedback - use PostgrestError and validate positive limit Co-authored-by: notkainoa <123281048+notkainoa@users.noreply.github.com> --- src/db/draw.ts | 14 ++++++++------ src/views/Pages.tsx | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/db/draw.ts b/src/db/draw.ts index cb23fdc..f422ac7 100644 --- a/src/db/draw.ts +++ b/src/db/draw.ts @@ -50,7 +50,7 @@ export async function createNewPage( const maxDrawings = import.meta.env.VITE_MAX_DRAWINGS_PER_USER; if (maxDrawings) { const limit = parseInt(maxDrawings, 10); - if (!isNaN(limit)) { + if (!isNaN(limit) && limit > 0) { // Count existing non-deleted drawings for this user const { count, error: countError } = await supabase .from(DB_NAME) @@ -64,11 +64,13 @@ export async function createNewPage( if (count !== null && count >= limit) { // Create a custom error to indicate limit reached - const limitError = new AuthError( - `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'}. Please delete some drawings to create new ones.`, - 403, - 'DrawingLimitReached' - ); + const limitError: PostgrestError = { + message: `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'}. Please delete some drawings to create new ones.`, + details: '', + hint: '', + code: 'DrawingLimitReached', + name: 'PostgrestError', + }; return { data: null, error: limitError }; } } diff --git a/src/views/Pages.tsx b/src/views/Pages.tsx index 3d8a303..1d5ef50 100644 --- a/src/views/Pages.tsx +++ b/src/views/Pages.tsx @@ -218,7 +218,7 @@ export default function Pages() { } async function handlePageCreate() { - // If root (null), createNewPage handles it as null + // If selectedFolderId is null, pass undefined to createNewPage which treats it as null const data = await createNewPage(undefined, selectedFolderId ?? undefined); if (data.data && data.data[0]?.page_id) { From c2df17e04f65e1013e531f13784453a5dfdddc3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:49:26 +0000 Subject: [PATCH 4/7] Add VITE_UNLIMITED_USERS bypass list for drawing limit Co-authored-by: notkainoa <123281048+notkainoa@users.noreply.github.com> --- .env.example | 6 ++++++ README.md | 11 +++++++++++ src/db/draw.ts | 52 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index a8358a5..e2d4155 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,9 @@ VITE_SENTRY_DSN=your_sentry_dsn # If not set, users can create unlimited drawings # Example: VITE_MAX_DRAWINGS_PER_USER=10 # VITE_MAX_DRAWINGS_PER_USER= + +# Unlimited Users (Optional) +# Comma-separated list of user emails that bypass the drawing limit +# These users can create unlimited drawings even if VITE_MAX_DRAWINGS_PER_USER is set +# Example: VITE_UNLIMITED_USERS=admin@example.com,manager@example.com +# VITE_UNLIMITED_USERS= diff --git a/README.md b/README.md index a5b7a5c..e4d9ae2 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,17 @@ For example, to limit users to 10 drawings: If this environment variable is not set, users can create unlimited drawings (default behavior). +#### Unlimited Users Bypass List + +You can also specify a list of user emails that bypass the drawing limit by setting the `VITE_UNLIMITED_USERS` environment variable. This is useful for administrators or premium users who should have unlimited access. + +For example: +- In Vercel: Add `VITE_UNLIMITED_USERS` with value `admin@example.com,manager@example.com,premium@example.com` +- In `.env` file: Add `VITE_UNLIMITED_USERS=admin@example.com,manager@example.com` + +Users in this list can create unlimited drawings even when `VITE_MAX_DRAWINGS_PER_USER` is set. The comparison is case-insensitive and supports multiple emails separated by commas. + + If you want to deploy using Docker, you can use the provided docker-compose file, using the instruction in the [Docker](https://github.com/kainoanewton/draw/blob/main/docs/docker.md) section. If you'd like to build the app yourself, run: diff --git a/src/db/draw.ts b/src/db/draw.ts index f422ac7..4762cf4 100644 --- a/src/db/draw.ts +++ b/src/db/draw.ts @@ -51,27 +51,41 @@ export async function createNewPage( if (maxDrawings) { const limit = parseInt(maxDrawings, 10); if (!isNaN(limit) && limit > 0) { - // Count existing non-deleted drawings for this user - const { count, error: countError } = await supabase - .from(DB_NAME) - .select('*', { count: 'exact', head: true }) - .eq("user_id", profile.user?.id) - .eq("is_deleted", false); - - if (countError) { - return { data: null, error: countError }; + // Check if user is in the unlimited users list (bypass list) + const unlimitedUsersEnv = import.meta.env.VITE_UNLIMITED_USERS; + const userEmail = profile.user?.email; + let isUnlimitedUser = false; + + if (unlimitedUsersEnv && userEmail) { + // Split by comma and trim whitespace from each email + const unlimitedUsers = unlimitedUsersEnv.split(',').map((email: string) => email.trim().toLowerCase()); + isUnlimitedUser = unlimitedUsers.includes(userEmail.toLowerCase()); } - if (count !== null && count >= limit) { - // Create a custom error to indicate limit reached - const limitError: PostgrestError = { - message: `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'}. Please delete some drawings to create new ones.`, - details: '', - hint: '', - code: 'DrawingLimitReached', - name: 'PostgrestError', - }; - return { data: null, error: limitError }; + // Only enforce limit if user is not in the unlimited users list + if (!isUnlimitedUser) { + // Count existing non-deleted drawings for this user + const { count, error: countError } = await supabase + .from(DB_NAME) + .select('*', { count: 'exact', head: true }) + .eq("user_id", profile.user?.id) + .eq("is_deleted", false); + + if (countError) { + return { data: null, error: countError }; + } + + if (count !== null && count >= limit) { + // Create a custom error to indicate limit reached + const limitError: PostgrestError = { + message: `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'}. Please delete some drawings to create new ones.`, + details: '', + hint: '', + code: 'DrawingLimitReached', + name: 'PostgrestError', + }; + return { data: null, error: limitError }; + } } } } From b599b3b00896988054dc622364d6346448e3c657 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:51:57 +0000 Subject: [PATCH 5/7] Optimize bypass list parsing with caching and improve count query Co-authored-by: notkainoa <123281048+notkainoa@users.noreply.github.com> --- src/db/draw.ts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/db/draw.ts b/src/db/draw.ts index 4762cf4..de84a39 100644 --- a/src/db/draw.ts +++ b/src/db/draw.ts @@ -11,6 +11,29 @@ export type DBResponse = { export const DB_NAME = "draw"; export const FOLDERS_DB_NAME = "folders"; +// Cache parsed unlimited users list to avoid repeated parsing +let cachedUnlimitedUsers: string[] = []; +let cachedUnlimitedUsersEnv: string | undefined = undefined; + +function getUnlimitedUsers(): string[] { + const unlimitedUsersEnv = import.meta.env.VITE_UNLIMITED_USERS; + + // Return cached list if environment variable hasn't changed + if (unlimitedUsersEnv === cachedUnlimitedUsersEnv) { + return cachedUnlimitedUsers; + } + + // Parse and cache the list + cachedUnlimitedUsersEnv = unlimitedUsersEnv; + if (unlimitedUsersEnv) { + cachedUnlimitedUsers = unlimitedUsersEnv.split(',').map((email: string) => email.trim().toLowerCase()); + } else { + cachedUnlimitedUsers = []; + } + + return cachedUnlimitedUsers; +} + export type Folder = { folder_id: string; name: string; @@ -52,13 +75,11 @@ export async function createNewPage( const limit = parseInt(maxDrawings, 10); if (!isNaN(limit) && limit > 0) { // Check if user is in the unlimited users list (bypass list) - const unlimitedUsersEnv = import.meta.env.VITE_UNLIMITED_USERS; const userEmail = profile.user?.email; let isUnlimitedUser = false; - if (unlimitedUsersEnv && userEmail) { - // Split by comma and trim whitespace from each email - const unlimitedUsers = unlimitedUsersEnv.split(',').map((email: string) => email.trim().toLowerCase()); + if (userEmail) { + const unlimitedUsers = getUnlimitedUsers(); isUnlimitedUser = unlimitedUsers.includes(userEmail.toLowerCase()); } @@ -67,7 +88,7 @@ export async function createNewPage( // Count existing non-deleted drawings for this user const { count, error: countError } = await supabase .from(DB_NAME) - .select('*', { count: 'exact', head: true }) + .select('page_id', { count: 'exact', head: true }) .eq("user_id", profile.user?.id) .eq("is_deleted", false); From 415d627f87dbd68b65897a2220c820beccc9f50f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 22:17:10 +0000 Subject: [PATCH 6/7] Merge main branch - resolve conflicts and integrate default folder feature Co-authored-by: notkainoa <123281048+notkainoa@users.noreply.github.com> --- src/components/SearchCommand.tsx | 44 ++++---- src/components/Sidebar.tsx | 150 +++++++++++++--------------- src/components/theme-provider.tsx | 4 +- src/components/ui/button.tsx | 18 ++-- src/components/ui/card.tsx | 2 +- src/components/ui/dialog.tsx | 2 +- src/components/ui/dropdown-menu.tsx | 4 +- src/components/ui/input.tsx | 4 +- src/contexts/FolderContext.tsx | 40 ++++---- src/db/draw.ts | 87 ++++++++-------- src/hooks/useAuth.tsx | 4 + src/hooks/useFolders.tsx | 9 +- src/hooks/useNetworkStatus.tsx | 5 +- src/hooks/useOfflineSync.tsx | 2 + src/hooks/usePages.tsx | 3 +- src/index.css | 80 ++++++++++----- src/main.tsx | 2 +- src/services/syncService.ts | 16 ++- src/views/HomePage.tsx | 1 + src/views/Page.tsx | 3 +- src/views/Pages.tsx | 100 ++++++------------- tailwind.config.ts | 2 +- 22 files changed, 299 insertions(+), 283 deletions(-) diff --git a/src/components/SearchCommand.tsx b/src/components/SearchCommand.tsx index 3762fc9..fa0fbbe 100644 --- a/src/components/SearchCommand.tsx +++ b/src/components/SearchCommand.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo, useCallback } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Command } from "cmdk"; import { useNavigate } from "@tanstack/react-router"; import { usePages } from "@/hooks/usePages"; @@ -27,7 +27,7 @@ interface SearchResult { title: string; subtitle: string; icon: React.ReactNode; - data?: { folder_id?: string; page_id?: string }; + data?: any; action?: () => void; } @@ -76,7 +76,7 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { }; // Action handlers - const handleCreateNewPage = useCallback(async () => { + const handleCreateNewPage = async () => { try { if (!user?.id) { toast.error("Please sign in to create a page"); @@ -113,9 +113,9 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { console.error("Error creating page:", error); toast.error("Failed to create page"); } - }, [user?.id, selectedFolderId, folders, onOpenChange, queryClient, navigate]); + }; - const handleCreateNewFolder = useCallback(async () => { + const handleCreateNewFolder = async () => { try { if (!user?.id) { toast.error("Please sign in to create a folder"); @@ -137,12 +137,12 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { console.error("Error creating folder:", error); toast.error("Failed to create folder"); } - }, [user?.id, folders?.length, onOpenChange, queryClient]); + }; - const handleOpenProfile = useCallback(() => { + const handleOpenProfile = () => { onOpenChange(false); openProfile(); - }, [onOpenChange, openProfile]); + }; // Enhanced search results with better filtering and sorting const searchResults = useMemo(() => { @@ -247,14 +247,14 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { return a.title.localeCompare(b.title); }); - }, [search, folders, pages, handleCreateNewPage, handleCreateNewFolder, handleOpenProfile]); + }, [search, folders, pages]); const handleResultSelect = (result: SearchResult) => { if (result.type === 'action' && result.action) { result.action(); - } else if (result.type === 'folder' && result.data?.folder_id) { + } else if (result.type === 'folder') { handleFolderSelect(result.data.folder_id); - } else if (result.type === 'drawing' && result.data?.page_id) { + } else if (result.type === 'drawing') { handlePageSelect(result.data.page_id); } }; @@ -271,17 +271,17 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { onClick={(e) => e.stopPropagation()} > -
+
{search && ( @@ -310,13 +310,13 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { value={result.id} onSelect={() => handleResultSelect(result)} className={cn( - "relative flex cursor-pointer select-none items-center rounded-md px-3 py-3 text-sm outline-none transition-all duration-150", - "hover:bg-background-hover", - "data-[selected]:bg-background-hover", + "relative flex cursor-pointer select-none items-center rounded-md px-3 py-3 text-sm outline-none transition-all duration-150 border-2 border-transparent", + "hover:bg-background-hover hover:border-text-muted", + "data-[selected]:bg-background-hover data-[selected]:border-text-muted", index > 0 && "mt-1" )} > -
+
{result.icon}
@@ -329,10 +329,10 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) {
{result.type === 'drawing' ? 'drawing' : result.type}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 45134a3..3a7cad3 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -13,11 +13,10 @@ import { Search, MoreHorizontal, User, - Folder as FolderIcon, + Folder, Edit, Trash2, - ChevronLeft, - Home + ChevronLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { @@ -28,7 +27,7 @@ import { } from "@/components/ui/dropdown-menu"; import { SearchCommand } from "./SearchCommand"; import { useState } from "react"; -import { createNewPage, createFolder, deleteFolder, updateFolder, type Folder } from "@/db/draw"; +import { createNewPage, createFolder, deleteFolder, updateFolder } from "@/db/draw"; import { toast } from "sonner"; import { useQueryClient } from "@tanstack/react-query"; @@ -78,7 +77,7 @@ function UserProfileFooter() {
-
- ); -} - function FoldersSection({ children, onCreateFolder @@ -159,8 +135,8 @@ function FoldersSection({ return (
-
- +
+ Folders
@@ -232,7 +208,7 @@ function FolderItem({ onCreateDrawing, onDelete }: { - folder: Folder; + folder: any; pageCount: number; isSelected: boolean; onSelect: () => void; @@ -279,10 +255,10 @@ function FolderItem({ + + {/* Folder Name - Same height as icon */} + {isEditingName ? ( +
+ setEditingName(e.target.value)} + onBlur={() => handleFolderNameUpdate(editingName)} + onKeyDown={handleNameKeyPress} + className="h-12 px-3 text-2xl font-bold bg-background-input border-2 border-border rounded-lg text-text-primary focus:outline-none focus:border-accent-blue font-virgil shadow-sm" + autoFocus + />
) : ( - // Folder View Header - <> - {/* Folder Icon - Same height as name */} - - - {/* Folder Name - Same height as icon */} - {isEditingName ? ( -
- setEditingName(e.target.value)} - onBlur={() => handleFolderNameUpdate(editingName)} - onKeyDown={handleNameKeyPress} - className="h-12 px-3 text-2xl font-bold bg-background-input border border-border rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-accent-blue/50 focus:border-accent-blue" - autoFocus - /> -
- ) : ( - - )} - + )}
@@ -357,25 +342,6 @@ export default function Pages() { {/* Pages Grid */}
- {/* Display Folders if at Root */} - {isRoot && folders && folders.length > 0 && ( - folders.map((folder) => ( - goToFolder(folder.folder_id)} - > - -
{renderIcon(folder.icon)}
- - {folder.name} - -

Folder

-
-
- )) - )} - {pagesLoading ? ( ) : pages && pages.length > 0 ? ( @@ -406,7 +372,7 @@ export default function Pages() { )) ) : ( - (!isRoot || (isRoot && (!folders || folders.length === 0))) && + )}
diff --git a/tailwind.config.ts b/tailwind.config.ts index 3c924c5..11188a6 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -42,7 +42,7 @@ export default { }, }, fontFamily: { - sans: ["Inter", "Segoe UI", "Helvetica Neue", "Arial", "sans-serif"], + sans: ["Virgil", "Inter", "Segoe UI", "Helvetica Neue", "Arial", "sans-serif"], virgil: ["Virgil", "sans-serif"], quicksand: ["Quicksand", "sans-serif"], }, From 059417c6f1f464e73ac9ca6e997d21da90d8faef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 23:06:17 +0000 Subject: [PATCH 7/7] Fix drawing limit count query to handle NULL is_deleted values Co-authored-by: notkainoa <123281048+notkainoa@users.noreply.github.com> --- src/db/draw.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/db/draw.ts b/src/db/draw.ts index 98e6ff7..3b71f09 100644 --- a/src/db/draw.ts +++ b/src/db/draw.ts @@ -86,20 +86,24 @@ export async function createNewPage( // Only enforce limit if user is not in the unlimited users list if (!isUnlimitedUser) { // Count existing non-deleted drawings for this user + // Using .not() to exclude only explicitly deleted drawings, including NULL as non-deleted const { count, error: countError } = await supabase .from(DB_NAME) .select('page_id', { count: 'exact', head: true }) .eq("user_id", profile.user?.id) - .eq("is_deleted", false); + .not('is_deleted', 'eq', true); if (countError) { return { data: null, error: countError }; } + // Log for debugging + console.log(`Drawing limit check: user has ${count} drawings, limit is ${limit}`); + if (count !== null && count >= limit) { // Create a custom error to indicate limit reached const limitError: PostgrestError = { - message: `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'}. Please delete some drawings to create new ones.`, + message: `You have reached the maximum limit of ${limit} drawing${limit === 1 ? '' : 's'} (you currently have ${count}). Please delete some drawings to create new ones.`, details: '', hint: '', code: 'DrawingLimitReached',