diff --git a/DARK_MODE_GUIDE.md b/DARK_MODE_GUIDE.md new file mode 100644 index 0000000..60329b4 --- /dev/null +++ b/DARK_MODE_GUIDE.md @@ -0,0 +1,303 @@ +# Dark Mode Implementation Guide + +## Overview + +BeenVoice implements a **media query-based dark mode** system that automatically adapts to the user's system preferences. This approach provides a seamless experience without requiring manual theme switching controls. + +## Implementation Approach + +### Media Query-Based vs Class-Based + +We chose **media query-based** dark mode (`@media (prefers-color-scheme: dark)`) over class-based dark mode for the following reasons: + +- **Automatic System Integration**: Respects user's OS/browser theme preference +- **No JavaScript Required**: Pure CSS solution with better performance +- **Better Accessibility**: Follows system accessibility settings +- **Seamless Experience**: No flash of incorrect theme on page load +- **Reduced Complexity**: No need for theme toggle components or state management + +## Configuration Changes + +### 1. Tailwind CSS Configuration + +**File:** `tailwind.config.ts` + +```typescript +export default { + darkMode: "media", // Changed from "class" to "media" + // ... rest of config +} satisfies Config; +``` + +### 2. Global CSS Updates + +**File:** `src/styles/globals.css` + +Key changes made: + +- Replaced `.dark { }` class selector with `@media (prefers-color-scheme: dark) { :root { } }` +- Maintained all existing CSS custom properties +- Updated dark mode color definitions to use media queries + +```css +@media (prefers-color-scheme: dark) { + :root { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + /* ... all other dark mode variables */ + } +} +``` + +## Component Updates + +### Core Layout Components + +#### 1. Root Layout (`src/app/layout.tsx`) +- Updated background gradients with dark mode variants +- Improved layering structure for background effects +- Added proper z-index management + +#### 2. Landing Page (`src/app/page.tsx`) +- Comprehensive dark mode classes for all sections +- Updated text colors, backgrounds, and hover states +- Maintained brand consistency with green color scheme + +#### 3. Authentication Pages +- **Sign In Page** (`src/app/auth/signin/page.tsx`) +- **Register Page** (`src/app/auth/register/page.tsx`) + +Both pages updated with: +- Dark background gradients +- Card component dark backgrounds +- Input field icon colors +- Text color variations +- Link hover states + +### Navigation Components + +#### 1. Navbar (`src/components/Navbar.tsx`) +- Glass morphism effect with dark background support +- Button variant adaptations +- Text color adjustments for user information + +#### 2. Sidebar (`src/components/Sidebar.tsx`) +- Dark background with transparency +- Navigation link states (active/hover) +- Border color adaptations +- Icon and text color updates + +#### 3. Mobile Sidebar (`src/components/SidebarTrigger.tsx`) +- Sheet component dark styling +- Navigation link consistency with desktop sidebar +- Border and background adaptations + +### Dashboard Components + +#### 1. Dashboard Page (`src/app/dashboard/page.tsx`) +- Welcome text color adjustments +- Maintained gradient text effects + +#### 2. Universal Table (`src/components/ui/universal-table.tsx`) +- Comprehensive table styling updates +- Header background and text colors +- Cell content color adaptations +- Status badge color schemes +- Pagination control styling +- Grid view card backgrounds + +## Color System + +### Text Colors +- **Primary Text**: `text-gray-900 dark:text-white` +- **Secondary Text**: `text-gray-700 dark:text-gray-300` +- **Muted Text**: `text-gray-500 dark:text-gray-400` +- **Icon Colors**: `text-gray-400 dark:text-gray-500` + +### Background Colors +- **Card Backgrounds**: `dark:bg-gray-800` +- **Hover States**: `hover:bg-gray-100 dark:hover:bg-gray-800` +- **Border Colors**: `border-gray-200 dark:border-gray-700` +- **Glass Effects**: `bg-white/60 dark:bg-gray-900/60` + +### Brand Colors +Maintained consistent green brand colors with dark mode adaptations: +- **Primary Green**: `text-green-600 dark:text-green-400` +- **Emerald Accents**: `bg-emerald-100 dark:bg-emerald-900/30` + +## Testing the Implementation + +### Manual Testing + +1. **System Theme Toggle**: + - Change your OS dark/light mode setting + - Verify automatic theme switching + - Check for smooth transitions + +2. **Page Coverage**: + - Landing page + - Authentication pages (sign in/register) + - Dashboard and navigation + - All table views and components + +3. **Component Testing**: + - Form elements (inputs, buttons, selects) + - Cards and containers + - Navigation states (hover, active) + - Text readability in both modes + +### Browser Developer Tools + +1. **Media Query Testing**: + ```css + /* In DevTools Console */ + document.documentElement.style.colorScheme = 'dark'; + document.documentElement.style.colorScheme = 'light'; + ``` + +2. **Emulation**: + - Chrome DevTools > Rendering > Emulate CSS prefers-color-scheme + - Firefox DevTools > Settings > Dark theme simulation + +### Test Component + +A comprehensive test component is available at `src/components/dark-mode-test.tsx` that displays: +- Color variations +- Button states +- Form elements +- Status indicators +- Background patterns +- Icon colors + +## Best Practices for Future Development + +### 1. Color Class Patterns + +Always pair light mode colors with dark mode variants: + +```tsx +// ✅ Good +
+ +// ❌ Avoid +
+``` + +### 2. Common Patterns + +**Text Colors:** +```tsx +// Primary text +className="text-gray-900 dark:text-white" + +// Secondary text +className="text-gray-700 dark:text-gray-300" + +// Muted text +className="text-gray-500 dark:text-gray-400" +``` + +**Backgrounds:** +```tsx +// Card backgrounds +className="bg-white dark:bg-gray-800" + +// Hover states +className="hover:bg-gray-100 dark:hover:bg-gray-800" + +// Borders +className="border-gray-200 dark:border-gray-700" +``` + +**Interactive Elements:** +```tsx +// Links +className="text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300" + +// Active states +className="bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400" +``` + +### 3. Component Development Guidelines + +1. **Always test both modes** during development +2. **Use semantic color tokens** from the design system when possible +3. **Maintain sufficient contrast** for accessibility +4. **Consider glass morphism effects** with appropriate alpha values +5. **Test with real content** to ensure readability + +### 4. shadcn/ui Components + +Most shadcn/ui components already include dark mode support: +- Button variants adapt automatically +- Input fields use CSS custom properties +- Card components respond to theme changes + +For custom components, follow the established patterns. + +## Troubleshooting + +### Common Issues + +1. **Colors Not Switching**: + - Verify `darkMode: "media"` in `tailwind.config.ts` + - Check CSS custom properties are properly defined + - Ensure no conflicting class-based dark mode styles + +2. **Flash of Incorrect Theme**: + - Should not occur with media query approach + - If present, check for JavaScript theme switching code + +3. **Incomplete Styling**: + - Search for hardcoded colors: `text-gray-XXX` without `dark:` variants + - Use component test page to verify all elements + +4. **Performance Issues**: + - Media query approach should have no performance impact + - CSS variables resolve efficiently + +### Debugging Tools + +1. **CSS Custom Properties Inspector**: + ```javascript + // In DevTools Console + getComputedStyle(document.documentElement).getPropertyValue('--background') + ``` + +2. **Media Query Detection**: + ```javascript + // Check current preference + window.matchMedia('(prefers-color-scheme: dark)').matches + ``` + +## Browser Support + +The media query-based approach is supported in: +- Chrome 76+ +- Firefox 67+ +- Safari 12.1+ +- Edge 79+ + +For older browsers, the light theme serves as a fallback. + +## Maintenance + +### Regular Checks + +1. **New Component Integration**: Ensure new components follow dark mode patterns +2. **Third-party Components**: Verify external components adapt to theme +3. **Asset Updates**: Check images/icons work in both modes +4. **Performance Monitoring**: Ensure no CSS bloat from unused dark mode classes + +### Updates and Migration + +If future requirements need class-based dark mode: +1. Update `tailwind.config.ts` to `darkMode: "class"` +2. Add theme toggle component +3. Implement theme persistence +4. Update CSS to use `.dark` selector instead of media queries + +## Conclusion + +The media query-based dark mode implementation provides a robust, performant, and user-friendly theming solution that automatically adapts to user preferences while maintaining design consistency and accessibility standards. \ No newline at end of file diff --git a/PDF_EXPORT_GUIDE.md b/PDF_EXPORT_GUIDE.md new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/PDF_EXPORT_GUIDE.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/PDF_GUIDELINES.md b/PDF_GUIDELINES.md new file mode 100644 index 0000000..ac9b460 --- /dev/null +++ b/PDF_GUIDELINES.md @@ -0,0 +1,49 @@ +# Invoice PDF Requirements + +## Purpose +Generate professional, print-ready invoice PDFs that represent the beenvoice brand and provide a good user experience. + +## Core Requirements + +### Visual Design +- Professional appearance suitable for business use +- Clean, modern layout that matches the app's design language +- Consistent branding with the beenvoice logo and colors +- Print-friendly design with good contrast and readability + +### Content Structure +- Header with logo, invoice title, number, and status +- Client information and invoice details in organized sections +- Itemized table with all invoice line items +- Clear total amount display +- Professional footer with branding + +### Typography & Layout +- Readable fonts that work across all systems +- Proper hierarchy with clear headings and body text +- Consistent spacing and alignment throughout +- Responsive layout that works on different page sizes + +### Technical Requirements +- Generate high-quality PDFs suitable for printing +- Support multi-page documents when content is long +- Ensure consistent rendering across different browsers +- Fast generation without blocking the UI +- Proper page numbering and navigation + +### User Experience +- PDFs should be immediately recognizable as beenvoice invoices +- Content should be well-organized and easy to scan +- Professional appearance that builds trust with clients +- Clear call-to-action and payment information + +### Brand Consistency +- Use the same logo and colors as the main application +- Maintain the same visual language and design principles +- Ensure the PDF feels like part of the beenvoice ecosystem + +## Quality Standards +- Professional enough for client-facing business use +- Consistent with modern invoice design standards +- Accessible and readable for all users +- Print-ready with proper margins and formatting \ No newline at end of file diff --git a/bun.lock b/bun.lock index 21f1cc9..de69554 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,9 @@ "name": "beenvoice", "dependencies": { "@auth/drizzle-adapter": "^1.7.2", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@libsql/client": "^0.14.0", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", @@ -13,21 +16,24 @@ "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", + "@react-pdf/renderer": "^4.3.0", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^5.69.0", "@trpc/client": "^11.0.0", "@trpc/react-query": "^11.0.0", "@trpc/server": "^11.0.0", "@types/bcryptjs": "^2.4.6", + "@types/file-saver": "^2.0.7", "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "drizzle-orm": "^0.41.0", - "html2canvas": "^1.4.1", - "jspdf": "^3.0.1", + "file-saver": "^2.0.5", "lucide": "^0.525.0", "lucide-react": "^0.525.0", "next": "^15.2.3", @@ -65,7 +71,6 @@ }, "trustedDependencies": [ "@tailwindcss/oxide", - "core-js", "unrs-resolver", ], "packages": { @@ -81,6 +86,14 @@ "@date-fns/tz": ["@date-fns/tz@1.2.0", "", {}, "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg=="], + "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], + + "@dnd-kit/core": ["@dnd-kit/core@6.3.1", "", { "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ=="], + + "@dnd-kit/sortable": ["@dnd-kit/sortable@10.0.0", "", { "dependencies": { "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.3.0", "react": ">=16.8.0" } }, "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg=="], + + "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], "@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" } }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="], @@ -285,6 +298,8 @@ "@petamoriken/float16": ["@petamoriken/float16@3.9.2", "", {}, "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], @@ -331,6 +346,10 @@ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.5", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA=="], + + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw=="], @@ -355,6 +374,32 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@react-pdf/fns": ["@react-pdf/fns@3.1.2", "", {}, "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g=="], + + "@react-pdf/font": ["@react-pdf/font@4.0.2", "", { "dependencies": { "@react-pdf/pdfkit": "^4.0.3", "@react-pdf/types": "^2.9.0", "fontkit": "^2.0.2", "is-url": "^1.2.4" } }, "sha512-/dAWu7Y2RD1RxarDZ9SkYPHgBYOhmcDnet4W/qN/m8k+A2Hr3ja54GymSR7GGxWBtxjKtNauVKrTa9LS1n8WUw=="], + + "@react-pdf/image": ["@react-pdf/image@3.0.3", "", { "dependencies": { "@react-pdf/png-js": "^3.0.0", "jay-peg": "^1.1.1" } }, "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ=="], + + "@react-pdf/layout": ["@react-pdf/layout@4.4.0", "", { "dependencies": { "@react-pdf/fns": "3.1.2", "@react-pdf/image": "^3.0.3", "@react-pdf/primitives": "^4.1.1", "@react-pdf/stylesheet": "^6.1.0", "@react-pdf/textkit": "^6.0.0", "@react-pdf/types": "^2.9.0", "emoji-regex": "^10.3.0", "queue": "^6.0.1", "yoga-layout": "^3.2.1" } }, "sha512-Aq+Cc6JYausWLoks2FvHe3PwK9cTuvksB2uJ0AnkKJEUtQbvCq8eCRb1bjbbwIji9OzFRTTzZij7LzkpKHjIeA=="], + + "@react-pdf/pdfkit": ["@react-pdf/pdfkit@4.0.3", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@react-pdf/png-js": "^3.0.0", "browserify-zlib": "^0.2.0", "crypto-js": "^4.2.0", "fontkit": "^2.0.2", "jay-peg": "^1.1.1", "linebreak": "^1.1.0", "vite-compatible-readable-stream": "^3.6.1" } }, "sha512-k+Lsuq8vTwWsCqTp+CCB4+2N+sOTFrzwGA7aw3H9ix/PDWR9QksbmNg0YkzGbLAPI6CeawmiLHcf4trZ5ecLPQ=="], + + "@react-pdf/png-js": ["@react-pdf/png-js@3.0.0", "", { "dependencies": { "browserify-zlib": "^0.2.0" } }, "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA=="], + + "@react-pdf/primitives": ["@react-pdf/primitives@4.1.1", "", {}, "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ=="], + + "@react-pdf/reconciler": ["@react-pdf/reconciler@1.1.4", "", { "dependencies": { "object-assign": "^4.1.1", "scheduler": "0.25.0-rc-603e6108-20241029" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg=="], + + "@react-pdf/render": ["@react-pdf/render@4.3.0", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@react-pdf/fns": "3.1.2", "@react-pdf/primitives": "^4.1.1", "@react-pdf/textkit": "^6.0.0", "@react-pdf/types": "^2.9.0", "abs-svg-path": "^0.1.1", "color-string": "^1.9.1", "normalize-svg-path": "^1.1.0", "parse-svg-path": "^0.1.2", "svg-arc-to-cubic-bezier": "^3.2.0" } }, "sha512-MdWfWaqO6d7SZD75TZ2z5L35V+cHpyA43YNRlJNG0RJ7/MeVGDQv12y/BXOJgonZKkeEGdzM3EpAt9/g4E22WA=="], + + "@react-pdf/renderer": ["@react-pdf/renderer@4.3.0", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@react-pdf/fns": "3.1.2", "@react-pdf/font": "^4.0.2", "@react-pdf/layout": "^4.4.0", "@react-pdf/pdfkit": "^4.0.3", "@react-pdf/primitives": "^4.1.1", "@react-pdf/reconciler": "^1.1.4", "@react-pdf/render": "^4.3.0", "@react-pdf/types": "^2.9.0", "events": "^3.3.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", "queue": "^6.0.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28gpA69fU9ZQrDzmd5xMJa1bDf8t0PT3ApUKBl2PUpoE/x4JlvCB5X66nMXrfFrgF2EZrA72zWQAkvbg7TE8zw=="], + + "@react-pdf/stylesheet": ["@react-pdf/stylesheet@6.1.0", "", { "dependencies": { "@react-pdf/fns": "3.1.2", "@react-pdf/types": "^2.9.0", "color-string": "^1.9.1", "hsl-to-hex": "^1.0.0", "media-engine": "^1.0.3", "postcss-value-parser": "^4.1.0" } }, "sha512-BGZ2sYNUp38VJUegjva/jsri3iiRGnVNjWI+G9dTwAvLNOmwFvSJzqaCsEnqQ/DW5mrTBk/577FhDY7pv6AidA=="], + + "@react-pdf/textkit": ["@react-pdf/textkit@6.0.0", "", { "dependencies": { "@react-pdf/fns": "3.1.2", "bidi-js": "^1.0.2", "hyphen": "^1.6.4", "unicode-properties": "^1.4.1" } }, "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw=="], + + "@react-pdf/types": ["@react-pdf/types@2.9.0", "", { "dependencies": { "@react-pdf/font": "^4.0.2", "@react-pdf/primitives": "^4.1.1", "@react-pdf/stylesheet": "^6.1.0" } }, "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.12.0", "", {}, "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw=="], @@ -415,20 +460,18 @@ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/file-saver": ["@types/file-saver@2.0.7", "", {}, "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], "@types/node": ["@types/node@20.19.6", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-uYssdp9z5zH5GQ0L4zEJ2ZuavYsJwkozjiUzCRfGtaaQcyjAMJ34aP8idv61QlqTozu6kudyr6JMq9Chf09dfA=="], - "@types/raf": ["@types/raf@3.4.3", "", {}, "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="], - "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], "@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="], - "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.36.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/type-utils": "8.36.0", "@typescript-eslint/utils": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.36.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg=="], @@ -489,6 +532,8 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "abs-svg-path": ["abs-svg-path@0.1.1", "", {}, "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -523,8 +568,6 @@ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], - "atob": ["atob@2.1.2", "", { "bin": { "atob": "bin/atob.js" } }, "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="], - "attr-accept": ["attr-accept@2.2.5", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -535,15 +578,19 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="], + "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], "bcryptjs": ["bcryptjs@3.0.2", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="], + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "btoa": ["btoa@1.2.1", "", { "bin": { "btoa": "bin/btoa.js" } }, "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="], + "brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="], + + "browserify-zlib": ["browserify-zlib@0.2.0", "", { "dependencies": { "pako": "~1.0.5" } }, "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -559,8 +606,6 @@ "caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="], - "canvg": ["canvg@3.0.11", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", "core-js": "^3.8.3", "raf": "^3.4.1", "regenerator-runtime": "^0.13.7", "rgbcolor": "^1.0.1", "stackblur-canvas": "^2.0.0", "svg-pathdata": "^6.0.3" } }, "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA=="], - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], @@ -569,6 +614,8 @@ "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], @@ -585,11 +632,9 @@ "copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="], - "core-js": ["core-js@3.44.0", "", {}, "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="], + "crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], @@ -619,9 +664,9 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dfa": ["dfa@1.2.0", "", {}, "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="], - "dompurify": ["dompurify@3.2.6", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ=="], + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], "drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="], @@ -691,6 +736,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], @@ -705,10 +752,10 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "file-saver": ["file-saver@2.0.5", "", {}, "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="], + "file-selector": ["file-selector@2.1.2", "", { "dependencies": { "tslib": "^2.7.0" } }, "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -719,6 +766,8 @@ "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "fontkit": ["fontkit@2.0.4", "", { "dependencies": { "@swc/helpers": "^0.5.12", "brotli": "^1.3.2", "clone": "^2.1.2", "dfa": "^1.2.0", "fast-deep-equal": "^3.1.3", "restructure": "^3.0.0", "tiny-inflate": "^1.0.3", "unicode-properties": "^1.4.0", "unicode-trie": "^2.0.0" } }, "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g=="], + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], @@ -767,7 +816,11 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="], + "hsl-to-hex": ["hsl-to-hex@1.0.0", "", { "dependencies": { "hsl-to-rgb-for-reals": "^1.1.0" } }, "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA=="], + + "hsl-to-rgb-for-reals": ["hsl-to-rgb-for-reals@1.1.1", "", {}, "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg=="], + + "hyphen": ["hyphen@1.10.6", "", {}, "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -775,6 +828,8 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], @@ -825,6 +880,8 @@ "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + "is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="], + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -839,6 +896,8 @@ "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + "jay-peg": ["jay-peg@1.1.1", "", { "dependencies": { "restructure": "^3.0.0" } }, "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww=="], + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], "jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="], @@ -857,8 +916,6 @@ "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - "jspdf": ["jspdf@3.0.1", "", { "dependencies": { "@babel/runtime": "^7.26.7", "atob": "^2.1.2", "btoa": "^1.2.1", "fflate": "^0.8.1" }, "optionalDependencies": { "canvg": "^3.0.11", "core-js": "^3.6.0", "dompurify": "^3.2.4", "html2canvas": "^1.0.0-rc.5" } }, "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg=="], - "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -893,6 +950,8 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], @@ -907,6 +966,8 @@ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + "media-engine": ["media-engine@1.0.3", "", {}, "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg=="], + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], @@ -937,6 +998,8 @@ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "normalize-svg-path": ["normalize-svg-path@1.1.0", "", { "dependencies": { "svg-arc-to-cubic-bezier": "^3.0.0" } }, "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg=="], + "oauth4webapi": ["oauth4webapi@3.5.5", "", {}, "sha512-1K88D2GiAydGblHo39NBro5TebGXa+7tYoyIbxvqv3+haDDry7CBE1eSYuNbOSsYCCU6y0gdynVZAkm4YPw4hg=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -963,16 +1026,18 @@ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-svg-path": ["parse-svg-path@0.1.2", "", {}, "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], @@ -981,6 +1046,8 @@ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="], "preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="], @@ -999,9 +1066,9 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="], - "raf": ["raf@3.4.1", "", { "dependencies": { "performance-now": "^2.1.0" } }, "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], @@ -1021,24 +1088,26 @@ "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], - "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="], - "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], - "rgbcolor": ["rgbcolor@1.0.1", "", {}, "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], @@ -1083,8 +1152,6 @@ "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], - "stackblur-canvas": ["stackblur-canvas@2.7.0", "", {}, "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ=="], - "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], @@ -1101,6 +1168,8 @@ "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], @@ -1113,7 +1182,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svg-pathdata": ["svg-pathdata@6.0.3", "", {}, "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw=="], + "svg-arc-to-cubic-bezier": ["svg-arc-to-cubic-bezier@3.2.0", "", {}, "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g=="], "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], @@ -1125,7 +1194,7 @@ "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], - "text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="], + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], @@ -1157,6 +1226,10 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unicode-properties": ["unicode-properties@1.4.1", "", { "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" } }, "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg=="], + + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], @@ -1165,7 +1238,9 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - "utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vite-compatible-readable-stream": ["vite-compatible-readable-stream@3.6.1", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ=="], "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], @@ -1187,6 +1262,8 @@ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], @@ -1197,6 +1274,10 @@ "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + "@react-pdf/layout/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + + "@react-pdf/reconciler/scheduler": ["scheduler@0.25.0-rc-603e6108-20241029", "", {}, "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg=="], @@ -1215,6 +1296,8 @@ "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "brotli/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -1239,6 +1322,10 @@ "next-auth/@auth/core": ["@auth/core@0.37.2", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "@types/cookie": "0.6.0", "cookie": "0.7.1", "jose": "^5.9.3", "oauth4webapi": "^3.0.0", "preact": "10.11.3", "preact-render-to-string": "5.2.3" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw=="], + "unicode-properties/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], diff --git a/drizzle.config.ts b/drizzle.config.ts index f386916..02ea44c 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -7,6 +7,7 @@ export default { dialect: "sqlite", dbCredentials: { url: env.DATABASE_URL, + authToken: env.DATABASE_AUTH_TOKEN, }, tablesFilter: ["beenvoice_*"], } satisfies Config; diff --git a/drizzle/0000_shiny_post.sql b/drizzle/0000_shiny_post.sql new file mode 100644 index 0000000..2c8694d --- /dev/null +++ b/drizzle/0000_shiny_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE `beenvoice_invoice_item` ADD COLUMN `position` integer DEFAULT 0 NOT NULL; +CREATE INDEX `invoice_item_position_idx` ON `beenvoice_invoice_item` (`position`); diff --git a/drizzle/0000_unique_loa.sql b/drizzle/0000_unique_loa.sql new file mode 100644 index 0000000..0417d8a --- /dev/null +++ b/drizzle/0000_unique_loa.sql @@ -0,0 +1,125 @@ +CREATE TABLE `beenvoice_account` ( + `userId` text(255) NOT NULL, + `type` text(255) NOT NULL, + `provider` text(255) NOT NULL, + `providerAccountId` text(255) NOT NULL, + `refresh_token` text, + `access_token` text, + `expires_at` integer, + `token_type` text(255), + `scope` text(255), + `id_token` text, + `session_state` text(255), + PRIMARY KEY(`provider`, `providerAccountId`), + FOREIGN KEY (`userId`) REFERENCES `beenvoice_user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `account_user_id_idx` ON `beenvoice_account` (`userId`);--> statement-breakpoint +CREATE TABLE `beenvoice_business` ( + `id` text(255) PRIMARY KEY NOT NULL, + `name` text(255) NOT NULL, + `email` text(255), + `phone` text(50), + `addressLine1` text(255), + `addressLine2` text(255), + `city` text(100), + `state` text(50), + `postalCode` text(20), + `country` text(100), + `website` text(255), + `taxId` text(100), + `logoUrl` text(500), + `isDefault` integer DEFAULT false, + `createdById` text(255) NOT NULL, + `createdAt` integer DEFAULT (unixepoch()) NOT NULL, + `updatedAt` integer, + FOREIGN KEY (`createdById`) REFERENCES `beenvoice_user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `business_created_by_idx` ON `beenvoice_business` (`createdById`);--> statement-breakpoint +CREATE INDEX `business_name_idx` ON `beenvoice_business` (`name`);--> statement-breakpoint +CREATE INDEX `business_email_idx` ON `beenvoice_business` (`email`);--> statement-breakpoint +CREATE INDEX `business_is_default_idx` ON `beenvoice_business` (`isDefault`);--> statement-breakpoint +CREATE TABLE `beenvoice_client` ( + `id` text(255) PRIMARY KEY NOT NULL, + `name` text(255) NOT NULL, + `email` text(255), + `phone` text(50), + `addressLine1` text(255), + `addressLine2` text(255), + `city` text(100), + `state` text(50), + `postalCode` text(20), + `country` text(100), + `createdById` text(255) NOT NULL, + `createdAt` integer DEFAULT (unixepoch()) NOT NULL, + `updatedAt` integer, + FOREIGN KEY (`createdById`) REFERENCES `beenvoice_user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `client_created_by_idx` ON `beenvoice_client` (`createdById`);--> statement-breakpoint +CREATE INDEX `client_name_idx` ON `beenvoice_client` (`name`);--> statement-breakpoint +CREATE INDEX `client_email_idx` ON `beenvoice_client` (`email`);--> statement-breakpoint +CREATE TABLE `beenvoice_invoice_item` ( + `id` text(255) PRIMARY KEY NOT NULL, + `invoiceId` text(255) NOT NULL, + `date` integer NOT NULL, + `description` text(500) NOT NULL, + `hours` real NOT NULL, + `rate` real NOT NULL, + `amount` real NOT NULL, + `position` integer DEFAULT 0 NOT NULL, + `createdAt` integer DEFAULT (unixepoch()) NOT NULL, + FOREIGN KEY (`invoiceId`) REFERENCES `beenvoice_invoice`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `invoice_item_invoice_id_idx` ON `beenvoice_invoice_item` (`invoiceId`);--> statement-breakpoint +CREATE INDEX `invoice_item_date_idx` ON `beenvoice_invoice_item` (`date`);--> statement-breakpoint +CREATE INDEX `invoice_item_position_idx` ON `beenvoice_invoice_item` (`position`);--> statement-breakpoint +CREATE TABLE `beenvoice_invoice` ( + `id` text(255) PRIMARY KEY NOT NULL, + `invoiceNumber` text(100) NOT NULL, + `businessId` text(255), + `clientId` text(255) NOT NULL, + `issueDate` integer NOT NULL, + `dueDate` integer NOT NULL, + `status` text(50) DEFAULT 'draft' NOT NULL, + `totalAmount` real DEFAULT 0 NOT NULL, + `taxRate` real DEFAULT 0 NOT NULL, + `notes` text(1000), + `createdById` text(255) NOT NULL, + `createdAt` integer DEFAULT (unixepoch()) NOT NULL, + `updatedAt` integer, + FOREIGN KEY (`businessId`) REFERENCES `beenvoice_business`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`clientId`) REFERENCES `beenvoice_client`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`createdById`) REFERENCES `beenvoice_user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `invoice_business_id_idx` ON `beenvoice_invoice` (`businessId`);--> statement-breakpoint +CREATE INDEX `invoice_client_id_idx` ON `beenvoice_invoice` (`clientId`);--> statement-breakpoint +CREATE INDEX `invoice_created_by_idx` ON `beenvoice_invoice` (`createdById`);--> statement-breakpoint +CREATE INDEX `invoice_number_idx` ON `beenvoice_invoice` (`invoiceNumber`);--> statement-breakpoint +CREATE INDEX `invoice_status_idx` ON `beenvoice_invoice` (`status`);--> statement-breakpoint +CREATE TABLE `beenvoice_session` ( + `sessionToken` text(255) PRIMARY KEY NOT NULL, + `userId` text(255) NOT NULL, + `expires` integer NOT NULL, + FOREIGN KEY (`userId`) REFERENCES `beenvoice_user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `session_userId_idx` ON `beenvoice_session` (`userId`);--> statement-breakpoint +CREATE TABLE `beenvoice_user` ( + `id` text(255) PRIMARY KEY NOT NULL, + `name` text(255), + `email` text(255) NOT NULL, + `password` text(255), + `emailVerified` integer DEFAULT (unixepoch()), + `image` text(255) +); +--> statement-breakpoint +CREATE TABLE `beenvoice_verification_token` ( + `identifier` text(255) NOT NULL, + `token` text(255) NOT NULL, + `expires` integer NOT NULL, + PRIMARY KEY(`identifier`, `token`) +); diff --git a/drizzle/0001_yielding_dormammu.sql b/drizzle/0001_yielding_dormammu.sql new file mode 100644 index 0000000..479b4e4 --- /dev/null +++ b/drizzle/0001_yielding_dormammu.sql @@ -0,0 +1,2 @@ +ALTER TABLE `beenvoice_invoice` ADD COLUMN `taxRate` real NOT NULL DEFAULT 0; +UPDATE `beenvoice_invoice` SET `taxRate` = 0 WHERE `taxRate` IS NULL; \ No newline at end of file diff --git a/drizzle/0003_huge_kinsey_walden.sql b/drizzle/0003_huge_kinsey_walden.sql new file mode 100644 index 0000000..fe454b9 --- /dev/null +++ b/drizzle/0003_huge_kinsey_walden.sql @@ -0,0 +1,29 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_beenvoice_invoice` ( + `id` text(255) PRIMARY KEY NOT NULL, + `invoiceNumber` text(100) NOT NULL, + `businessId` text(255), + `clientId` text(255) NOT NULL, + `issueDate` integer NOT NULL, + `dueDate` integer NOT NULL, + `status` text(50) DEFAULT 'draft' NOT NULL, + `totalAmount` real DEFAULT 0 NOT NULL, + `taxRate` real DEFAULT 0 NOT NULL, + `notes` text(1000), + `createdById` text(255) NOT NULL, + `createdAt` integer DEFAULT (unixepoch()) NOT NULL, + `updatedAt` integer, + FOREIGN KEY (`businessId`) REFERENCES `beenvoice_business`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`clientId`) REFERENCES `beenvoice_client`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`createdById`) REFERENCES `beenvoice_user`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_beenvoice_invoice`("id", "invoiceNumber", "businessId", "clientId", "issueDate", "dueDate", "status", "totalAmount", "taxRate", "notes", "createdById", "createdAt", "updatedAt") SELECT "id", "invoiceNumber", "businessId", "clientId", "issueDate", "dueDate", "status", "totalAmount", "taxRate", "notes", "createdById", "createdAt", "updatedAt" FROM `beenvoice_invoice`;--> statement-breakpoint +DROP TABLE `beenvoice_invoice`;--> statement-breakpoint +ALTER TABLE `__new_beenvoice_invoice` RENAME TO `beenvoice_invoice`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE INDEX `invoice_business_id_idx` ON `beenvoice_invoice` (`businessId`);--> statement-breakpoint +CREATE INDEX `invoice_client_id_idx` ON `beenvoice_invoice` (`clientId`);--> statement-breakpoint +CREATE INDEX `invoice_created_by_idx` ON `beenvoice_invoice` (`createdById`);--> statement-breakpoint +CREATE INDEX `invoice_number_idx` ON `beenvoice_invoice` (`invoiceNumber`);--> statement-breakpoint +CREATE INDEX `invoice_status_idx` ON `beenvoice_invoice` (`status`); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..1df98e1 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,884 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "d29d5fc3-5c1a-4506-b057-2c117bcd2017", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "beenvoice_account": { + "name": "beenvoice_account", + "columns": { + "userId": { + "name": "userId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_account_userId_beenvoice_user_id_fk": { + "name": "beenvoice_account_userId_beenvoice_user_id_fk", + "tableFrom": "beenvoice_account", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "beenvoice_account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "beenvoice_account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_business": { + "name": "beenvoice_business", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "addressLine1": { + "name": "addressLine1", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "addressLine2": { + "name": "addressLine2", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postalCode": { + "name": "postalCode", + "type": "text(20)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "website": { + "name": "website", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "taxId": { + "name": "taxId", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logoUrl": { + "name": "logoUrl", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isDefault": { + "name": "isDefault", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "createdById": { + "name": "createdById", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "business_created_by_idx": { + "name": "business_created_by_idx", + "columns": [ + "createdById" + ], + "isUnique": false + }, + "business_name_idx": { + "name": "business_name_idx", + "columns": [ + "name" + ], + "isUnique": false + }, + "business_email_idx": { + "name": "business_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "business_is_default_idx": { + "name": "business_is_default_idx", + "columns": [ + "isDefault" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_business_createdById_beenvoice_user_id_fk": { + "name": "beenvoice_business_createdById_beenvoice_user_id_fk", + "tableFrom": "beenvoice_business", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "createdById" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_client": { + "name": "beenvoice_client", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "addressLine1": { + "name": "addressLine1", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "addressLine2": { + "name": "addressLine2", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postalCode": { + "name": "postalCode", + "type": "text(20)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdById": { + "name": "createdById", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "client_created_by_idx": { + "name": "client_created_by_idx", + "columns": [ + "createdById" + ], + "isUnique": false + }, + "client_name_idx": { + "name": "client_name_idx", + "columns": [ + "name" + ], + "isUnique": false + }, + "client_email_idx": { + "name": "client_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_client_createdById_beenvoice_user_id_fk": { + "name": "beenvoice_client_createdById_beenvoice_user_id_fk", + "tableFrom": "beenvoice_client", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "createdById" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_invoice_item": { + "name": "beenvoice_invoice_item", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "invoiceId": { + "name": "invoiceId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hours": { + "name": "hours", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rate": { + "name": "rate", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "invoice_item_invoice_id_idx": { + "name": "invoice_item_invoice_id_idx", + "columns": [ + "invoiceId" + ], + "isUnique": false + }, + "invoice_item_date_idx": { + "name": "invoice_item_date_idx", + "columns": [ + "date" + ], + "isUnique": false + }, + "invoice_item_position_idx": { + "name": "invoice_item_position_idx", + "columns": [ + "position" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk": { + "name": "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk", + "tableFrom": "beenvoice_invoice_item", + "tableTo": "beenvoice_invoice", + "columnsFrom": [ + "invoiceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_invoice": { + "name": "beenvoice_invoice", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "invoiceNumber": { + "name": "invoiceNumber", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "businessId": { + "name": "businessId", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "clientId": { + "name": "clientId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issueDate": { + "name": "issueDate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dueDate": { + "name": "dueDate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "totalAmount": { + "name": "totalAmount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "taxRate": { + "name": "taxRate", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text(1000)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdById": { + "name": "createdById", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invoice_business_id_idx": { + "name": "invoice_business_id_idx", + "columns": [ + "businessId" + ], + "isUnique": false + }, + "invoice_client_id_idx": { + "name": "invoice_client_id_idx", + "columns": [ + "clientId" + ], + "isUnique": false + }, + "invoice_created_by_idx": { + "name": "invoice_created_by_idx", + "columns": [ + "createdById" + ], + "isUnique": false + }, + "invoice_number_idx": { + "name": "invoice_number_idx", + "columns": [ + "invoiceNumber" + ], + "isUnique": false + }, + "invoice_status_idx": { + "name": "invoice_status_idx", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_invoice_businessId_beenvoice_business_id_fk": { + "name": "beenvoice_invoice_businessId_beenvoice_business_id_fk", + "tableFrom": "beenvoice_invoice", + "tableTo": "beenvoice_business", + "columnsFrom": [ + "businessId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "beenvoice_invoice_clientId_beenvoice_client_id_fk": { + "name": "beenvoice_invoice_clientId_beenvoice_client_id_fk", + "tableFrom": "beenvoice_invoice", + "tableTo": "beenvoice_client", + "columnsFrom": [ + "clientId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "beenvoice_invoice_createdById_beenvoice_user_id_fk": { + "name": "beenvoice_invoice_createdById_beenvoice_user_id_fk", + "tableFrom": "beenvoice_invoice", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "createdById" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_session": { + "name": "beenvoice_session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_session_userId_beenvoice_user_id_fk": { + "name": "beenvoice_session_userId_beenvoice_user_id_fk", + "tableFrom": "beenvoice_session", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_user": { + "name": "beenvoice_user", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "image": { + "name": "image", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_verification_token": { + "name": "beenvoice_verification_token", + "columns": { + "identifier": { + "name": "identifier", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "beenvoice_verification_token_identifier_token_pk": { + "columns": [ + "identifier", + "token" + ], + "name": "beenvoice_verification_token_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..d83920d --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,683 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "5672a328-2801-45d8-9e27-bb9fe07e6c0e", + "prevId": "4d0fc78f-75b4-4059-b7f0-1aa656f007b7", + "tables": { + "beenvoice_account": { + "name": "beenvoice_account", + "columns": { + "userId": { + "name": "userId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_account_userId_beenvoice_user_id_fk": { + "name": "beenvoice_account_userId_beenvoice_user_id_fk", + "tableFrom": "beenvoice_account", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "beenvoice_account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "beenvoice_account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_client": { + "name": "beenvoice_client", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "addressLine1": { + "name": "addressLine1", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "addressLine2": { + "name": "addressLine2", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "city": { + "name": "city", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text(50)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "postalCode": { + "name": "postalCode", + "type": "text(20)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdById": { + "name": "createdById", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "client_created_by_idx": { + "name": "client_created_by_idx", + "columns": [ + "createdById" + ], + "isUnique": false + }, + "client_name_idx": { + "name": "client_name_idx", + "columns": [ + "name" + ], + "isUnique": false + }, + "client_email_idx": { + "name": "client_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_client_createdById_beenvoice_user_id_fk": { + "name": "beenvoice_client_createdById_beenvoice_user_id_fk", + "tableFrom": "beenvoice_client", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "createdById" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_invoice_item": { + "name": "beenvoice_invoice_item", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "invoiceId": { + "name": "invoiceId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hours": { + "name": "hours", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rate": { + "name": "rate", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "invoice_item_invoice_id_idx": { + "name": "invoice_item_invoice_id_idx", + "columns": [ + "invoiceId" + ], + "isUnique": false + }, + "invoice_item_date_idx": { + "name": "invoice_item_date_idx", + "columns": [ + "date" + ], + "isUnique": false + }, + "invoice_item_position_idx": { + "name": "invoice_item_position_idx", + "columns": [ + "position" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk": { + "name": "beenvoice_invoice_item_invoiceId_beenvoice_invoice_id_fk", + "tableFrom": "beenvoice_invoice_item", + "tableTo": "beenvoice_invoice", + "columnsFrom": [ + "invoiceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_invoice": { + "name": "beenvoice_invoice", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "invoiceNumber": { + "name": "invoiceNumber", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "clientId": { + "name": "clientId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issueDate": { + "name": "issueDate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dueDate": { + "name": "dueDate", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "totalAmount": { + "name": "totalAmount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "taxRate": { + "name": "taxRate", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text(1000)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdById": { + "name": "createdById", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invoice_client_id_idx": { + "name": "invoice_client_id_idx", + "columns": [ + "clientId" + ], + "isUnique": false + }, + "invoice_created_by_idx": { + "name": "invoice_created_by_idx", + "columns": [ + "createdById" + ], + "isUnique": false + }, + "invoice_number_idx": { + "name": "invoice_number_idx", + "columns": [ + "invoiceNumber" + ], + "isUnique": false + }, + "invoice_status_idx": { + "name": "invoice_status_idx", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_invoice_clientId_beenvoice_client_id_fk": { + "name": "beenvoice_invoice_clientId_beenvoice_client_id_fk", + "tableFrom": "beenvoice_invoice", + "tableTo": "beenvoice_client", + "columnsFrom": [ + "clientId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "beenvoice_invoice_createdById_beenvoice_user_id_fk": { + "name": "beenvoice_invoice_createdById_beenvoice_user_id_fk", + "tableFrom": "beenvoice_invoice", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "createdById" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_session": { + "name": "beenvoice_session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "beenvoice_session_userId_beenvoice_user_id_fk": { + "name": "beenvoice_session_userId_beenvoice_user_id_fk", + "tableFrom": "beenvoice_session", + "tableTo": "beenvoice_user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_user": { + "name": "beenvoice_user", + "columns": { + "id": { + "name": "id", + "type": "text(255)", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "image": { + "name": "image", + "type": "text(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "beenvoice_verification_token": { + "name": "beenvoice_verification_token", + "columns": { + "identifier": { + "name": "identifier", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "beenvoice_verification_token_identifier_token_pk": { + "columns": [ + "identifier", + "token" + ], + "name": "beenvoice_verification_token_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..c7773bb --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1752275489999, + "tag": "0000_unique_loa", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ff2b6e4..1553e0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "0.1.0", "dependencies": { "@auth/drizzle-adapter": "^1.7.2", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@libsql/client": "^0.14.0", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", @@ -17,21 +20,23 @@ "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", + "@react-pdf/renderer": "^4.3.0", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^5.69.0", "@trpc/client": "^11.0.0", "@trpc/react-query": "^11.0.0", "@trpc/server": "^11.0.0", "@types/bcryptjs": "^2.4.6", + "@types/file-saver": "^2.0.7", "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "drizzle-orm": "^0.41.0", - "html2canvas": "^1.4.1", - "jspdf": "^3.0.1", + "file-saver": "^2.0.5", "lucide": "^0.525.0", "lucide-react": "^0.525.0", "next": "^15.2.3", @@ -138,6 +143,59 @@ "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", "license": "MIT" }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "dev": true, @@ -1344,6 +1402,29 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", @@ -1557,6 +1638,186 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@react-pdf/fns": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz", + "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==", + "license": "MIT" + }, + "node_modules/@react-pdf/font": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.2.tgz", + "integrity": "sha512-/dAWu7Y2RD1RxarDZ9SkYPHgBYOhmcDnet4W/qN/m8k+A2Hr3ja54GymSR7GGxWBtxjKtNauVKrTa9LS1n8WUw==", + "license": "MIT", + "dependencies": { + "@react-pdf/pdfkit": "^4.0.3", + "@react-pdf/types": "^2.9.0", + "fontkit": "^2.0.2", + "is-url": "^1.2.4" + } + }, + "node_modules/@react-pdf/image": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz", + "integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/png-js": "^3.0.0", + "jay-peg": "^1.1.1" + } + }, + "node_modules/@react-pdf/layout": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.0.tgz", + "integrity": "sha512-Aq+Cc6JYausWLoks2FvHe3PwK9cTuvksB2uJ0AnkKJEUtQbvCq8eCRb1bjbbwIji9OzFRTTzZij7LzkpKHjIeA==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/image": "^3.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.0", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.0", + "emoji-regex": "^10.3.0", + "queue": "^6.0.1", + "yoga-layout": "^3.2.1" + } + }, + "node_modules/@react-pdf/layout/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/@react-pdf/pdfkit": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.3.tgz", + "integrity": "sha512-k+Lsuq8vTwWsCqTp+CCB4+2N+sOTFrzwGA7aw3H9ix/PDWR9QksbmNg0YkzGbLAPI6CeawmiLHcf4trZ5ecLPQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/png-js": "^3.0.0", + "browserify-zlib": "^0.2.0", + "crypto-js": "^4.2.0", + "fontkit": "^2.0.2", + "jay-peg": "^1.1.1", + "linebreak": "^1.1.0", + "vite-compatible-readable-stream": "^3.6.1" + } + }, + "node_modules/@react-pdf/png-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz", + "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==", + "license": "MIT", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/@react-pdf/primitives": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz", + "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==", + "license": "MIT" + }, + "node_modules/@react-pdf/reconciler": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz", + "integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "scheduler": "0.25.0-rc-603e6108-20241029" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/reconciler/node_modules/scheduler": { + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", + "license": "MIT" + }, + "node_modules/@react-pdf/render": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.0.tgz", + "integrity": "sha512-MdWfWaqO6d7SZD75TZ2z5L35V+cHpyA43YNRlJNG0RJ7/MeVGDQv12y/BXOJgonZKkeEGdzM3EpAt9/g4E22WA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.0", + "abs-svg-path": "^0.1.1", + "color-string": "^1.9.1", + "normalize-svg-path": "^1.1.0", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + } + }, + "node_modules/@react-pdf/renderer": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.0.tgz", + "integrity": "sha512-28gpA69fU9ZQrDzmd5xMJa1bDf8t0PT3ApUKBl2PUpoE/x4JlvCB5X66nMXrfFrgF2EZrA72zWQAkvbg7TE8zw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/font": "^4.0.2", + "@react-pdf/layout": "^4.4.0", + "@react-pdf/pdfkit": "^4.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/reconciler": "^1.1.4", + "@react-pdf/render": "^4.3.0", + "@react-pdf/types": "^2.9.0", + "events": "^3.3.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "queue": "^6.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/stylesheet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.0.tgz", + "integrity": "sha512-BGZ2sYNUp38VJUegjva/jsri3iiRGnVNjWI+G9dTwAvLNOmwFvSJzqaCsEnqQ/DW5mrTBk/577FhDY7pv6AidA==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/types": "^2.9.0", + "color-string": "^1.9.1", + "hsl-to-hex": "^1.0.0", + "media-engine": "^1.0.3", + "postcss-value-parser": "^4.1.0" + } + }, + "node_modules/@react-pdf/textkit": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz", + "integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==", + "license": "MIT", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "bidi-js": "^1.0.2", + "hyphen": "^1.6.4", + "unicode-properties": "^1.4.1" + } + }, + "node_modules/@react-pdf/types": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.0.tgz", + "integrity": "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ==", + "license": "MIT", + "dependencies": { + "@react-pdf/font": "^4.0.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "dev": true, @@ -1762,6 +2023,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "dev": true, @@ -1779,13 +2046,6 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/raf": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", - "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/react": { "version": "19.1.8", "devOptional": true, @@ -1802,13 +2062,6 @@ "@types/react": "^19.0.0" } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/ws": { "version": "8.18.1", "license": "MIT", @@ -2085,6 +2338,12 @@ "darwin" ] }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "dev": true, @@ -2315,18 +2574,6 @@ "node": ">= 0.4" } }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/attr-accept": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", @@ -2371,14 +2618,25 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, "node_modules/bcryptjs": { "version": "3.0.2", @@ -2389,6 +2647,15 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "dev": true, @@ -2409,16 +2676,22 @@ "node": ">=8" } }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" } }, "node_modules/buffer-from": { @@ -2505,26 +2778,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvg": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", - "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@types/raf": "^3.4.0", - "core-js": "^3.8.3", - "raf": "^3.4.1", - "regenerator-runtime": "^0.13.7", - "rgbcolor": "^1.0.1", - "stackblur-canvas": "^2.0.0", - "svg-pathdata": "^6.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -2562,6 +2815,15 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clsx": { "version": "2.1.1", "license": "MIT", @@ -2594,13 +2856,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "devOptional": true, "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", "license": "MIT", - "optional": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -2631,18 +2891,6 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/core-js": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz", - "integrity": "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -2675,14 +2923,11 @@ "dev": true, "license": "ISC" }, - "node_modules/css-line-break": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", - "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "license": "MIT", - "dependencies": { - "utrie": "^1.0.2" - } + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" }, "node_modules/csstype": { "version": "3.1.3", @@ -2832,6 +3077,12 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/doctrine": { "version": "2.1.0", "dev": true, @@ -2843,16 +3094,6 @@ "node": ">=0.10.0" } }, - "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true, - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/drizzle-kit": { "version": "0.30.6", "dev": true, @@ -3638,9 +3879,17 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -3721,12 +3970,6 @@ "node": "^12.20 || >= 14.13" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, @@ -3738,6 +3981,12 @@ "node": ">=16.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/file-selector": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", @@ -3793,6 +4042,23 @@ "dev": true, "license": "ISC" }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/for-each": { "version": "0.3.5", "dev": true, @@ -4080,19 +4346,27 @@ "node": ">= 0.4" } }, - "node_modules/html2canvas": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", - "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "node_modules/hsl-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", + "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", "license": "MIT", "dependencies": { - "css-line-break": "^2.1.0", - "text-segmentation": "^1.0.3" - }, - "engines": { - "node": ">=8.0.0" + "hsl-to-rgb-for-reals": "^1.1.0" } }, + "node_modules/hsl-to-rgb-for-reals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", + "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", + "license": "ISC" + }, + "node_modules/hyphen": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz", + "integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==", + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "dev": true, @@ -4124,6 +4398,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "dev": true, @@ -4155,8 +4435,7 @@ }, "node_modules/is-arrayish": { "version": "0.3.2", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -4451,6 +4730,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "dev": true, @@ -4530,6 +4815,15 @@ "node": ">= 0.4" } }, + "node_modules/jay-peg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", + "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", + "license": "MIT", + "dependencies": { + "restructure": "^3.0.0" + } + }, "node_modules/jiti": { "version": "2.4.2", "dev": true, @@ -4590,24 +4884,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jspdf": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz", - "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.7", - "atob": "^2.1.2", - "btoa": "^1.2.1", - "fflate": "^0.8.1" - }, - "optionalDependencies": { - "canvg": "^3.0.11", - "core-js": "^3.6.0", - "dompurify": "^3.2.4", - "html2canvas": "^1.0.0-rc.5" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "dev": true, @@ -4738,6 +5014,25 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/locate-path": { "version": "6.0.0", "dev": true, @@ -4798,6 +5093,12 @@ "node": ">= 0.4" } }, + "node_modules/media-engine": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", + "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "dev": true, @@ -5111,6 +5412,15 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "license": "MIT", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, "node_modules/oauth4webapi": { "version": "3.5.5", "license": "MIT", @@ -5284,6 +5594,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -5295,6 +5611,12 @@ "node": ">=6" } }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "dev": true, @@ -5316,13 +5638,6 @@ "dev": true, "license": "MIT" }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "license": "MIT", - "optional": true - }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -5373,6 +5688,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/preact": { "version": "10.24.3", "license": "MIT", @@ -5520,6 +5841,15 @@ "node": ">=6" } }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -5539,16 +5869,6 @@ ], "license": "MIT" }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "license": "MIT", - "optional": true, - "dependencies": { - "performance-now": "^2.1.0" - } - }, "node_modules/react": { "version": "19.1.0", "license": "MIT", @@ -5698,13 +6018,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "optional": true - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "dev": true, @@ -5724,6 +6037,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "dev": true, @@ -5759,6 +6081,12 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, "node_modules/reusify": { "version": "1.1.0", "dev": true, @@ -5768,16 +6096,6 @@ "node": ">=0.10.0" } }, - "node_modules/rgbcolor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", - "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", - "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", - "optional": true, - "engines": { - "node": ">= 0.8.15" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -5818,6 +6136,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "dev": true, @@ -6052,7 +6390,6 @@ "node_modules/simple-swizzle": { "version": "0.2.2", "license": "MIT", - "optional": true, "dependencies": { "is-arrayish": "^0.3.1" } @@ -6096,16 +6433,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stackblur-canvas": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", - "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.14" - } - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "dev": true, @@ -6124,6 +6451,15 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "dev": true, @@ -6297,15 +6633,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-pathdata": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", - "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - } + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", + "license": "ISC" }, "node_modules/tailwind-merge": { "version": "3.3.1", @@ -6354,14 +6686,11 @@ "node": ">=18" } }, - "node_modules/text-segmentation": { + "node_modules/tiny-inflate": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", - "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "license": "MIT", - "dependencies": { - "utrie": "^1.0.2" - } + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.14", @@ -6557,6 +6886,32 @@ "version": "6.21.0", "license": "MIT" }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/unrs-resolver": { "version": "1.11.1", "dev": true, @@ -6641,13 +6996,24 @@ } } }, - "node_modules/utrie": { + "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", - "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite-compatible-readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", + "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", "license": "MIT", "dependencies": { - "base64-arraybuffer": "^1.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/web-streams-polyfill": { @@ -6798,6 +7164,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, "node_modules/zod": { "version": "3.25.76", "license": "MIT", diff --git a/package.json b/package.json index 5b02c74..e682787 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", + "deploy": "drizzle-kit push && next build", "dev": "next dev --turbo", "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", @@ -21,6 +22,9 @@ }, "dependencies": { "@auth/drizzle-adapter": "^1.7.2", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@libsql/client": "^0.14.0", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", @@ -29,21 +33,24 @@ "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", + "@react-pdf/renderer": "^4.3.0", "@t3-oss/env-nextjs": "^0.12.0", "@tanstack/react-query": "^5.69.0", "@trpc/client": "^11.0.0", "@trpc/react-query": "^11.0.0", "@trpc/server": "^11.0.0", "@types/bcryptjs": "^2.4.6", + "@types/file-saver": "^2.0.7", "bcryptjs": "^3.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "drizzle-orm": "^0.41.0", - "html2canvas": "^1.4.1", - "jspdf": "^3.0.1", + "file-saver": "^2.0.5", "lucide": "^0.525.0", "lucide-react": "^0.525.0", "next": "^15.2.3", diff --git a/public/beenvoice-logo.png b/public/beenvoice-logo.png new file mode 100644 index 0000000..397be10 Binary files /dev/null and b/public/beenvoice-logo.png differ diff --git a/public/beenvoice-logo.svg b/public/beenvoice-logo.svg new file mode 100644 index 0000000..2f956dc --- /dev/null +++ b/public/beenvoice-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/fonts/azeret/AzeretMono-Regular.ttf b/public/fonts/azeret/AzeretMono-Regular.ttf new file mode 100644 index 0000000..cffa6eb Binary files /dev/null and b/public/fonts/azeret/AzeretMono-Regular.ttf differ diff --git a/public/fonts/inter/Inter-Italic-Variable.ttf b/public/fonts/inter/Inter-Italic-Variable.ttf new file mode 100644 index 0000000..43ed4f5 Binary files /dev/null and b/public/fonts/inter/Inter-Italic-Variable.ttf differ diff --git a/public/fonts/inter/Inter-Variable.ttf b/public/fonts/inter/Inter-Variable.ttf new file mode 100644 index 0000000..e31b51e Binary files /dev/null and b/public/fonts/inter/Inter-Variable.ttf differ diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index 6116817..d3b71b6 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -1,8 +1,8 @@ "use client"; import Link from "next/link"; -import { useState } from "react"; -import { useRouter } from "next/navigation"; +import { useState, Suspense } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Button } from "~/components/ui/button"; @@ -11,8 +11,10 @@ import { toast } from "sonner"; import { Logo } from "~/components/logo"; import { User, Mail, Lock, ArrowRight } from "lucide-react"; -export default function RegisterPage() { +function RegisterForm() { const router = useRouter(); + const searchParams = useSearchParams(); + const callbackUrl = searchParams.get("callbackUrl") ?? "/dashboard"; const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [email, setEmail] = useState(""); @@ -25,17 +27,21 @@ export default function RegisterPage() { const res = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - firstName, - lastName, - email, - password + body: JSON.stringify({ + firstName, + lastName, + email, + password, }), }); setLoading(false); if (res.ok) { toast.success("Account created successfully! Please sign in."); - router.push("/auth/signin"); + const signInUrl = + callbackUrl !== "/dashboard" + ? `/auth/signin?callbackUrl=${encodeURIComponent(callbackUrl)}` + : "/auth/signin"; + router.push(signInUrl); } else { const error = await res.text(); toast.error(error || "Failed to create account"); @@ -43,21 +49,25 @@ export default function RegisterPage() { } return ( -
+
{/* Logo and Welcome */} -
+

