diff --git a/app/components/layout/header.tsx b/app/components/layout/header.tsx new file mode 100644 index 0000000..725246e --- /dev/null +++ b/app/components/layout/header.tsx @@ -0,0 +1,318 @@ +import { Form, Link, useMatches, useSubmit } from '@remix-run/react' +import { useEffect, useRef, useState } from 'react' + +import { getUserImgSrc } from '#app/utils/misc.tsx' +import { useOptionalUser, useUser } from '#app/utils/user.ts' +import { SearchBar } from '../search-bar.tsx' +import { Button } from '../ui/button.tsx' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuPortal, + DropdownMenuTrigger, +} from '../ui/dropdown-menu.tsx' +import { Icon } from '../ui/icon.tsx' +import { MobileNav } from './mobile-nav.tsx' + +export function HeaderNav({ + sticky, + mobileNavItems, + clean = false, +}: { + sticky?: boolean + mobileNavItems?: { + title: string + items: { + title: string + href: string + }[] + }[] + clean?: boolean +}) { + const user = useOptionalUser() + + const [searchExpand, setSearchExpand] = useState(false) + const searchRef = useRef(null) + + const matches = useMatches() + const isProjectPage = matches.find( + match => match.id === 'routes/projects+/index', + ) + + const handleSearchExpand = () => { + setSearchExpand(true) + // searchRef.current?.focus() + } + + useEffect(() => { + if (searchExpand && searchRef.current) { + searchRef.current.focus() + } + }, [searchExpand]) + + return ( + <> +
+ +
+ + ) +} + +function UserDropdown() { + const user = useUser() + const submit = useSubmit() + const formRef = useRef(null) + + return ( + + +
e.preventDefault()} + className="relative rounded-full border border-primary bg-secondary p-1" + > +
+
+
+ {user.name + {/* + {user.name ?? user.username} + */} +
+
+ + + + +
+ {user.name +
+
+ {user.username} +
{user.name}
+
+ +
+
+ +
BACKER
+
+ + + Home + + + + + Discover + + + + + Backed Projects + + +
+
+ +
CREATOR
+
+ + + Studio + + + + + Created Projects + + + + + Start a New Project + + +
+ { + event.preventDefault() + submit(formRef.current) + }} + className="cursor-pointer p-4 text-sm" + > +
+ + + +
+
+
+
+
+ ) +} diff --git a/app/components/layout/mobile-nav.tsx b/app/components/layout/mobile-nav.tsx new file mode 100644 index 0000000..6cc0b9d --- /dev/null +++ b/app/components/layout/mobile-nav.tsx @@ -0,0 +1,77 @@ +import { Link } from '@remix-run/react' +import { Icon } from '../ui/icon.tsx' +import { Sheet, SheetContent, SheetTrigger } from '../ui/sheet.tsx' + +export function MobileNav({ + menuItems, +}: { + menuItems?: { + title: string + items: { + title: string + href: string + }[] + }[] +}) { + return ( + + + + Toggle Menu + + +
+
+ + + + + + BloomBacker + +
+ +
    + {menuItems?.map(item => ( +
  • +
    + {item.title} +
    + +
      + {item.items.map(subItem => ( +
    • + + {subItem.title} + +
    • + ))} +
    +
  • + ))} +
+
+
+
+ ) +} diff --git a/app/components/search-bar.tsx b/app/components/search-bar.tsx index 816950e..3b0522d 100644 --- a/app/components/search-bar.tsx +++ b/app/components/search-bar.tsx @@ -1,26 +1,36 @@ import { Form, useSearchParams, useSubmit } from '@remix-run/react' import { useId } from 'react' -import { useDebounce, useIsPending } from '#app/utils/misc.tsx' +import { cn, useDebounce, useIsPending } from '#app/utils/misc.tsx' import { Icon } from './ui/icon.tsx' import { Input } from './ui/input.tsx' import { Label } from './ui/label.tsx' import { StatusButton } from './ui/status-button.tsx' export function SearchBar({ + action, status, - autoFocus = false, + autoFocusSearch = false, autoSubmit = false, + onSubmitHandler, + searchRef, + className, }: { + action: string status: 'idle' | 'pending' | 'success' | 'error' - autoFocus?: boolean + autoFocusSearch?: boolean + onSubmitHandler?: (e: React.FormEvent) => void autoSubmit?: boolean + searchRef?: React.RefObject + className?: string }) { const id = useId() const [searchParams] = useSearchParams() + let defaultValue = searchParams.get('search') || '' + const submit = useSubmit() const isSubmitting = useIsPending({ formMethod: 'GET', - formAction: '/users', + formAction: action, }) const handleFormChange = useDebounce((form: HTMLFormElement) => { @@ -30,34 +40,37 @@ export function SearchBar({ return (
autoSubmit && handleFormChange(e.currentTarget)} + onSubmit={onSubmitHandler} > -
+
-
- - - Search - -
+ + + Search + ) } diff --git a/app/components/ui/icons/name.d.ts b/app/components/ui/icons/name.d.ts new file mode 100644 index 0000000..dc9db94 --- /dev/null +++ b/app/components/ui/icons/name.d.ts @@ -0,0 +1,36 @@ +// This file is generated by npm run build:icons + +export type IconName = + | 'arrow-left' + | 'arrow-right' + | 'avatar' + | 'camera' + | 'check' + | 'clock' + | 'compass' + | 'cross-1' + | 'dots-horizontal' + | 'double-arrow-left' + | 'double-arrow-right' + | 'download' + | 'envelope-closed' + | 'exit' + | 'eye-open' + | 'file-text' + | 'gift' + | 'github-logo' + | 'laptop' + | 'link-2' + | 'lock-closed' + | 'lock-open-1' + | 'magnifying-glass' + | 'menu' + | 'moon' + | 'pencil-1' + | 'pencil-2' + | 'plus' + | 'question-mark-circled' + | 'reset' + | 'sun' + | 'trash' + | 'update' diff --git a/app/components/ui/icons/sprite.svg b/app/components/ui/icons/sprite.svg new file mode 100644 index 0000000..4488199 --- /dev/null +++ b/app/components/ui/icons/sprite.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/components/ui/sheet.tsx b/app/components/ui/sheet.tsx new file mode 100644 index 0000000..9c6fb7d --- /dev/null +++ b/app/components/ui/sheet.tsx @@ -0,0 +1,138 @@ +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "#app/utils/misc.tsx" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/app/root.tsx b/app/root.tsx index 9507025..06df9cd 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -230,8 +230,6 @@ function App() { m => m.id === 'routes/c.$companyId+/_layout_company', ) - console.log('matches', matches) - const isOnSearchPage = matches.find(m => m.id === 'routes/users+/index') const searchBar = isOnSearchPage ? null : useToast(data.toast) diff --git a/other/svg-icons/compass.svg b/other/svg-icons/compass.svg new file mode 100644 index 0000000..03738b9 --- /dev/null +++ b/other/svg-icons/compass.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/other/svg-icons/double-arrow-left.svg b/other/svg-icons/double-arrow-left.svg new file mode 100644 index 0000000..705234a --- /dev/null +++ b/other/svg-icons/double-arrow-left.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/other/svg-icons/double-arrow-right.svg b/other/svg-icons/double-arrow-right.svg new file mode 100644 index 0000000..034ff25 --- /dev/null +++ b/other/svg-icons/double-arrow-right.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/other/svg-icons/eye-open.svg b/other/svg-icons/eye-open.svg new file mode 100644 index 0000000..036bd44 --- /dev/null +++ b/other/svg-icons/eye-open.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/other/svg-icons/gift.svg b/other/svg-icons/gift.svg new file mode 100644 index 0000000..e7cbb5e --- /dev/null +++ b/other/svg-icons/gift.svg @@ -0,0 +1,8 @@ + + + diff --git a/other/svg-icons/menu.svg b/other/svg-icons/menu.svg new file mode 100644 index 0000000..e2e4aed --- /dev/null +++ b/other/svg-icons/menu.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 8fb90e1..6ea2bbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,9 @@ "@epic-web/totp": "^1.1.1", "@nasa-gcn/remix-seo": "^2.0.0", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^5.8.1", + "@prisma/client": "^5.9.0", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", @@ -56,7 +57,7 @@ "litefs-js": "^1.1.2", "lru-cache": "^10.2.0", "morgan": "^1.10.0", - "prisma": "^5.8.1", + "prisma": "^5.9.0", "qrcode": "^1.5.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -82,7 +83,7 @@ "@remix-run/serve": "^2.5.1", "@remix-run/testing": "^2.5.1", "@sly-cli/sly": "^1.8.0", - "@testing-library/jest-dom": "^6.3.0", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "@total-typescript/ts-reset": "^0.5.1", @@ -1998,9 +1999,9 @@ } }, "node_modules/@prisma/client": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.8.1.tgz", - "integrity": "sha512-xQtMPfbIwLlbm0VVIVQY2yqQVOxPwRQhvIp7Z3m2900g1bu/zRHKhYZJQWELqmjl6d8YwBy0K2NvMqh47v1ubw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.9.0.tgz", + "integrity": "sha512-dHvFZgCT0BpRS+gRhk3S+50DstXMmVowxbrPeUJaK7sjNq5OhzfpT/OGE1kq9z5Q8WmOwIXJXyxP8O2CmP+nSg==", "hasInstallScript": true, "engines": { "node": ">=16.13" @@ -2015,43 +2016,43 @@ } }, "node_modules/@prisma/debug": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.8.1.tgz", - "integrity": "sha512-tjuw7eA0Us3T42jx9AmAgL58rzwzpFGYc3R7Y4Ip75EBYrKMBA1YihuWMcBC92ILmjlQ/u3p8VxcIE0hr+fZfg==" + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.9.0.tgz", + "integrity": "sha512-3Uhj5YSPqaIfzJQ6JQzCNBXeBTy0x803fGIoo2tvP/KIEd+o4o49JxCQtKtP8aeef5iNh5Nn9Z25wDrdLjS80A==" }, "node_modules/@prisma/engines": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.8.1.tgz", - "integrity": "sha512-TJgYLRrZr56uhqcXO4GmP5be+zjCIHtLDK20Cnfg+o9d905hsN065QOL+3Z0zQAy6YD31Ol4u2kzSfRmbJv/uA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.9.0.tgz", + "integrity": "sha512-BH1fpXbMH09TwfZH5FVMJwRp6afEhKzqwebbCLdaEkJDuhxA//iwbILLqGFtGTgZbdBNUOThIK+UC3++5kWMTg==", "hasInstallScript": true, "dependencies": { - "@prisma/debug": "5.8.1", - "@prisma/engines-version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", - "@prisma/fetch-engine": "5.8.1", - "@prisma/get-platform": "5.8.1" + "@prisma/debug": "5.9.0", + "@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", + "@prisma/fetch-engine": "5.9.0", + "@prisma/get-platform": "5.9.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2.tgz", - "integrity": "sha512-f5C3JM3l9yhGr3cr4FMqWloFaSCpNpMi58Om22rjD2DOz3owci2mFdFXMgnAGazFPKrCbbEhcxdsRfspEYRoFQ==" + "version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64.tgz", + "integrity": "sha512-HFl7275yF0FWbdcNvcSRbbu9JCBSLMcurYwvWc8WGDnpu7APxQo2ONtZrUggU3WxLxUJ2uBX+0GOFIcJeVeOOQ==" }, "node_modules/@prisma/fetch-engine": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.8.1.tgz", - "integrity": "sha512-+bgjjoSFa6uYEbAPlklfoVSStOEfcpheOjoBoNsNNSQdSzcwE2nM4Q0prun0+P8/0sCHo18JZ9xqa8gObvgOUw==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.9.0.tgz", + "integrity": "sha512-NL8Vm8Vl2d6NOSkkPGN5TTTz4s6cyCleXOzqtOFWzfKFJ4wtN2Shu7llOT+ykf6nDzh1lCN2JHUt1S6FGFZGig==", "dependencies": { - "@prisma/debug": "5.8.1", - "@prisma/engines-version": "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2", - "@prisma/get-platform": "5.8.1" + "@prisma/debug": "5.9.0", + "@prisma/engines-version": "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64", + "@prisma/get-platform": "5.9.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.8.1.tgz", - "integrity": "sha512-wnA+6HTFcY+tkykMokix9GiAkaauPC5W/gg0O5JB0J8tCTNWrqpnQ7AsaGRfkYUbeOIioh6woDjQrGTTRf1Zag==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.9.0.tgz", + "integrity": "sha512-8CatX+E6eZxcOjJZe5hF8EXxdb5GsQTA/u7pdmUJSxGLacW9K3r5vDdgV8s22PubObQQ6979/rkCMItbCrG4Yg==", "dependencies": { - "@prisma/debug": "5.8.1" + "@prisma/debug": "5.9.0" } }, "node_modules/@radix-ui/primitive": { @@ -2175,6 +2176,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", @@ -5172,9 +5209,9 @@ "dev": true }, "node_modules/@testing-library/jest-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.3.0.tgz", - "integrity": "sha512-hJVIrkFizEQxoWsGBlycTcQhrpoCH4DhXfrnHFFXgkx3Xdm15zycsq5Ep+vpw4W8S0NJa8cxDHcuJib+1tEbhg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.0.tgz", + "integrity": "sha512-GgGT3OR8qhIjk2SBMy51AYDWoMnAyR/cwjZO4SttuBmIQ9wWy9QmVOeaSbgT5Bm0J6qLBaf4+dsJWfisvafoaA==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -15497,12 +15534,12 @@ } }, "node_modules/prisma": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.8.1.tgz", - "integrity": "sha512-N6CpjzECnUHZ5beeYpDzkt2rYpEdAeqXX2dweu6BoQaeYkNZrC/WJHM+5MO/uidFHTak8QhkPKBWck1o/4MD4A==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.9.0.tgz", + "integrity": "sha512-0UcOofjNuAnd227JMaPqZvP01dsUXw9EXB9iC8fyoZtfv7zkQ0ozxyjY1g+vcjFPOnNLICMnLHx+lM5BJZYqOQ==", "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.8.1" + "@prisma/engines": "5.9.0" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 1cfc483..431f091 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,9 @@ "@epic-web/totp": "^1.1.1", "@nasa-gcn/remix-seo": "^2.0.0", "@paralleldrive/cuid2": "^2.2.2", - "@prisma/client": "^5.8.1", + "@prisma/client": "^5.9.0", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", @@ -87,7 +88,7 @@ "litefs-js": "^1.1.2", "lru-cache": "^10.2.0", "morgan": "^1.10.0", - "prisma": "^5.8.1", + "prisma": "^5.9.0", "qrcode": "^1.5.3", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -113,7 +114,7 @@ "@remix-run/serve": "^2.5.1", "@remix-run/testing": "^2.5.1", "@sly-cli/sly": "^1.8.0", - "@testing-library/jest-dom": "^6.3.0", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.2", "@total-typescript/ts-reset": "^0.5.1", diff --git a/prisma/migrations/20240130113044_/migration.sql b/prisma/migrations/20240130113044_/migration.sql new file mode 100644 index 0000000..b274935 --- /dev/null +++ b/prisma/migrations/20240130113044_/migration.sql @@ -0,0 +1,89 @@ +-- CreateTable +CREATE TABLE "Company" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "UserCompany" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "userId" TEXT NOT NULL, + "companyId" TEXT NOT NULL, + "isOwner" BOOLEAN NOT NULL DEFAULT false, + CONSTRAINT "UserCompany_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "UserCompany_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Account" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "companyId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "balance" REAL NOT NULL, + "type" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Account_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Transaction" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "companyId" TEXT NOT NULL, + "date" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "accountId" INTEGER NOT NULL, + "description" TEXT NOT NULL, + "amount" REAL NOT NULL, + "type" TEXT NOT NULL, + "category" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "userId" TEXT, + CONSTRAINT "Transaction_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Transaction_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Transaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Invoice" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "companyId" TEXT NOT NULL, + "dateIssued" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "dueDate" DATETIME NOT NULL, + "totalAmount" REAL NOT NULL, + "status" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "userId" TEXT, + CONSTRAINT "Invoice_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "Invoice_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "InvoiceItem" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "invoiceId" INTEGER NOT NULL, + "description" TEXT NOT NULL, + "quantity" INTEGER NOT NULL, + "price" REAL NOT NULL, + "total" REAL NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "InvoiceItem_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_RoleToUserCompany" ( + "A" TEXT NOT NULL, + "B" INTEGER NOT NULL, + CONSTRAINT "_RoleToUserCompany_A_fkey" FOREIGN KEY ("A") REFERENCES "Role" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_RoleToUserCompany_B_fkey" FOREIGN KEY ("B") REFERENCES "UserCompany" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "_RoleToUserCompany_AB_unique" ON "_RoleToUserCompany"("A", "B"); + +-- CreateIndex +CREATE INDEX "_RoleToUserCompany_B_index" ON "_RoleToUserCompany"("B");