Skip to content
This repository was archived by the owner on Nov 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Dear Copilot,

## Project Overview

BottleCRM is a dynamic, SaaS CRM platform designed to streamline the entire CRM needs of startups and enterprises. Built with modern web technologies, it offers a seamless experience for users through robust role-based access control (RBAC). Each user role is equipped with tailored functionalities to enhance efficiency, engagement, and management, ensuring a streamlined and secure business process.

user types we have

- Org
- user(s)
- Admin
- super admin - anyone with @micropyramid.com email to manage whole platform

## Project Context

BottleCRM is a modern CRM application built with:
- **Framework**: SvelteKit 2.21.x, Svelte 5.x, Prisma
- **Styling**: tailwind 4.1.x css
- **Database**: postgresql
- **Icons**: fontawesome, we are migrating to lucide icons

## Important Notes
- We need to ensure access control is strictly enforced based on user roles. No record is accessible unless the user or the org has the appropriate permissions.
- When implementing forms in sveltekit A form label must be associated with a control
13 changes: 12 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,16 @@
"[svelte]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
}
},
"github.copilot.chat.codeGeneration.instructions": [
{
"file": "prisma/schema.prisma",
},
{
"file": "src/hooks.server.js",
},
{
"file": "src/lib/prisma.js",
},
]
}
2 changes: 2 additions & 0 deletions src/routes/(admin)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@
<!-- Mobile search -->
<div class="pt-2">
<div class="relative">
<label for="admin-search" class="sr-only">Search</label>
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
id="admin-search"
type="text"
placeholder="Search..."
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
Expand Down
67 changes: 36 additions & 31 deletions src/routes/(admin)/admin/blogs/[id]/edit/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -92,55 +92,60 @@
<input type="hidden" name="title" value={editable_title} />
<input type="hidden" name="slug" value={slug} />
<div>
<label class="block font-medium mb-1">
<label for="blog-title" class="block font-medium mb-1">
Title:
<input
bind:value={editable_title}
required
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</label>
<input
id="blog-title"
bind:value={editable_title}
required
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label class="block font-medium mb-1">
<label for="blog-seo-title" class="block font-medium mb-1">
SEO Title:
<input
name="seoTitle"
bind:value={blog.seoTitle}
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</label>
<input
id="blog-seo-title"
name="seoTitle"
bind:value={blog.seoTitle}
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label class="block font-medium mb-1">
<label for="blog-seo-description" class="block font-medium mb-1">
SEO Description:
<textarea
name="seoDescription"
bind:value={blog.seoDescription}
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
></textarea>
</label>
<textarea
id="blog-seo-description"
name="seoDescription"
bind:value={blog.seoDescription}
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
<div>
<label class="block font-medium mb-1">
<label for="blog-excerpt" class="block font-medium mb-1">
Excerpt:
<textarea
name="excerpt"
bind:value={blog.excerpt}
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
></textarea>
</label>
<textarea
id="blog-excerpt"
name="excerpt"
bind:value={blog.excerpt}
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
></textarea>
</div>
<div>
<label class="block font-medium mb-1">
<label for="blog-slug" class="block font-medium mb-1">
Slug:
<input
bind:value={slug}
required
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
disabled={!blog.draft}
/>
</label>
<input
id="blog-slug"
bind:value={slug}
required
class="mt-1 block w-full rounded border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500"
disabled={!blog.draft}
/>
{#if !blog.draft}
<p class="text-xs text-gray-500 mt-1">
Slug can only be edited in draft mode.
Expand Down
4 changes: 4 additions & 0 deletions src/routes/(app)/app/accounts/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@
<div class="flex flex-col sm:flex-row gap-3">
<!-- Search -->
<div class="relative">
<label for="accounts-search" class="sr-only">Search accounts</label>
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
id="accounts-search"
placeholder="Search accounts..."
class="pl-10 pr-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent min-w-[250px]"
bind:value={searchQuery}
Expand All @@ -111,8 +113,10 @@

<!-- Status Filter -->
<div class="relative">
<label for="accounts-status-filter" class="sr-only">Filter accounts by status</label>
<Filter class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<select
id="accounts-status-filter"
class="pl-10 pr-8 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 appearance-none min-w-[120px]"
bind:value={statusFilter}
onchange={updateQueryParams}
Expand Down
9 changes: 6 additions & 3 deletions src/routes/(app)/app/invoices/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
<!-- Search -->
<div class="flex-1 flex items-center bg-white/80 backdrop-blur-md rounded-xl shadow px-4 py-2 border border-blue-200">
<svg class="w-5 h-5 text-blue-400 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" placeholder="Search invoices..." class="bg-transparent outline-none flex-1 text-blue-900 placeholder-blue-400" />
<label for="invoice-search" class="sr-only">Search invoices</label>
<input id="invoice-search" type="text" placeholder="Search invoices..." class="bg-transparent outline-none flex-1 text-blue-900 placeholder-blue-400" />
</div>
<!-- Status Filter -->
<div class="flex items-center bg-white/80 backdrop-blur-md rounded-xl shadow px-4 py-2 border border-blue-200">
<svg class="w-5 h-5 text-purple-400 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M16 3v4M8 3v4"/></svg>
<select class="bg-transparent outline-none text-blue-900 font-semibold">
<label for="invoice-status-filter" class="sr-only">Filter by status</label>
<select id="invoice-status-filter" class="bg-transparent outline-none text-blue-900 font-semibold">
<option>All Statuses</option>
<option>Paid</option>
<option>Unpaid</option>
Expand All @@ -24,7 +26,8 @@
<!-- Date Range -->
<div class="flex items-center bg-white/80 backdrop-blur-md rounded-xl shadow px-4 py-2 border border-blue-200">
<svg class="w-5 h-5 text-blue-400 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
<input type="text" placeholder="Date range" class="bg-transparent outline-none text-blue-900 placeholder-blue-400 w-28" />
<label for="invoice-date-range" class="sr-only">Date range filter</label>
<input id="invoice-date-range" type="text" placeholder="Date range" class="bg-transparent outline-none text-blue-900 placeholder-blue-400 w-28" />
</div>
</div>
<div class="flex flex-col gap-5">
Expand Down
14 changes: 8 additions & 6 deletions src/routes/(app)/app/leads/open/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,10 @@
<!-- Search and Filter Toggle -->
<div class="flex flex-col sm:flex-row gap-4 mb-4">
<div class="flex-1 relative">
<label for="lead-search" class="sr-only">Search leads</label>
<Search class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 dark:text-gray-500" />
<input
id="lead-search"
type="text"
placeholder="Search by name, company, or email..."
bind:value={searchQuery}
Expand All @@ -222,24 +224,24 @@
{#if showFilters}
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg" transition:fade>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Status</label>
<select bind:value={statusFilter} class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<label for="status-filter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Status</label>
<select id="status-filter" bind:value={statusFilter} class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
{#each statuses as status}
<option value={status.value}>{status.label}</option>
{/each}
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Source</label>
<select bind:value={sourceFilter} class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<label for="source-filter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Source</label>
<select id="source-filter" bind:value={sourceFilter} class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
{#each sources as source}
<option value={source.value}>{source.label}</option>
{/each}
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Rating</label>
<select bind:value={ratingFilter} class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
<label for="rating-filter" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Rating</label>
<select id="rating-filter" bind:value={ratingFilter} class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
{#each ratings as rating}
<option value={rating.value}>{rating.label}</option>
{/each}
Expand Down
4 changes: 4 additions & 0 deletions src/routes/(app)/app/opportunities/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,13 @@
<!-- Search -->
<div class="flex-1">
<div class="relative">
<label for="opportunities-search" class="sr-only">Search opportunities</label>
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search class="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
id="opportunities-search"
bind:value={searchTerm}
placeholder="Search opportunities, accounts, or owners..."
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md leading-5 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
Expand All @@ -217,7 +219,9 @@

<!-- Stage Filter -->
<div class="sm:w-48">
<label for="opportunities-stage-filter" class="sr-only">Filter opportunities by stage</label>
<select
id="opportunities-stage-filter"
bind:value={selectedStage}
class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
Expand Down
3 changes: 2 additions & 1 deletion src/routes/(app)/app/tasks/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
{/if}
<form class="board-card board-create" method="POST" action="?/create">
<div class="plus-icon">+</div>
<input name="name" placeholder="New board name" required />
<label for="board-name" class="sr-only">Board name</label>
<input id="board-name" name="name" placeholder="New board name" required />
<button type="submit">Create Board</button>
</form>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/routes/(app)/app/users/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@
{#if user.editingRole}
<form method="POST" action="?/edit_role" class="flex items-center gap-2">
<input type="hidden" name="user_id" value={user.id} />
<label for="role-select-{user.id}" class="sr-only">User Role</label>
<select
id="role-select-{user.id}"
name="role"
class="rounded-lg border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-xs focus:border-blue-500 dark:focus:border-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400"
>
Expand Down
2 changes: 2 additions & 0 deletions src/routes/(site)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,9 @@
</div>
{/if}
<div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-3">
<label for="newsletter-email" class="sr-only">Enter your email address</label>
<input
id="newsletter-email"
type="email"
name="email"
placeholder="Enter your email"
Expand Down
3 changes: 2 additions & 1 deletion src/routes/(site)/features/contact-management/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@
<div class="flex items-center space-x-4">
<div class="relative">
<Search class="w-5 h-5 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2" />
<input type="text" placeholder="Search contacts..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-64" />
<label for="contact-search-demo" class="sr-only">Search contacts</label>
<input id="contact-search-demo" type="text" placeholder="Search contacts..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-64" />
</div>
<button class="flex items-center px-4 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">
<Filter class="w-4 h-4 mr-2" />
Expand Down
2 changes: 2 additions & 0 deletions src/routes/(site)/migration/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,9 @@
{#if !subscribed}
<div class="max-w-md mx-auto">
<div class="flex flex-col sm:flex-row gap-4">
<label for="email-updates" class="sr-only">Email address for updates</label>
<input
id="email-updates"
type="email"
bind:value={emailForUpdates}
placeholder="Enter your email address"
Expand Down