Join beenvoice

-

Create your account to get started

+

+ Create your account to get started +

{/* Registration Form */} - + - Create Account + + Create Account +
@@ -65,12 +75,12 @@ export default function RegisterPage() {
- + setFirstName(e.target.value)} + onChange={(e) => setFirstName(e.target.value)} required autoFocus className="pl-10" @@ -81,12 +91,12 @@ export default function RegisterPage() {
- + setLastName(e.target.value)} + onChange={(e) => setLastName(e.target.value)} required className="pl-10" placeholder="Last name" @@ -97,12 +107,12 @@ export default function RegisterPage() {
- + setEmail(e.target.value)} + onChange={(e) => setEmail(e.target.value)} required className="pl-10" placeholder="Enter your email" @@ -112,19 +122,21 @@ export default function RegisterPage() {
- + setPassword(e.target.value)} + onChange={(e) => setPassword(e.target.value)} required minLength={6} className="pl-10" placeholder="Create a password" />
-

Must be at least 6 characters

+

+ Must be at least 6 characters +

- Don't have an account? - + + Don't have an account?{" "} + + Create one now
@@ -110,8 +119,10 @@ export default function SignInPage() { {/* Features */} -
-

Simple invoicing for freelancers and small businesses

+
+

+ Simple invoicing for freelancers and small businesses +

✓ Easy client management ✓ Professional invoices @@ -121,4 +132,28 @@ export default function SignInPage() {
); -} \ No newline at end of file +} + +export default function SignInPage() { + return ( + +
+
+ +
+

+ Welcome back +

+

Loading...

+
+
+
+
+ } + > + + + ); +} diff --git a/src/app/clients/layout.tsx b/src/app/clients/layout.tsx new file mode 100644 index 0000000..dec0c33 --- /dev/null +++ b/src/app/clients/layout.tsx @@ -0,0 +1,20 @@ +import { Navbar } from "~/components/Navbar"; +import { Sidebar } from "~/components/Sidebar"; + +export default function ClientsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> + +
+ +
+ {children} +
+
+ + ); +} \ No newline at end of file diff --git a/src/app/clients/page.tsx b/src/app/clients/page.tsx new file mode 100644 index 0000000..1e7bf51 --- /dev/null +++ b/src/app/clients/page.tsx @@ -0,0 +1,42 @@ +import Link from "next/link"; +import { auth } from "~/server/auth"; +import { api, HydrateClient } from "~/trpc/server"; +import { Button } from "~/components/ui/button"; +import { ClientList } from "~/components/client-list"; +import { Plus } from "lucide-react"; + +export default async function ClientsPage() { + const session = await auth(); + + if (!session?.user) { + return ( +
+
+

Access Denied

+

Please sign in to view clients

+ + + +
+
+ ); + } + + // Prefetch clients data + void api.clients.getAll.prefetch(); + + return ( + +
+
+

Clients

+

+ Manage your client relationships +

+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/_components/dashboard-components.tsx b/src/app/dashboard/_components/dashboard-components.tsx new file mode 100644 index 0000000..5e1a335 --- /dev/null +++ b/src/app/dashboard/_components/dashboard-components.tsx @@ -0,0 +1,243 @@ +"use client"; + +import { api } from "~/trpc/react"; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { Button } from "~/components/ui/button"; +import { + Users, + FileText, + TrendingUp, + Calendar, + Plus, + ArrowRight +} from "lucide-react"; +import Link from "next/link"; +import { DashboardStatsSkeleton, DashboardActivitySkeleton } from "~/components/ui/skeleton"; + +// Client component for dashboard stats +export function DashboardStats() { + const { data: clients, isLoading: clientsLoading } = api.clients.getAll.useQuery(); + const { data: invoices, isLoading: invoicesLoading } = api.invoices.getAll.useQuery(); + + if (clientsLoading || invoicesLoading) { + return ; + } + + const totalClients = clients?.length ?? 0; + const totalInvoices = invoices?.length ?? 0; + const totalRevenue = invoices?.reduce((sum, invoice) => sum + invoice.totalAmount, 0) ?? 0; + const pendingInvoices = invoices?.filter(invoice => invoice.status === "sent" || invoice.status === "draft").length ?? 0; + + // Calculate month-over-month changes (simplified) + const lastMonthClients = 0; // This would need historical data + const lastMonthInvoices = 0; + const lastMonthRevenue = 0; + + return ( +
+ + + Total Clients +
+ +
+
+ +
{totalClients}
+

+ {totalClients > lastMonthClients ? "+" : ""}{totalClients - lastMonthClients} from last month +

+
+
+ + + + Total Invoices +
+ +
+
+ +
{totalInvoices}
+

+ {totalInvoices > lastMonthInvoices ? "+" : ""}{totalInvoices - lastMonthInvoices} from last month +

+
+
+ + + + Revenue +
+ +
+
+ +
${totalRevenue.toFixed(2)}
+

+ {totalRevenue > lastMonthRevenue ? "+" : ""}{((totalRevenue - lastMonthRevenue) / (lastMonthRevenue || 1) * 100).toFixed(1)}% from last month +

+
+
+ + + + Pending Invoices +
+ +
+
+ +
{pendingInvoices}
+

+ Due this month +

+
+
+
+ ); +} + +// Client component for dashboard cards +export function DashboardCards() { + return ( +
+ + + +
+ +
+ Manage Clients +
+
+ +

