Compare commits
11 Commits
7268fdd356
...
b338468043
| Author | SHA1 | Date | |
|---|---|---|---|
| b338468043 | |||
| 2d90bcdf79 | |||
| ab3217df20 | |||
| f2f23c238d | |||
| 957af1c257 | |||
| 91e0bf315d | |||
| 7b2ccd47f4 | |||
| c8d1241580 | |||
| 40ea0bb081 | |||
| bdbe55e137 | |||
| b0b38ebb1c |
2
backend/.env
Normal file
2
backend/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
CLERK_PUBLISHABLE_KEY=pk_test_ZmxleGlibGUtdGVybWl0ZS04OC5jbGVyay5hY2NvdW50cy5kZXYk
|
||||
CLERK_SECRET_KEY=sk_test_PhrqpgKR4jHGBqeSAw2X4WwHYqJ34GDZgtzEgXgNkX
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -2,3 +2,4 @@ node_modules
|
||||
local.db
|
||||
drizzle
|
||||
bun.lockb
|
||||
dist
|
||||
|
||||
23
backend/Dockerfile
Normal file
23
backend/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM oven/bun:latest AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
|
||||
COPY bun.lock ./
|
||||
|
||||
RUN bun install
|
||||
|
||||
COPY src ./src
|
||||
|
||||
RUN ls && bun run build
|
||||
|
||||
FROM oven/bun:latest AS run
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/dist/ .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["bun", "run", "main.js"]
|
||||
@@ -3,6 +3,7 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@clerk/express": "^1.7.4",
|
||||
"@libsql/client": "^0.15.9",
|
||||
"@types/express": "^5.0.1",
|
||||
"cors": "^2.8.5",
|
||||
@@ -18,6 +19,14 @@
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@clerk/backend": ["@clerk/backend@2.4.0", "", { "dependencies": { "@clerk/shared": "^3.10.2", "@clerk/types": "^4.63.0", "cookie": "1.0.2", "snakecase-keys": "8.0.1", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-CTnd6Ut7lQDSvfEBtD+JHbGE4wIbKIp2zbeYkeGN9IP6wA6gZ++T7FQPGItLzGQmipCX3GPQ6mvEFf/ch39BwA=="],
|
||||
|
||||
"@clerk/express": ["@clerk/express@1.7.4", "", { "dependencies": { "@clerk/backend": "^2.4.0", "@clerk/shared": "^3.10.2", "@clerk/types": "^4.63.0", "tslib": "2.8.1" }, "peerDependencies": { "express": "^4.17.0 || ^5.0.0" } }, "sha512-lZDOVreDMnMfgTUVGIBc6HSy3ubqEFOS6pjMcVLZsPlwSF+n5vcf0A0tQ+xgL6dVgmSWSu0SZ/0FvzR44qf8sw=="],
|
||||
|
||||
"@clerk/shared": ["@clerk/shared@3.10.2", "", { "dependencies": { "@clerk/types": "^4.63.0", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.9.0", "swr": "^2.3.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-4bp080EPX9Z/qLSdf9V22NNATC5GFjfEOtlfAOw2Xq24IAIxve3ePd92TcAsMHYThn3Pmwcj7upnvUB24IXKQw=="],
|
||||
|
||||
"@clerk/types": ["@clerk/types@4.63.0", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-U3FTDzKx8uGve8gtaRv/QpfhEjK/dg9m9BuzIhYEowZ56jNimXDaXuijAcCZEeKwf+DDQmAPaNOinev6D2qtiQ=="],
|
||||
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||
|
||||
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||
@@ -106,6 +115,8 @@
|
||||
|
||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||
|
||||
"@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="],
|
||||
|
||||
"@types/body-parser": ["@types/body-parser@1.19.5", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg=="],
|
||||
|
||||
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||
@@ -156,14 +167,20 @@
|
||||
|
||||
"cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
||||
|
||||
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
|
||||
|
||||
"dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.31.1", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.2", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q=="],
|
||||
@@ -196,6 +213,8 @@
|
||||
|
||||
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
|
||||
|
||||
"fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
|
||||
@@ -216,6 +235,8 @@
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||
|
||||
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
@@ -234,8 +255,14 @@
|
||||
|
||||
"js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="],
|
||||
|
||||
"js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
|
||||
|
||||
"libsql": ["libsql@0.5.13", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.13", "@libsql/darwin-x64": "0.5.13", "@libsql/linux-arm-gnueabihf": "0.5.13", "@libsql/linux-arm-musleabihf": "0.5.13", "@libsql/linux-arm64-gnu": "0.5.13", "@libsql/linux-arm64-musl": "0.5.13", "@libsql/linux-x64-gnu": "0.5.13", "@libsql/linux-x64-musl": "0.5.13", "@libsql/win32-x64-msvc": "0.5.13" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg=="],
|
||||
|
||||
"lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
|
||||
|
||||
"map-obj": ["map-obj@4.3.0", "", {}, "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
@@ -250,6 +277,8 @@
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
@@ -276,6 +305,8 @@
|
||||
|
||||
"raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="],
|
||||
|
||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
@@ -298,6 +329,10 @@
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="],
|
||||
|
||||
"snakecase-keys": ["snakecase-keys@8.0.1", "", { "dependencies": { "map-obj": "^4.1.0", "snake-case": "^3.0.4", "type-fest": "^4.15.0" } }, "sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw=="],
|
||||
|
||||
"socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="],
|
||||
|
||||
"socket.io-adapter": ["socket.io-adapter@2.5.5", "", { "dependencies": { "debug": "~4.3.4", "ws": "~8.17.1" } }, "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg=="],
|
||||
@@ -308,18 +343,30 @@
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="],
|
||||
|
||||
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"swr": ["swr@2.3.4", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tsx": ["tsx@4.20.3", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
@@ -328,6 +375,8 @@
|
||||
|
||||
"ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
|
||||
|
||||
"@clerk/backend/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
|
||||
"@libsql/isomorphic-ws/ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.9",
|
||||
"@types/express": "^5.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"express": "^5.1.0",
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/main.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"tsx": "^4.20.3"
|
||||
}
|
||||
}
|
||||
"main": "src/main.ts",
|
||||
"dependencies": {
|
||||
"@clerk/express": "^1.7.4",
|
||||
"@libsql/client": "^0.15.9",
|
||||
"@types/express": "^5.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.44.2",
|
||||
"express": "^5.1.0",
|
||||
"socket.io": "^4.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/main.ts",
|
||||
"build": "bun build src/main.ts --outdir ./dist --target bun"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.31.1",
|
||||
"tsx": "^4.20.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,20 @@ import http from 'http'
|
||||
import taskRouter from './routers/task';
|
||||
import eventRouter from './routers/event'
|
||||
|
||||
import { Server} from 'socket.io'
|
||||
import { Server } from 'socket.io'
|
||||
import { clerkMiddleware, requireAuth } from '@clerk/express';
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app)
|
||||
const io = new Server (server, {
|
||||
cors:{
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
},
|
||||
})
|
||||
|
||||
app.use(clerkMiddleware())
|
||||
app.use(cors())
|
||||
app.use(requireAuth())
|
||||
app.use('/tasks', taskRouter)
|
||||
app.use('/events', eventRouter)
|
||||
app.use(express.json());
|
||||
@@ -24,16 +27,16 @@ app.get('/', (req, res) => {
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('A user connected:', socket.id);
|
||||
console.log('A user connected:', socket.id);
|
||||
|
||||
socket.on('change', () => {
|
||||
console.log('Message received');
|
||||
socket.broadcast.emit('change')
|
||||
});
|
||||
socket.on('change', () => {
|
||||
console.log('Message received');
|
||||
socket.broadcast.emit('change')
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected:', socket.id);
|
||||
});
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected:', socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(8080, () => {
|
||||
|
||||
@@ -2,64 +2,76 @@ import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { event } from '../db/schema';
|
||||
import { eq, ne, gt, gte } from 'drizzle-orm';
|
||||
import { eq, ne, gt, gte, and } from 'drizzle-orm';
|
||||
import { Router } from "express";
|
||||
import { getAuth } from '@clerk/express';
|
||||
|
||||
const db = drizzle("file:local.db");
|
||||
const userId = "Detlef";
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(cors())
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
res.status(200).send(await db.select().from(event))
|
||||
const { userId } = getAuth(req)
|
||||
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(await db.select().from(event).where(eq(event.userid, userId)))
|
||||
});
|
||||
|
||||
router.get('/:id', (req, res) => {
|
||||
router.post('/', async (req, res) => {
|
||||
console.log("loll")
|
||||
const newEvent: typeof event.$inferInsert = req.body
|
||||
const { userId } = getAuth(req)
|
||||
|
||||
const id = req.params['id'];
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
newEvent.userid = userId
|
||||
|
||||
const returnedEvent = await db.insert(event).values(newEvent).returning()
|
||||
console.log(returnedEvent)
|
||||
|
||||
res.status(201).json(returnedEvent);
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
const { userId } = getAuth(req)
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedEvent: Partial<typeof event.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
|
||||
const event = { id: id, name: 'Pary' } //TODO
|
||||
res.json(event);
|
||||
});
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
console.log("loll")
|
||||
const newEvent: typeof event.$inferInsert = req.body
|
||||
newEvent.userid = userId
|
||||
await db.update(event).set(updatedEvent).where(and(eq(event.id, id), eq(event.userid, userId)))
|
||||
|
||||
const returnedEvent = await db.insert(event).values(newEvent).returning()
|
||||
console.log(returnedEvent)
|
||||
|
||||
res.status(201).json(returnedEvent);
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedEvent: Partial<typeof event.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
await db.update(event).set(updatedEvent).where(eq(event.id, id))
|
||||
|
||||
res.status(200).json(updatedEvent);
|
||||
res.status(200).json(updatedEvent);
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const id = parseInt(req.params['id']);
|
||||
const { userId } = getAuth(req)
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
const success = await db.delete(event).where(eq(event.id, id))
|
||||
res.send("Deleted");
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
await db.delete(event).where(and(eq(event.id, id), eq(event.userid, userId)))
|
||||
res.send("Deleted");
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router
|
||||
|
||||
@@ -2,14 +2,14 @@ import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { task } from '../db/schema';
|
||||
import { eq, ne, gt, gte } from 'drizzle-orm';
|
||||
import { eq, ne, gt, gte, and } from 'drizzle-orm';
|
||||
import { Router } from "express";
|
||||
import { getAuth } from '@clerk/express';
|
||||
|
||||
const db = drizzle("file:local.db");
|
||||
const userId = "Detlef";
|
||||
|
||||
type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
[K in keyof T]: T[K];
|
||||
} & {};
|
||||
|
||||
type TaskResponse = Prettify<Omit<typeof task.$inferSelect, 'done'> & { done: boolean }>
|
||||
@@ -20,59 +20,95 @@ router.use(cors())
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const tasks: typeof task.$inferSelect[] = await db.select().from(task)
|
||||
console.log(tasks)
|
||||
res.status(200).send(tasks.map<TaskResponse>(task => {
|
||||
return { ...task, done: task.done === 1 }
|
||||
}));
|
||||
const { userId } = getAuth(req)
|
||||
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
const tasks: typeof task.$inferSelect[] = await db.select().from(task)
|
||||
.where(eq(task.userid, userId))
|
||||
console.log(tasks)
|
||||
|
||||
res.status(200).send(tasks.map<TaskResponse>(task => {
|
||||
return { ...task, done: task.done === 1 }
|
||||
}));
|
||||
});
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const id = parseInt(req.params['id']);
|
||||
const { userId } = getAuth(req)
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
const returnedTask = await db.select().from(task).where(eq(task.id, id))
|
||||
//
|
||||
console.log(returnedTask)
|
||||
res.json(returnedTask);
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
|
||||
const returnedTask = await db.select().from(task)
|
||||
.where(and(eq(task.id, id), eq(task.userid, userId)))
|
||||
|
||||
//
|
||||
console.log(returnedTask)
|
||||
res.json(returnedTask);
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
|
||||
const newTask = req.body
|
||||
newTask.userid = userId
|
||||
const newTask = req.body
|
||||
const { userId } = getAuth(req)
|
||||
newTask.userid = userId
|
||||
|
||||
console.log(newTask)
|
||||
const returnedTasks = await db.insert(task).values(newTask).returning()
|
||||
console.log(returnedTasks)
|
||||
console.log(newTask)
|
||||
const returnedTasks = await db.insert(task).values(newTask).returning()
|
||||
console.log(returnedTasks)
|
||||
|
||||
res.status(201).json(returnedTasks[0]);
|
||||
res.status(201).json(returnedTasks[0]);
|
||||
});
|
||||
|
||||
router.put('/:id', async (req, res) => {
|
||||
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedTask: Partial<typeof task.$inferSelect> = req.body
|
||||
const { userId } = getAuth(req)
|
||||
const id = parseInt(req.params['id']);
|
||||
const updatedTask: Partial<typeof task.$inferSelect> = req.body
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
await db.update(task).set(updatedTask).where(eq(task.id, id))
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json(updatedTask);
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
|
||||
await db.update(task).set(updatedTask).where(and(eq(task.id, id), eq(task.userid, userId)))
|
||||
|
||||
res.status(200).json(updatedTask);
|
||||
});
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const id = parseInt(req.params['id']);
|
||||
const { userId } = getAuth(req)
|
||||
const id = parseInt(req.params['id']);
|
||||
|
||||
const success = await db.delete(task).where(eq(task.id, id))
|
||||
res.send("Deleted");
|
||||
if (userId == null) {
|
||||
res.status(400).send({ error: 'Not Authorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
res.status(400).send({ error: 'Needs an id' });
|
||||
return;
|
||||
}
|
||||
|
||||
await db.delete(task).where(and(eq(task.id, id), eq(task.userid, userId)))
|
||||
res.send("Deleted");
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router
|
||||
|
||||
@@ -95,18 +95,23 @@ function cancel() {
|
||||
<UModal v-model:open="open" :title="modalTitle" :description="modalDescription">
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-2">
|
||||
<UInput type="text" placeholder="Name" v-model="titleField" required />
|
||||
<div class="flex flex-row gap-2">
|
||||
<UFormField label="Name">
|
||||
<UInput type="text" placeholder="Name" class="w-full" v-model="titleField" required />
|
||||
</UFormField>
|
||||
<UFormField label="Start" :ui="{ container: 'flex flex-row gap-2' }">
|
||||
<UInput class="grow" placeholder="2025-06-16" v-model="fromDateField" icon="i-lucide-calendar"
|
||||
required />
|
||||
<UInput class="grow" placeholder="15:34" v-model="fromTimeField" icon="i-lucide-clock" required />
|
||||
</div>
|
||||
<div class="flex flex-row gap-2">
|
||||
</UFormField>
|
||||
<UFormField label="End" :ui="{ container: 'flex flex-row gap-2' }">
|
||||
<UInput class="grow" placeholder="2025-06-16" v-model="toDateField" icon="i-lucide-calendar"
|
||||
required />
|
||||
<UInput class="grow" placeholder="15:34" v-model="toTimeField" icon="i-lucide-clock" required />
|
||||
</div>
|
||||
<UTextarea type="text" placeholder="Description" v-model="descriptionField" required />
|
||||
</UFormField>
|
||||
<UFormField label="Name">
|
||||
<UTextarea type="text" placeholder="Description" class="w-full" v-model="descriptionField"
|
||||
required />
|
||||
</UFormField>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
isScheduled: boolean
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="[&>*]:p-3 w-full">
|
||||
<UCard class="[&>*]:p-3 w-full" :variant="isScheduled ? 'subtle' : 'outline'">
|
||||
<slot />
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
@@ -14,14 +14,24 @@ const emits = defineEmits<{
|
||||
(e: 'edit-task', task: Task): void
|
||||
(e: 'edit-event', event: Event): void
|
||||
(e: 'delete-event', id: number): void
|
||||
(e: 'delete-task', id: number): void
|
||||
}>()
|
||||
|
||||
function deleteItem(event: Event) {
|
||||
if (event.task !== undefined) {
|
||||
emits('delete-task', event.task.id ?? -1)
|
||||
} else {
|
||||
emits('delete-event', event.id ?? -1)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="flex grow" :ui="{ body: 'w-full h-full' }">
|
||||
<Calendar @create="(event) => emits('createEvent', event)" @edit-task="(task) => emits('edit-task', task)" @edit="(event) => emits('edit-event', event)" @delete="(event) => emits('delete-event', event.id ?? -1)" v-model:events="events" v-model:date="date" ,
|
||||
v-model:dragged-task="draggedTask" v-model:tasks="tasks">
|
||||
<UCard class="md:flex grow hidden" :ui="{ body: 'w-full h-full' }">
|
||||
<Calendar @create="(event) => emits('createEvent', event)" @edit-task="(task) => emits('edit-task', task)"
|
||||
@edit="(event) => emits('edit-event', event)" @delete="deleteItem" v-model:events="events"
|
||||
v-model:date="date" , v-model:dragged-task="draggedTask" v-model:tasks="tasks">
|
||||
</Calendar>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
@@ -9,11 +9,15 @@ const colorMode = useColorMode();
|
||||
const toast = useToast()
|
||||
const auth = useAuth()
|
||||
const clerk = useClerk()
|
||||
const user = useUser()
|
||||
|
||||
const currentTheme = ref<'dark' | 'system' | 'light'>(colorMode.preference as 'dark' | 'system' | 'light');
|
||||
const showTaskCreateModal = ref(false);
|
||||
const showTaskEditModal = ref(false);
|
||||
const taskFormModalInput = ref<Partial<Task>>({});
|
||||
const showDeleteModal = ref(false);
|
||||
const deleteContext = ref<Task>();
|
||||
const editContext = ref<Task>();
|
||||
|
||||
const date = defineModel<DateTime>('date', { required: true })
|
||||
const tasks = defineModel<Task[]>('tasks', { required: true })
|
||||
@@ -23,6 +27,7 @@ const emits = defineEmits<{
|
||||
(e: 'deleteTask', id: number): void
|
||||
(e: 'editTask', task: Task): void
|
||||
(e: 'scheduleTask', task: Task): void
|
||||
(e: 'dismissSchedule'): void
|
||||
}>()
|
||||
|
||||
const isLight = computed(() => currentTheme.value === 'light');
|
||||
@@ -96,20 +101,23 @@ function addTask(task: Task) {
|
||||
console.log(tasks.value)
|
||||
emits('createTask', task)
|
||||
}
|
||||
function deleteTask(task: Task) {
|
||||
if (task.id === undefined) {
|
||||
function deleteTask() {
|
||||
if (deleteContext.value === undefined || deleteContext.value.id === undefined) {
|
||||
toast.add({
|
||||
title: "Task does not exist anymore"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tasks.value = tasks.value.filter(t => t.id !== task.id)
|
||||
tasks.value = tasks.value.filter(t => t.id !== (deleteContext.value?.id ?? -1))
|
||||
|
||||
emits('deleteTask', task.id)
|
||||
emits('deleteTask', deleteContext.value.id)
|
||||
deleteContext.value = undefined
|
||||
showDeleteModal.value = false
|
||||
}
|
||||
|
||||
function editTask(task: Task) {
|
||||
editContext.value?.updateWithOtherTask(task)
|
||||
emits('editTask', task)
|
||||
}
|
||||
|
||||
@@ -119,33 +127,51 @@ function openTaskFormModal(task: Partial<Task>) {
|
||||
}
|
||||
|
||||
function openTaskEditModal(task: Task) {
|
||||
editContext.value = task
|
||||
taskFormModalInput.value = task
|
||||
showTaskEditModal.value = true
|
||||
}
|
||||
|
||||
function openDeleteModal(task: Task) {
|
||||
deleteContext.value = task
|
||||
showDeleteModal.value = true
|
||||
}
|
||||
|
||||
function scheduleTask(task: Task) {
|
||||
emits('scheduleTask', task)
|
||||
}
|
||||
|
||||
function dismissSchedule() {
|
||||
emits('dismissSchedule')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="flex w-64 h-full" :ui="{ body: 'w-full' }">
|
||||
<UCard class="flex md:w-64 w-full h-full" :ui="{ body: 'w-full' }">
|
||||
<UiTaskFormModal v-model:open="showTaskCreateModal" :input="taskFormModalInput" action="create"
|
||||
@submnitted="addTask" />
|
||||
<UiTaskFormModal v-model:open="showTaskEditModal" :input="taskFormModalInput" action="edit"
|
||||
@submnitted="editTask" />
|
||||
|
||||
<div class="flex flex-col h-full w-full gap-5">
|
||||
<header class="flex flex-col gap-2">
|
||||
<UModal v-model:open="showDeleteModal" title="Delete Task"
|
||||
description="Are you sure you want to Delete this Task">
|
||||
<template #footer>
|
||||
<UButton color="primary" @click="() => deleteTask()">Delete</UButton>
|
||||
<UButton @click="showDeleteModal = false">Cancel</UButton>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<div class="flex flex-col h-full w-full gap-5" @dragenter="dismissSchedule">
|
||||
<header class="flex-col gap-2 md:flex hidden">
|
||||
<Title1>Calendar</Title1>
|
||||
<UCalendar v-model="selectedDate" />
|
||||
</header>
|
||||
<div class="flex flex-col grow justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col grow justify-between overflow-x-hidden">
|
||||
<div class="flex flex-col gap-2 h-full overflow-x-hidden">
|
||||
<Title1>Tasks</Title1>
|
||||
<div class="flex gap-2 flex-col">
|
||||
<ListItem v-for="task in todoTasks">
|
||||
<div class="flex gap-2 grow flex-col overflow-auto px-1 py-2">
|
||||
<ListItem v-for="task in todoTasks" :is-scheduled="task.scheduled_at !== undefined">
|
||||
<div class="flex w-full gap-4 items-center" @dragstart="scheduleTask(task)"
|
||||
draggable="true">
|
||||
<span
|
||||
@@ -156,12 +182,12 @@ function scheduleTask(task: Task) {
|
||||
<UButton size="xs" color="neutral" class="flex justify-center"
|
||||
icon="mingcute:pencil-line" @click="() => openTaskEditModal(task)" />
|
||||
<UButton size="xs" color="primary" class="flex justify-center"
|
||||
icon="octicon:trashcan-16" @click="() => deleteTask(task)" />
|
||||
icon="octicon:trashcan-16" @click="() => openDeleteModal(task)" />
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
<USeparator label="Done" v-if="todoTasks.length !== 0" />
|
||||
<ListItem v-for="task in doneTasks">
|
||||
<ListItem v-for="task in doneTasks" :is-scheduled="task.scheduled_at !== undefined">
|
||||
<div class="flex w-full gap-4 items-center" @dragstart="scheduleTask(task)"
|
||||
draggable="true">
|
||||
<span
|
||||
@@ -171,8 +197,8 @@ function scheduleTask(task: Task) {
|
||||
<div class="flex gap-1">
|
||||
<UButton size="xs" color="neutral" class="flex justify-center"
|
||||
icon="mingcute:pencil-line" @click="() => openTaskEditModal(task)" />
|
||||
<UButton size="xs" color="primary" class="flex justify-center"
|
||||
@click="() => deleteTask(task)" icon="octicon:trashcan-16" />
|
||||
<UButton size="xs" color="primary" class="flex justify-center shadow-xl"
|
||||
@click="() => openDeleteModal(task)" icon="octicon:trashcan-16" />
|
||||
</div>
|
||||
</div>
|
||||
</ListItem>
|
||||
@@ -188,15 +214,13 @@ function scheduleTask(task: Task) {
|
||||
<UDropdownMenu :items="dropDownItems" size="xl" :ui="{
|
||||
content: 'w-60'
|
||||
}">
|
||||
<UButton variant="ghost" class="flex gap-1 items-center w-full text-text">
|
||||
<UAvatar
|
||||
src="https://avatars.githubusercontent.com/u/33062936?s=400&u=9ee792d29ebcacccdbfb5af0539aab313d6d7185&v=4" />
|
||||
Quirin Ecker
|
||||
<UButton variant="ghost" class="flex gap-4 items-center w-full text-text">
|
||||
<UAvatar :src="user.user.value?.imageUrl" />
|
||||
{{ user.user.value?.username }}
|
||||
</UButton>
|
||||
</UDropdownMenu>
|
||||
</footer>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { UFormField } from '#components';
|
||||
import { UFormField, UInputMenu } from '#components';
|
||||
import { DateTime } from 'luxon';
|
||||
import * as z from 'zod';
|
||||
|
||||
@@ -33,6 +33,20 @@ const modalDescription = computed(() => {
|
||||
return props.action === 'create' ? 'Create task with description, due date and name' : 'Edit description, due date and name'
|
||||
})
|
||||
|
||||
const timeSuggestions = ref<string[]>((() => {
|
||||
const times = [];
|
||||
|
||||
for (let hour = 0; hour < 24; hour++) {
|
||||
for (let minute = 0; minute < 60; minute += 10) {
|
||||
const hh = String(hour).padStart(2, '0');
|
||||
const mm = String(minute).padStart(2, '0');
|
||||
times.push(`${hh}:${mm}`);
|
||||
}
|
||||
}
|
||||
|
||||
return times;
|
||||
})())
|
||||
|
||||
watchEffect(() => {
|
||||
titleField.value = props.input.title ?? ''
|
||||
descriptionField.value = props.input.description ?? ''
|
||||
@@ -89,6 +103,11 @@ function cancel() {
|
||||
open.value = false
|
||||
}
|
||||
|
||||
const formatOptions: Intl.NumberFormatOptions = {
|
||||
signDisplay: 'negative',
|
||||
minimumFractionDigits: 1
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -96,28 +115,30 @@ function cancel() {
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-2">
|
||||
<UFormField label="Title">
|
||||
<UInput type="text" class="w-full" placeholder="Name" v-model="titleField" required />
|
||||
<UFormField label="Title" class="flex-1/2">
|
||||
<UInput type="text" class="w-full" placeholder="Name" v-model="titleField" required />
|
||||
</UFormField>
|
||||
<UFormField label="Estimated time in hours">
|
||||
<UInput type="number" class="grow" placeholder="estimated time in hours" v-model="estimatedTimeField"
|
||||
icon="mdi:stopwatch-outline" required />
|
||||
<UFormField label="Estimated time in hours" class="flex-1/2">
|
||||
<UInputNumber class="grow" placeholder="estimated time in hours"
|
||||
v-model="estimatedTimeField" icon="mdi:stopwatch-outline" :step-snapping="false" :format-options="formatOptions" :min="0" required />
|
||||
</UFormField>
|
||||
</div>
|
||||
<UFormField label="Deadline" :ui="{ container: 'flex flex-row gap-2'}">
|
||||
<UInput type="date" class="grow" placeholder="due data e.g 2025-06-16" v-model="dueDateField"
|
||||
<UFormField label="Deadline" :ui="{ container: 'flex flex-row gap-2' }">
|
||||
<UInput type="date" class="flex-1/2" placeholder="due data e.g 2025-06-16" v-model="dueDateField"
|
||||
icon="i-lucide-calendar" required />
|
||||
<UInput class="grow" placeholder="due time e.g 15:34" v-model="dueTimeField" icon="i-lucide-clock"
|
||||
required />
|
||||
<UInput class="flex-1/2" placeholder="due time e.g 15:34" v-model="dueTimeField"
|
||||
icon="i-lucide-clock" :create-item="true" required />
|
||||
</UFormField>
|
||||
<UFormField label="Scheduled at" :ui="{ container: 'flex flex-row gap-2'}" v-if="props.input.scheduled_at">
|
||||
<UInput type="date" class="grow" placeholder="schedule data e.g 2025-06-16" v-model="scheduledAtDateField"
|
||||
icon="i-lucide-calendar" required />
|
||||
<UInput class="grow" placeholder="schedule time e.g 15:34" v-model="scheduledAtTimeField" icon="i-lucide-clock"
|
||||
required />
|
||||
<UFormField label="Scheduled at" :ui="{ container: 'flex flex-row gap-2' }"
|
||||
v-if="props.input.scheduled_at">
|
||||
<UInput type="date" class="flex-1/2" placeholder="schedule data e.g 2025-06-16"
|
||||
v-model="scheduledAtDateField" icon="i-lucide-calendar" required />
|
||||
<UInput class="flex-1/2" placeholder="schedule time e.g 15:34" v-model="scheduledAtTimeField"
|
||||
icon="i-lucide-clock" :create-item="true" required/>
|
||||
</UFormField>
|
||||
<UFormField label="Description">
|
||||
<UTextarea type="text" class="w-full" placeholder="Description" v-model="descriptionField" required />
|
||||
<UTextarea type="text" class="w-full" placeholder="Description" v-model="descriptionField"
|
||||
required />
|
||||
</UFormField>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -12,12 +12,16 @@ const date = defineModel<DateTime>('date', { required: true })
|
||||
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask', { required: true })
|
||||
const draggedEvent = ref<DraggedEvent | undefined>()
|
||||
const createInput = ref<Partial<SimpleEvent>>({})
|
||||
const createModalOpened = ref(false)
|
||||
const editInput = ref<Partial<SimpleEvent>>({})
|
||||
const editContext = ref<{ event: Event }>()
|
||||
const createModalOpened = ref(false)
|
||||
const editModalOpened = ref(false)
|
||||
const deleteModalOpened = ref(false)
|
||||
const editTaskModalOpened = ref(false)
|
||||
const editTaskContext = ref<Task>()
|
||||
const taskFormModalInput = ref<Partial<Task>>({})
|
||||
const deleteContext = ref<{ event: Event }>()
|
||||
const taskDraggingActive = ref(true)
|
||||
|
||||
type Day = {
|
||||
date: DateTime
|
||||
@@ -86,6 +90,22 @@ const days = computed<Day[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const deleteTitle = computed(() => {
|
||||
if (deleteContext.value === undefined || deleteContext.value.event.task === undefined) {
|
||||
return 'Delete Event'
|
||||
} else {
|
||||
return 'Delete Task'
|
||||
}
|
||||
})
|
||||
|
||||
const deleteDescription = computed(() => {
|
||||
if (deleteContext.value === undefined || deleteContext.value.event.task === undefined) {
|
||||
return 'Are you sure you want to delete this event?'
|
||||
} else {
|
||||
return 'Are you sure you want to delete this task?'
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'create', event: Event): void
|
||||
(e: 'edit', event: Event): void
|
||||
@@ -123,9 +143,15 @@ function create(simple: SimpleEvent) {
|
||||
}
|
||||
|
||||
function openEditModal(event: Event) {
|
||||
editInput.value = event.toSimple()
|
||||
editContext.value = { event: event }
|
||||
editModalOpened.value = true
|
||||
if (event.task !== undefined) {
|
||||
taskFormModalInput.value = event.task
|
||||
editTaskContext.value = event.task
|
||||
editTaskModalOpened.value = true
|
||||
} else {
|
||||
editInput.value = event.toSimple()
|
||||
editContext.value = { event: event }
|
||||
editModalOpened.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function edit(simple: SimpleEvent) {
|
||||
@@ -134,11 +160,26 @@ function edit(simple: SimpleEvent) {
|
||||
emits('edit', editContext.value.event)
|
||||
}
|
||||
|
||||
function editTask(task: Task) {
|
||||
editTaskModalOpened.value = false
|
||||
editTaskContext.value?.updateWithOtherTask(task)
|
||||
emits('edit-task', task)
|
||||
}
|
||||
|
||||
function openDeleteModal(event: Event) {
|
||||
deleteContext.value = { event: event }
|
||||
deleteModalOpened.value = true
|
||||
}
|
||||
|
||||
function deleteItem() {
|
||||
if (deleteContext.value === undefined) return
|
||||
if (deleteContext.value.event.task !== undefined) {
|
||||
deleteTask()
|
||||
} else {
|
||||
deleteEvent()
|
||||
}
|
||||
}
|
||||
|
||||
function deleteEvent() {
|
||||
if (deleteContext.value === undefined) return
|
||||
emits('delete', deleteContext.value?.event)
|
||||
@@ -158,25 +199,49 @@ function deleteEvent() {
|
||||
deleteModalOpened.value = false
|
||||
}
|
||||
|
||||
function deleteTask() {
|
||||
if (deleteContext.value === undefined || deleteContext.value.event.task === undefined) return
|
||||
emits('delete', deleteContext.value.event)
|
||||
tasks.value = tasks.value.filter(t => t.id !== (deleteContext.value?.event.task?.id ?? -1))
|
||||
deleteContext.value = undefined
|
||||
deleteModalOpened.value = false
|
||||
}
|
||||
|
||||
function moveEvent(event: Event) {
|
||||
if (event.task !== undefined) {
|
||||
emits('edit-task', event.task)
|
||||
} else emits('edit', event)
|
||||
}
|
||||
|
||||
function dragEnter(_: DragEvent) {
|
||||
if (draggedTask.value !== undefined) {
|
||||
draggedTask.value.active = true
|
||||
}
|
||||
}
|
||||
|
||||
function rawEdit(event: Event) {
|
||||
if (event.task === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
emits('edit-task', event.task)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full flex flex-col">
|
||||
<div class="w-full h-full flex flex-col" @dragenter="dragEnter">
|
||||
<EventFormModal action="create" @submnitted="event => create(event)" :input="createInput"
|
||||
v-model:open="createModalOpened" />
|
||||
<EventFormModal action="edit" @submnitted="event => edit(event)" :input="editInput"
|
||||
v-model:open="editModalOpened" />
|
||||
|
||||
<UModal v-model:open="deleteModalOpened" title="Delete Event"
|
||||
description="Are you sure you want to delete this event?">
|
||||
<UiTaskFormModal v-model:open="editTaskModalOpened" :input="taskFormModalInput" action="edit"
|
||||
@submnitted="editTask" />
|
||||
|
||||
<UModal v-model:open="deleteModalOpened" :title="deleteTitle" :description="deleteDescription">
|
||||
<template #footer>
|
||||
<UButton variant="solid" @click="deleteEvent">
|
||||
<UButton variant="solid" @click="deleteItem">
|
||||
Delete
|
||||
</UButton>
|
||||
<UButton variant="solid" @click="deleteModalOpened = false">
|
||||
@@ -189,8 +254,10 @@ function moveEvent(event: Event) {
|
||||
<CalendarHeader :seperators="seperators" />
|
||||
|
||||
<CalendarCollumn v-for="day in days" :seperators="seperators" :day="day.date" :events="day.events"
|
||||
:date="date" v-model:draggedEvent="draggedEvent" @quick-create="openCreateModal" @edit="openEditModal"
|
||||
@delete="openDeleteModal" @moved="moveEvent" @edit-task="(task) => emits('edit-task', task)" v-model:dragged-task="draggedTask" />
|
||||
:task-dragging-active="taskDraggingActive" :date="date" v-model:draggedEvent="draggedEvent"
|
||||
@quick-create="openCreateModal" @edit="openEditModal" @delete="openDeleteModal" @moved="moveEvent"
|
||||
@edit-task="(task) => emits('edit-task', task)" @raw-edit="(event) => rawEdit(event)"
|
||||
v-model:dragged-task="draggedTask" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -10,12 +10,14 @@ const props = defineProps<{
|
||||
seperators: Seperator[],
|
||||
day: DateTime
|
||||
events: CollissionWrapper[][]
|
||||
date: DateTime
|
||||
date: DateTime,
|
||||
taskDraggingActive: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'quick-create', day: DateTime, event: EventDimensions): void,
|
||||
(e: 'edit', event: Event): void
|
||||
(e: 'raw-edit', event: Event): void
|
||||
(e: 'moved', event: Event): void
|
||||
(e: 'delete', event: Event): void
|
||||
(e: 'edit-task', task: Task): void
|
||||
@@ -165,12 +167,24 @@ function updateTaskWithDraggedTask() {
|
||||
draggedTask.value.target.scheduled_at = draggedTask.value.dragInfo.date.startOf('day').plus({
|
||||
minutes: draggedTask.value.dragInfo.top / (column.value?.offsetHeight ?? 1) * 24 * 60
|
||||
})
|
||||
|
||||
|
||||
emit('edit-task', draggedTask.value.target)
|
||||
|
||||
draggedTask.value = undefined
|
||||
}
|
||||
|
||||
const moveColor = computed(() => {
|
||||
if (draggedTask.value !== undefined) {
|
||||
return 'secondary'
|
||||
}
|
||||
|
||||
if (draggedEvent.value !== undefined && draggedEvent.value.target.task !== undefined) {
|
||||
return 'secondary'
|
||||
}
|
||||
|
||||
return 'primary'
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -187,23 +201,23 @@ function updateTaskWithDraggedTask() {
|
||||
<CalendarSeperator v-for="sep in seperators" :seperator="sep">
|
||||
<hr class="w-full border-muted">
|
||||
</CalendarSeperator>
|
||||
<div class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
|
||||
:style="{ height: `${height}px`, top: `${top}px` }"></div>
|
||||
<UBadge class="absolute w-11/12 p-0" variant="subtle" :style="{ height: `${height}px`, top: `${top}px` }">
|
||||
</UBadge>
|
||||
|
||||
<div v-for="[index, column] in events.entries()" class="flex flex-row w-11/12 h-full absolute top-0">
|
||||
<CalendarEvent v-for="event in column" :event="event" :columnIndex="index" @move="eventMove"
|
||||
@edit="event => emit(`edit`, event)" @delete="event => emit(`delete`, event)" />
|
||||
@edit="event => emit(`edit`, event)" @delete="event => emit(`delete`, event)" @raw-edit="event => emit(`raw-edit`, event)" />
|
||||
</div>
|
||||
|
||||
<div v-if="draggedEvent !== undefined && draggedEvent.date.equals(props.day)"
|
||||
class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
|
||||
:style="{ height: `${draggedEvent.height}px`, top: `${draggedEvent.top}px` }"></div>
|
||||
<div v-if="draggedTask !== undefined && draggedTask.dragInfo !== undefined && draggedTask.dragInfo.date.equals(props.day)"
|
||||
class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
|
||||
:style="{ height: `${draggedTask.dragInfo.height}px`, top: `${draggedTask.dragInfo.top}px` }"></div>
|
||||
<UBadge v-if="draggedEvent !== undefined && draggedEvent.date.equals(props.day)" class="absolute w-11/12"
|
||||
variant="subtle" :color="moveColor"
|
||||
:style="{ height: `${draggedEvent.height}px`, top: `${draggedEvent.top}px` }"></UBadge>
|
||||
<UBadge
|
||||
v-if="draggedTask !== undefined && draggedTask.dragInfo !== undefined && taskDraggingActive && draggedTask.dragInfo.date.equals(props.day) && draggedTask.active"
|
||||
class="absolute w-11/12" variant="subtle" :color="moveColor"
|
||||
:style="{ height: `${draggedTask.dragInfo.height}px`, top: `${draggedTask.dragInfo.top}px` }"></UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -10,6 +10,7 @@ const props = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
(e: 'move', mouseEvent: MouseEvent, event: Event): void,
|
||||
(e: 'edit', event: Event): void
|
||||
(e: 'raw-edit', event: Event): void
|
||||
(e: 'delete', event: Event): void
|
||||
}>()
|
||||
|
||||
@@ -35,28 +36,50 @@ const top = computed(() => {
|
||||
return Math.min(dimensions.value.from, dimensions.value.to)
|
||||
})
|
||||
|
||||
const color = computed(() => {
|
||||
if (props.event.event.task !== undefined) {
|
||||
return 'secondary'
|
||||
} else {
|
||||
return 'primary'
|
||||
}
|
||||
})
|
||||
|
||||
function dragStart(e: DragEvent) {
|
||||
console.log("start drag")
|
||||
emit('move', e, props.event.event)
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function removeSchedule(event: Event) {
|
||||
if (event.task === undefined) return
|
||||
event.task.scheduled_at = undefined
|
||||
emit('raw-edit', event)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover :content="{ side: 'right' }" arrow>
|
||||
<div class="absolute rounded-lg h-0 top-20 bg-black opacity-45 p-2 flex flex-col z-10" @mousedown.stop
|
||||
<UBadge class="absolute z-10 items-start flex flex-col" variant="subtle" :color="color" @mousedown.stop
|
||||
@mouseover.stop @mouseup.stop draggable="true" @dragstart="dragStart"
|
||||
:style="{ top: `${top}%`, height: `${height}%`, left: `${left}%`, width: `${widht}%` }">
|
||||
<div>{{ event.event.from.toFormat('HH:mm') }} - {{ event.event.to.toFormat('HH:mm') }}</div>
|
||||
<div class="flex items-center gap-1 overflow-hidden">
|
||||
{{ event.event.from.toFormat('HH:mm') }} - {{ event.event.to.toFormat('HH:mm') }}
|
||||
<UIcon name="material-symbols:task-alt" v-if="event.event.task?.done" />
|
||||
|
||||
</div>
|
||||
<div>{{ event.event.title }}</div>
|
||||
</div>
|
||||
</UBadge>
|
||||
<template #content>
|
||||
<UCard class="w-xl">
|
||||
<template #header>
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<h1>{{ event.event.title }}</h1>
|
||||
<nav class="flex flex-row gap-2">
|
||||
<UTooltip text="remove schedule" v-if="event.event.task !== undefined">
|
||||
<UButton icon="material-symbols:cancel-outline"
|
||||
@click="() => removeSchedule(event.event)" />
|
||||
</UTooltip>
|
||||
<UButton icon="i-lucide-pencil" @click="emit('edit', event.event)"></UButton>
|
||||
<UButton icon="i-lucide-trash" @click="emit('delete', event.event)"></UButton>
|
||||
</nav>
|
||||
|
||||
@@ -5,21 +5,33 @@ import MainContent from '~/components/ui/MainContent.vue';
|
||||
import Sidebar from '~/components/ui/Sidebar.vue';
|
||||
import { Event, type SerializableEvent } from '~/utils/event';
|
||||
|
||||
const {$socket} = useNuxtApp()
|
||||
const { $socket } = useNuxtApp()
|
||||
const auth = useAuth()
|
||||
|
||||
const date = ref<DateTime>(DateTime.now())
|
||||
const events = ref<Event[]>([])
|
||||
const tasks = ref<Task[]>([])
|
||||
const draggedTask = ref<DraggedTask | undefined>(undefined)
|
||||
|
||||
async function fetchData<T>(path: string) {
|
||||
const requestHeaders = useRequestHeaders(["cookie"]);
|
||||
if (import.meta.client) {
|
||||
return axios.get<T[]>(path, await getAuthHeader()).then(res => res.data)
|
||||
} else {
|
||||
return axios.get<T[]>(path, {
|
||||
headers: requestHeaders,
|
||||
}).then(res => res.data);
|
||||
}
|
||||
}
|
||||
|
||||
const { data: eventsResponse, refresh: refreshEvent } = await useAsyncData<SerializableEvent[]>(
|
||||
'events',
|
||||
() => axios.get<SerializableEvent[]>('/events').then(res => res.data)
|
||||
() => fetchData('/events')
|
||||
);
|
||||
|
||||
const { data: tasksResponse, refresh: refreshTask } = await useAsyncData<SerializableTask[]>(
|
||||
'tasks',
|
||||
() => axios.get<SerializableTask[]>('/tasks').then(res => res.data)
|
||||
() => fetchData('/tasks')
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
@@ -33,15 +45,23 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
|
||||
async function getAuthHeader() {
|
||||
return {
|
||||
'headers': {
|
||||
'Authorization': `Bearer ${await auth.getToken.value()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function postEvent(event: Event) {
|
||||
console.log('posting Event')
|
||||
await axios.post('/events', event.toSerializable())
|
||||
await axios.post('/events', event.toSerializable(), await getAuthHeader())
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function postTask(task: Task) {
|
||||
console.log('posting Task')
|
||||
const createdTask = await axios.post<SerializableTask>('/tasks', task)
|
||||
const createdTask = await axios.post<SerializableTask>('/tasks', task, await getAuthHeader())
|
||||
console.log(createdTask)
|
||||
task.id = createdTask.data.id
|
||||
$socket.emit('change')
|
||||
@@ -49,34 +69,46 @@ async function postTask(task: Task) {
|
||||
|
||||
async function deleteEvent(id: number) {
|
||||
console.log('deleting Event')
|
||||
await axios.delete(`/events/${id}`)
|
||||
await axios.delete(`/events/${id}`, await getAuthHeader())
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function deleteTask(id: number) {
|
||||
console.log('deleting Task')
|
||||
await axios.delete(`/tasks/${id}`)
|
||||
await axios.delete(`/tasks/${id}`, await getAuthHeader())
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function putEvent(event: Event) {
|
||||
console.log('editing event')
|
||||
await axios.put(`/events/${event.id}`, event)
|
||||
await axios.put(`/events/${event.id}`, event, await getAuthHeader())
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
async function putTask(task: Task) {
|
||||
console.log('editing task')
|
||||
await axios.put(`/tasks/${task.id}`, task)
|
||||
console.log(task.scheduled_at)
|
||||
await axios.put(
|
||||
`/tasks/${task.id}`,
|
||||
{...task, scheduled_at: task.scheduled_at ?? null},
|
||||
await getAuthHeader()
|
||||
)
|
||||
await refreshTask()
|
||||
$socket.emit('change')
|
||||
}
|
||||
|
||||
function scheduleTask(task: Task) {
|
||||
draggedTask.value = { target: task, dragInfo: undefined }
|
||||
draggedTask.value = { target: task, dragInfo: undefined, active: false }
|
||||
}
|
||||
|
||||
function dismissSchedule() {
|
||||
console.log(draggedTask.value?.active)
|
||||
if (draggedTask.value !== undefined && draggedTask.value.active) {
|
||||
draggedTask.value.active = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -84,9 +116,11 @@ function scheduleTask(task: Task) {
|
||||
<template>
|
||||
<div class="h-screen w-screen p-4 flex flex-row gap-5">
|
||||
<Sidebar v-if="tasks !== null" v-model:tasks="tasks" v-model:date="date" @create-task="postTask"
|
||||
@delete-task="deleteTask" @schedule-task="scheduleTask" @edit-task="putTask"/>
|
||||
@delete-task="deleteTask" @schedule-task="scheduleTask" @edit-task="putTask"
|
||||
@dismiss-schedule="dismissSchedule" />
|
||||
<MainContent v-if="events !== null" v-model:events="events" v-model:date="date"
|
||||
v-model:dragged-task="draggedTask" v-model:tasks="tasks" @create-event="postEvent" @edit-task="putTask" @edit-event="putEvent" @delete-event="deleteEvent"/>
|
||||
v-model:dragged-task="draggedTask" v-model:tasks="tasks" @create-event="postEvent" @edit-task="putTask"
|
||||
@edit-event="putEvent" @delete-event="deleteEvent" @delete-task="deleteTask" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -56,6 +56,15 @@ export class Task {
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
updateWithOtherTask(otherTask: Task) {
|
||||
this.title = otherTask.title
|
||||
this.description = otherTask.description
|
||||
this.done = otherTask.done
|
||||
this.estimated_time = otherTask.estimated_time
|
||||
this.due_date = otherTask.due_date
|
||||
this.scheduled_at = otherTask.scheduled_at
|
||||
}
|
||||
}
|
||||
|
||||
export type SimpleTask = {
|
||||
@@ -83,6 +92,7 @@ export type SerializableTask = {
|
||||
|
||||
export type DraggedTask = {
|
||||
target: Task,
|
||||
active: boolean,
|
||||
dragInfo: {
|
||||
top: number,
|
||||
date: DateTime
|
||||
@@ -91,7 +101,7 @@ export type DraggedTask = {
|
||||
}
|
||||
|
||||
function stringToDate(date: string | undefined) {
|
||||
if (date === undefined) {
|
||||
if (date === undefined || date === null) {
|
||||
return undefined
|
||||
}
|
||||
return DateTime.fromISO(date)
|
||||
|
||||
Reference in New Issue
Block a user