diff --git a/components.json b/components.json new file mode 100644 index 0000000..c5ebd12 --- /dev/null +++ b/components.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": { + "@magicui": "https://magicui.design/r/{name}" + } +} diff --git a/package-lock.json b/package-lock.json index 855cbcc..f949ff4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,15 @@ "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", "@tanstack/react-query": "^5.100.7", + "clsx": "^2.1.1", "dotenv": "^17.4.2", + "lucide-react": "^1.17.0", + "motion": "^12.40.0", "next": "16.2.4", "prisma": "^7.8.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -25,7 +29,7 @@ "eslint": "^9", "eslint-config-next": "16.2.4", "tailwindcss": "^4", - "ts-node": "^10.9.2", + "tsx": "^4.19.0", "typescript": "^5" } }, @@ -282,30 +286,6 @@ "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@electric-sql/pglite": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", @@ -366,6 +346,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -2065,34 +2487,6 @@ "react": "^18 || ^19" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2775,19 +3169,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -2821,13 +3202,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3308,6 +3682,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3348,13 +3731,6 @@ "dev": true, "license": "MIT" }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3537,16 +3913,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3823,6 +4189,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4452,6 +4860,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/framer-motion": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", + "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.40.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5803,6 +6253,15 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/lucide-react": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", + "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -5813,13 +6272,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5877,6 +6329,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.40.0.tgz", + "integrity": "sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.40.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.40.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", + "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7388,6 +7881,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", @@ -7483,50 +7986,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -7559,6 +8018,25 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7789,13 +8267,6 @@ "punycode": "^2.1.0" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/valibot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", @@ -7940,16 +8411,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 89d9c55..feec39a 100644 --- a/package.json +++ b/package.json @@ -7,19 +7,30 @@ "build": "next build", "start": "next start", "lint": "eslint", - "prisma:push": "prisma db push", + "prisma:generate": "prisma generate", "prisma:migrate": "prisma migrate dev", - "prisma:seed": "ts-node prisma/seed.ts" + "prisma:migrate:deploy": "prisma migrate deploy", + "prisma:migrate:reset": "prisma migrate reset", + "prisma:seed": "tsx prisma/seed.ts", + "prisma:studio": "prisma studio", + "prisma:push": "prisma db push" + }, + "prisma": { + "seed": "tsx prisma/seed.ts" }, "dependencies": { "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", "@tanstack/react-query": "^5.100.7", + "clsx": "^2.1.1", "dotenv": "^17.4.2", + "lucide-react": "^1.17.0", + "motion": "^12.40.0", "next": "16.2.4", "prisma": "^7.8.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -29,7 +40,7 @@ "eslint": "^9", "eslint-config-next": "16.2.4", "tailwindcss": "^4", - "ts-node": "^10.9.2", + "tsx": "^4.19.0", "typescript": "^5" } } diff --git a/prisma/migrations/20260531202816_init/migration.sql b/prisma/migrations/20260531202816_init/migration.sql new file mode 100644 index 0000000..eb4c953 --- /dev/null +++ b/prisma/migrations/20260531202816_init/migration.sql @@ -0,0 +1,170 @@ +-- CreateEnum +CREATE TYPE "LinkType" AS ENUM ('WEBSITE', 'GITHUB', 'TWITTER', 'LINKEDIN', 'FACEBOOK', 'INSTAGRAM', 'OTHER'); + +-- CreateTable +CREATE TABLE "venue" ( + "id" UUID NOT NULL, + "name" VARCHAR(255) NOT NULL, + "city" VARCHAR(255) NOT NULL, + "neighborhood" VARCHAR(255) NOT NULL, + "total_rooms" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "venue_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "event" ( + "id" UUID NOT NULL, + "title" VARCHAR(255) NOT NULL, + "description" TEXT, + "venueId" UUID, + "is_online" BOOLEAN NOT NULL DEFAULT false, + "start_date" TIMESTAMP(3) NOT NULL, + "end_date" TIMESTAMP(3) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "room" ( + "id" UUID NOT NULL, + "venueId" UUID NOT NULL, + "name" VARCHAR(100) NOT NULL, + "capacity" INTEGER, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "room_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "event_session" ( + "id" UUID NOT NULL, + "eventId" UUID NOT NULL, + "roomId" UUID, + "title" VARCHAR(255) NOT NULL, + "description" TEXT, + "start_time" TIMESTAMP(3) NOT NULL, + "end_time" TIMESTAMP(3) NOT NULL, + "capacity" INTEGER, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "event_session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "speaker" ( + "id" UUID NOT NULL, + "name" VARCHAR(255) NOT NULL, + "avatar_url" TEXT, + "bio" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "speaker_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "speaker_external_link" ( + "id" UUID NOT NULL, + "speakerId" UUID NOT NULL, + "link_type" "LinkType" NOT NULL, + "url" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "speaker_external_link_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "event_session_speaker" ( + "eventSessionId" UUID NOT NULL, + "speakerId" UUID NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "event_session_speaker_pkey" PRIMARY KEY ("eventSessionId","speakerId") +); + +-- CreateTable +CREATE TABLE "session_registration" ( + "id" TEXT NOT NULL, + "eventSessionId" UUID NOT NULL, + "name" VARCHAR(255) NOT NULL, + "email" VARCHAR(255) NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "session_registration_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "question" ( + "id" TEXT NOT NULL, + "eventSessionId" UUID NOT NULL, + "content" TEXT NOT NULL, + "author_name" VARCHAR(100) NOT NULL DEFAULT 'Anonymous', + "upvotes" INTEGER NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "question_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "room_venueId_idx" ON "room"("venueId"); + +-- CreateIndex +CREATE UNIQUE INDEX "room_venueId_name_key" ON "room"("venueId", "name"); + +-- CreateIndex +CREATE INDEX "event_session_eventId_idx" ON "event_session"("eventId"); + +-- CreateIndex +CREATE INDEX "event_session_start_time_idx" ON "event_session"("start_time"); + +-- CreateIndex +CREATE INDEX "event_session_roomId_idx" ON "event_session"("roomId"); + +-- CreateIndex +CREATE UNIQUE INDEX "event_session_roomId_start_time_key" ON "event_session"("roomId", "start_time"); + +-- CreateIndex +CREATE INDEX "session_registration_eventSessionId_idx" ON "session_registration"("eventSessionId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_registration_eventSessionId_email_key" ON "session_registration"("eventSessionId", "email"); + +-- CreateIndex +CREATE INDEX "question_eventSessionId_idx" ON "question"("eventSessionId"); + +-- CreateIndex +CREATE INDEX "question_upvotes_idx" ON "question"("upvotes"); + +-- AddForeignKey +ALTER TABLE "event" ADD CONSTRAINT "event_venueId_fkey" FOREIGN KEY ("venueId") REFERENCES "venue"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "room" ADD CONSTRAINT "room_venueId_fkey" FOREIGN KEY ("venueId") REFERENCES "venue"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "event_session" ADD CONSTRAINT "event_session_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "event"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "event_session" ADD CONSTRAINT "event_session_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "room"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "speaker_external_link" ADD CONSTRAINT "speaker_external_link_speakerId_fkey" FOREIGN KEY ("speakerId") REFERENCES "speaker"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "event_session_speaker" ADD CONSTRAINT "event_session_speaker_eventSessionId_fkey" FOREIGN KEY ("eventSessionId") REFERENCES "event_session"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "event_session_speaker" ADD CONSTRAINT "event_session_speaker_speakerId_fkey" FOREIGN KEY ("speakerId") REFERENCES "speaker"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "session_registration" ADD CONSTRAINT "session_registration_eventSessionId_fkey" FOREIGN KEY ("eventSessionId") REFERENCES "event_session"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "question" ADD CONSTRAINT "question_eventSessionId_fkey" FOREIGN KEY ("eventSessionId") REFERENCES "event_session"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..8277428 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,855 @@ +import "dotenv/config"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "@prisma/client"; + +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL!, +}); +const prisma = new PrismaClient({ adapter }); + +async function main() { + console.log("🌱 Seeding database..."); + + // ────────────────────────────────────────────── + // 1. Clean existing data (reverse FK order) + // ────────────────────────────────────────────── + console.log(" Cleaning existing data..."); + await prisma.question.deleteMany(); + await prisma.sessionRegistration.deleteMany(); + await prisma.eventSessionSpeaker.deleteMany(); + await prisma.eventSession.deleteMany(); + await prisma.speakerExternalLink.deleteMany(); + await prisma.speaker.deleteMany(); + await prisma.room.deleteMany(); + await prisma.event.deleteMany(); + await prisma.venue.deleteMany(); + + // ────────────────────────────────────────────── + // 2. Venues + // ────────────────────────────────────────────── + console.log(" Creating venues..."); + const venues = await Promise.all([ + prisma.venue.create({ + data: { + id: "550e8400-e29b-41d4-a716-446655440001", + name: "HEI", + city: "Antananarivo", + neighborhood: "Ivandry", + totalRooms: 4, + }, + }), + prisma.venue.create({ + data: { + id: "550e8400-e29b-41d4-a716-446655440002", + name: "Convention Center", + city: "Antananarivo", + neighborhood: "Anjanahary", + totalRooms: 3, + }, + }), + prisma.venue.create({ + data: { + id: "550e8400-e29b-41d4-a716-446655440003", + name: "Digital Lab", + city: "Antsirabe", + neighborhood: "Centre", + totalRooms: 2, + }, + }), + prisma.venue.create({ + data: { + id: "550e8400-e29b-41d4-a716-446655440004", + name: "University Center", + city: "Fianarantsoa", + neighborhood: "Tanambao", + totalRooms: 3, + }, + }), + prisma.venue.create({ + data: { + id: "550e8400-e29b-41d4-a716-446655440005", + name: "Tech Hub", + city: "Toamasina", + neighborhood: "Port", + totalRooms: 2, + }, + }), + ]); + console.log(` Created ${venues.length} venues`); + + // ────────────────────────────────────────────── + // 3. Events + // ────────────────────────────────────────────── + console.log(" Creating events..."); + const events = await Promise.all([ + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440001", + title: "DevFest Antananarivo 2024", + description: + "Annual developer festival with workshops and talks about web, mobile, and cloud technologies.", + venueId: "550e8400-e29b-41d4-a716-446655440001", + isOnline: false, + startDate: new Date("2024-11-15T08:00:00Z"), + endDate: new Date("2024-11-16T18:00:00Z"), + }, + }), + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440002", + title: "Tech Innovation Summit 2026", + description: + "Ongoing summit about AI, blockchain, and digital transformation in Madagascar.", + venueId: "550e8400-e29b-41d4-a716-446655440002", + isOnline: false, + startDate: new Date("2026-05-19T09:00:00Z"), + endDate: new Date("2026-05-22T17:00:00Z"), + }, + }), + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440003", + title: "Startup Weekend Antsirabe", + description: + "54-hour weekend event to pitch ideas, form teams, and launch startups.", + venueId: "550e8400-e29b-41d4-a716-446655440003", + isOnline: false, + startDate: new Date("2026-07-10T18:00:00Z"), + endDate: new Date("2026-07-12T20:00:00Z"), + }, + }), + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440004", + title: "AI Conference Fianarantsoa", + description: + "Exploring artificial intelligence applications in education and healthcare.", + venueId: "550e8400-e29b-41d4-a716-446655440004", + isOnline: false, + startDate: new Date("2026-08-20T09:00:00Z"), + endDate: new Date("2026-08-21T18:00:00Z"), + }, + }), + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440005", + title: "Mobile Dev Workshop Toamasina", + description: + "Hands-on workshop for Flutter and React Native development.", + venueId: "550e8400-e29b-41d4-a716-446655440005", + isOnline: false, + startDate: new Date("2026-09-05T10:00:00Z"), + endDate: new Date("2026-09-05T17:00:00Z"), + }, + }), + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440006", + title: "Cloud & DevOps Day", + description: + "Full day dedicated to cloud infrastructure and DevOps practices.", + venueId: "550e8400-e29b-41d4-a716-446655440001", + isOnline: false, + startDate: new Date("2026-10-15T09:00:00Z"), + endDate: new Date("2026-10-15T18:00:00Z"), + }, + }), + prisma.event.create({ + data: { + id: "660e8400-e29b-41d4-a716-446655440007", + title: "Global Web Development Summit", + description: + "International online summit covering the latest in web technologies.", + venueId: null, + isOnline: true, + startDate: new Date("2026-05-20T08:00:00Z"), + endDate: new Date("2026-05-22T18:00:00Z"), + }, + }), + ]); + console.log(` Created ${events.length} events`); + + // ────────────────────────────────────────────── + // 4. Rooms + // ────────────────────────────────────────────── + console.log(" Creating rooms..."); + const rooms = await Promise.all([ + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440001", + venueId: "550e8400-e29b-41d4-a716-446655440001", + name: "Grand AmphithéÒtre", + capacity: 300, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440002", + venueId: "550e8400-e29b-41d4-a716-446655440001", + name: "Salle NP", + capacity: 80, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440003", + venueId: "550e8400-e29b-41d4-a716-446655440001", + name: "Labo Informatique", + capacity: 40, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440004", + venueId: "550e8400-e29b-41d4-a716-446655440001", + name: "Salle Pi", + capacity: 50, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440005", + venueId: "550e8400-e29b-41d4-a716-446655440002", + name: "Grand Hall", + capacity: 500, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440006", + venueId: "550e8400-e29b-41d4-a716-446655440002", + name: "Salle Ravinala", + capacity: 150, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440007", + venueId: "550e8400-e29b-41d4-a716-446655440002", + name: "Espace Workshop", + capacity: 60, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440008", + venueId: "550e8400-e29b-41d4-a716-446655440003", + name: "Main Room", + capacity: 100, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440009", + venueId: "550e8400-e29b-41d4-a716-446655440003", + name: "Training Room", + capacity: 30, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440010", + venueId: "550e8400-e29b-41d4-a716-446655440004", + name: "Amphi Principal", + capacity: 250, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440011", + venueId: "550e8400-e29b-41d4-a716-446655440004", + name: "Salle 101", + capacity: 60, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440012", + venueId: "550e8400-e29b-41d4-a716-446655440004", + name: "Salle 102", + capacity: 60, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440013", + venueId: "550e8400-e29b-41d4-a716-446655440005", + name: "Salle Principale", + capacity: 120, + }, + }), + prisma.room.create({ + data: { + id: "770e8400-e29b-41d4-a716-446655440014", + venueId: "550e8400-e29b-41d4-a716-446655440005", + name: "Co-working Space", + capacity: 40, + }, + }), + ]); + console.log(` Created ${rooms.length} rooms`); + + // ────────────────────────────────────────────── + // 5. Speakers + // ────────────────────────────────────────────── + console.log(" Creating speakers..."); + const speakers = await Promise.all([ + prisma.speaker.create({ + data: { + id: "880e8400-e29b-41d4-a716-446655440001", + name: "Rija Rakoto", + avatarUrl: "https://i.pravatar.cc/300?u=rija", + bio: "Full-stack developer with 12 years experience.", + }, + }), + prisma.speaker.create({ + data: { + id: "880e8400-e29b-41d4-a716-446655440002", + name: "Miora Rasoanaivo", + avatarUrl: "https://i.pravatar.cc/300?u=miora", + bio: "Cloud architect at NovaTech.", + }, + }), + prisma.speaker.create({ + data: { + id: "880e8400-e29b-41d4-a716-446655440003", + name: "Lova Andrian", + avatarUrl: "https://i.pravatar.cc/300?u=lova", + bio: "AI researcher specializing in agricultural applications.", + }, + }), + prisma.speaker.create({ + data: { + id: "880e8400-e29b-41d4-a716-446655440004", + name: "Tiana Randria", + avatarUrl: "https://i.pravatar.cc/300?u=tiana", + bio: "Serial entrepreneur and product management expert.", + }, + }), + prisma.speaker.create({ + data: { + id: "880e8400-e29b-41d4-a716-446655440005", + name: "Haja Rasolofo", + avatarUrl: "https://i.pravatar.cc/300?u=haja", + bio: "Tech lead and content creator.", + }, + }), + ]); + console.log(` Created ${speakers.length} speakers`); + + // ────────────────────────────────────────────── + // 6. Speaker External Links + // ────────────────────────────────────────────── + console.log(" Creating speaker external links..."); + const speakerLinks = await Promise.all([ + prisma.speakerExternalLink.create({ + data: { + id: "990e8400-e29b-41d4-a716-446655440001", + speakerId: "880e8400-e29b-41d4-a716-446655440001", + linkType: "TWITTER", + url: "https://twitter.com/rijarakoto", + }, + }), + prisma.speakerExternalLink.create({ + data: { + id: "990e8400-e29b-41d4-a716-446655440002", + speakerId: "880e8400-e29b-41d4-a716-446655440001", + linkType: "GITHUB", + url: "https://github.com/rijarakoto", + }, + }), + prisma.speakerExternalLink.create({ + data: { + id: "990e8400-e29b-41d4-a716-446655440003", + speakerId: "880e8400-e29b-41d4-a716-446655440002", + linkType: "LINKEDIN", + url: "https://linkedin.com/in/miora", + }, + }), + prisma.speakerExternalLink.create({ + data: { + id: "990e8400-e29b-41d4-a716-446655440004", + speakerId: "880e8400-e29b-41d4-a716-446655440003", + linkType: "TWITTER", + url: "https://twitter.com/lovaai", + }, + }), + prisma.speakerExternalLink.create({ + data: { + id: "990e8400-e29b-41d4-a716-446655440005", + speakerId: "880e8400-e29b-41d4-a716-446655440004", + linkType: "LINKEDIN", + url: "https://linkedin.com/in/tiana", + }, + }), + prisma.speakerExternalLink.create({ + data: { + id: "990e8400-e29b-41d4-a716-446655440006", + speakerId: "880e8400-e29b-41d4-a716-446655440005", + linkType: "OTHER", + url: "https://youtube.com/@hajatech", + }, + }), + ]); + console.log(` Created ${speakerLinks.length} speaker links`); + + // ────────────────────────────────────────────── + // 7. Event Sessions + // ────────────────────────────────────────────── + console.log(" Creating event sessions..."); + const sessionData = [ + { + id: "aa0e8400-e29b-41d4-a716-446655440001", + eventId: "660e8400-e29b-41d4-a716-446655440001", + roomId: "770e8400-e29b-41d4-a716-446655440001", + title: "Keynote: Web Development Trends", + description: "Overview of the latest trends in web development for 2024.", + startTime: new Date("2024-11-15T09:00:00Z"), + endTime: new Date("2024-11-15T10:30:00Z"), + capacity: 300, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440002", + eventId: "660e8400-e29b-41d4-a716-446655440001", + roomId: "770e8400-e29b-41d4-a716-446655440003", + title: "Workshop: React Hooks Deep Dive", + description: "Advanced React patterns with hooks.", + startTime: new Date("2024-11-15T11:00:00Z"), + endTime: new Date("2024-11-15T13:00:00Z"), + capacity: 40, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440003", + eventId: "660e8400-e29b-41d4-a716-446655440001", + roomId: "770e8400-e29b-41d4-a716-446655440002", + title: "Cloud Migration Strategies", + description: "How to migrate legacy apps to the cloud.", + startTime: new Date("2024-11-15T14:00:00Z"), + endTime: new Date("2024-11-15T15:30:00Z"), + capacity: 80, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440004", + eventId: "660e8400-e29b-41d4-a716-446655440001", + roomId: "770e8400-e29b-41d4-a716-446655440004", + title: "Networking Session", + description: "Meet fellow developers and share experiences.", + startTime: new Date("2024-11-15T16:00:00Z"), + endTime: new Date("2024-11-15T18:00:00Z"), + capacity: 50, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440005", + eventId: "660e8400-e29b-41d4-a716-446655440001", + roomId: "770e8400-e29b-41d4-a716-446655440001", + title: "AI for Beginners", + description: "Introduction to AI concepts and tools.", + startTime: new Date("2024-11-16T09:00:00Z"), + endTime: new Date("2024-11-16T12:00:00Z"), + capacity: 300, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440006", + eventId: "660e8400-e29b-41d4-a716-446655440001", + roomId: "770e8400-e29b-41d4-a716-446655440001", + title: "Closing Panel", + description: "Discussion on the future of tech in Madagascar.", + startTime: new Date("2024-11-16T14:00:00Z"), + endTime: new Date("2024-11-16T17:00:00Z"), + capacity: 300, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440007", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440005", + title: "Opening Ceremony", + description: "Welcome and introduction to the summit.", + startTime: new Date("2026-05-19T09:00:00Z"), + endTime: new Date("2026-05-19T10:00:00Z"), + capacity: 500, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440008", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440005", + title: "AI in Healthcare", + description: "How AI is transforming healthcare in Africa.", + startTime: new Date("2026-05-19T10:30:00Z"), + endTime: new Date("2026-05-19T12:00:00Z"), + capacity: 500, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440009", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440007", + title: "Blockchain Workshop", + description: "Hands-on blockchain development.", + startTime: new Date("2026-05-19T14:00:00Z"), + endTime: new Date("2026-05-19T17:00:00Z"), + capacity: 60, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440010", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440005", + title: "Digital Transformation Panel", + description: "Panel discussion on digital transformation strategies.", + startTime: new Date("2026-05-20T09:00:00Z"), + endTime: new Date("2026-05-20T11:00:00Z"), + capacity: 500, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440011", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440006", + title: "Startup Pitch Competition", + description: "Local startups pitch their ideas.", + startTime: new Date("2026-05-20T11:30:00Z"), + endTime: new Date("2026-05-20T13:30:00Z"), + capacity: 150, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440012", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440005", + title: "Cybersecurity Essentials", + description: "Protecting your digital assets.", + startTime: new Date("2026-05-21T09:00:00Z"), + endTime: new Date("2026-05-21T11:00:00Z"), + capacity: 500, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440013", + eventId: "660e8400-e29b-41d4-a716-446655440002", + roomId: "770e8400-e29b-41d4-a716-446655440005", + title: "Closing & Awards", + description: "Summit closing ceremony and awards.", + startTime: new Date("2026-05-22T15:00:00Z"), + endTime: new Date("2026-05-22T17:00:00Z"), + capacity: 500, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440014", + eventId: "660e8400-e29b-41d4-a716-446655440003", + roomId: "770e8400-e29b-41d4-a716-446655440008", + title: "Pitch Workshop", + description: "Learn how to pitch your startup idea.", + startTime: new Date("2026-07-10T18:00:00Z"), + endTime: new Date("2026-07-10T20:00:00Z"), + capacity: 100, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440015", + eventId: "660e8400-e29b-41d4-a716-446655440003", + roomId: "770e8400-e29b-41d4-a716-446655440008", + title: "Team Formation", + description: "Form teams and start brainstorming.", + startTime: new Date("2026-07-10T20:00:00Z"), + endTime: new Date("2026-07-10T22:00:00Z"), + capacity: 100, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440016", + eventId: "660e8400-e29b-41d4-a716-446655440003", + roomId: "770e8400-e29b-41d4-a716-446655440009", + title: "Mentoring Sessions", + description: "Get advice from experienced mentors.", + startTime: new Date("2026-07-11T09:00:00Z"), + endTime: new Date("2026-07-11T18:00:00Z"), + capacity: 30, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440017", + eventId: "660e8400-e29b-41d4-a716-446655440003", + roomId: "770e8400-e29b-41d4-a716-446655440008", + title: "Final Presentations", + description: "Teams present their projects.", + startTime: new Date("2026-07-12T14:00:00Z"), + endTime: new Date("2026-07-12T18:00:00Z"), + capacity: 100, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440018", + eventId: "660e8400-e29b-41d4-a716-446655440004", + roomId: "770e8400-e29b-41d4-a716-446655440010", + title: "Machine Learning Fundamentals", + description: "Introduction to ML algorithms and tools.", + startTime: new Date("2026-08-20T09:00:00Z"), + endTime: new Date("2026-08-20T12:00:00Z"), + capacity: 250, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440019", + eventId: "660e8400-e29b-41d4-a716-446655440004", + roomId: "770e8400-e29b-41d4-a716-446655440011", + title: "Deep Learning Workshop", + description: "Practical deep learning with TensorFlow.", + startTime: new Date("2026-08-20T14:00:00Z"), + endTime: new Date("2026-08-20T17:00:00Z"), + capacity: 60, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440020", + eventId: "660e8400-e29b-41d4-a716-446655440004", + roomId: "770e8400-e29b-41d4-a716-446655440010", + title: "AI Ethics Panel", + description: "Discussion on ethical AI development.", + startTime: new Date("2026-08-21T09:00:00Z"), + endTime: new Date("2026-08-21T11:00:00Z"), + capacity: 250, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440021", + eventId: "660e8400-e29b-41d4-a716-446655440004", + roomId: "770e8400-e29b-41d4-a716-446655440012", + title: "NLP Applications", + description: "Natural Language Processing use cases.", + startTime: new Date("2026-08-21T14:00:00Z"), + endTime: new Date("2026-08-21T17:00:00Z"), + capacity: 60, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440022", + eventId: "660e8400-e29b-41d4-a716-446655440005", + roomId: "770e8400-e29b-41d4-a716-446655440013", + title: "Flutter Basics", + description: "Getting started with Flutter development.", + startTime: new Date("2026-09-05T10:00:00Z"), + endTime: new Date("2026-09-05T13:00:00Z"), + capacity: 120, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440023", + eventId: "660e8400-e29b-41d4-a716-446655440005", + roomId: "770e8400-e29b-41d4-a716-446655440014", + title: "React Native Advanced", + description: "Advanced patterns in React Native.", + startTime: new Date("2026-09-05T14:00:00Z"), + endTime: new Date("2026-09-05T17:00:00Z"), + capacity: 40, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440024", + eventId: "660e8400-e29b-41d4-a716-446655440006", + roomId: "770e8400-e29b-41d4-a716-446655440003", + title: "Docker & Kubernetes 101", + description: "Container orchestration fundamentals.", + startTime: new Date("2026-10-15T09:00:00Z"), + endTime: new Date("2026-10-15T12:00:00Z"), + capacity: 40, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440025", + eventId: "660e8400-e29b-41d4-a716-446655440006", + roomId: "770e8400-e29b-41d4-a716-446655440003", + title: "CI/CD Pipeline Workshop", + description: "Build automated deployment pipelines.", + startTime: new Date("2026-10-15T14:00:00Z"), + endTime: new Date("2026-10-15T17:00:00Z"), + capacity: 40, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440026", + eventId: "660e8400-e29b-41d4-a716-446655440006", + roomId: "770e8400-e29b-41d4-a716-446655440001", + title: "AWS Best Practices", + description: "Optimize your cloud infrastructure.", + startTime: new Date("2026-10-15T14:00:00Z"), + endTime: new Date("2026-10-15T16:00:00Z"), + capacity: 300, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440027", + eventId: "660e8400-e29b-41d4-a716-446655440007", + roomId: null, + title: "Keynote: The State of Web 2026", + description: "Live stream keynote covering the latest web platform features.", + startTime: new Date("2026-05-20T09:00:00Z"), + endTime: new Date("2026-05-20T10:30:00Z"), + capacity: null, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440028", + eventId: "660e8400-e29b-41d4-a716-446655440007", + roomId: null, + title: "Workshop: Web Components in Production", + description: "Build reusable components with the native web platform.", + startTime: new Date("2026-05-20T11:00:00Z"), + endTime: new Date("2026-05-20T13:00:00Z"), + capacity: null, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440029", + eventId: "660e8400-e29b-41d4-a716-446655440007", + roomId: null, + title: "Panel: The Future of JavaScript", + description: "Experts discuss where JavaScript is heading.", + startTime: new Date("2026-05-21T09:00:00Z"), + endTime: new Date("2026-05-21T10:30:00Z"), + capacity: null, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440030", + eventId: "660e8400-e29b-41d4-a716-446655440007", + roomId: null, + title: "Building Accessible Web Apps", + description: "Practical guide to web accessibility.", + startTime: new Date("2026-05-21T14:00:00Z"), + endTime: new Date("2026-05-21T16:00:00Z"), + capacity: null, + }, + { + id: "aa0e8400-e29b-41d4-a716-446655440031", + eventId: "660e8400-e29b-41d4-a716-446655440007", + roomId: null, + title: "Closing: What is Next for the Web", + description: "Closing keynote and community announcements.", + startTime: new Date("2026-05-22T16:00:00Z"), + endTime: new Date("2026-05-22T18:00:00Z"), + capacity: null, + }, + ]; + const sessions = await Promise.all( + sessionData.map((s) => prisma.eventSession.create({ data: s })) + ); + console.log(` Created ${sessions.length} event sessions`); + + // ────────────────────────────────────────────── + // 8. Event Session Speakers (many-to-many) + // ────────────────────────────────────────────── + console.log(" Creating event-session-speaker relations..."); + const sessionSpeakerPairs = [ + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440001", speakerId: "880e8400-e29b-41d4-a716-446655440001" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440002", speakerId: "880e8400-e29b-41d4-a716-446655440001" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440003", speakerId: "880e8400-e29b-41d4-a716-446655440002" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440005", speakerId: "880e8400-e29b-41d4-a716-446655440003" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440006", speakerId: "880e8400-e29b-41d4-a716-446655440004" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440006", speakerId: "880e8400-e29b-41d4-a716-446655440005" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440007", speakerId: "880e8400-e29b-41d4-a716-446655440004" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440008", speakerId: "880e8400-e29b-41d4-a716-446655440003" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440009", speakerId: "880e8400-e29b-41d4-a716-446655440005" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440010", speakerId: "880e8400-e29b-41d4-a716-446655440002" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440011", speakerId: "880e8400-e29b-41d4-a716-446655440004" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440014", speakerId: "880e8400-e29b-41d4-a716-446655440004" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440018", speakerId: "880e8400-e29b-41d4-a716-446655440003" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440019", speakerId: "880e8400-e29b-41d4-a716-446655440003" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440024", speakerId: "880e8400-e29b-41d4-a716-446655440002" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440025", speakerId: "880e8400-e29b-41d4-a716-446655440002" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440026", speakerId: "880e8400-e29b-41d4-a716-446655440002" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440027", speakerId: "880e8400-e29b-41d4-a716-446655440001" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440028", speakerId: "880e8400-e29b-41d4-a716-446655440001" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440029", speakerId: "880e8400-e29b-41d4-a716-446655440004" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440029", speakerId: "880e8400-e29b-41d4-a716-446655440005" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440030", speakerId: "880e8400-e29b-41d4-a716-446655440005" }, + { eventSessionId: "aa0e8400-e29b-41d4-a716-446655440031", speakerId: "880e8400-e29b-41d4-a716-446655440001" }, + ]; + const ess = await Promise.all( + sessionSpeakerPairs.map((pair) => + prisma.eventSessionSpeaker.create({ data: pair }) + ) + ); + console.log(` Created ${ess.length} session-speaker relations`); + + // ────────────────────────────────────────────── + // 9. Questions + // ────────────────────────────────────────────── + console.log(" Creating questions..."); + const questions = await Promise.all([ + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440001", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440002", + content: "What are the prerequisites for the React workshop?", + authorName: "Alice", + upvotes: 12, + }, + }), + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440002", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440001", + content: "Will the slides be available after the keynote?", + authorName: "Bob", + upvotes: 8, + }, + }), + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440003", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440005", + content: "Can you recommend resources for learning AI?", + authorName: "Charlie", + upvotes: 15, + }, + }), + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440004", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440009", + content: "How does blockchain apply to Madagascar?", + authorName: "Diana", + upvotes: 5, + }, + }), + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440005", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440010", + content: "What tools do you use for digital transformation?", + authorName: "Anonymous", + upvotes: 3, + }, + }), + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440006", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440011", + content: "Is there funding available for startups?", + authorName: "Eric", + upvotes: 10, + }, + }), + prisma.question.create({ + data: { + id: "bb0e8400-e29b-41d4-a716-446655440007", + eventSessionId: "aa0e8400-e29b-41d4-a716-446655440024", + content: "Can we access the workshop materials online?", + authorName: "Faly", + upvotes: 7, + }, + }), + ]); + console.log(` Created ${questions.length} questions`); + + // ────────────────────────────────────────────── + // 10. Update venue total_rooms to match actual room counts + // ────────────────────────────────────────────── + console.log(" Updating venue total_rooms..."); + const venuesWithCounts = await Promise.all( + venues.map(async (venue) => { + const count = await prisma.room.count({ + where: { venueId: venue.id }, + }); + await prisma.venue.update({ + where: { id: venue.id }, + data: { totalRooms: count }, + }); + return { name: venue.name, totalRooms: count }; + }) + ); + venuesWithCounts.forEach((v) => + console.log(` ${v.name}: ${v.totalRooms} rooms`) + ); + + console.log("βœ… Seeding complete!"); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error("❌ Seeding failed:", e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/public/liveqa.jpg b/public/liveqa.jpg new file mode 100644 index 0000000..950f8e6 Binary files /dev/null and b/public/liveqa.jpg differ diff --git a/public/speaker-1.png b/public/speaker-1.png new file mode 100644 index 0000000..b95493e Binary files /dev/null and b/public/speaker-1.png differ diff --git a/public/speaker-2.jpg b/public/speaker-2.jpg new file mode 100644 index 0000000..1aaff94 Binary files /dev/null and b/public/speaker-2.jpg differ diff --git a/public/speaker-2.png b/public/speaker-2.png new file mode 100644 index 0000000..de9e6cc Binary files /dev/null and b/public/speaker-2.png differ diff --git a/public/speaker-3.webp b/public/speaker-3.webp new file mode 100644 index 0000000..8ff179c Binary files /dev/null and b/public/speaker-3.webp differ diff --git a/public/speaker-4.png b/public/speaker-4.png new file mode 100644 index 0000000..455637d Binary files /dev/null and b/public/speaker-4.png differ diff --git a/src/app/globals.css b/src/app/globals.css index ca74c4b..3cff7a0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,331 +1,289 @@ @import "tailwindcss"; -@theme { - --color-cream: #FFFDF7; - --color-charcoal: #282828; - --color-yellow: #EAE151; - --color-yellow-dark: #D4CA3C; - --color-paper: #F5F2E9; - --color-ink: #1A1814; - --color-matcha: #7B8B6F; - --color-matcha-light: #E8EDE4; - --color-sakura: #F2E0DE; - --color-sakura-light: #FDF5F4; - --color-nori: #1C2E1A; - --color-rice: #FEFEF5; - --color-coral: #E8635A; - --color-indigo: #4A5A8A; - --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif; - --font-mono: var(--font-geist-mono), ui-monospace, monospace; -} - -* { box-sizing: border-box; } +/* HiBento β€” Acid Chartreuse Theme (Γ‰nergique, Fluorescent, Social) + Fond: Charcoal #2D2A32 Β· Surface: #3A3740 + Texte: Near White #FAFDF6 Β· Primary Accent: Chartreuse #DDD92A + Secondary: Soft Chartreuse #EAE151 Β· Pale: #EEEFA8 + Destructive: Rose #FF4D6D (preserved from original) + No gradients - flat design with glow effects +*/ -body { - background: var(--color-cream); - color: var(--color-ink); +@theme inline { + /* Acid Chartreuse Palette (7 colors) */ + --color-charcoal: hsl(260 9% 18%); /* #2D2A32 β€” Background */ + --color-surface: hsl(260 8% 24%); /* #3A3740 β€” Cards, Muted */ + --color-ivory: hsl(84 64% 98%); /* #FAFDF6 β€” Foreground */ + --color-chartreuse: hsl(59 73% 52%); /* #DDD92A β€” Primary Accent */ + --color-chartreuse-soft: hsl(57 79% 62%); /* #EAE151 β€” Secondary */ + --color-chartreuse-pale: hsl(61 69% 80%); /* #EEEFA8 β€” Muted Foreground */ + --color-rose: hsl(348 100% 63%); /* #FF4D6D β€” Destructive (preserved) */ + + /* Mapped to shadcn/ui tokens */ + --color-background: var(--color-charcoal); + --color-foreground: var(--color-ivory); + + --color-card: var(--color-surface); + --color-card-foreground: var(--color-ivory); + --color-popover: var(--color-surface); + --color-popover-foreground: var(--color-ivory); + + /* Primary Accent: Chartreuse (#DDD92A) */ + --color-primary: var(--color-chartreuse); + --color-primary-foreground: var(--color-charcoal); + + /* Accent (same as primary for HiBento) */ + --color-accent: var(--color-chartreuse); + --color-accent-foreground: var(--color-charcoal); + + /* Secondary: Soft Chartreuse (#EAE151) */ + --color-secondary: var(--color-chartreuse-soft); + --color-secondary-foreground: var(--color-charcoal); + + --color-muted: var(--color-surface); + --color-muted-foreground: var(--color-chartreuse-pale); + + /* Destructive: Rose (#FF4D6D β€” preserved) */ + --color-destructive: var(--color-rose); + --color-destructive-foreground: var(--color-ivory); + + --color-border: hsl(260 8% 22%); /* ~#34313A */ + --color-input: var(--color-surface); + --color-ring: var(--color-chartreuse); + + --radius: 0.75rem; /* 12px - Cartes gΓ©nΓ©reuses */ + --radius-pill: 999px; /* Pill-shaped buttons */ + --radius-lg: 1rem; /* 16px pour casser la rigiditΓ© */ + + /* HiBento custom tokens (remapped for Acid Chartreuse) */ + --color-espresso: var(--color-ivory); + --color-espresso-soft: var(--color-chartreuse-pale); + --color-terracotta: var(--color-chartreuse); + --color-terracotta-deep: hsl(59 73% 32%); + --color-cream: var(--color-charcoal); + --color-cream-soft: var(--color-surface); + --color-paper: var(--color-surface); + + + /* Shadows with Chartreuse glow */ + --shadow-soft: 0 1px 2px hsl(260 9% 10% / 0.4), 0 8px 24px -8px hsl(260 9% 10% / 0.5); + --shadow-deep: 0 30px 60px -20px hsl(var(--color-chartreuse) / 0.25); + --shadow-glow: 0 0 60px hsl(var(--color-chartreuse) / 0.45); + + /* Easing curves */ + --ease-editorial: cubic-bezier(0.22, 1, 0.36, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + + /* Animations - v4 pattern: --animate-* auto-generates animate-* utilities */ + --animate-fade-in: fade-in 0.6s ease-out forwards; + --animate-marquee: marquee 40s linear infinite; + --animate-blink: blink 1.4s ease-in-out infinite; + --animate-float-soft: float-soft 6s ease-in-out infinite; + --animate-drift: drift 9s ease-in-out infinite; + + /* Fonts - set by next/font variables */ + --font-sans: var(--font-manrope), system-ui, sans-serif; + --font-serif: var(--font-sora), system-ui, sans-serif; + --font-mono: var(--font-mono), ui-monospace, monospace; } -::-webkit-scrollbar { width: 4px; } -::-webkit-scrollbar-track { background: var(--color-paper); } -::-webkit-scrollbar-thumb { background: var(--color-charcoal); border-radius: 0; } +@utility container { + padding-left: 1rem; + padding-right: 1rem; + margin-left: auto; + margin-right: auto; + width: 100%; + @media (min-width: 640px) { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + @media (min-width: 1024px) { + padding-left: 2rem; + padding-right: 2rem; + } +} -/* ====== Bento Card ====== */ -.bento-card { - background: var(--color-rice); - border: 1px solid color-mix(in srgb, var(--color-charcoal) 10%, transparent); - padding: 1.5rem; - transition: all 0.3s; +/* Base styles - standard CSS (not @layer) */ +html { + scroll-behavior: smooth; } -.bento-card:hover { - border-color: color-mix(in srgb, var(--color-charcoal) 30%, transparent); - box-shadow: 4px 4px 0 0 rgba(234, 225, 81, 0.3); + +body { + margin: 0; + background-color: hsl(var(--color-background)); + color: hsl(var(--color-foreground)); + -webkit-font-smoothing: antialiased; + font-family: var(--font-sans); /* Manrope via --font-manrope */ + font-feature-settings: 'ss01', 'cv11'; } -/* ====== Tags ====== */ -.tag-pill { - padding: 0.25rem 0.75rem; - font-size: 0.75rem; - font-weight: 500; - border: 1px solid color-mix(in srgb, var(--color-charcoal) 20%, transparent); - background: var(--color-paper); - color: var(--color-charcoal); - letter-spacing: 0.05em; -} - -/* ====== Live Badge ====== */ -.live-badge { - background: var(--color-nori); - color: var(--color-cream); - padding: 0.125rem 0.5rem; - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.1em; - animation: livePulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; -} - -/* ====== Buttons ====== */ -.btn-primary { - background: var(--color-charcoal); - color: var(--color-cream); - padding: 0.75rem 1.5rem; - font-size: 0.875rem; - font-weight: 700; - letter-spacing: 0.05em; - border: 1px solid var(--color-charcoal); - transition: all 0.2s; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} -.btn-primary:hover { background: var(--color-yellow); color: var(--color-charcoal); } - -.btn-outline { - background: transparent; - color: var(--color-charcoal); - padding: 0.75rem 1.5rem; - font-size: 0.875rem; - font-weight: 700; - letter-spacing: 0.05em; - border: 1px solid var(--color-charcoal); - transition: all 0.2s; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} -.btn-outline:hover { background: var(--color-yellow); } - -.btn-yellow { - background: var(--color-yellow); - color: var(--color-charcoal); - padding: 0.75rem 1.5rem; - font-size: 0.875rem; - font-weight: 700; - letter-spacing: 0.05em; - border: 1px solid var(--color-yellow-dark); - transition: all 0.2s; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} -.btn-yellow:hover { background: var(--color-yellow-dark); } - -.btn-ghost { - background: transparent; - color: var(--color-charcoal); - padding: 0.75rem 1rem; - font-size: 0.875rem; - font-weight: 600; - transition: all 0.2s; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} -.btn-ghost:hover { background: color-mix(in srgb, var(--color-charcoal) 5%, transparent); } - -.btn-sm { padding: 0.5rem 1rem; font-size: 0.75rem; } - -/* ====== Inputs ====== */ -.input-field { - width: 100%; - background: var(--color-cream); - border: 1px solid color-mix(in srgb, var(--color-charcoal) 20%, transparent); - padding: 0.75rem 1rem; - font-size: 0.875rem; - color: var(--color-charcoal); - transition: all 0.2s; -} -.input-field:focus { border-color: var(--color-charcoal); outline: none; } -.input-field::placeholder { color: color-mix(in srgb, var(--color-charcoal) 40%, transparent); } - -/* ====== Chat Components ====== */ -.chat-bubble { - padding: 0.875rem 1rem; - border-radius: 0; - max-width: 85%; - position: relative; - line-height: 1.5; -} -.chat-bubble--attendee { - background: var(--color-paper); - border: 1px solid color-mix(in srgb, var(--color-charcoal) 10%, transparent); - align-self: flex-start; -} -.chat-bubble--speaker { - background: var(--color-matcha-light); - border: 1px solid var(--color-matcha); - align-self: flex-start; - border-left: 3px solid var(--color-matcha); -} -.chat-bubble--self { - background: var(--color-charcoal); - color: var(--color-cream); - align-self: flex-end; -} - -.chat-input-bar { - background: var(--color-rice); - border-top: 1px solid color-mix(in srgb, var(--color-charcoal) 10%, transparent); - padding: 1rem 1.5rem; -} - -/* ====== Toast ====== */ -.toast-container { - position: fixed; - bottom: 1.5rem; - right: 1.5rem; - z-index: 9999; - display: flex; - flex-direction: column; - gap: 0.5rem; - pointer-events: none; +::selection { + background: hsl(var(--color-chartreuse)); + color: hsl(var(--color-charcoal)); } -.toast { - background: var(--color-charcoal); - color: var(--color-cream); - padding: 0.75rem 1.25rem; - font-size: 0.8125rem; + +/* Custom utility classes - Tailwind v4 @utility syntax */ + +/* No gradients - flat design */ + +@utility text-display { + font-family: var(--font-serif); /* Sora via --font-sora */ + font-weight: 800; + line-height: 0.95; + letter-spacing: -0.045em; +} + +@utility label-mono { + font-family: var(--font-mono); + font-size: 0.7rem; + letter-spacing: 0.12em; + text-transform: uppercase; font-weight: 500; - border: 1px solid color-mix(in srgb, var(--color-cream) 15%, transparent); - pointer-events: auto; - animation: toastIn 0.3s ease-out, toastOut 0.3s ease-in 2.7s forwards; - display: flex; - align-items: center; - gap: 0.5rem; -} -.toast--success { border-left: 3px solid var(--color-yellow); } -.toast--error { border-left: 3px solid var(--color-coral); } - -/* ====== Shoji Panel (Japanese sliding door animation) ====== */ -.shoji-panel { - overflow: hidden; - max-height: 0; +} + +@utility grain { + &::before { + content: ''; + position: absolute; + inset: 0; + pointer-events: none; + opacity: 0.05; + mix-blend-mode: overlay; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); + } +} + +@utility marquee { + animation: var(--animate-marquee); + will-change: transform; +} + +@utility reveal-up { opacity: 0; - transition: max-height 0.5s cubic-bezier(0.22, 1, 0.36, 1), - opacity 0.4s ease-out, - margin 0.3s ease-out; + transform: translateY(40px); + transition: all 1s var(--ease-editorial); + will-change: opacity, transform; } -.shoji-panel--open { - max-height: 2000px; + +/* Standard CSS for compound selector (not @utility) */ +.reveal-up.in-view { opacity: 1; + transform: translateY(0); } -/* ====== Timeline ====== */ -.timeline-track { - position: relative; -} -.timeline-track::before { - content: ''; - position: absolute; - left: 0.75rem; - top: 0; - bottom: 0; - width: 1px; - background: color-mix(in srgb, var(--color-charcoal) 15%, transparent); -} -.timeline-dot { - width: 0.625rem; - height: 0.625rem; - border-radius: 50%; - position: absolute; - left: 0.4375rem; - top: 1.375rem; - z-index: 1; +@utility blink-dot { + animation: var(--animate-blink); } -/* ====== Skeleton ====== */ -.skeleton { - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--color-charcoal) 6%, transparent) 25%, - color-mix(in srgb, var(--color-charcoal) 12%, transparent) 50%, - color-mix(in srgb, var(--color-charcoal) 6%, transparent) 75% - ); - background-size: 200% 100%; - animation: shimmer 1.5s infinite; +/* Animations */ +@keyframes marquee { + from { transform: translateX(0); } + to { transform: translateX(-50%); } } -/* ====== Geometries ====== */ -.bg-grid { - background-image: - linear-gradient(color-mix(in srgb, var(--color-charcoal) 4%, transparent) 1px, transparent 1px), - linear-gradient(90deg, color-mix(in srgb, var(--color-charcoal) 4%, transparent) 1px, transparent 1px); - background-size: 48px 48px; +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.35; } } -.bg-dots { - background-image: radial-gradient(circle, color-mix(in srgb, var(--color-charcoal) 8%, transparent) 1px, transparent 1px); - background-size: 24px 24px; +@keyframes float-soft { + 0%, 100% { transform: translateY(0) rotate(0deg); } + 50% { transform: translateY(-12px) rotate(2deg); } } -.bg-yellow-dots { - background-image: radial-gradient(circle, rgba(234, 225, 81, 0.25) 1.5px, transparent 1.5px); - background-size: 32px 32px; +@keyframes draw-line { + from { stroke-dashoffset: 1000; } + to { stroke-dashoffset: 0; } } -/* ====== Animations ====== */ -@keyframes slideUp { - from { opacity: 0; transform: translateY(12px); } +@keyframes fade-in { + from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } -.animate-slide-up { animation: slideUp 0.4s ease-out; } -@keyframes slideDown { - from { opacity: 0; transform: translateY(-8px); } - to { opacity: 1; transform: translateY(0); } +/* Apply animations via utility classes */ +@utility animate-float-soft { + animation: var(--animate-float-soft); + will-change: transform; } -.animate-slide-down { animation: slideDown 0.3s ease-out; } -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } +@utility animate-fade-in { + animation: var(--animate-fade-in); + will-change: opacity, transform; } -.animate-fade-in { animation: fadeIn 0.3s ease-out; } -@keyframes livePulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.6; } +/* Performance optimization utilities */ +@utility will-change-transform { + will-change: transform; +} + +@utility will-change-opacity { + will-change: opacity; +} + +@utility content-auto { + content-visibility: auto; } -@keyframes shimmer { - 0% { background-position: 200% 0; } - 100% { background-position: -200% 0; } +/* font-display is now handled by text-display utility - removed duplicate */ + +/* Update body to use Manrope */ +body { + font-family: var(--font-sans); /* Manrope via --font-manrope */ +} + +@utility squircle { + border-radius: var(--radius); } -@keyframes toastIn { - from { opacity: 0; transform: translateX(100%) translateY(10px); } - to { opacity: 1; transform: translateX(0) translateY(0); } +@utility squircle-lg { + border-radius: var(--radius-lg); } -@keyframes toastOut { - from { opacity: 1; transform: translateX(0); } - to { opacity: 0; transform: translateX(100%); } + +@utility pill { + border-radius: var(--radius-pill); +} + +@utility lift { + transition: transform 0.5s var(--ease-spring), box-shadow 0.5s var(--ease-editorial); + &:hover { + transform: translateY(-3px); + } } -@keyframes dotPulse { - 0%, 100% { transform: scale(1); opacity: 1; } - 50% { transform: scale(1.5); opacity: 0.5; } +@utility squish { + transition: transform 0.15s var(--ease-spring); + &:active { + transform: scale(0.97); + } } -.animate-dot-pulse { animation: dotPulse 1.5s ease-in-out infinite; } -/* ====== Emoji reaction picker ====== */ -.reaction-btn { - width: 2rem; - height: 2rem; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid color-mix(in srgb, var(--color-charcoal) 15%, transparent); - background: var(--color-cream); - font-size: 1rem; - cursor: pointer; - transition: all 0.15s; +@utility drift { + animation: var(--animate-drift); + will-change: transform; } -.reaction-btn:hover { - background: var(--color-yellow); - border-color: var(--color-yellow-dark); - transform: scale(1.15); + +@utility mood-bg { + background: hsl(var(--color-background)); } -.reaction-btn--active { - background: var(--color-yellow); - border-color: var(--color-yellow-dark); + +@utility glow-chip { + background: hsl(var(--color-chartreuse)); + color: hsl(var(--color-charcoal)); + box-shadow: 0 0 0 1px hsl(var(--color-chartreuse) / 0.4), + 0 8px 30px -8px hsl(var(--color-chartreuse) / 0.6); +} + +@utility blob { + position: absolute; + border-radius: 9999px; + filter: blur(60px); + opacity: 0.55; + pointer-events: none; +} + +/* Keyframes for drift animation */ +@keyframes drift { + 0%, 100% { transform: translate(0, 0) rotate(0deg); } + 33% { transform: translate(8px, -14px) rotate(2deg); } + 66% { transform: translate(-10px, 6px) rotate(-2deg); } } diff --git a/src/app/page.tsx b/src/app/page.tsx index 2d44a6e..6ad0c11 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,32 +1,30 @@ "use client"; import Link from "next/link"; +import { ArrowRight } from "lucide-react"; export default function Home() { return ( -
- {/* Hero */} -
-
-