+ Add new clients and manage your existing client relationships. +

+
+ + +
+
+
+ + + + +
+ +
+ Create Invoices +
+
+ +

+ Generate professional invoices and track payments. +

+
+ + +
+
+
+
+ ); +} + +// Client component for recent activity +export function DashboardActivity() { + const { data: invoices, isLoading } = api.invoices.getAll.useQuery(); + + if (isLoading) { + return ; + } + + const recentInvoices = invoices?.slice(0, 5) ?? []; + + return ( + + + Recent Activity + + + {recentInvoices.length === 0 ? ( +
+
+ +
+

No recent activity

+

Start by adding your first client or creating an invoice

+
+ ) : ( +
+ {recentInvoices.map((invoice) => ( +
+
+
+ +
+
+

Invoice #{invoice.invoiceNumber}

+

+ {invoice.client?.name ?? "Unknown Client"} • ${invoice.totalAmount.toFixed(2)} +

+
+
+
+ + {invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} + + +
+
+ ))} +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/businesses/[id]/edit/page.tsx b/src/app/dashboard/businesses/[id]/edit/page.tsx new file mode 100644 index 0000000..719aca9 --- /dev/null +++ b/src/app/dashboard/businesses/[id]/edit/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { BusinessForm } from "~/components/business-form"; +import { useParams } from "next/navigation"; + +export default function EditBusinessPage() { + const params = useParams(); + const businessId = Array.isArray(params?.id) ? params.id[0] : params?.id; + if (!businessId) return null; + return ; +} \ No newline at end of file diff --git a/src/app/dashboard/businesses/_components/businesses-table.tsx b/src/app/dashboard/businesses/_components/businesses-table.tsx new file mode 100644 index 0000000..5853428 --- /dev/null +++ b/src/app/dashboard/businesses/_components/businesses-table.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { api } from "~/trpc/react"; +import { UniversalTable } from "~/components/ui/universal-table"; +import { TableSkeleton } from "~/components/ui/skeleton"; + +export function BusinessesTable() { + const { isLoading } = api.businesses.getAll.useQuery(); + + if (isLoading) { + return ; + } + + return ; +} \ No newline at end of file diff --git a/src/app/dashboard/businesses/new/page.tsx b/src/app/dashboard/businesses/new/page.tsx new file mode 100644 index 0000000..304ccc3 --- /dev/null +++ b/src/app/dashboard/businesses/new/page.tsx @@ -0,0 +1,5 @@ +import { BusinessForm } from "~/components/business-form"; + +export default function NewBusinessPage() { + return ; +} \ No newline at end of file diff --git a/src/app/dashboard/businesses/page.tsx b/src/app/dashboard/businesses/page.tsx new file mode 100644 index 0000000..95d04ce --- /dev/null +++ b/src/app/dashboard/businesses/page.tsx @@ -0,0 +1,35 @@ +import Link from "next/link"; + +import { api, HydrateClient } from "~/trpc/server"; +import { Button } from "~/components/ui/button"; +import { Plus } from "lucide-react"; +import { BusinessesTable } from "./_components/businesses-table"; + +export default async function BusinessesPage() { + return ( +
+
+
+

+ Businesses +

+

+ Manage your businesses and their information. +

+
+ +
+ + + +
+ ); +} diff --git a/src/app/dashboard/clients/[id]/edit/page.tsx b/src/app/dashboard/clients/[id]/edit/page.tsx index f3de7b8..eee52cb 100644 --- a/src/app/dashboard/clients/[id]/edit/page.tsx +++ b/src/app/dashboard/clients/[id]/edit/page.tsx @@ -1,45 +1,26 @@ -import { auth } from "~/server/auth"; import { HydrateClient } from "~/trpc/server"; -import { Button } from "~/components/ui/button"; import { ClientForm } from "~/components/client-form"; -import Link from "next/link"; interface EditClientPageProps { params: Promise<{ id: string }>; } export default async function EditClientPage({ params }: EditClientPageProps) { - const session = await auth(); const { id } = await params; - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to edit clients

- - - -
-
- ); - } - return (
-

Edit Client

-

Update client information below.

+

+ Edit Client +

+

+ Update client information below. +

); -} \ No newline at end of file +} diff --git a/src/app/dashboard/clients/_components/clients-table.tsx b/src/app/dashboard/clients/_components/clients-table.tsx new file mode 100644 index 0000000..6f96cad --- /dev/null +++ b/src/app/dashboard/clients/_components/clients-table.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { api } from "~/trpc/react"; +import { UniversalTable } from "~/components/ui/universal-table"; +import { TableSkeleton } from "~/components/ui/skeleton"; + +export function ClientsTable() { + const { isLoading } = api.clients.getAll.useQuery(); + + if (isLoading) { + return ; + } + + return ; +} \ No newline at end of file diff --git a/src/app/dashboard/clients/new/page.tsx b/src/app/dashboard/clients/new/page.tsx index f03acbe..c6e98ae 100644 --- a/src/app/dashboard/clients/new/page.tsx +++ b/src/app/dashboard/clients/new/page.tsx @@ -1,40 +1,20 @@ -import { auth } from "~/server/auth"; import { HydrateClient } from "~/trpc/server"; -import { Button } from "~/components/ui/button"; import { ClientForm } from "~/components/client-form"; -import Link from "next/link"; export default async function NewClientPage() { - const session = await auth(); - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to create clients

- - - -
-
- ); - } - return (
-

Add Client

-

Enter client details below to add a new client.

+

+ Add Client +

+

+ Enter client details below to add a new client. +

); -} \ No newline at end of file +} diff --git a/src/app/dashboard/clients/page.tsx b/src/app/dashboard/clients/page.tsx index 447d645..6779dff 100644 --- a/src/app/dashboard/clients/page.tsx +++ b/src/app/dashboard/clients/page.tsx @@ -1,51 +1,35 @@ import Link from "next/link"; -import { auth } from "~/server/auth"; + import { api, HydrateClient } from "~/trpc/server"; import { Button } from "~/components/ui/button"; -import { UniversalTable } from "~/components/ui/universal-table"; import { Plus } from "lucide-react"; +import { ClientsTable } from "./_components/clients-table"; export default async function ClientsPage() { - const session = await auth(); - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to view clients

- - - -
-
- ); - } - - // Prefetch clients data - void api.clients.getAll.prefetch(); - return (
-
+
-

Clients

-

Manage your clients and their information.

+

+ Clients +

+

+ Manage your clients and their information. +

-
- +
); -} \ No newline at end of file +} diff --git a/src/app/dashboard/invoices/[id]/_components/unified-invoice-page.tsx b/src/app/dashboard/invoices/[id]/_components/unified-invoice-page.tsx new file mode 100644 index 0000000..add49db --- /dev/null +++ b/src/app/dashboard/invoices/[id]/_components/unified-invoice-page.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { InvoiceView } from "~/components/invoice-view"; +import { InvoiceForm } from "~/components/invoice-form"; + +interface UnifiedInvoicePageProps { + invoiceId: string; + mode: string; +} + +export function UnifiedInvoicePage({ + invoiceId, + mode, +}: UnifiedInvoicePageProps) { + return ( +
+ {/* Always render InvoiceForm to preserve state, but hide when in view mode */} +
+ +
+ + {/* Show InvoiceView only when in view mode */} + {mode === "view" && } +
+ ); +} diff --git a/src/app/dashboard/invoices/[id]/edit/page.tsx b/src/app/dashboard/invoices/[id]/edit/page.tsx deleted file mode 100644 index 543ccb8..0000000 --- a/src/app/dashboard/invoices/[id]/edit/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { auth } from "~/server/auth"; -import { api, HydrateClient } from "~/trpc/server"; -import { Button } from "~/components/ui/button"; -import { InvoiceForm } from "~/components/invoice-form"; -import Link from "next/link"; -import { notFound } from "next/navigation"; - -interface EditInvoicePageProps { - params: Promise<{ id: string }>; -} - -export default async function EditInvoicePage({ params }: EditInvoicePageProps) { - const session = await auth(); - const { id } = await params; - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to edit invoices

- - - -
-
- ); - } - - // Prefetch invoice data - try { - await api.invoices.getById.prefetch({ id: id }); - } catch (error) { - notFound(); - } - - return ( -
-
-