- WHERE EVENTS
COME ALIVE -

-

- Real-time event platform for Madagascar. Browse sessions, connect with speakers, and interact live. -

- - EXPLORE EVENTS - - - - -
+
+
+

+ WHERE EVENTS COME ALIVE +

+

+ Real-time event management and participant engagement platform for + Madagascar's tech communities. +

+ + EXPLORE EVENTS + +
-
-
-

HIBENTO Β© 2026

-
+
+ HIBENTO © 2026
); -} \ No newline at end of file +} diff --git a/src/components/ui/typing-animation.tsx b/src/components/ui/typing-animation.tsx new file mode 100644 index 0000000..73f52a4 --- /dev/null +++ b/src/components/ui/typing-animation.tsx @@ -0,0 +1,236 @@ +"use client" + +import { + useEffect, + useMemo, + useRef, + useState, + type ComponentType, + type RefAttributes, + type RefObject, +} from "react" +import { + motion, + useInView, + type DOMMotionComponents, + type HTMLMotionProps, + type MotionProps, +} from "motion/react" + +import { cn } from "@/lib/utils" + +const motionElements = { + article: motion.article, + div: motion.div, + h1: motion.h1, + h2: motion.h2, + h3: motion.h3, + h4: motion.h4, + h5: motion.h5, + h6: motion.h6, + li: motion.li, + p: motion.p, + section: motion.section, + span: motion.span, +} as const + +type MotionElementType = Extract< + keyof DOMMotionComponents, + keyof typeof motionElements +> +type TypingAnimationMotionComponent = ComponentType< + Omit, "ref"> & RefAttributes +> + +interface TypingAnimationProps extends Omit { + children?: string + words?: string[] + className?: string + duration?: number + typeSpeed?: number + deleteSpeed?: number + delay?: number + pauseDelay?: number + loop?: boolean + as?: MotionElementType + startOnView?: boolean + showCursor?: boolean + blinkCursor?: boolean + cursorStyle?: "line" | "block" | "underscore" +} + +export function TypingAnimation({ + children, + words, + className, + duration = 100, + typeSpeed, + deleteSpeed, + delay = 0, + pauseDelay = 1000, + loop = false, + as: Component = "span", + startOnView = true, + showCursor = true, + blinkCursor = true, + cursorStyle = "line", + ...props +}: TypingAnimationProps) { + const MotionComponent = motionElements[ + Component + ] as TypingAnimationMotionComponent + + const [displayedText, setDisplayedText] = useState("") + const [currentWordIndex, setCurrentWordIndex] = useState(0) + const [currentCharIndex, setCurrentCharIndex] = useState(0) + const [phase, setPhase] = useState<"typing" | "pause" | "deleting">("typing") + const elementRef = useRef(null) + const isInView = useInView(elementRef as RefObject, { + amount: 0.3, + once: true, + }) + + const wordsToAnimate = useMemo( + () => words ?? (children ? [children] : []), + [words, children] + ) + const hasMultipleWords = wordsToAnimate.length > 1 + + const typingSpeed = typeSpeed ?? duration + const deletingSpeed = deleteSpeed ?? typingSpeed / 2 + + const shouldStart = startOnView ? isInView : true + const animationSourceKey = useMemo( + () => (words ? words.join("\u0000") : (children ?? "")), + [words, children] + ) + + useEffect(() => { + setDisplayedText("") + setCurrentWordIndex(0) + setCurrentCharIndex(0) + setPhase("typing") + }, [animationSourceKey]) + + useEffect(() => { + let timeout: ReturnType | null = null + + if (shouldStart && wordsToAnimate.length > 0) { + const timeoutDelay = + delay > 0 && displayedText === "" + ? delay + : phase === "typing" + ? typingSpeed + : phase === "deleting" + ? deletingSpeed + : pauseDelay + + timeout = setTimeout(() => { + const currentWord = wordsToAnimate[currentWordIndex] || "" + const graphemes = Array.from(currentWord) + + switch (phase) { + case "typing": + if (currentCharIndex < graphemes.length) { + setDisplayedText( + graphemes.slice(0, currentCharIndex + 1).join("") + ) + setCurrentCharIndex(currentCharIndex + 1) + } else { + if (hasMultipleWords || loop) { + const isLastWord = + currentWordIndex === wordsToAnimate.length - 1 + if (!isLastWord || loop) { + setPhase("pause") + } + } + } + break + + case "pause": + setPhase("deleting") + break + + case "deleting": + if (currentCharIndex > 0) { + setDisplayedText( + graphemes.slice(0, currentCharIndex - 1).join("") + ) + setCurrentCharIndex(currentCharIndex - 1) + } else { + const nextIndex = (currentWordIndex + 1) % wordsToAnimate.length + setCurrentWordIndex(nextIndex) + setPhase("typing") + } + break + } + }, timeoutDelay) + } + + return () => { + if (timeout !== null) { + clearTimeout(timeout) + } + } + }, [ + shouldStart, + phase, + currentCharIndex, + currentWordIndex, + displayedText, + wordsToAnimate, + hasMultipleWords, + loop, + typingSpeed, + deletingSpeed, + pauseDelay, + delay, + ]) + + const currentWordGraphemes = Array.from( + wordsToAnimate[currentWordIndex] || "" + ) + const isComplete = + !loop && + currentWordIndex === wordsToAnimate.length - 1 && + currentCharIndex >= currentWordGraphemes.length && + phase !== "deleting" + + const shouldShowCursor = + showCursor && + !isComplete && + (hasMultipleWords || loop || currentCharIndex < currentWordGraphemes.length) + + const getCursorChar = () => { + switch (cursorStyle) { + case "block": + return "β–Œ" + case "underscore": + return "_" + case "line": + default: + return "|" + } + } + + return ( + + {displayedText} + {shouldShowCursor && ( + + {getCursorChar()} + + )} + + ) +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/tsconfig.json b/tsconfig.json index cf9c65d..759e6d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,7 +23,9 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, "include": [ @@ -30,5 +36,7 @@ ".next/dev/types/**/*.ts", "**/*.mts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] }