Edit Invoice

-

Update the invoice details below.

-
- - - -
- ); -} \ No newline at end of file diff --git a/src/app/dashboard/invoices/[id]/page.tsx b/src/app/dashboard/invoices/[id]/page.tsx index 7853c93..7221284 100644 --- a/src/app/dashboard/invoices/[id]/page.tsx +++ b/src/app/dashboard/invoices/[id]/page.tsx @@ -1,63 +1,72 @@ -import { auth } from "~/server/auth"; import { api, HydrateClient } from "~/trpc/server"; -import { Button } from "~/components/ui/button"; import { InvoiceView } from "~/components/invoice-view"; +import { InvoiceForm } from "~/components/invoice-form"; + import Link from "next/link"; import { notFound } from "next/navigation"; -import { Edit } from "lucide-react"; +import { Edit, Eye, ArrowLeft } from "lucide-react"; +import { UnifiedInvoicePage } from "./_components/unified-invoice-page"; interface InvoicePageProps { params: Promise<{ id: string }>; + searchParams: Promise<{ mode?: string }>; } -export default async function InvoicePage({ params }: InvoicePageProps) { - const session = await auth(); +export default async function InvoicePage({ + params, + searchParams, +}: InvoicePageProps) { const { id } = await params; - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to view invoices

- - - -
-
- ); - } - - // Prefetch invoice data - try { - await api.invoices.getById.prefetch({ id: id }); - } catch (error) { - notFound(); - } + const { mode = "view" } = await searchParams; return (
-
-
-

Invoice Details

-

View and manage invoice information.

-
-
- + + + +
+
+ +
+ + +
- - -
); -} \ No newline at end of file +} diff --git a/src/app/dashboard/invoices/_components/invoices-table.tsx b/src/app/dashboard/invoices/_components/invoices-table.tsx new file mode 100644 index 0000000..a038851 --- /dev/null +++ b/src/app/dashboard/invoices/_components/invoices-table.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { api } from "~/trpc/react"; +import { UniversalTable } from "~/components/ui/universal-table"; +import { TableSkeleton } from "~/components/ui/skeleton"; + +export function InvoicesTable() { + const { isLoading } = api.invoices.getAll.useQuery(); + + if (isLoading) { + return ; + } + + return ; +} \ No newline at end of file diff --git a/src/app/dashboard/invoices/import/page.tsx b/src/app/dashboard/invoices/import/page.tsx index dfeffed..b1d56fc 100644 --- a/src/app/dashboard/invoices/import/page.tsx +++ b/src/app/dashboard/invoices/import/page.tsx @@ -1,36 +1,16 @@ -import Link from "next/link"; -import { auth } from "~/server/auth"; -import { api, HydrateClient } from "~/trpc/server"; -import { Button } from "~/components/ui/button"; +import { HydrateClient } from "~/trpc/server"; import { CSVImportPage } from "~/components/csv-import-page"; export default async function ImportPage() { - const session = await auth(); - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to import invoices

- - - -
-
- ); - } - return (
-

Import Invoices

-

Upload CSV files to create invoices in batch.

+

+ Import Invoices +

+

+ Upload CSV files to create invoices in batch. +

@@ -38,4 +18,4 @@ export default async function ImportPage() {
); -} \ No newline at end of file +} diff --git a/src/app/dashboard/invoices/new/page.tsx b/src/app/dashboard/invoices/new/page.tsx index 71ad71c..eef90d4 100644 --- a/src/app/dashboard/invoices/new/page.tsx +++ b/src/app/dashboard/invoices/new/page.tsx @@ -1,40 +1,20 @@ -import { auth } from "~/server/auth"; import { HydrateClient } from "~/trpc/server"; -import { Button } from "~/components/ui/button"; -import Link from "next/link"; import { InvoiceForm } from "~/components/invoice-form"; export default async function NewInvoicePage() { - const session = await auth(); - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to create invoices

- - - -
-
- ); - } - return (
-

Create Invoice

-

Fill out the details below to create a new invoice.

+

+ Create Invoice +

+

+ Fill out the details below to create a new invoice. +

); -} \ No newline at end of file +} diff --git a/src/app/dashboard/invoices/page.tsx b/src/app/dashboard/invoices/page.tsx index 6901524..fdd0607 100644 --- a/src/app/dashboard/invoices/page.tsx +++ b/src/app/dashboard/invoices/page.tsx @@ -1,49 +1,38 @@ import Link from "next/link"; -import { auth } from "~/server/auth"; + import { api, HydrateClient } from "~/trpc/server"; import { Button } from "~/components/ui/button"; -import { UniversalTable } from "~/components/ui/universal-table"; import { Plus, Upload } from "lucide-react"; +import { InvoicesTable } from "./_components/invoices-table"; export default async function InvoicesPage() { - const session = await auth(); - - if (!session?.user) { - return ( -
-
-

Access Denied

-

Please sign in to view invoices

- - - -
-
- ); - } - - // Prefetch invoices data - void api.invoices.getAll.prefetch(); - return (
-
+
-

Invoices

-

Manage your invoices and payments.

+

+ Invoices +

+

+ Manage your invoices and payments. +

- -
- +
); -} \ No newline at end of file +} diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 8030b60..c905a40 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -7,7 +7,15 @@ export default function DashboardLayout({ children }: { children: React.ReactNod <> -
+ {/* Mobile layout - no left margin */} +
+
+ + {children} +
+
+ {/* Desktop layout - with sidebar margin */} +
{children} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 302b4c1..f1a6a07 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,191 +1,31 @@ -import { redirect } from "next/navigation"; import { auth } from "~/server/auth"; -import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; -import { Button } from "~/components/ui/button"; -import { - Users, - FileText, - TrendingUp, - Calendar, - Plus, - ArrowRight -} from "lucide-react"; -import Link from "next/link"; +import { api, HydrateClient } from "~/trpc/server"; +import { + DashboardStats, + DashboardCards, + DashboardActivity, +} from "./_components/dashboard-components"; export default async function DashboardPage() { const session = await auth(); - if (!session?.user) { - redirect("/auth/signin"); - } - return (
{/* Header */}
-

- Welcome back, {session.user.name?.split(" ")[0] ?? "User"}! +

+ Welcome back, {session?.user?.name?.split(" ")[0] ?? "User"}!

-

+

Here's what's happening with your invoicing business

- {/* Quick Stats */} -
- - - Total Clients -
- -
-
- -
0
-

- +0 from last month -

-
-
- - - - Total Invoices -
- -
-
- -
0
-

- +0 from last month -

-
-
- - - - Revenue -
- -
-
- -
$0
-

- +0% from last month -

-
-
- - - - Pending Invoices -
- -
-
- -
0
-

- Due this month -

-
-
-
- - {/* Quick Actions */} -
- - - -
- -
- Manage Clients -
-
- -

- Add new clients and manage your existing client relationships. -

-
- - -
-
-
- - - - -
- -
- Create Invoices -
-
- -

- Generate professional invoices and track payments. -

-
- - -
-
-
-
- - {/* Recent Activity */} - - - Recent Activity - - -
-
- -
-

No recent activity

-

Start by adding your first client or creating an invoice

-
-
-
+ + + + +
); -} \ No newline at end of file +} diff --git a/src/app/invoices/layout.tsx b/src/app/invoices/layout.tsx new file mode 100644 index 0000000..2c2672c --- /dev/null +++ b/src/app/invoices/layout.tsx @@ -0,0 +1,20 @@ +import { Navbar } from "~/components/Navbar"; +import { Sidebar } from "~/components/Sidebar"; + +export default function InvoicesLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> + +
+ +
+ {children} +
+
+ + ); +} \ No newline at end of file diff --git a/src/app/invoices/page.tsx b/src/app/invoices/page.tsx new file mode 100644 index 0000000..c7edef9 --- /dev/null +++ b/src/app/invoices/page.tsx @@ -0,0 +1,42 @@ +import Link from "next/link"; +import { auth } from "~/server/auth"; +import { api, HydrateClient } from "~/trpc/server"; +import { Button } from "~/components/ui/button"; +import { InvoiceList } from "~/components/invoice-list"; +import { Plus } from "lucide-react"; + +export default async function InvoicesPage() { + const session = await auth(); + + if (!session?.user) { + return ( +
+
+

Access Denied

+

Please sign in to view invoices

+ + + +
+
+ ); + } + + // Prefetch invoices data + void api.invoices.getAll.prefetch(); + + return ( + +
+
+

Invoices

+

+ Manage your invoices and payments +

+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 5dd734d..0a82a3d 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -3,29 +3,31 @@ import Link from "next/link"; import { useSession, signOut } from "next-auth/react"; import { Button } from "~/components/ui/button"; import { Logo } from "./logo"; +import { SidebarTrigger } from "./SidebarTrigger"; export function Navbar() { const { data: session } = useSession(); return ( -
+
-
-
+
+
+
-
+
{session?.user ? ( <> - + {session.user.name ?? session.user.email} @@ -36,7 +38,7 @@ export function Navbar() { @@ -44,7 +46,7 @@ export function Navbar() { diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 586336e..50cd0cd 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -2,72 +2,19 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Sheet, SheetContent, SheetTrigger } from "~/components/ui/sheet"; -import { Button } from "~/components/ui/button"; -import { MenuIcon, Settings, LayoutDashboard, Users, FileText } from "lucide-react"; -import { useState } from "react"; +import { Settings, LayoutDashboard, Users, FileText, Building } from "lucide-react"; const navLinks = [ { name: "Dashboard", href: "/dashboard", icon: LayoutDashboard }, { name: "Clients", href: "/dashboard/clients", icon: Users }, + { name: "Businesses", href: "/dashboard/businesses", icon: Building }, { name: "Invoices", href: "/dashboard/invoices", icon: FileText }, ]; export function Sidebar() { const pathname = usePathname(); - const [open, setOpen] = useState(false); return ( - <> - {/* Mobile trigger */} -
- - - - - - - - -
- {/* Desktop sidebar */}
- ); } \ No newline at end of file diff --git a/src/components/SidebarTrigger.tsx b/src/components/SidebarTrigger.tsx new file mode 100644 index 0000000..02ba6c1 --- /dev/null +++ b/src/components/SidebarTrigger.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { Sheet, SheetContent, SheetTrigger, SheetHeader, SheetTitle } from "~/components/ui/sheet"; +import { Button } from "~/components/ui/button"; +import { MenuIcon, Settings, LayoutDashboard, Users, FileText } from "lucide-react"; +import { useState } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +const navLinks = [ + { name: "Dashboard", href: "/dashboard", icon: LayoutDashboard }, + { name: "Clients", href: "/dashboard/clients", icon: Users }, + { name: "Invoices", href: "/dashboard/invoices", icon: FileText }, +]; + +export function SidebarTrigger() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + + return ( + + + + + + + Navigation + + + {/* Navigation */} + + + + ); +} \ No newline at end of file diff --git a/src/components/business-form.tsx b/src/components/business-form.tsx new file mode 100644 index 0000000..4e6d1bd --- /dev/null +++ b/src/components/business-form.tsx @@ -0,0 +1,454 @@ +"use client"; + +import { Building, Mail, MapPin, Phone, Save, Globe, BadgeDollarSign, Image, Star } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { Button } from "~/components/ui/button"; +import { Card, CardContent } from "~/components/ui/card"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; +import { SearchableSelect } from "~/components/ui/select"; +import { FormSkeleton } from "~/components/ui/skeleton"; +import { api } from "~/trpc/react"; + +interface BusinessFormProps { + businessId?: string; + mode: "create" | "edit"; +} + +export function BusinessForm({ businessId, mode }: BusinessFormProps) { + const router = useRouter(); + const [formData, setFormData] = useState({ + name: "", + email: "", + phone: "", + addressLine1: "", + addressLine2: "", + city: "", + state: "", + postalCode: "", + country: "", + website: "", + taxId: "", + logoUrl: "", + isDefault: false, + }); + const [loading, setLoading] = useState(false); + + // Fetch business data if editing + const { data: business, isLoading: isLoadingBusiness } = api.businesses.getById.useQuery( + { id: businessId! }, + { enabled: mode === "edit" && !!businessId } + ); + + const createBusiness = api.businesses.create.useMutation({ + onSuccess: () => { + toast.success("Business created successfully"); + router.push("/dashboard/businesses"); + }, + onError: (error) => { + toast.error(error.message || "Failed to create business"); + }, + }); + + const updateBusiness = api.businesses.update.useMutation({ + onSuccess: () => { + toast.success("Business updated successfully"); + router.push("/dashboard/businesses"); + }, + onError: (error) => { + toast.error(error.message || "Failed to update business"); + }, + }); + + // Load business data when editing + useEffect(() => { + if (business && mode === "edit") { + setFormData({ + name: business.name, + email: business.email ?? "", + phone: business.phone ?? "", + addressLine1: business.addressLine1 ?? "", + addressLine2: business.addressLine2 ?? "", + city: business.city ?? "", + state: business.state ?? "", + postalCode: business.postalCode ?? "", + country: business.country ?? "", + website: business.website ?? "", + taxId: business.taxId ?? "", + logoUrl: business.logoUrl ?? "", + isDefault: business.isDefault ?? false, + }); + } + }, [business, mode]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + if (mode === "create") { + await createBusiness.mutateAsync(formData); + } else { + await updateBusiness.mutateAsync({ + id: businessId!, + ...formData, + }); + } + } finally { + setLoading(false); + } + }; + + const handleInputChange = (field: string, value: string | boolean) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + // Phone number formatting (reuse from client-form) + const formatPhoneNumber = (value: string) => { + const phoneNumber = value.replace(/\D/g, ''); + if (phoneNumber.length <= 3) { + return phoneNumber; + } else if (phoneNumber.length <= 6) { + return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3)}`; + } else { + return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3, 6)}-${phoneNumber.slice(6, 10)}`; + } + }; + + const handlePhoneChange = (value: string) => { + const formatted = formatPhoneNumber(value); + handleInputChange("phone", formatted); + }; + + // US States and Countries (reuse from client-form) + const US_STATES = [ + { value: "__placeholder__", label: "Select State" }, + { value: "AL", label: "Alabama" }, + { value: "AK", label: "Alaska" }, + { value: "AZ", label: "Arizona" }, + { value: "AR", label: "Arkansas" }, + { value: "CA", label: "California" }, + { value: "CO", label: "Colorado" }, + { value: "CT", label: "Connecticut" }, + { value: "DE", label: "Delaware" }, + { value: "FL", label: "Florida" }, + { value: "GA", label: "Georgia" }, + { value: "HI", label: "Hawaii" }, + { value: "ID", label: "Idaho" }, + { value: "IL", label: "Illinois" }, + { value: "IN", label: "Indiana" }, + { value: "IA", label: "Iowa" }, + { value: "KS", label: "Kansas" }, + { value: "KY", label: "Kentucky" }, + { value: "LA", label: "Louisiana" }, + { value: "ME", label: "Maine" }, + { value: "MD", label: "Maryland" }, + { value: "MA", label: "Massachusetts" }, + { value: "MI", label: "Michigan" }, + { value: "MN", label: "Minnesota" }, + { value: "MS", label: "Mississippi" }, + { value: "MO", label: "Missouri" }, + { value: "MT", label: "Montana" }, + { value: "NE", label: "Nebraska" }, + { value: "NV", label: "Nevada" }, + { value: "NH", label: "New Hampshire" }, + { value: "NJ", label: "New Jersey" }, + { value: "NM", label: "New Mexico" }, + { value: "NY", label: "New York" }, + { value: "NC", label: "North Carolina" }, + { value: "ND", label: "North Dakota" }, + { value: "OH", label: "Ohio" }, + { value: "OK", label: "Oklahoma" }, + { value: "OR", label: "Oregon" }, + { value: "PA", label: "Pennsylvania" }, + { value: "RI", label: "Rhode Island" }, + { value: "SC", label: "South Carolina" }, + { value: "SD", label: "South Dakota" }, + { value: "TN", label: "Tennessee" }, + { value: "TX", label: "Texas" }, + { value: "UT", label: "Utah" }, + { value: "VT", label: "Vermont" }, + { value: "VA", label: "Virginia" }, + { value: "WA", label: "Washington" }, + { value: "WV", label: "West Virginia" }, + { value: "WI", label: "Wisconsin" }, + { value: "WY", label: "Wyoming" } + ]; + + const MOST_USED_COUNTRIES = [ + { value: "United States", label: "United States" }, + { value: "United Kingdom", label: "United Kingdom" }, + { value: "Canada", label: "Canada" }, + { value: "Australia", label: "Australia" }, + { value: "Germany", label: "Germany" }, + { value: "France", label: "France" }, + { value: "India", label: "India" } + ]; + + const ALL_COUNTRIES = [ + "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia", "Cameroon", "Canada", "Central African Republic", "Chad", "Chile", "China", "Colombia", "Comoros", "Congo", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Ivory Coast", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "North Korea", "North Macedonia", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Korea", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe" + ]; + + const OTHER_COUNTRIES = ALL_COUNTRIES + .filter(c => !MOST_USED_COUNTRIES.some(mc => mc.value === c)) + .map(country => ({ value: country, label: country })) + .sort((a, b) => a.label.localeCompare(b.label)); + + const ALL_COUNTRIES_OPTIONS = [ + { value: "__placeholder__", label: "Select country" }, + ...MOST_USED_COUNTRIES, + ...OTHER_COUNTRIES + ]; + + if (mode === "edit" && isLoadingBusiness) { + return ( + + + + + + ); + } + + return ( + + +
+ {/* Basic Information Section */} +
+
+ +

Business Information

+
+
+
+ + handleInputChange("name", e.target.value)} + required + placeholder="Enter business name" + className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+ +
+ + handleInputChange("email", e.target.value)} + placeholder="business@example.com" + className="h-12 pl-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+
+
+ + {/* Contact Information Section */} +
+
+ +

Contact Information

+
+
+
+ +
+ + handlePhoneChange(e.target.value)} + placeholder="(555) 123-4567" + className="h-12 pl-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+
+ +
+ + handleInputChange("website", e.target.value)} + placeholder="https://yourbusiness.com" + className="h-12 pl-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+
+
+ + {/* Address Section */} +
+
+ +

Address

+
+
+
+ + handleInputChange("addressLine1", e.target.value)} + placeholder="123 Main St" + className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+ + handleInputChange("addressLine2", e.target.value)} + placeholder="Suite 100" + className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+ + handleInputChange("city", e.target.value)} + placeholder="City" + className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+ + handleInputChange("state", value)} + options={US_STATES} + placeholder="Select State" + searchPlaceholder="Search states..." + /> +
+
+ + handleInputChange("postalCode", e.target.value)} + placeholder="ZIP or postal code" + className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+ + handleInputChange("country", value)} + options={ALL_COUNTRIES_OPTIONS} + placeholder="Select country" + searchPlaceholder="Search countries..." + /> +
+
+
+ + {/* Tax, Logo, Default Section */} +
+
+ +

Other Details

+
+
+
+ + handleInputChange("taxId", e.target.value)} + placeholder="Tax ID or VAT number" + className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+ +
+ + handleInputChange("logoUrl", e.target.value)} + placeholder="https://yourbusiness.com/logo.png" + className="h-12 pl-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+
+
+ handleInputChange("isDefault", e.target.checked)} + className="h-5 w-5 text-emerald-600 border-gray-300 rounded focus:ring-emerald-500" + /> + +
+
+
+ +
+ + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/client-form.tsx b/src/components/client-form.tsx index 8750a6b..cae111f 100644 --- a/src/components/client-form.tsx +++ b/src/components/client-form.tsx @@ -9,6 +9,7 @@ import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; +import { FormSkeleton } from "~/components/ui/skeleton"; import { api } from "~/trpc/react"; interface ClientFormProps { @@ -129,17 +130,9 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { if (mode === "edit" && isLoadingClient) { return ( - - - Loading client... - - -
-
-
-
-
-
+ + + ); @@ -147,25 +140,6 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { return ( - {/* */} - {/*
*/} - {/* - - */} - {/*
*/} - {/* - {mode === "create" ? "Add New Client" : "Edit Client"} - */} - {/*

- {mode === "create" - ? "Create a new client profile with complete contact information" - : "Update your client's information" - } -

*/} - {/*
*/}
{/* Basic Information Section */} @@ -250,7 +224,7 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { id="addressLine1" value={formData.addressLine1} onChange={(e) => handleInputChange("addressLine1", e.target.value)} - placeholder="Street address, P.O. box, company name" + placeholder="123 Main Street" className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" />
@@ -262,10 +236,12 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { id="addressLine2" value={formData.addressLine2} onChange={(e) => handleInputChange("addressLine2", e.target.value)} - placeholder="Apartment, suite, unit, building, floor, etc." + placeholder="Suite 100" className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" />
+
+
@@ -302,10 +279,11 @@ export function ClientForm({ clientId, mode }: ClientFormProps) { id="postalCode" value={formData.postalCode} onChange={(e) => handleInputChange("postalCode", e.target.value)} - placeholder="ZIP or postal code" + placeholder="12345" className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" />
+
- {/* Action Buttons */} -
+ {/* Submit Button */} +
- -
diff --git a/src/components/dark-mode-test.tsx b/src/components/dark-mode-test.tsx new file mode 100644 index 0000000..d5677c8 --- /dev/null +++ b/src/components/dark-mode-test.tsx @@ -0,0 +1,232 @@ +"use client"; + +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; +import { Badge } from "~/components/ui/badge"; +import { + Sun, + Moon, + Palette, + Check, + X, + Info, + AlertCircle, + Settings, + User, + Mail +} from "lucide-react"; + +export function DarkModeTest() { + return ( +
+ {/* Header */} +
+

+ Dark Mode Test Suite +

+

+ Testing media query-based dark mode implementation +

+
+
+ + Light Mode +
+
+
+ + Dark Mode (Auto) +
+
+
+ +
+ {/* Color Test Card */} + + + + + Color Tests + + + +
+

Text Colors:

+
Primary Text
+
Secondary Text
+
Muted Text
+
Success Text
+
Error Text
+
+
+
+ + {/* Button Test Card */} + + + Button Variants + + +
+ + + + + +
+
+
+ + {/* Form Elements Card */} + + + Form Elements + + +
+ +
+ + +
+
+
+ + +
+
+
+ + {/* Status Badges Card */} + + + Status Indicators + + +
+ Default + Secondary + Error + Outline +
+
+
+ + Success Status +
+
+ + Error Status +
+
+ + Info Status +
+
+ + Warning Status +
+
+
+
+ + {/* Background Test Card */} + + + Background Tests + + +
+
+

Light Background

+
+
+

Medium Background

+
+
+

Card Background

+
+
+
+
+ + {/* Icon Test Card */} + + + Icon Colors + + +
+
+ + Default +
+
+ + Success +
+
+ + Error +
+
+ + Info +
+
+
+
+
+ + {/* System Information */} + + + System Information + + +
+
+

Dark Mode Method:

+

Media Query (@media (prefers-color-scheme: dark))

+
+
+

Tailwind Config:

+

darkMode: "media"

+
+
+

CSS Variables:

+

oklch() color space

+
+
+
+
+ + {/* Instructions */} + + + Testing Instructions + + +

• Change your system theme between light and dark to test automatic switching

+

• All UI elements should adapt colors automatically

+

• Text should remain readable in both modes

+

• Icons and buttons should have appropriate contrast

+

• Form elements should be clearly visible and functional

+
+
+
+ ); +} diff --git a/src/components/dark-mode-toggle.tsx b/src/components/dark-mode-toggle.tsx new file mode 100644 index 0000000..de88208 --- /dev/null +++ b/src/components/dark-mode-toggle.tsx @@ -0,0 +1,143 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Sun, Moon, Monitor } from "lucide-react"; +import { Button } from "~/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu"; + +type Theme = "light" | "dark" | "system"; + +export function DarkModeToggle() { + const [theme, setTheme] = useState("system"); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + + // Get stored theme preference or default to system + const storedTheme = localStorage.getItem("theme") as Theme | null; + setTheme(storedTheme || "system"); + + // Listen for system preference changes when using system theme + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleSystemChange = () => { + const currentTheme = localStorage.getItem("theme"); + if (!currentTheme || currentTheme === "system") { + applyTheme("system"); + } + }; + + mediaQuery.addEventListener("change", handleSystemChange); + + return () => { + mediaQuery.removeEventListener("change", handleSystemChange); + }; + }, []); + + const applyTheme = (newTheme: Theme) => { + const root = document.documentElement; + + if (newTheme === "light") { + root.classList.remove("dark"); + root.classList.add("light"); + } else if (newTheme === "dark") { + root.classList.remove("light"); + root.classList.add("dark"); + } else { + // System theme - remove manual classes and let CSS media query handle it + root.classList.remove("light", "dark"); + const systemDark = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + if (systemDark) { + root.classList.add("dark"); + } + } + }; + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme); + + if (newTheme === "system") { + localStorage.removeItem("theme"); + } else { + localStorage.setItem("theme", newTheme); + } + + applyTheme(newTheme); + }; + + // Don't render until mounted to avoid hydration mismatch + if (!mounted) { + return ( + + ); + } + + const getIcon = () => { + switch (theme) { + case "light": + return ; + case "dark": + return ; + case "system": + return ; + } + }; + + const getLabel = () => { + switch (theme) { + case "light": + return "Light mode"; + case "dark": + return "Dark mode"; + case "system": + return "System theme"; + } + }; + + return ( + + + + + + handleThemeChange("light")} + className={theme === "light" ? "bg-accent" : ""} + > + + Light + + handleThemeChange("dark")} + className={theme === "dark" ? "bg-accent" : ""} + > + + Dark + + handleThemeChange("system")} + className={theme === "system" ? "bg-accent" : ""} + > + + System + + + + ); +} diff --git a/src/components/dashboard-breadcrumbs.tsx b/src/components/dashboard-breadcrumbs.tsx index d5d4af8..3663e14 100644 --- a/src/components/dashboard-breadcrumbs.tsx +++ b/src/components/dashboard-breadcrumbs.tsx @@ -6,6 +6,8 @@ import Link from "next/link"; import { ChevronRight } from "lucide-react"; import React from "react"; import { api } from "~/trpc/react"; +import { format } from "date-fns"; +import { Skeleton } from "~/components/ui/skeleton"; function isUUID(str: string) { return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(str); @@ -20,11 +22,21 @@ export function DashboardBreadcrumbs() { if (segments[1] === "clients" && segments[2] && isUUID(segments[2])) { clientId = segments[2]; } - const { data: client } = api.clients.getById.useQuery( + const { data: client, isLoading: clientLoading } = api.clients.getById.useQuery( { id: clientId ?? "" }, { enabled: !!clientId } ); + // Find invoiceId if present + let invoiceId: string | undefined = undefined; + if (segments[1] === "invoices" && segments[2] && isUUID(segments[2])) { + invoiceId = segments[2]; + } + const { data: invoice, isLoading: invoiceLoading } = api.invoices.getById.useQuery( + { id: invoiceId ?? "" }, + { enabled: !!invoiceId } + ); + // Generate breadcrumb items based on pathname const breadcrumbs = React.useMemo(() => { const items = []; @@ -32,9 +44,16 @@ export function DashboardBreadcrumbs() { const segment = segments[i]; const path = `/${segments.slice(0, i + 1).join('/')}`; if (segment === 'dashboard') continue; - let label = segment; + + let label: string | React.ReactElement = segment ?? ""; if (segment === 'clients') label = 'Clients'; - if (isUUID(segment ?? "") && client) label = client.name ?? ""; + if (isUUID(segment ?? "") && clientLoading) label = ; + else if (isUUID(segment ?? "") && client) label = client.name ?? ""; + if (isUUID(segment ?? "") && invoiceLoading) label = ; + else if (isUUID(segment ?? "") && invoice) { + const issueDate = new Date(invoice.issueDate); + label = format(issueDate, "MMM dd, yyyy"); + } if (segment === 'invoices') label = 'Invoices'; if (segment === 'new') label = 'New'; // Only show 'Edit' if not the last segment @@ -49,29 +68,29 @@ export function DashboardBreadcrumbs() { }); } return items; - }, [segments, client]); + }, [segments, client, invoice, clientLoading, invoiceLoading]); if (breadcrumbs.length === 0) return null; return ( - - + + - Dashboard + Dashboard {breadcrumbs.map((crumb) => ( - + {crumb.isLast ? ( - {crumb.label} + {crumb.label} ) : ( - {crumb.label} + {crumb.label} )} diff --git a/src/components/editable-invoice-items.tsx b/src/components/editable-invoice-items.tsx new file mode 100644 index 0000000..bd62b1c --- /dev/null +++ b/src/components/editable-invoice-items.tsx @@ -0,0 +1,282 @@ +"use client"; + +import * as React from "react"; +import { useEffect, useState } from "react"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + type DragEndEvent, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { + useSortable, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { Input } from "~/components/ui/input"; +import { Button } from "~/components/ui/button"; +import { Trash2, GripVertical, CalendarIcon } from "lucide-react"; +import { format } from "date-fns"; +import { Calendar } from "~/components/ui/calendar"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; + +interface InvoiceItem { + id: string; + date: Date; + description: string; + hours: number; + rate: number; + amount: number; +} + +interface EditableInvoiceItemsProps { + items: InvoiceItem[]; + onItemsChange: (items: InvoiceItem[]) => void; + onRemoveItem: (index: number) => void; +} + +function SortableItem({ + item, + index, + onItemChange, + onRemove +}: { + item: InvoiceItem; + index: number; + onItemChange: (index: number, field: string, value: any) => void; + onRemove: (index: number) => void; +}) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: item.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + const handleItemChange = (field: string, value: any) => { + onItemChange(index, field, value); + }; + + return ( +
+ {/* Drag Handle */} +
+ +
+ + {/* Date */} +
+ + + + + + { + handleItemChange("date", selectedDate || new Date()) + }} + /> + + +
+ + {/* Description */} +
+ handleItemChange("description", e.target.value)} + placeholder="Work description" + className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+ + {/* Hours */} +
+ handleItemChange("hours", e.target.value)} + placeholder="0" + className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+ + {/* Rate */} +
+ handleItemChange("rate", e.target.value)} + placeholder="0.00" + className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + /> +
+ + {/* Amount */} +
+
+ ${item.amount.toFixed(2)} +
+
+ + {/* Remove Button */} +
+ +
+
+ ); +} + +export function EditableInvoiceItems({ items, onItemsChange, onRemoveItem }: EditableInvoiceItemsProps) { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (active.id !== over?.id) { + const oldIndex = items.findIndex(item => item.id === active.id); + const newIndex = items.findIndex(item => item.id === over?.id); + + const newItems = arrayMove(items, oldIndex, newIndex); + onItemsChange(newItems); + } + }; + + const handleItemChange = (index: number, field: string, value: any) => { + const newItems = [...items]; + if (field === "hours" || field === "rate") { + if (newItems[index]) { + newItems[index][field as "hours" | "rate"] = parseFloat(value) || 0; + newItems[index].amount = newItems[index].hours * newItems[index].rate; + } + } else if (field === "date") { + if (newItems[index]) { + newItems[index][field as "date"] = value; + } + } else { + if (newItems[index]) { + newItems[index][field as "description"] = value; + } + } + onItemsChange(newItems); + }; + + // Show skeleton loading on server-side + if (!isClient) { + return ( +
+ {items.map((item, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ ); + } + + return ( + + item.id)} strategy={verticalListSortingStrategy}> +
+ {items.map((item, index) => ( + + ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/invoice-form.tsx b/src/components/invoice-form.tsx index b10eb73..9913174 100644 --- a/src/components/invoice-form.tsx +++ b/src/components/invoice-form.tsx @@ -3,17 +3,41 @@ import * as React from "react"; import { useState, useEffect } from "react"; import { api } from "~/trpc/react"; -import { Card, CardContent } from "~/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Button } from "~/components/ui/button"; import { Label } from "~/components/ui/label"; import { DatePicker } from "~/components/ui/date-picker"; +import { Badge } from "~/components/ui/badge"; +import { Separator } from "~/components/ui/separator"; +import { SearchableSelect } from "~/components/ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; import { toast } from "sonner"; -import { Calendar, FileText, User, Plus, Trash2 } from "lucide-react"; +import { + Calendar, + FileText, + User, + Plus, + Trash2, + DollarSign, + Clock, + Edit3, + Save, + X, + AlertCircle, + Building +} from "lucide-react"; import { useRouter } from "next/navigation"; import { format } from "date-fns"; +import { FormSkeleton } from "~/components/ui/skeleton"; +import { EditableInvoiceItems } from "~/components/editable-invoice-items"; -const STATUS_OPTIONS = ["draft", "sent", "paid", "overdue"]; +const STATUS_OPTIONS = [ + { value: "draft", label: "Draft", color: "bg-gray-100 text-gray-800" }, + { value: "sent", label: "Sent", color: "bg-blue-100 text-blue-800" }, + { value: "paid", label: "Paid", color: "bg-green-100 text-green-800" }, + { value: "overdue", label: "Overdue", color: "bg-red-100 text-red-800" }, +] as const; interface InvoiceFormProps { invoiceId?: string; @@ -23,19 +47,23 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { const router = useRouter(); const [formData, setFormData] = useState({ invoiceNumber: `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}-${Date.now().toString().slice(-6)}`, + businessId: "", clientId: "", issueDate: new Date(), dueDate: new Date(), status: "draft" as "draft" | "sent" | "paid" | "overdue", notes: "", + taxRate: 0, items: [ - { date: new Date(), description: "", hours: 0, rate: 0, amount: 0 }, + { id: crypto.randomUUID(), date: new Date(), description: "", hours: 0, rate: 0, amount: 0 }, ], }); const [loading, setLoading] = useState(false); + const [defaultRate, setDefaultRate] = useState(0); - // Fetch clients for dropdown + // Fetch clients and businesses for dropdowns const { data: clients, isLoading: loadingClients } = api.clients.getAll.useQuery(); + const { data: businesses, isLoading: loadingBusinesses } = api.businesses.getAll.useQuery(); // Fetch existing invoice data if editing const { data: existingInvoice, isLoading: loadingInvoice } = api.invoices.getById.useQuery( @@ -48,49 +76,43 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { if (existingInvoice && invoiceId) { setFormData({ invoiceNumber: existingInvoice.invoiceNumber, + businessId: existingInvoice.businessId ?? "", clientId: existingInvoice.clientId, issueDate: new Date(existingInvoice.issueDate), dueDate: new Date(existingInvoice.dueDate), status: existingInvoice.status as "draft" | "sent" | "paid" | "overdue", - notes: existingInvoice.notes || "", + notes: existingInvoice.notes ?? "", + taxRate: existingInvoice.taxRate, items: existingInvoice.items?.map(item => ({ + id: crypto.randomUUID(), date: new Date(item.date), description: item.description, hours: item.hours, rate: item.rate, amount: item.amount, - })) || [{ date: new Date(), description: "", hours: 0, rate: 0, amount: 0 }], + })) || [{ id: crypto.randomUUID(), date: new Date(), description: "", hours: 0, rate: 0, amount: 0 }], }); + + // Set default rate from first item + if (existingInvoice.items?.[0]) { + setDefaultRate(existingInvoice.items[0].rate); + } } }, [existingInvoice, invoiceId]); - // Calculate total amount - const totalAmount = formData.items.reduce( - (sum, item) => sum + (item.hours * item.rate), - 0 - ); + // Calculate totals + const totals = React.useMemo(() => { + const subtotal = formData.items.reduce((sum, item) => sum + (item.hours * item.rate), 0); + const taxAmount = (subtotal * formData.taxRate) / 100; + const total = subtotal + taxAmount; + return { + subtotal, + taxAmount, + total, + }; + }, [formData.items, formData.taxRate]); + - // Update item amount on change - const handleItemChange = (idx: number, field: string, value: any) => { - setFormData((prev) => { - const items = [...prev.items]; - if (field === "hours" || field === "rate") { - if (items[idx]) { - items[idx][field as "hours" | "rate"] = parseFloat(value) || 0; - items[idx].amount = items[idx].hours * items[idx].rate; - } - } else if (field === "date") { - if (items[idx]) { - items[idx][field as "date"] = value; - } - } else { - if (items[idx]) { - items[idx][field as "description"] = value; - } - } - return { ...prev, items }; - }); - }; // Add new item const addItem = () => { @@ -98,16 +120,30 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { ...prev, items: [ ...prev.items, - { date: new Date(), description: "", hours: 0, rate: 0, amount: 0 }, + { id: crypto.randomUUID(), date: new Date(), description: "", hours: 0, rate: defaultRate, amount: 0 }, ], })); }; // Remove item const removeItem = (idx: number) => { + if (formData.items.length > 1) { + setFormData((prev) => ({ + ...prev, + items: prev.items.filter((_, i) => i !== idx), + })); + } + }; + + // Apply default rate to all items + const applyDefaultRate = () => { setFormData((prev) => ({ ...prev, - items: prev.items.filter((_, i) => i !== idx), + items: prev.items.map(item => ({ + ...item, + rate: defaultRate, + amount: item.hours * defaultRate, + })), })); }; @@ -135,13 +171,45 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { // Handle form submit const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + + // Validate form + if (!formData.businessId) { + toast.error("Please select a business"); + return; + } + + if (!formData.clientId) { + toast.error("Please select a client"); + return; + } + + if (formData.items.some(item => !item.description.trim())) { + toast.error("Please fill in all item descriptions"); + return; + } + + if (formData.items.some(item => item.hours <= 0)) { + toast.error("Please enter valid hours for all items"); + return; + } + + if (formData.items.some(item => item.rate <= 0)) { + toast.error("Please enter valid rates for all items"); + return; + } + setLoading(true); try { + // In the handleSubmit, ensure items are sent in the current array order with no sorting const submitData = { ...formData, - items: formData.items.map(item => ({ - ...item, + items: formData.items.map((item) => ({ date: new Date(item.date), + description: item.description, + hours: item.hours, + rate: item.rate, + amount: item.amount, + // position will be set by backend based on array order })), }; @@ -161,40 +229,150 @@ export function InvoiceForm({ invoiceId }: InvoiceFormProps) { // Show loading state while fetching existing invoice data if (invoiceId && loadingInvoice) { return ( - - -
-
-
- -

Invoice Details

-
-
-
-
-
-
-
-
+
+ {/* Invoice Details Card Skeleton */} + + +
+
+ +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+ ))} +
+
+
+ + {/* Invoice Items Card Skeleton */} + + +
+
+
+
+
+ + {/* Items Table Header Skeleton */} +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+ ))} +
+ + {/* Items Skeleton */} +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ {Array.from({ length: 8 }).map((_, j) => ( +
+ ))} +
+ ))} +
+
+
+ + {/* Form Controls Bar Skeleton */} +
+
+
+
+
+
+
- - +
+
+ ); + } + + const selectedClient = clients?.find(c => c.id === formData.clientId); + const selectedBusiness = businesses?.find(b => b.id === formData.businessId); + + // Show loading state while fetching clients + if (loadingClients) { + return ( +
+ {/* Invoice Details Card Skeleton */} + + +
+
+ +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+
+
+
+ ))} +
+
+
+ + {/* Invoice Items Card Skeleton */} + + +
+
+
+
+
+ + {/* Items Table Header Skeleton */} +
+ {Array.from({ length: 8 }).map((_, i) => ( +
+ ))} +
+ + {/* Items Skeleton */} +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ {Array.from({ length: 8 }).map((_, j) => ( +
+ ))} +
+ ))} +
+
+
+ + {/* Form Controls Bar Skeleton */} +
+
+
+
+
+
+
+
+
+
+
+
); } return ( - - -
- {/* Invoice Details */} -
-
+ + {/* Invoice Details Card */} + + + -

Invoice Details

-
-
+ Invoice Details + + + +
+ +
+ + setFormData(f => ({ ...f, businessId: value }))} + options={businesses?.map(business => ({ value: business.id, label: business.name })) ?? []} + placeholder="Select a business" + searchPlaceholder="Search businesses..." + disabled={loadingBusinesses} + /> +
+
- + />
+ +
+ + +
+
setFormData(f => ({ ...f, issueDate: date || new Date() }))} + onDateChange={date => setFormData(f => ({ ...f, issueDate: date ?? new Date() }))} placeholder="Select issue date" required />
+
setFormData(f => ({ ...f, dueDate: date || new Date() }))} + onDateChange={date => setFormData(f => ({ ...f, dueDate: date ?? new Date() }))} placeholder="Select due date" required />
+
-
-
-
+ +
+ setFormData(f => ({ ...f, notes: e.target.value }))} - placeholder="Additional notes (optional)" - className="h-12 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" + id="taxRate" + type="number" + step="0.01" + min="0" + max="100" + value={formData.taxRate} + onChange={e => setFormData(f => ({ ...f, taxRate: parseFloat(e.target.value) || 0 }))} + placeholder="0.00" + className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" />
-
- {/* Invoice Items */} -
-
- -

Invoice Items

+ {selectedBusiness && ( +
+
+ + Business Information +
+
+

{selectedBusiness.name}

+ {selectedBusiness.email &&

{selectedBusiness.email}

} + {selectedBusiness.phone &&

{selectedBusiness.phone}

} + {selectedBusiness.addressLine1 && ( +

{selectedBusiness.addressLine1}

+ )} + {(selectedBusiness.city ?? selectedBusiness.state ?? selectedBusiness.postalCode) && ( +

+ {[selectedBusiness.city, selectedBusiness.state, selectedBusiness.postalCode] + .filter(Boolean) + .join(", ")} +

+ )} +
-
- {formData.items.map((item, idx) => ( -
-
- - handleItemChange(idx, "date", new Date(e.target.value))} - className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" - required - /> -
-
- - handleItemChange(idx, "description", e.target.value)} - placeholder="Description" - className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" - required - /> -
-
- - handleItemChange(idx, "hours", e.target.value)} - className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" - required - /> -
-
- - handleItemChange(idx, "rate", e.target.value)} - className="h-10 border-gray-200 focus:border-emerald-500 focus:ring-emerald-500" - required - /> -
-
- - -
-
- {formData.items.length > 1 && ( - - )} -
-
- ))} - + )} + + {selectedClient && ( +
+
+ + Client Information +
+
+

{selectedClient.name}

+ {selectedClient.email &&

{selectedClient.email}

} + {selectedClient.phone &&

{selectedClient.phone}

} +
-
+ )} - {/* Total Amount */} -
- Total: ${totalAmount.toFixed(2)} -
+
+ +