From 459f244b9592317cce2e6e42df3e17d5f6d4ec6b Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Sun, 28 Jan 2024 14:51:00 +0000 Subject: [PATCH] Socket stuff with models re-added --- .eslintrc.cjs | 1 + .idea/.gitignore | 8 - .idea/codeStyles/Project.xml | 61 ---- .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/dataSources.xml | 12 - .idea/inspectionProfiles/Project_Default.xml | 6 - .idea/modules.xml | 8 - .idea/parentgrine-server.iml | 12 - .idea/vcs.xml | 6 - .vscode/launch.json | 14 - .vscode/settings.json | 13 +- README.md | 70 ++--- bun.lockb | Bin 225525 -> 270228 bytes components.json | 7 +- drizzle.config.ts | 18 +- drizzle/meta/_journal.json | 13 - kirimase.config.json | 14 +- package.json | 78 ++--- public/android-chrome-192x192.png | Bin 8186 -> 0 bytes public/android-chrome-512x512.png | Bin 28528 -> 0 bytes public/apple-touch-icon.png | Bin 7370 -> 0 bytes public/favicon copy.ico | Bin 15406 -> 0 bytes public/favicon-16x16.png | Bin 332 -> 0 bytes public/favicon-32x32.png | Bin 737 -> 0 bytes public/img/default-avatar.png | Bin 15772 -> 0 bytes public/site.webmanifest | 19 -- scripts/reset.sh | 19 -- src/app/(app)/children/page.tsx | 19 ++ src/app/(app)/dashboard/page.tsx | 10 + src/app/(app)/layout.tsx | 19 ++ src/app/(debug)/debug/page.tsx | 18 -- src/app/(parent)/children/page.tsx | 10 - src/app/(parent)/dashboard/page.tsx | 23 -- src/app/(parent)/layout.tsx | 19 -- src/app/account/AccountCard.tsx | 45 +++ src/app/account/UpdateEmailCard.tsx | 56 ++++ src/app/account/UpdateNameCard.tsx | 56 ++++ src/app/account/UserSettings.tsx | 17 ++ src/app/account/page.tsx | 16 + src/app/api/account/route.ts | 15 + src/app/api/auth/[...nextauth]/route.ts | 13 +- src/app/api/child/register/route.ts | 4 - src/app/api/child/route.ts | 25 -- src/app/api/device/connect/route.ts | 63 ---- src/app/api/responses/index.ts | 10 - src/app/devices/page.tsx | 19 ++ src/app/layout.tsx | 45 ++- src/app/loading.tsx | 25 ++ src/app/page.tsx | 21 +- src/app/settings/page.tsx | 106 +++++++ src/components/Navbar.tsx | 45 +++ src/components/Sidebar.tsx | 55 ++++ src/components/SidebarItems.tsx | 90 ++++++ src/components/ThemeProvider.tsx | 9 + src/components/children/child-form.tsx | 168 +++++++++++ src/components/children/child-list.tsx | 75 +++++ src/components/children/child-modal.tsx | 66 ++++ src/components/children/children-filter.tsx | 15 +- src/components/debug/HeadersPrinter.tsx | 18 -- src/components/debug/SecureDebugDetails.tsx | 11 - src/components/devices/DeviceForm.tsx | 175 +++++++++++ src/components/devices/DeviceList.tsx | 52 ++++ src/components/devices/DeviceModal.tsx | 65 ++++ src/components/forms/add-child-form.tsx | 122 -------- src/components/forms/user-auth-form.tsx | 9 +- src/components/header/auth-header.tsx | 8 +- src/components/main-nav.tsx | 24 +- src/components/maps/main-map.tsx | 70 +++-- src/components/maps/map-viewtype-selector.tsx | 25 +- src/components/pages/dashboard-page.tsx | 23 ++ src/components/pages/home-page.tsx | 173 ++++++----- src/components/providers/theme-provider.tsx | 9 - src/components/ui/ThemeToggle.tsx | 40 +++ src/components/ui/carousel.tsx | 11 +- src/components/ui/pagination.tsx | 27 +- src/config/nav.ts | 15 + src/env.js | 36 +-- src/env.mjs | 36 +++ src/lib/api/children/mutations.ts | 58 ++++ src/lib/api/children/queries.ts | 31 ++ src/lib/api/devices/mutations.ts | 58 ++++ src/lib/api/devices/queries.ts | 19 ++ src/lib/auth/Provider.tsx | 11 + src/lib/auth/utils.ts | 35 ++- src/lib/db/migrations/meta/_journal.json | 1 + src/lib/hooks/useValidatedForm.tsx | 38 +++ src/lib/models/child.ts | 9 - src/lib/models/device.ts | 7 - src/lib/models/location-update.ts | 2 +- src/lib/models/ping.ts | 6 - src/lib/services/auth/api.ts | 6 - src/lib/services/auth/provider.tsx | 12 - src/lib/types/nav.ts | 6 - src/lib/utils.ts | 14 +- src/lib/validations/auth.ts | 5 - src/lib/validations/child.ts | 5 - src/lib/validations/connect-device.ts | 5 - src/server/api/root.ts | 8 +- src/server/api/routers/child.ts | 31 -- src/server/api/routers/children.ts | 42 +++ src/server/api/routers/devices.ts | 32 ++ src/server/api/routers/post.ts | 32 ++ src/server/api/trpc.ts | 10 +- src/server/auth.ts | 70 ----- src/server/db/index.ts | 29 +- src/server/db/migrate.ts | 36 +++ .../db/migrations/0000_slim_katie_power.sql | 57 ++-- .../migrations/0001_sticky_frightful_four.sql | 19 ++ .../db/migrations/meta/0000_snapshot.json | 285 ++++++++++++++++++ .../db/migrations/meta/0001_snapshot.json | 199 +++++------- src/server/db/migrations/meta/_journal.json | 20 ++ src/server/db/schema.ts | 135 --------- src/server/db/schema/_root.ts | 5 + src/server/db/schema/auth.ts | 58 ++++ src/server/db/schema/children.ts | 48 +++ src/server/db/schema/devices.ts | 49 +++ src/server/db/scripts/auth.ts | 23 -- src/server/db/scripts/seed copy.ts | 74 ----- src/server/db/scripts/seed.ts | 63 ---- src/styles/globals.css | 156 +++++----- src/trpc/react.tsx | 13 +- src/trpc/server.ts | 10 +- tailwind.config.ts | 21 +- tsconfig.json | 33 +- 124 files changed, 2744 insertions(+), 1652 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/dataSources.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/parentgrine-server.iml delete mode 100644 .idea/vcs.xml delete mode 100644 .vscode/launch.json delete mode 100644 drizzle/meta/_journal.json delete mode 100644 public/android-chrome-192x192.png delete mode 100644 public/android-chrome-512x512.png delete mode 100644 public/apple-touch-icon.png delete mode 100644 public/favicon copy.ico delete mode 100644 public/favicon-16x16.png delete mode 100644 public/favicon-32x32.png delete mode 100644 public/img/default-avatar.png delete mode 100644 public/site.webmanifest delete mode 100755 scripts/reset.sh create mode 100644 src/app/(app)/children/page.tsx create mode 100644 src/app/(app)/dashboard/page.tsx create mode 100644 src/app/(app)/layout.tsx delete mode 100644 src/app/(debug)/debug/page.tsx delete mode 100644 src/app/(parent)/children/page.tsx delete mode 100644 src/app/(parent)/dashboard/page.tsx delete mode 100644 src/app/(parent)/layout.tsx create mode 100644 src/app/account/AccountCard.tsx create mode 100644 src/app/account/UpdateEmailCard.tsx create mode 100644 src/app/account/UpdateNameCard.tsx create mode 100644 src/app/account/UserSettings.tsx create mode 100644 src/app/account/page.tsx create mode 100644 src/app/api/account/route.ts delete mode 100644 src/app/api/child/register/route.ts delete mode 100644 src/app/api/child/route.ts delete mode 100644 src/app/api/device/connect/route.ts delete mode 100644 src/app/api/responses/index.ts create mode 100644 src/app/devices/page.tsx create mode 100644 src/app/loading.tsx create mode 100644 src/app/settings/page.tsx create mode 100644 src/components/Navbar.tsx create mode 100644 src/components/Sidebar.tsx create mode 100644 src/components/SidebarItems.tsx create mode 100644 src/components/ThemeProvider.tsx create mode 100644 src/components/children/child-form.tsx create mode 100644 src/components/children/child-list.tsx create mode 100644 src/components/children/child-modal.tsx delete mode 100644 src/components/debug/HeadersPrinter.tsx delete mode 100644 src/components/debug/SecureDebugDetails.tsx create mode 100644 src/components/devices/DeviceForm.tsx create mode 100644 src/components/devices/DeviceList.tsx create mode 100644 src/components/devices/DeviceModal.tsx delete mode 100644 src/components/forms/add-child-form.tsx create mode 100644 src/components/pages/dashboard-page.tsx delete mode 100644 src/components/providers/theme-provider.tsx create mode 100644 src/components/ui/ThemeToggle.tsx create mode 100644 src/config/nav.ts create mode 100644 src/env.mjs create mode 100644 src/lib/api/children/mutations.ts create mode 100644 src/lib/api/children/queries.ts create mode 100644 src/lib/api/devices/mutations.ts create mode 100644 src/lib/api/devices/queries.ts create mode 100644 src/lib/auth/Provider.tsx create mode 100644 src/lib/db/migrations/meta/_journal.json create mode 100644 src/lib/hooks/useValidatedForm.tsx delete mode 100644 src/lib/models/child.ts delete mode 100644 src/lib/models/device.ts delete mode 100644 src/lib/models/ping.ts delete mode 100644 src/lib/services/auth/api.ts delete mode 100644 src/lib/services/auth/provider.tsx delete mode 100644 src/lib/types/nav.ts delete mode 100644 src/lib/validations/auth.ts delete mode 100644 src/lib/validations/child.ts delete mode 100644 src/lib/validations/connect-device.ts delete mode 100644 src/server/api/routers/child.ts create mode 100644 src/server/api/routers/children.ts create mode 100644 src/server/api/routers/devices.ts create mode 100644 src/server/api/routers/post.ts delete mode 100644 src/server/auth.ts create mode 100644 src/server/db/migrate.ts rename drizzle/0000_wide_gravity.sql => src/server/db/migrations/0000_slim_katie_power.sql (50%) create mode 100644 src/server/db/migrations/0001_sticky_frightful_four.sql create mode 100644 src/server/db/migrations/meta/0000_snapshot.json rename drizzle/meta/0000_snapshot.json => src/server/db/migrations/meta/0001_snapshot.json (68%) create mode 100644 src/server/db/migrations/meta/_journal.json delete mode 100644 src/server/db/schema.ts create mode 100644 src/server/db/schema/_root.ts create mode 100644 src/server/db/schema/auth.ts create mode 100644 src/server/db/schema/children.ts create mode 100644 src/server/db/schema/devices.ts delete mode 100644 src/server/db/scripts/auth.ts delete mode 100644 src/server/db/scripts/seed copy.ts delete mode 100644 src/server/db/scripts/seed.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c061075..de82a8c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -17,6 +17,7 @@ const config = { "@typescript-eslint/array-type": "off", "@typescript-eslint/consistent-type-definitions": "off", "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-non-null-asserted-optional-chain": ["off"], "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/consistent-type-imports": [ "warn", diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 9fbc5b9..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml deleted file mode 100644 index 800272b..0000000 --- a/.idea/dataSources.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://localhost:5432/parentgrine - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index a862b72..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/parentgrine-server.iml b/.idea/parentgrine-server.iml deleted file mode 100644 index 24643cc..0000000 --- a/.idea/parentgrine-server.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e3a678d..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Next.js: debug server-side", - "type": "node-terminal", - "request": "launch", - "command": "NODE_ENV=development next dev -p 3002 & local-ssl-proxy --config ./ssl-proxy.json" - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index 908255f..0b7dd4e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,13 @@ { - "cmake.configureOnOpen": false, - "workbench.editor.labelFormat": "short" + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + ".vscode": true, + ".next": true, + "node_modules": true + } } diff --git a/README.md b/README.md index aa85ddd..fba19ed 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,28 @@ -# Kidarr ServerπŸš€ +# Create T3 App -Welcome to Kidarr, the ultimate child location tracking app that prioritises safety and peace of mind for parents! -🌟 +This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. -[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -[![GitHub issues](https://img.shields.io/github/issues/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/issues) -[![GitHub stars](https://img.shields.io/github/stars/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/kid-arr/kidarr-server)](https://github.com/kid-arr/kidarr-server/network) +## What's next? How do I make an app with this? -## Overview +We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. -Kidarr is a secure and user-friendly mobile application designed to help parents keep track of their children's -whereabouts in real-time. With advanced location tracking features, intuitive UI, and robust security measures, -Kidarr provides parents with the peace of mind they deserve. +If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. -## Features +- [Next.js](https://nextjs.org) +- [NextAuth.js](https://next-auth.js.org) +- [Prisma](https://prisma.io) +- [Tailwind CSS](https://tailwindcss.com) +- [tRPC](https://trpc.io) -- **Real-time Location Tracking**: Stay informed about your child's location at all times. +## Learn More -- **Geofencing**: Set up safe zones and receive notifications when your child enters or leaves predefined areas. +To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: -- **SOS Alert**: In case of an emergency, your child can send an SOS alert with their current location. +- [Documentation](https://create.t3.gg/) +- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) β€” Check out these awesome tutorials -- **History Tracking**: View a detailed history of your child's movement over time. +You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) β€” your feedback and contributions are welcome! -- **Privacy and Security**: We prioritize the privacy and security of your data. All communication is encrypted, and - location data is accessible only to authorized users. +## How do I deploy this? -## Getting Started - -### Prerequisites - -- iOS or Android device for your child -- Web, iOS or Android device for the parent - -### Installation - -1. Clone the repository: `git clone https://github.com/kid-arr/kidarr-server.git` -2. Follow the installation instructions in the [documentation](docs/INSTALL.md). - -## Usage - -1. Open the Kidarr app on your child's device. -2. Log in with your parent account. -3. Enjoy peace of mind by tracking your child's location. - -For more detailed instructions, check out our [User Guide](docs/UserGuide.md). - -## Contributing - -We welcome contributions from the community! Whether you're a developer, designer, or tester, your input is valuable. -Check out our [contribution guidelines](CONTRIBUTING.md) for more information. - -## Support - -If you encounter any issues or have questions, feel free -to [open an issue](https://github.com/kid-arr/kidarr-server/issues). We're here to help! - -## License - -Kidarr is licensed under the [MIT License](LICENSE). - -Happy tracking! 🌍✨ +Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. diff --git a/bun.lockb b/bun.lockb index 404c637532dbeafc587aa50827c070ed6730c9f3..4a8d7c50732d2a355f3af1128edf4b1bc7038d69 100755 GIT binary patch delta 81443 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 X{73caocvJ$b^Rt3O}Vjq;OGAbx`>lx delta 51197 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%0A3`ljw^1u4khvRFme{sC1-i< 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 U02WlPlL6olt$|U0t7;YYKVV}>PXGV_ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png deleted file mode 100644 index a033189ba887232ae884be35efce43a8bc21d4e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7370 zcmcgx7S%XOG!7flr$&`((EGL4HDAb5(@%L2)GCcEGZ4r zaee=VJ9qAf=bSGm=ETgIdCn(YZ8Z`?dO{EgL;|QQ>;2OW|A7GSpOhQJ6ZxmWUV3VZ zpz3kPZ4iht0#JUY|JGt}@xia)1!MffT#?i#^~wI<)BO1&D4i4lwrV^jcKA`W`bMS; z!G}!Zat6C$UCJbr4K}-xhayyp>nC>E=rg%~`OSa$Q@MHK|MWg^*^kUc?oV^>-|v5H zoo1wT$?>%fC(Y3M^!QVtMFm|deEPgErsERncv~OCE2K~$^B=G#q?m#vvHn0f-d06Q zy;cdh?R6dxshEOMW0so;35o>Uz%virRbG z%k$%9OW^ni#R8KwPv9t=Dm#Y3hEg7-_>cAj`E|CE~Z)$#)5mT;P}th}Su8D=4Ylrur*a0&F3wpZKf> z0U@l&D!3UNvRw7+srR1Wn+nmeO)42|6GmZCWr_B;37@T(&$^L z+fi7S9MT_=)m$igtkwhv5~tK%}{ljfgBeR(RNj zxk^p(f*!WKSqJs6H%bJ{;EPz?`Av0ewHDjU)_FMIu49Z#VJ(4g|r>~3*@p9@>IX)DNf#CzbGt5WRUG#-)D>sj5ZTY z!lbD1AmU@T5P(G{rWLSA#@M_fji5a-LEKou86LL=y-4T^?(J#4XHLN+AcCwXAud5$7}4uwLdydT_P=&H{U#$U%^W&IwYp z1de)_4hX1kc(2-($SDrbzIoqxCP>(bF#)eUU>vgi(#)@eUSZhoq7#T^n4GrE#&|^Y z?klORr>8Eg*t|7^=WatwpVFK(zLb+frb-A;3PbC;6sBWT&iAu0S40JYb<4DxzszF0 zv^cRIWIq){{vdk0N9n!7h7%Tcz5x;BV`0uc;ai>9XuOPr7-e`)rpCw&Y<87Plcg^V zR+m3Oy(5AN6uFOcfbw4wz7xul(E9bhS$0(;EWzUv3@{^bgdE6ghSv}`GqFTvV$Dxf z56C0MyB5xwP!QHoe)G!!uX))|Ww$R(Tpv7LV4q4iRa%zgNylH?_k(vhkK+TKmsP7d zS!dxfuE!n=*@Mm()!)7ZRLlsfw;MQqlBY$gHNBKGY zu`LzFJ&$w7~*9#U5ky&sEYJ(irA|C{5-N01~a>d@Bo+@~jnMm~CL zjeU?`wAaVtONA1p4%S`eFg+Fa{B~i0`RS*e;C;Q~F{9h#0-|cKtUp1sew<06?3w!; zoXShhR2LK5^~c(Go*x#YSnf7wrML9v95sCjr8#7aYr*KZ5jFQ;DJ!&X#*2)T@9ESH zatI(7M$s+=Y}%9h_8xd0#=4>iuxLt!jPzSDtYD4 zpW0Gf3MbY=@C#>P)=4?^I>!3u1)Eqv?O`n6x%%CLOqhlE5XcxRD8g zmI|PsH4BtShFK#J=dM&Jb_?P{le=|;A=ZPHQVsFbiixsq`GKB=!wKvGFmGG`O+$=b=*A*JzxqsEe${29{x=%+PGfIxTq_Yvx_Brd3ZY{T2q z7=N`?yIe!^-d<(_BBudK>U3fdTx^QicxhrQ$3;s3*@QGTK9Bn|y@1ou*49pi4_!WDhkJ8`{$#%}O4blV9pJKI zD1(-hBT);kUV*7sEm3T0H|C3c#)}HNoKxO53a}pDGF6s|aMqmn(CT@*_x|U40YCk9^_}+OTfYVG(I(Xq5%|A6ZA zns1FylKBrL)=YOkT@Vb|rDQ17!baP=-T#@&bTt7j9%yV9K3p=4MQ;@`wmbt?O$Q&c z^r^}6bW4cqQIX1-CnTAaG7c*poyXa2xY47O((1m8PjLHB+~gW+PGUAWWS;~`zhF0(?jZ}r+9nqZG8&+Ri0{Q5IfT4{2whNC5q zfka)RDD(4XyZ$#e)rd0sju*Ss^7gGi>-!eW8sG)CSGB=GX8L(P<@w7ZHNoHpg$L$U zxw{i($#0Zkl2?YLLV4gw%cX7eKdjsNJ2X|ef>cx<+(O$w2iYvmUkswIQ|Qm-L1-Fn ze0|aR$8LlTjc5X|lvT{W;92d7H7M5F`kwTG)fj^`p!wM=E6e*`H+^7_GYe?%7kS8L z7wNDa#)BBH^f(8*x8zT9VR~fSr2P>|RO|eqd6uX=ft2+N#E3}{Je^xWL8vU_{zOl;F-|qb#j?E?+Z3CMYT));Xj>M2uhN)L zlRHH{rK+yr#|mnNk++2t&kt9f`<1G?F(aTei|I75n`_(OL#tE3nnKA_uy~l9{JkI{ zJFOvm*oh+?U$j+sHQ{+B6tY@W{~?@%3eVO^4nyT17Bustg8+ny27 zm9Sr+?lX2`e&uO{D`tcG8P->1zIu-7I%#IJ4IuK;k`~C~>d+iznWM1{MRNo?pcb6~2fjXU|FcX2A(DN-*cvOR<}jaC{U+amkp` zWEd!dV3rn{R!+BbToRtH?~5~VkYY7NyZN=C`onqA_|^uJ^pxdS1JoXPhER&Yq)5^~$z!tTU~2HV(0#VmJKguIhYtV< zw+n0%+KFmRRmnPp;f1`~e4Q#+YxPQ#sko`8^WFW~!p|KmK1$Ty9_|TqE;qHe^o*{p zTp3&i-UJ*G#*&~m_HAndwI1+*zZR~Zf@Z=SCx^jWi+QHrfPHF5ocqz23W7EXqggQ< zC)mAmGv?(tdJb^V7Q7m6-( zi|)3kQz z=dJ){x7EqRB#DgvaA9yp+iTuEBQ9AEoqfFWV#Ipoh@DLBw6(u1mpfneQ_!4tzQ$Gf zJX+E)xC3ivS85kO>HFPKDYCud%+S6mtkwqWbp5%VvSl3WYq0KcV2TE5n%hvk(pJU8 zSl)F-bfV9va-kC4Va)!tYlg1x0aZ1SrsG-9f0WJ}GmY6AR1Xb3;87 z=$J12Vktoi4y#B^*jdaw8^+1X3h(>CwN)RjU7aAl@a%(c>P6=ErRzt7oWM6MJ!z1J zLXLQIYuv3~ed68^PWao2r+VB>iZh&4Bi=g(%^p)ZSA{7@K`OXd5>Dxa6e)75#=64- zwvjuJJn*mc|1!`K#MXi`js`nt1sgNON%+0!0MeW+6>qoJp5XGK52ZusI^^CARvv;w zu|Rt*dnv&CA|F$T$P_vH?R{{Pq?bNu+d`T>H(t#^lozfOBj`^Rqh7Bz$mY=lMGjbg zk*}6jTf&-rCUYu+dB67hx7_QFarS)YifL3ojB$ZWchxao{<4RuK#Z&3hc0IigRTY9 ztMgTUgq%d>@X;2Gzaa;A?6qk?+7s*@`PuIX-he8gaFqtvQDYuzgD*Pl1*a#5lU-43 zo!tqzC^Lgol|eM$ROR*AKw~5K*B+}v_tR-Le~ku8j`heyR10BZ{sYY!9G1z%LCI5( zHm~mFX!nWXY-(b9k&X9V_IzO}B0QRMyuDGQ(XX_tem&G9=hys|%fgYQ6PPsA6Rfx= zGd}TB_{!!eA6!UL=Kt+heL!z(;jv78f;HN6LTMueH5ZGE+EhPoehJItA7_XmACeVL zhVX4S&6Kta2tmvRpASjdm1$v%qAFl)&+I@`@7r<8#&>em0!}?8&kz+sj>Lvc`Sevi z;kDTG!{ISkLdoh+;h%@{H9F|oBjLIBNr6m0y|kGIQP1zWJ~V<+=IYhzsV#fnA{oWh z{zFH_(8`I>zj3=!DS)JRW8hpI9cqSn;jXSRq#nuzlU1KKDSpml_Ew<<`?{5Ke$eX~ zKfF!)C{Xr^!C}dSTJXAkqsbe>ADN|F=kz)wf==YURK2@RwOHr^_;*rr(-h@>+oA_B zZ(wzrubEXSh8yd&;g@p-NHU|c+g-b9a7kf_Y|iB-xc!M9efMQAuKg9}6nse^b9!xe z>8U%HK#nriLz){)tV)+}_m;SxRx6 zjB&oQ{3$c}0X~EEpDJ~}LxqkCZnj3jUx^q~F{!1*X3mvo#5TMVQdkYXUVqX+V=h8~ zh$+?gmSLl+Cw{3dJ(PIfxBVOY!4#oa6uz*U?#c2^l|K=^t=QM(0EKyX?1OERzPXUQ zrTH>ieYP=odZr|78*l?!nx{`>Dc0m=sz!dVX6jpsu#XPbip1JIxxQxv2Cv!EY1V^= zaWI2x8V7LC3oEX3FS_R?ravS@z7@;IE(~F9`M?%>#hb1Lj$VIxI&(mBDZA!s*slcW z^i3%(I_x~EnF}mEgnHl~;3~Z|?pWrZiR^da5AU9&m(pd(+x`|d?eU%cN>y>x#MgRu z)LviTYfP^5EjKl=A#ihC$6R{cV|&CH2Gki*s6;ID|^@A$Mi92>Yu zXYd?N0!6m3P6||opx3XRT_H@_jYCz<&WedaPVEZaG9L*!9=|#n-cRd5@aono6pF+0 zBotmgDe-?EQE{1m?a*jdeDfe|dUTDu2*5P-f%Epe=hXbx3z(VKv-g&m@tv{7IKB(p zDmkUU3s;#a8WB=24Hml%w3~kLwZXJk#mxNppb?xwDl6uYasDiy*Wfi1Bp;ZXyaylUvaGaE7F(S^)J zg%tax`ZW1}dfq{%6k`%ucXzdZlj^^TC&Q<&-Z_c)-*^D~Vk6T7ZH_5pPNG|%MXNJ; zPFt7=n8E(Fy+Xv2u;^+F@=DY@{;;jtiXvIg84yA&v&@ z3+j`U)}eRFe~;NFYd3$0xjUwlZPtoonaXU6%0~K*OeTuv3;8gFSZ=b(C>DCqj%@21 zHfc^xudsS*iB^%LjMsVspX8ky{tZ5zb(`IBTH#%8(+F_eBK_g`p0SJyg{C5K8l6P_ z4gJ9wXJ8`e5K#?1ihL=FW7brwR4KE;v;Tz(bzHDL`tGsX9(L!5qSx3Ho;0xv@;5J{ z799Kki!iU6y7c40mw@-Wf-?-y)eHuf(5vpW-;Ey$TL?mTYWth| z9oS4{G4{^h9+<@D#`Z5SZgMvp47dOKu`Z@GFEq^T@P~%lbs#6$eFCQc zl>_CTgRxo1l*v*m$db>Rym#P_?6VX^PLBu=O=uh^!9qSI?Dzo8shDR#wejH5QQiBC zbA4bu31bGxXMY0>baY?~$JGp+(oN7I{}qs%t9G4?mh@9I$&l`QS9ofw=tJPh=&>i{ z$5h^eW++!dt znyZ@K4v+t%u_+an%EP`N93)}tfa)Y1q2(-08l;&I-$W*Hs6AU`{i2~g`3PE`MRKtY zOGwzf!Sz1pW$9xWq2pe1fNre!r-9J;7nP7)4cHQ7PW$*bBk}Bxc#4FV`CdH(5 zG|W_K7q}ih)Rx`Bw=)V4Y5|2ec$@zDLWs&L2-t@i%WeaT*Patv2xxH`g$^G)n^~K% ztCsPDYTTQ_-(G}o1k%1mDl>rwhaOAkSF$Qx>sq9LOZq0C5<|1CF?HP%^cJ+&A`bsa zx^m;O)@cLodh~^{{1@(NF`}bM{j4TA;V2(qPQ*;Ed(YEATK%M3&-T_^^;1-3fA{cE zpoN%FAyvoEf(l&gLD&0`BJ?)Ve0Wb%k0nnY?4qdZmI@HiQ&4@57@yNpBs)Kuf>`pm z@jcX|QI~%uBP1(%ee%JwX!UW-m1j1Dg?sAM+s^E6xt>WwQ$^ZN8V4b8KMpbCWzFsA z7x;}`kia1MV_3~q6LL&9qP=3l!r%B!d{8SpKYl)r{=ea21>h^)4)Ovj&RC1hFV=bh zsjobFZp%$RulSxc5hn!^t50P$6p|)7k0v@K9y(}S)4a)%6dV$haY#DL%{alDV0*ni z5_gvNZw=6LlDrkT=NVO@t3)NtZ_=~dZsINH$v`6GiQP$cb2_lv)em{^bWOsdD_&pw zb%x%?XZ)r+b!G`$X0e1a$7>gENR=&#S4+ktjmKZ+sPN~PUW2xGb5`gNJOwB4n-|+| zcLIqMZW*q4!tC{HPbc)ejoO~{$IRfi_)&&|2^Zn}fuF7Urc%RqR0cq{!>y6$;omlM z)=1a1-e{l6#k3#zs!)jX(0Rxc=HPYNR7(vmzZ$(=QtRIIu}Z|M_7gh%o_Z8u4X#Z{ zAm0jL=?{hnIo_w##=Y$eHvsKfY#J|=fDc~)?t0LN#LCQssvLqi$f2lqAL$eHv;7|R zIz%#!fZka(;BHxTiX8*q^y?spa?cR45EhbcLEmSNky&4Ol8FpfXGB=ua01LPh^6xE zp~oL}Q_HA5cv;B$_wm--N-_^Fn`?RUvMqy4u*~wvgMP`3zNako31d+)~B&}7|`gMI5R^a z3qpQx@1^ovJJ#lT1dQ=-tz{L*99-(L3?3TcHn>@Q`%bp4PJ>HpWz7w<{B-poLsyN~ zp4|~rSjB{DX~$8fjnK#HrP(1uBHcFLg9XlCyfjlfZuhV@hGlC-Rcn%!?4ZVijTOe0 zH5-r24*pJhj+s{k(H2RFM+YXM>ng(oXw`orOsWaD?i~Y|iZB-LjZS zXGpi2=J5yrJ?*ex9imGYK<3wkEb}+?d1S7=F1ha5-g~@3RwpiD@?UXlg4;+?jWvhi rV>RHb9_o&>Z3%_Bu0DK;?i%ciZXJ-_=ehqrl^{SxTe(`%>fQeU9DYXo diff --git a/public/favicon copy.ico b/public/favicon copy.ico deleted file mode 100644 index efb824ed47e5fc5398a8c3bdf3bbce62bf3c6341..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeI2d$3Ja9LM)XULo)I<5D3_g{V}x7%rkFkCY}vn%neJW=W0mYXrfIl(lUex!*DfR@VuF*3{tZR=E_62m6mF+dT${ zVP2dHw)qguhv@em%C)wr2Zz9C>qCChe`i7tRDm%t32dV#BqGPs3$*_@=n7eI+Q~SR z4LM+ZItApK);~PTnP9v#?pf}7)$dcKwlu7dwE=zpGPo3sp`Le-oO4Kt+S982_KY0e zP6k0c=m*`PKN!cZguakUd(eDF84IMf1>B2ELape;y(3o7QV_2^wq9|sU!{RZVj8Ky z^#O1XOSM+g!9g$w3S~U}6J7}ml6>_2LM`EBENq9XB8AgA&Vb#}FjAJx!$KN!-%(G1 zIh6Z>V;BHyp#zwIct6?NMT5(s8VrHAKwShszz)#&%-eQCb4aFE)7TgA4VWi79`|MC z+?;p(?8HCgIeY+FQDwzFok9cV$Rj|TAA&v*PktY*f(PT>EMl4Ow*|~WYQtzS7syZb zxS_3PgSmubDz28%z^FnrqOWMPUtljRfE&PEN+psnTtL0hm&}KLf=W;r-ArY_2|}E| z`S30jB=fu#pik>ldtfpYM#h7%@9=n(vfKn4(ZA<{@$EHm?S(es^gnXUy&;T4+AHkW zccpj^A6ikP|VM^@HG`Q};7_{vXSOn&t(em412^!23>N6S2U3rvNv;27sYHBkOe>HAr46GY$R zvc&ZvJ-QA<8`kIC-!|U?cbmhID8D7!T9bwy#VfK z1*tC;TnDqENLJ)nOygmAt3cbs*gj~ApeRJaFR=VJ=0oaFnpXwUWg3Fyzt zcw(Gc3T+?}Ri)C?a2O^-6ENobZmE=GHqPtsJ&M^;J)WNiJHR#J-&cH>0niLmrB*cT z8qohM7PAwdZx6FzKbU8@mNvkf;94=Bg>wBDR+V|cy(oIDQ@M5>7+{S**YCIRBe)l3L!zogMb~Z}&~HNNr}4&b zj56No2gQ}X@-Xa%5(;eXaD%WxJ_+86i!~IYfk|phz3&tRG zoCy$ZM=s-P45iz^afRb_{;Gqy>&uaH=k_BQ4ox6cxn3jMHpaQWRzYXb2ja;%XwI}3 ztiJ=6fw8zYIH&u-9K<}fq@?Xe!fJ3Xo&)D5Hm_+wSq~Tt!=M{9he2Qr+YU1z{O;kj z#FRx(&V@ORcHIe|!ENCDdtZ6tht9rHoC+mojNNy3C+57RXp2V}XnX KG8QOB3;YAI4pf!^ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png deleted file mode 100644 index c31bb38b21ede10c52cc0fef42c4ef0f99fa3907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 332 zcmV-S0ki&zP)Px$21!IgR5(wq(>*K3VHC&l&rKdCF(?zeO_D?=Wn(arvXHV-Mv1{YP~HHG5HG=W z4|bu%=Ap28e(0)e_}~Ao+h5(zcDl~_p3d+5u96y~RAawtfI5tz4<9Jw7~lDUDZn5m zaD)@gq7`je!$TI51%#<9=*A3QV&N3}v4LwGU=cUrJqs8`4?3}s(54Glc*8y>^MH^q zVGqqX%Rq)Pjtd;(7GEjA4xaFglHgi3z^0000N*$ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png deleted file mode 100644 index dc9110bc0794fc6eb3efbc78912d904e27040b45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737 zcmV<70v`Q|P)Px%n@L1LR9Hvt*ImfZXB@}z*KHo=At}}t5j&A0BC(ukXE@NTsLA6b4<{6*C~8)# z9V`yEgtbHsnNglXkx-%)ngdB%R?3p{Fwgp~@4tTc?f>xm-}l79eVzRO{eIW)`+Z-Z z>-%tBE#C2HdB^^B0(3-yF6e{Dc-G#nY!;vgW??oy!&CIaU7W{m9KegRKxF}9wezq6 zm$4Nm@sR)GXfP&YAqM2{g)$Ij0ES~bUSS0;6%5C_U+@wB#Ci5n2J{0QVn1f9^`5#VHZYZx|Ic3fS(JK z@8C2-$>~KSjbb2ZV-F(;%Z!MFwV0pIj$k78pZr)9U`94h5@8TR9oEW42kvg~4kE=?mR@#YVm*M6`AN<_oB_5PU*k~5MeKdl|EL*JfSMkS(y(Ii48~yzcI3m) zv#9){;h?8kqWZo)3biM1j*F{Ispq5s7UK*yX0<{eGPZdf# zY&)@|0PS{M`LGk9Q-PuaWAktsWi^`6Jq*DO49b2U_khZgD|&x)z*hae*{Cc6j6v^o z9LMbY+1UanI$Rtk;t~?)|Nm_i0lMLX!lfe40&(MsOGdnl<5XNkI<(^Vt`58gRU>~P Tqoii600000NkvXXu0mjf_%TyA diff --git a/public/img/default-avatar.png b/public/img/default-avatar.png deleted file mode 100644 index eafe9f43feff7463d3c0aa0e9a755ddb71e67f1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15772 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 +
+

Here are your children.

+ +
+ + + ); +} diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx new file mode 100644 index 0000000..279597b --- /dev/null +++ b/src/app/(app)/dashboard/page.tsx @@ -0,0 +1,10 @@ +import DashboardPage from "@/components/pages/dashboard-page"; +import { checkAuth } from "@/lib/auth/utils"; +import React from "react"; + +const Dashboard = async () => { + await checkAuth(); + return ; +}; + +export default Dashboard; diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx new file mode 100644 index 0000000..30ec414 --- /dev/null +++ b/src/app/(app)/layout.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { SiteHeader } from '@/components/header/site-header'; +import { SocketProvider } from '@/lib/services/realtime/socket-provider'; + +type DashboardLayoutProps = { + children?: React.ReactNode +} +const AppLayout = async ({ children }: DashboardLayoutProps) => { + return ( + +
+ +
{children}
+
+
+ ); + +}; +export default AppLayout; diff --git a/src/app/(debug)/debug/page.tsx b/src/app/(debug)/debug/page.tsx deleted file mode 100644 index f2070aa..0000000 --- a/src/app/(debug)/debug/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { SecureDebugDetails } from "@/components/debug/SecureDebugDetails"; -import React from "react"; -import HeadersPrinter from "@/components/debug/HeadersPrinter"; - -const DebugPage = async () => { - return ( - <> -

- This is what we know -

-
- - -
- - ); -}; -export default DebugPage; diff --git a/src/app/(parent)/children/page.tsx b/src/app/(parent)/children/page.tsx deleted file mode 100644 index 6803a32..0000000 --- a/src/app/(parent)/children/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import ChildrenList from '@/components/children/children-list'; -import { api } from '@/trpc/server'; - -const ChildrenPage = async () => { - const kids = await api.child.mine.query(); - return ; -}; - -export default ChildrenPage; diff --git a/src/app/(parent)/dashboard/page.tsx b/src/app/(parent)/dashboard/page.tsx deleted file mode 100644 index 09063c1..0000000 --- a/src/app/(parent)/dashboard/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { api } from '@/trpc/server'; -import ChildrenFilter from '@/components/children/children-filter'; -import dynamic from 'next/dynamic'; -import { MapViewTypeSelector } from '@/components/maps/map-viewtype-selector'; - -const Dashboard = async () => { - const kids = await api.child.mine.query(); - const Map = dynamic(() => import('@/components/maps/main-map'), { - ssr: false, - }); - return
-
- - -
-
- -
-
; -}; - -export default Dashboard; diff --git a/src/app/(parent)/layout.tsx b/src/app/(parent)/layout.tsx deleted file mode 100644 index 6344c39..0000000 --- a/src/app/(parent)/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { SiteHeader } from '@/components/header/site-header'; -import React from 'react'; -import { SocketProvider } from '@/lib/services/realtime/socket-provider'; - -type DashboardLayoutProps = { - children?: React.ReactNode -} -const DashboardLayout = async ({ children }: DashboardLayoutProps) => { - return ( - -
- -
{children}
-
-
- ); - -}; -export default DashboardLayout; diff --git a/src/app/account/AccountCard.tsx b/src/app/account/AccountCard.tsx new file mode 100644 index 0000000..2e37f10 --- /dev/null +++ b/src/app/account/AccountCard.tsx @@ -0,0 +1,45 @@ +import { Card } from "@/components/ui/card"; + +interface AccountCardProps { + params: { + header: string; + description: string; + price?: number; + }; + children: React.ReactNode; +} + +export function AccountCard({ params, children }: AccountCardProps) { + const { header, description } = params; + return ( + +
+

{header}

+

{description}

+
+ {children} +
+ ); +} + +export function AccountCardBody({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +export function AccountCardFooter({ + description, + children, +}: { + children: React.ReactNode; + description: string; +}) { + return ( + + ); +} diff --git a/src/app/account/UpdateEmailCard.tsx b/src/app/account/UpdateEmailCard.tsx new file mode 100644 index 0000000..99ec6af --- /dev/null +++ b/src/app/account/UpdateEmailCard.tsx @@ -0,0 +1,56 @@ +import { AccountCard, AccountCardFooter, AccountCardBody } from "./AccountCard"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useToast } from "@/components/ui/use-toast"; +import { useTransition } from "react"; +import { useRouter } from "next/navigation"; + +export default function UpdateEmailCard({ email }: { email: string }) { + const { toast } = useToast(); + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + + const handleSubmit = async (event: React.SyntheticEvent) => { + event.preventDefault(); + const target = event.target as HTMLFormElement; + const form = new FormData(target); + const { email } = Object.fromEntries(form.entries()) as { email: string }; + if (email.length < 3) { + toast({ + description: "Email must be longer than 3 characters.", + variant: "destructive", + }); + return; + } + + startTransition(async () => { + const res = await fetch("/api/account", { + method: "PUT", + body: JSON.stringify({ email }), + headers: { "Content-Type": "application/json" }, + }); + if (res.status === 200) + toast({ description: "Successfully updated email!" }); + router.refresh(); + }); + }; + + return ( + +
+ + + + + + +
+
+ ); +} diff --git a/src/app/account/UpdateNameCard.tsx b/src/app/account/UpdateNameCard.tsx new file mode 100644 index 0000000..228cf03 --- /dev/null +++ b/src/app/account/UpdateNameCard.tsx @@ -0,0 +1,56 @@ +"use client"; +import { AccountCard, AccountCardFooter, AccountCardBody } from "./AccountCard"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useToast } from "@/components/ui/use-toast"; +import { useTransition } from "react"; +import { useRouter } from "next/navigation"; + +export default function UpdateNameCard({ name }: { name: string }) { + const { toast } = useToast(); + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + const handleSubmit = async (event: React.SyntheticEvent) => { + event.preventDefault(); + const target = event.target as HTMLFormElement; + const form = new FormData(target); + const { name } = Object.fromEntries(form.entries()) as { name: string }; + if (name.length < 3) { + toast({ + description: "Name must be longer than 3 characters.", + variant: "destructive", + }); + return; + } + + startTransition(async () => { + const res = await fetch("/api/account", { + method: "PUT", + body: JSON.stringify({ name }), + headers: { "Content-Type": "application/json" }, + }); + if (res.status === 200) + toast({ description: "Successfully updated name!" }); + router.refresh(); + }); + }; + + return ( + +
+ + + + + + +
+
+ ); +} diff --git a/src/app/account/UserSettings.tsx b/src/app/account/UserSettings.tsx new file mode 100644 index 0000000..9d38a40 --- /dev/null +++ b/src/app/account/UserSettings.tsx @@ -0,0 +1,17 @@ +"use client"; +import UpdateNameCard from "./UpdateNameCard"; +import UpdateEmailCard from "./UpdateEmailCard"; +import { AuthSession } from "@/lib/auth/utils"; + +export default function UserSettings({ + session, +}: { + session: AuthSession["session"]; +}) { + return ( + <> + + + + ); +} diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx new file mode 100644 index 0000000..0588287 --- /dev/null +++ b/src/app/account/page.tsx @@ -0,0 +1,16 @@ +import UserSettings from "./UserSettings"; +import { checkAuth, getUserAuth } from "@/lib/auth/utils"; + +export default async function Account() { + await checkAuth(); + const { session } = await getUserAuth(); + + return ( +
+

Account

+
+ +
+
+ ); +} diff --git a/src/app/api/account/route.ts b/src/app/api/account/route.ts new file mode 100644 index 0000000..4f16cf9 --- /dev/null +++ b/src/app/api/account/route.ts @@ -0,0 +1,15 @@ +import { getUserAuth } from "@/lib/auth/utils"; +import { db } from "@/server/db/index"; +import { users } from "@/server/db/schema/auth"; +import { eq } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; + +export async function PUT(request: Request) { + const { session } = await getUserAuth(); + if (!session) return new Response("Error", { status: 400 }); + const body = (await request.json()) as { name?: string; email?: string }; + + await db.update(users).set({ ...body }).where(eq(users.id, session.user.id)); + revalidatePath("/account"); + return new Response(JSON.stringify({ message: "ok" }), { status: 200 }); +} diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 1570f88..f9f233c 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,7 +1,14 @@ -import NextAuth from "next-auth"; +import { DefaultSession } from "next-auth"; +import NextAuth from "next-auth/next"; +import { authOptions } from "@/lib/auth/utils"; -import { authOptions } from "@/server/auth"; +declare module "next-auth" { + interface Session { + user: DefaultSession["user"] & { + id: string; + }; + } +} -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; diff --git a/src/app/api/child/register/route.ts b/src/app/api/child/register/route.ts deleted file mode 100644 index 8383880..0000000 --- a/src/app/api/child/register/route.ts +++ /dev/null @@ -1,4 +0,0 @@ -export async function POST(req: Request) { - const body = await req.json(); - console.log("route", "register", body); -} diff --git a/src/app/api/child/route.ts b/src/app/api/child/route.ts deleted file mode 100644 index 014d00e..0000000 --- a/src/app/api/child/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { db } from "@/server/db"; -import { child } from "@/server/db/schema"; -import { StatusCodes, getReasonPhrase } from "http-status-codes"; -import { NextResponse } from "next/server"; -import { eq } from "drizzle-orm"; -import { getServerAuthSession } from "@/server/auth"; - -export async function GET(request: Request) { - const session = await getServerAuthSession(); - if (!session || !session.user) - return NextResponse.json( - { error: getReasonPhrase(StatusCodes.UNAUTHORIZED) }, - { status: StatusCodes.UNAUTHORIZED }, - ); - const activeChildren = await db.query.child.findMany({ - where: eq(child.parentId, session.user.id), - with: { - devices: { - with: { pings: true }, - }, - }, - }); - - return NextResponse.json(activeChildren); -} diff --git a/src/app/api/device/connect/route.ts b/src/app/api/device/connect/route.ts deleted file mode 100644 index 353c95a..0000000 --- a/src/app/api/device/connect/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { db } from '@/server/db'; -import { eq } from 'drizzle-orm'; -import { StatusCodes } from 'http-status-codes'; -import { child, device } from '@/server/db/schema'; -import { createApiKey } from '@/lib/services/auth/api'; -import { badRequest } from '@/app/api/responses'; - -type DeviceConnectRequest = { - deviceId: string; - childId: string; - deviceName: string; -} -const POST = async (req: Request, res: Response) => { - if (req.method === 'POST') { - const { deviceId, childId, deviceName } = await req.json() as DeviceConnectRequest; - console.log('route', 'childId', childId); - console.log('route', 'deviceId', deviceId); - - if (!childId || !deviceId) { - return badRequest('Invalid registration request'); - } - - const childToRegister = ( - await db.selectDistinct().from(child).where(eq(child.id, childId)) - )[0]; - - if (!childToRegister) { - return badRequest('Invalid registration request'); - } - - let done = false; - let pin = 2021; - while (!done) { - pin = Math.floor(1000 + Math.random() * 9000); - console.log('route', 'device/connect', 'checking for PIN', pin); - const exists = await db - .selectDistinct() - .from(device) - .where(eq(device.pin, pin)); - console.log('route', 'exists', exists); - done = exists.length === 0; - } - - const apiKey = createApiKey(); - await db - .insert(device) - .values({ - childId: childToRegister.id, - deviceId: deviceId, - deviceName: deviceName, - pin, - apiKey: apiKey, - }) - .execute(); - return Response.json( - { childId, deviceId, deviceName, pin, apiKey }, - { status: StatusCodes.CREATED }, - ); - } - return badRequest('Invalid registration request'); -}; - -export { POST }; diff --git a/src/app/api/responses/index.ts b/src/app/api/responses/index.ts deleted file mode 100644 index 5cd6bad..0000000 --- a/src/app/api/responses/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { StatusCodes } from 'http-status-codes'; - -export const notAuthorised = () => - new Response('Not authorised', { - status: StatusCodes.BAD_REQUEST, - }); -export const badRequest = (message: string) => - new Response(message, { - status: StatusCodes.BAD_REQUEST, - }); diff --git a/src/app/devices/page.tsx b/src/app/devices/page.tsx new file mode 100644 index 0000000..7003893 --- /dev/null +++ b/src/app/devices/page.tsx @@ -0,0 +1,19 @@ +import DeviceList from "@/components/devices/DeviceList"; +import NewDeviceModal from "@/components/devices/DeviceModal"; +import { api } from "@/trpc/server"; +import { checkAuth } from "@/lib/auth/utils"; + +export default async function Devices() { + await checkAuth(); + const { devices } = await api.devices.getDevices.query(); + + return ( +
+
+

Devices

+ +
+ +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 835e5d3..e096f50 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,26 +1,21 @@ import "@/styles/globals.css"; -import { Analytics } from "@vercel/analytics/react"; -import { ABeeZee as TheFont } from "next/font/google"; -import { cookies } from "next/headers"; + +import { Inter } from "next/font/google"; import { TRPCReactProvider } from "@/trpc/react"; -import { ThemeProvider } from "@/components/providers/theme-provider"; -import { type Metadata } from "next"; -import NextAuthProvider from "@/lib/services/auth/provider"; +import { ThemeProvider } from "@/components/ThemeProvider"; +import { Toaster } from "@/components/ui/toaster"; +import NextAuthProvider from "@/lib/auth/Provider"; -const inter = TheFont({ - weight: "400", +const inter = Inter({ subsets: ["latin"], variable: "--font-sans", }); -export const metadata: Metadata = { +export const metadata = { title: "Kidarr", - description: "Radar for your kids", - manifest: "/site.webmanifest", - icons: { - icon: "/favicon.ico", - }, + description: "Radarr for your kids", + icons: [{ rel: "icon", url: "/favicon.ico" }], }; export default function RootLayout({ @@ -29,16 +24,20 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - + - - - - {children} - - - - + + + {children} + + + + ); diff --git a/src/app/loading.tsx b/src/app/loading.tsx new file mode 100644 index 0000000..568eb6e --- /dev/null +++ b/src/app/loading.tsx @@ -0,0 +1,25 @@ +export default function Loading() { + return ( +
+
+ + Loading... +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 3a2fa6e..6c5d549 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,17 @@ -import { getServerAuthSession } from '@/server/auth'; -import HomePage from '@/components/pages/home-page'; -import { redirect } from 'next/navigation'; + +import HomePage from "@/components/pages/home-page"; +import { getUserAuth } from "@/lib/auth/utils"; +import { redirect } from "next/navigation"; export default async function Home() { - const session = await getServerAuthSession(); - - if (session?.user) { - redirect('/dashboard'); + const { session } = await getUserAuth(); + if (session) { + redirect("/dashboard"); } - return ; + + return ( +
+ +
+ ); } diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..a801d9e --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { useTheme } from "next-themes"; + +export default function Page() { + const { setTheme } = useTheme(); + return ( +
+

Settings

+
+
+

Appearance

+

+ Customize the appearance of the app. Automatically switch between + day and night themes. +

+
+ + + +
+
+ ); +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 0000000..26b5257 --- /dev/null +++ b/src/components/Navbar.tsx @@ -0,0 +1,45 @@ +"use client"; + +import Link from "next/link"; +import { useState } from "react"; +import { usePathname } from "next/navigation"; + +import { Button } from "@/components/ui/button"; + +import { AlignRight } from "lucide-react"; +import { defaultLinks } from "@/config/nav"; + +export default function Navbar() { + const [open, setOpen] = useState(false); + const pathname = usePathname(); + return ( +
+ + {open ? ( +
+
    + {defaultLinks.map((link) => ( +
  • setOpen(false)} className=""> + + {link.title} + +
  • + ))} +
+
+ ) : null} +
+ ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..8d1d63f --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,55 @@ +import Link from "next/link"; + +import SidebarItems from "./SidebarItems"; +import { Avatar, AvatarFallback } from "./ui/avatar"; + +import { AuthSession, getUserAuth } from "@/lib/auth/utils"; + +const Sidebar = async () => { + const session = await getUserAuth(); + if (session.session === null) return null; + + return ( + + ); +}; + +export default Sidebar; + +const UserDetails = ({ session }: { session: AuthSession }) => { + if (session.session === null) return null; + const { user } = session.session; + + if (!user?.name || user.name.length == 0) return null; + + return ( + +
+
+

{user.name ?? "John Doe"}

+

+ {user.email ?? "john@doe.com"} +

+
+ + + {user.name + ? user.name + ?.split(" ") + .map((word) => word[0].toUpperCase()) + .join("") + : "~"} + + +
+ + ); +}; diff --git a/src/components/SidebarItems.tsx b/src/components/SidebarItems.tsx new file mode 100644 index 0000000..2569741 --- /dev/null +++ b/src/components/SidebarItems.tsx @@ -0,0 +1,90 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { LucideIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { defaultLinks, additionalLinks } from "@/config/nav"; + +export interface SidebarLink { + title: string; + href: string; + icon: LucideIcon; +} + +const SidebarItems = () => { + return ( + <> + + {additionalLinks.length > 0 + ? additionalLinks.map((l) => ( + + )) + : null} + + ); +}; +export default SidebarItems; + +const SidebarLinkGroup = ({ + links, + title, + border, +}: { + links: SidebarLink[]; + title?: string; + border?: boolean; +}) => { + const pathname = usePathname(); + + return ( +
+ {title ? ( +

+ {title} +

+ ) : null} +
    + {links.map((link) => ( +
  • + +
  • + ))} +
+
+ ); +}; +const SidebarLink = ({ + link, + active, +}: { + link: SidebarLink; + active: boolean; +}) => { + return ( + +
+
+ + {link.title} +
+ + ); +}; diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..b0ff266 --- /dev/null +++ b/src/components/ThemeProvider.tsx @@ -0,0 +1,9 @@ +"use client"; + +import * as React from "react"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { type ThemeProviderProps } from "next-themes/dist/types"; + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children}; +} diff --git a/src/components/children/child-form.tsx b/src/components/children/child-form.tsx new file mode 100644 index 0000000..8f015c4 --- /dev/null +++ b/src/components/children/child-form.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { + type Child, + type NewChildParams, + insertChildParams, +} from "@/server/db/schema/children"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { api as trpc } from "@/trpc/react"; +import { Button } from "@/components/ui/button"; +import { type z } from "zod"; +import { useRouter } from "next/navigation"; +import { useToast } from "@/components/ui/use-toast"; + +const ChildForm = ({ + child, + closeModal, +}: { + child?: Child; + closeModal?: () => void; +}) => { + const { toast } = useToast(); + + const editing = !!child?.id; + + const router = useRouter(); + const utils = trpc.useContext(); + + const form = useForm>({ + // latest Zod release has introduced a TS error with zodResolver + // open issue: https://github.com/colinhacks/zod/issues/2663 + // errors locally but not in production + resolver: zodResolver(insertChildParams), + defaultValues: child ?? { + name: "", + email: "", + avatar: "", + }, + }); + + const onSuccess = async ( + action: "create" | "update" | "delete", + data?: { error?: string }, + ) => { + if (data?.error) { + toast({ + title: `${action + .slice(0, 1) + .toUpperCase() + .concat(action.slice(1))} Failed`, + description: data.error, + variant: "destructive", + }); + return; + } + + await utils.children.getChildren.invalidate(); + router.refresh(); + if (closeModal) closeModal(); + toast({ + title: "Success", + description: `Child ${action}d!`, + variant: "default", + }); + }; + + const { mutate: createChild, isLoading: isCreating } = + trpc.children.createChild.useMutation({ + onSuccess: (res) => onSuccess("create"), + }); + + const { mutate: updateChild, isLoading: isUpdating } = + trpc.children.updateChild.useMutation({ + onSuccess: (res) => onSuccess("update"), + }); + + const { mutate: deleteChild, isLoading: isDeleting } = + trpc.children.deleteChild.useMutation({ + onSuccess: (res) => onSuccess("delete"), + }); + + const handleSubmit = (values: NewChildParams) => { + if (editing) { + updateChild({ ...values, id: child.id }); + } else { + createChild(values); + } + }; + return ( +
+ + ( + + Name + + + + + + + )} + /> + ( + + Email + + + + + + + )} + /> + ( + + Avatar + + + + + + + )} + /> + + {editing ? ( + + ) : null} + + + ); +}; + +export default ChildForm; diff --git a/src/components/children/child-list.tsx b/src/components/children/child-list.tsx new file mode 100644 index 0000000..43cc0c2 --- /dev/null +++ b/src/components/children/child-list.tsx @@ -0,0 +1,75 @@ +"use client"; +import { type CompleteChild } from "@/server/db/schema/children"; +import { api as trpc } from "@/trpc/react"; +import ChildModal from "./child-modal"; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Button } from "@/components/ui/button"; +import { Icons } from "@/components/icons"; +import ConnectDeviceDialog from "@/components/children/connect-device-dialog"; + +export default function ChildList({ children }: { children: CompleteChild[] }) { + const { data: c } = trpc.children.getChildren.useQuery(undefined, { + initialData: { children }, + refetchOnMount: false, + }); + + if (c.children.length === 0) { + return ; + } + + return ( + + + + Name + Last seen at + Actions + + + + {c.children?.map((kid) => )} + +
+ ); +} + +const Child = ({ child }: { child: CompleteChild }) => { + return ( + + {child.name} + Douglas + +
+ + +
+
+
+ ); +}; + +const EmptyState = () => { + return ( +
+

+ No children +

+

+ Get started by creating a new child. +

+
+ +
+
+ ); +}; diff --git a/src/components/children/child-modal.tsx b/src/components/children/child-modal.tsx new file mode 100644 index 0000000..7018f27 --- /dev/null +++ b/src/components/children/child-modal.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import ChildForm from "./child-form"; +import { type Child } from "@/server/db/schema/children"; + +export default function ChildModal({ + child, + emptyState, +}: { + child?: Child; + emptyState?: boolean; +}) { + const [open, setOpen] = useState(false); + const closeModal = () => setOpen(false); + const editing = !!child?.id; + return ( + + + {emptyState ? ( + + ) : ( + + )} + + + + {editing ? "Edit" : "Create"} Child + +
+ +
+
+
+ ); +} diff --git a/src/components/children/children-filter.tsx b/src/components/children/children-filter.tsx index 0991ffc..9e0bfc5 100644 --- a/src/components/children/children-filter.tsx +++ b/src/components/children/children-filter.tsx @@ -1,17 +1,16 @@ -import React from 'react'; -import ChildSelectList from './child-select-list'; +import React from "react"; +import ChildSelectList from "./child-select-list"; -import AddChildComponent from './add-child-component'; -import type ChildModel from '@/lib/models/child'; +import { type CompleteChild } from "@/server/db/schema/children"; type ChildrenFilterProps = { - kids: ChildModel[]; -} + kids: CompleteChild[]; +}; const ChildrenFilter: React.FC = async ({ kids }) => { return ( -
+
- + {/* */}
); }; diff --git a/src/components/debug/HeadersPrinter.tsx b/src/components/debug/HeadersPrinter.tsx deleted file mode 100644 index 3fdc952..0000000 --- a/src/components/debug/HeadersPrinter.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -import React from "react"; -import { headers } from "next/headers"; -import { getRequestHeaders } from "@/lib/helpers/headers"; -import { Card, CardContent, CardHeader } from "@/components/ui/card"; - -const HeadersPrinter = () => { - const header = headers(); - - const request = getRequestHeaders(header); - return ( - - Request Headers - {request} - - ); -}; -export default HeadersPrinter; diff --git a/src/components/debug/SecureDebugDetails.tsx b/src/components/debug/SecureDebugDetails.tsx deleted file mode 100644 index 4687cd0..0000000 --- a/src/components/debug/SecureDebugDetails.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import PrintEnv from "@/components/widgets/print-env"; -import { authOptions } from "@/server/auth"; -import { getServerSession } from "next-auth"; -import { headers } from "next/headers"; - -export const SecureDebugDetails = async () => { - const session = await getServerSession(authOptions); - const request = headers(); - return ; -}; diff --git a/src/components/devices/DeviceForm.tsx b/src/components/devices/DeviceForm.tsx new file mode 100644 index 0000000..4c24825 --- /dev/null +++ b/src/components/devices/DeviceForm.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { Device, NewDeviceParams, insertDeviceParams } from "@/server/db/schema/devices"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; + +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { api as trpc } from "@/trpc/react"; +import { Button } from "@/components/ui/button"; +import { z } from "zod"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useRouter } from "next/navigation"; +import { useToast } from "@/components/ui/use-toast"; + +const DeviceForm = ({ + device, + closeModal, +}: { + device?: Device; + closeModal?: () => void; +}) => { + const { toast } = useToast(); + const { data: children } = trpc.children.getChildren.useQuery(); + const editing = !!device?.id; + + const router = useRouter(); + const utils = trpc.useContext(); + + const form = useForm>({ + // latest Zod release has introduced a TS error with zodResolver + // open issue: https://github.com/colinhacks/zod/issues/2663 + // errors locally but not in production + resolver: zodResolver(insertDeviceParams), + defaultValues: device ?? { + name: "", + deviceId: "", + childId: "" + }, + }); + + const onSuccess = async (action: "create" | "update" | "delete", + data?: { error?: string }, + ) => { + if (data?.error) { + toast({ + title: `${action + .slice(0, 1) + .toUpperCase() + .concat(action.slice(1))} Failed`, + description: data.error, + variant: "destructive", + }); + return; + } + + await utils.devices.getDevices.invalidate(); + router.refresh(); + if (closeModal) closeModal(); + toast({ + title: 'Success', + description: `Device ${action}d!`, + variant: "default", + }); + }; + + const { mutate: createDevice, isLoading: isCreating } = + trpc.devices.createDevice.useMutation({ + onSuccess: (res) => onSuccess("create"), + }); + + const { mutate: updateDevice, isLoading: isUpdating } = + trpc.devices.updateDevice.useMutation({ + onSuccess: (res) => onSuccess("update"), + }); + + const { mutate: deleteDevice, isLoading: isDeleting } = + trpc.devices.deleteDevice.useMutation({ + onSuccess: (res) => onSuccess("delete"), + }); + + const handleSubmit = (values: NewDeviceParams) => { + if (editing) { + updateDevice({ ...values, id: device.id }); + } else { + createDevice(values); + } + }; + return ( +
+ + ( + Name + + + + + + + )} + /> + ( + Device Id + + + + + + + )} + /> + ( + Child Id + + + + + + + )} + /> + + {editing ? ( + + ) : null} + + + ); +}; + +export default DeviceForm; diff --git a/src/components/devices/DeviceList.tsx b/src/components/devices/DeviceList.tsx new file mode 100644 index 0000000..04cdd86 --- /dev/null +++ b/src/components/devices/DeviceList.tsx @@ -0,0 +1,52 @@ +"use client"; +import { CompleteDevice } from "@/server/db/schema/devices"; +import { api as trpc } from "@/trpc/react"; +import DeviceModal from "./DeviceModal"; + + +export default function DeviceList({ devices }: { devices: CompleteDevice[] }) { + const { data: d } = trpc.devices.getDevices.useQuery(undefined, { + initialData: { devices }, + refetchOnMount: false, + }); + + if (d.devices.length === 0) { + return ; + } + + return ( +
    + {d.devices.map((device) => ( + + ))} +
+ ); +} + +const Device = ({ device }: { device: CompleteDevice }) => { + return ( +
  • +
    +
    {device.device.name}
    +
    + +
  • + ); +}; + +const EmptyState = () => { + return ( +
    +

    + No devices +

    +

    + Get started by creating a new device. +

    +
    + +
    +
    + ); +}; + diff --git a/src/components/devices/DeviceModal.tsx b/src/components/devices/DeviceModal.tsx new file mode 100644 index 0000000..e85aecb --- /dev/null +++ b/src/components/devices/DeviceModal.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import DeviceForm from "./DeviceForm"; +import { Device } from "@/server/db/schema/devices"; + +export default function DeviceModal({ + device, + emptyState, +}: { + device?: Device; + emptyState?: boolean; +}) { + const [open, setOpen] = useState(false); + const closeModal = () => setOpen(false); + const editing = !!device?.id; + return ( + + + { emptyState ? ( + + ) : ( + )} + + + + { editing ? "Edit" : "Create" } Device + +
    + +
    +
    +
    + ); +} diff --git a/src/components/forms/add-child-form.tsx b/src/components/forms/add-child-form.tsx deleted file mode 100644 index 471eade..0000000 --- a/src/components/forms/add-child-form.tsx +++ /dev/null @@ -1,122 +0,0 @@ -"use client"; - -import * as React from "react"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import type * as z from "zod"; - -import { cn } from "@/lib/utils"; -import { newChildSchema } from "@/lib/validations/child"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { toast } from "@/components/ui/use-toast"; -import { Icons } from "@/components/icons"; -import { DialogFooter } from "../ui/dialog"; -import { - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { api } from "@/trpc/react"; -import { useRouter } from "next/navigation"; - -type AddChildFormProps = React.HTMLAttributes; -type FormData = z.infer; - -const AddChildForm: React.FC = ({ className, ...props }) => { - const router = useRouter(); - const [PIN, setPIN] = React.useState(""); - const utils = api.useUtils(); - const createChild = api.child.create.useMutation(); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(newChildSchema), - }); - - const onSubmit = async (data: FormData) => { - try { - const result = await createChild.mutateAsync({ name: data.name }); - toast({ description: "Added new child" }); - await utils.child.invalidate(); - console.log("add-child-form", "onSubmit", createChild.data); - if (result) { - setPIN(result.id); - } else { - toast({ description: "Something went wrong", variant: "destructive" }); - } - } catch (err) { - toast({ description: "Something went wrong", variant: "destructive" }); - } - }; - - if (PIN) { - return ( - <> - Successfully added child -
    - {`Your child's PIN is ${PIN}`} - -
    - - ); - } - return ( -
    - - Add Child - - { - "Enter your child's details below and press save, then use the displayed PIN to register their device." - } - - -
    -
    -
    - - - {errors?.name && ( -

    {errors.name.message}

    - )} -
    -
    -
    - - - -
    -
    -
    - ); -}; -export default AddChildForm; diff --git a/src/components/forms/user-auth-form.tsx b/src/components/forms/user-auth-form.tsx index 05722bf..29e2679 100644 --- a/src/components/forms/user-auth-form.tsx +++ b/src/components/forms/user-auth-form.tsx @@ -1,19 +1,22 @@ "use client"; import * as React from "react"; -import { redirect, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; import { signIn } from "next-auth/react"; import { useForm } from "react-hook-form"; -import type * as z from "zod"; import { cn } from "@/lib/utils"; -import { userAuthSchema } from "@/lib/validations/auth"; import { buttonVariants } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { toast } from "@/components/ui/use-toast"; import { Icons } from "@/components/icons"; +import { z } from "zod"; + +const userAuthSchema = z.object({ + email: z.string().email(), +}) type UserAuthFormProps = React.HTMLAttributes; diff --git a/src/components/header/auth-header.tsx b/src/components/header/auth-header.tsx index 6595ae6..b882252 100644 --- a/src/components/header/auth-header.tsx +++ b/src/components/header/auth-header.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Button, buttonVariants } from "@/components/ui/button"; -import { signIn, signOut, useSession } from "next-auth/react"; +import { buttonVariants } from "@/components/ui/button"; +import { signOut, useSession } from "next-auth/react"; import { DropdownMenu, DropdownMenuContent, @@ -29,8 +29,8 @@ const AuthHeader = () => { diff --git a/src/components/main-nav.tsx b/src/components/main-nav.tsx index 6c6cf92..6f68149 100644 --- a/src/components/main-nav.tsx +++ b/src/components/main-nav.tsx @@ -20,19 +20,17 @@ export function MainNav({ items }: MainNavProps) { {items?.length ? ( ) : null} diff --git a/src/components/maps/main-map.tsx b/src/components/maps/main-map.tsx index 8303d09..c597cbb 100644 --- a/src/components/maps/main-map.tsx +++ b/src/components/maps/main-map.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ "use client"; import "leaflet/dist/leaflet.css"; import React, { useEffect, useState } from "react"; @@ -10,12 +11,12 @@ import { Polyline, } from "react-leaflet"; import { usePingSocket } from "@/lib/hooks/use-ping-socket"; -import type ChildModel from "@/lib/models/child"; import MapMarker from "@/components/maps/map-marker"; import { getLatestPing } from "@/lib/helpers/location/ping"; +import { type CompleteChild } from "@/server/db/schema/children"; type MainMapProps = { - kids: ChildModel[]; + kids: CompleteChild[]; }; const MainMap: React.FC = ({ kids }) => { const [isMounted, setIsMounted] = useState(false); @@ -24,42 +25,47 @@ const MainMap: React.FC = ({ kids }) => { console.log("MainMap", "kids", kids); }, [kids]); // Draw lines between each ping showing the direction of travel - const renderLines = (kids: ChildModel[]) => { - return kids?.map((kid) => - kid.devices?.map((device) => - device.pings?.map((ping, index) => { - const nextPing = device.pings[index + 1]; - if (nextPing) { - return ( - <> - - - - ); - } - return null; - }), + const renderLines = (children: CompleteChild[]) => { + return children?.map((child) => + child.devices?.map( + (device) =>

    I AM DEVICE

    , + + // device.pings?.map((ping, index) => { + // const nextPing = device.pings[index + 1]; + // if (nextPing) { + // return ( + // <> + // + // + // + // ); + // } + // return null; + // }), ), ); }; return ( isMounted && (
    +

    + This is the main +

    { - const [currentView, setCurrentView] = React.useState('location'); + const [currentView, setCurrentView] = React.useState("location"); return ( - setCurrentView(value)}> + setCurrentView(value)} + > - + Location - + Route - ); + + ); }; diff --git a/src/components/pages/dashboard-page.tsx b/src/components/pages/dashboard-page.tsx new file mode 100644 index 0000000..334d473 --- /dev/null +++ b/src/components/pages/dashboard-page.tsx @@ -0,0 +1,23 @@ +import dynamic from "next/dynamic"; +import { api } from "@/trpc/server"; +import ChildrenFilter from "../children/children-filter"; +import { MapViewTypeSelector } from "../maps/map-viewtype-selector"; + +const DashboardPage = async () => { + const { children } = await api.children.getChildren.query(); + const Map = dynamic(() => import("@/components/maps/main-map"), { + ssr: false, + }); + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; +export default DashboardPage; diff --git a/src/components/pages/home-page.tsx b/src/components/pages/home-page.tsx index 785a9aa..1c71444 100644 --- a/src/components/pages/home-page.tsx +++ b/src/components/pages/home-page.tsx @@ -6,95 +6,94 @@ import { Card, CardHeader, CardTitle, CardContent } from "../ui/card"; import Link from "next/link"; function HomePage() { - return ( - <> -
    -
    -

    Track Your Children with Ease

    -

    - Kidarr helps you keep an eye on your loved ones and ensure their - safety. -

    - - Let's go - -
    -
    + return ( + <> +
    +
    +

    Track Your Children with Ease

    +

    + Kidarr helps you keep an eye on your loved ones and ensure their + safety. +

    + + {`Let's go`} +
    +
    -
    -
    - - - Real-Time Location Tracking - - -

    - Instantly know where your children are at all times with - accurate GPS tracking. -

    -
    -
    - - - Geofencing Alerts - - -

    - Receive notifications when your child enters or leaves - designated safe zones. -

    -
    -
    - - - Activity Monitoring - - -

    - { - "View your child's activity history, including visited places and routes taken." - } -

    -
    -
    -
    -
    -
    -
    -

    - Keep Your Children Safe Today! -

    -

    - Download Kidarr now and stay connected with your loved ones. -

    +
    +
    + + + Real-Time Location Tracking + + +

    + Instantly know where your children are at all times with + accurate GPS tracking. +

    +
    +
    + + + Geofencing Alerts + + +

    + Receive notifications when your child enters or leaves + designated safe zones. +

    +
    +
    + + + Activity Monitoring + + +

    + { + "View your child's activity history, including visited places and routes taken." + } +

    +
    +
    +
    +
    +
    +
    +

    + Keep Your Children Safe Today! +

    +

    + Download Kidarr now and stay connected with your loved ones. +

    - - Download Now - -
    -
    -
    -

    - An open source experiment from PodNoms - source code available{" "} - - here - -

    -
    - - ); + + Download Now + +
    +
    +
    +

    + An open source experiment from PodNoms - source code available{" "} + + here + +

    +
    + + ); } export default HomePage; diff --git a/src/components/providers/theme-provider.tsx b/src/components/providers/theme-provider.tsx deleted file mode 100644 index d4b4bbf..0000000 --- a/src/components/providers/theme-provider.tsx +++ /dev/null @@ -1,9 +0,0 @@ -'use client' - -import * as React from 'react' -import { ThemeProvider as NextThemesProvider } from 'next-themes' -import { type ThemeProviderProps } from 'next-themes/dist/types' - -export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children} -} diff --git a/src/components/ui/ThemeToggle.tsx b/src/components/ui/ThemeToggle.tsx new file mode 100644 index 0000000..63ff415 --- /dev/null +++ b/src/components/ui/ThemeToggle.tsx @@ -0,0 +1,40 @@ +"use client"; + +import * as React from "react"; +import { MoonIcon, SunIcon } from "lucide-react"; +import { useTheme } from "next-themes"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export function ModeToggle() { + const { setTheme } = useTheme(); + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ); +} diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx index df0c256..ec505d0 100644 --- a/src/components/ui/carousel.tsx +++ b/src/components/ui/carousel.tsx @@ -2,18 +2,21 @@ import * as React from "react" import useEmblaCarousel, { - type EmblaCarouselType as CarouselApi, - type EmblaOptionsType as CarouselOptions, - type EmblaPluginType as CarouselPlugin, + type UseEmblaCarouselType, } from "embla-carousel-react" import { ArrowLeft, ArrowRight } from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + type CarouselProps = { opts?: CarouselOptions - plugins?: CarouselPlugin[] + plugins?: CarouselPlugin orientation?: "horizontal" | "vertical" setApi?: (api: CarouselApi) => void } diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx index 6e011c6..ea40d19 100644 --- a/src/components/ui/pagination.tsx +++ b/src/components/ui/pagination.tsx @@ -12,6 +12,7 @@ const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( {...props} /> ) +Pagination.displayName = "Pagination" const PaginationContent = React.forwardRef< HTMLUListElement, @@ -44,19 +45,17 @@ const PaginationLink = ({ size = "icon", ...props }: PaginationLinkProps) => ( - - - + ) PaginationLink.displayName = "PaginationLink" @@ -90,6 +89,7 @@ const PaginationNext = ({ ) +PaginationNext.displayName = "PaginationNext" const PaginationEllipsis = ({ className, @@ -104,6 +104,7 @@ const PaginationEllipsis = ({ More pages ) +PaginationEllipsis.displayName = "PaginationEllipsis" export { Pagination, diff --git a/src/config/nav.ts b/src/config/nav.ts new file mode 100644 index 0000000..950b870 --- /dev/null +++ b/src/config/nav.ts @@ -0,0 +1,15 @@ +import { SidebarLink } from "@/components/SidebarItems"; +import { Cog, Globe, HomeIcon } from "lucide-react"; + +type AdditionalLinks = { + title: string; + links: SidebarLink[]; +}; + +export const defaultLinks: SidebarLink[] = [ + { href: "/", title: "Home", icon: HomeIcon }, + { href: "/account", title: "Account", icon: Cog }, + { href: "/settings", title: "Settings", icon: Cog }, +]; + +export const additionalLinks: AdditionalLinks[] = []; diff --git a/src/env.js b/src/env.js index ade7aad..5c2f937 100644 --- a/src/env.js +++ b/src/env.js @@ -1,5 +1,5 @@ -import { createEnv } from '@t3-oss/env-nextjs'; -import { z } from 'zod'; +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod"; export const env = createEnv({ /** @@ -7,30 +7,7 @@ export const env = createEnv({ * isn't built with invalid env vars. */ server: { - DATABASE_URL: z - .string() - .url() - .refine( - (str) => !str.includes('YOUR_PG_URL_HERE'), - 'You forgot to change the default URL', - ), - NODE_ENV: z - .enum(['development', 'test', 'production']) - .default('development'), - NEXTAUTH_SECRET: - process.env.NODE_ENV === 'production' - ? z.string() - : z.string().optional(), - NEXTAUTH_URL: z.preprocess( - // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL - // Since NextAuth.js automatically uses the VERCEL_URL if present. - (str) => process.env.VERCEL_URL ?? str, - // VERCEL_URL doesn't include `https` so it cant be validated as a URL - process.env.VERCEL ? z.string() : z.string().url(), - ), - GOOGLE_CLIENT_ID: z.string(), - GOOGLE_CLIENT_SECRET: z.string(), - ALLOWED_DEBUG_IP: z.string().optional(), + NODE_ENV: z.enum(["development", "test", "production"]), }, /** @@ -47,13 +24,8 @@ export const env = createEnv({ * middlewares) or client-side so we need to destruct manually. */ runtimeEnv: { - DATABASE_URL: process.env.DATABASE_URL, NODE_ENV: process.env.NODE_ENV, - NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, - NEXTAUTH_URL: process.env.NEXTAUTH_URL, - GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, - GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, - ALLOWED_DEBUG_IP: process.env.ALLOWED_DEBUG_IP, + // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/src/env.mjs b/src/env.mjs new file mode 100644 index 0000000..484d66e --- /dev/null +++ b/src/env.mjs @@ -0,0 +1,36 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod"; + +export const env = createEnv({ + server: { + NODE_ENV: z + .enum(["development", "test", "production"]) + .default("development"), + DATABASE_URL: z.string().min(1), + + NEXTAUTH_SECRET: process.env.NODE_ENV === "production" + ? z.string().min(1) + : z.string().min(1).optional(), + NEXTAUTH_URL: z.preprocess( + // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL + // Since NextAuth.js automatically uses the VERCEL_URL if present. + (str) => process.env.VERCEL_URL ?? str, + // VERCEL_URL doesn't include `https` so it cant be validated as a URL + process.env.VERCEL_URL ? z.string().min(1) : z.string().url() + ), + GOOGLE_CLIENT_ID: z.string().min(1), + GOOGLE_CLIENT_SECRET: z.string().min(1), + }, + client: { + // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), + }, + // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually + // runtimeEnv: { + // DATABASE_URL: process.env.DATABASE_URL, + // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, + // }, + // For Next.js >= 13.4.4, you only need to destructure client variables: + experimental__runtimeEnv: { + // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, + }, +}); diff --git a/src/lib/api/children/mutations.ts b/src/lib/api/children/mutations.ts new file mode 100644 index 0000000..51ba174 --- /dev/null +++ b/src/lib/api/children/mutations.ts @@ -0,0 +1,58 @@ +import { db } from "@/server/db/index"; +import { and, eq } from "drizzle-orm"; +import { + ChildId, + NewChildParams, + UpdateChildParams, + updateChildSchema, + insertChildSchema, + children, + childIdSchema +} from "@/server/db/schema/children"; +import { getUserAuth } from "@/lib/auth/utils"; + +export const createChild = async (child: NewChildParams) => { + const { session } = await getUserAuth(); + const newChild = insertChildSchema.parse({ ...child, userId: session?.user.id! }); + try { + const [c] = await db.insert(children).values(newChild).returning(); + return { child: c }; + } catch (err) { + const message = (err as Error).message ?? "Error, please try again"; + console.error(message); + throw { error: message }; + } +}; + +export const updateChild = async (id: ChildId, child: UpdateChildParams) => { + const { session } = await getUserAuth(); + const { id: childId } = childIdSchema.parse({ id }); + const newChild = updateChildSchema.parse({ ...child, userId: session?.user.id! }); + try { + const [c] = await db + .update(children) + .set(newChild) + .where(and(eq(children.id, childId!), eq(children.userId, session?.user.id!))) + .returning(); + return { child: c }; + } catch (err) { + const message = (err as Error).message ?? "Error, please try again"; + console.error(message); + throw { error: message }; + } +}; + +export const deleteChild = async (id: ChildId) => { + const { session } = await getUserAuth(); + const { id: childId } = childIdSchema.parse({ id }); + try { + const [c] = await db.delete(children).where(and(eq(children.id, childId!), eq(children.userId, session?.user.id!))) + .returning(); + return { child: c }; + } catch (err) { + const message = (err as Error).message ?? "Error, please try again"; + console.error(message); + throw { error: message }; + } +}; + diff --git a/src/lib/api/children/queries.ts b/src/lib/api/children/queries.ts new file mode 100644 index 0000000..66848fb --- /dev/null +++ b/src/lib/api/children/queries.ts @@ -0,0 +1,31 @@ +import { db } from "@/server/db/index"; +import { eq, and } from "drizzle-orm"; +import { getUserAuth } from "@/lib/auth/utils"; +import { + type ChildId, + childIdSchema, + children, +} from "@/server/db/schema/children"; +import { devices } from "@/server/db/schema/devices"; + +export const getChildren = async () => { + const { session } = await getUserAuth(); + const c = await db + .select() + .from(children) + .where(eq(children.userId, session?.user.id!)) + .orderBy(children.name); + return { children: c.map((c) => ({ ...c, devices: [] })) }; +}; + +export const getChildById = async (id: ChildId) => { + const { session } = await getUserAuth(); + const { id: childId } = childIdSchema.parse({ id }); + const [c] = await db + .select() + .from(children) + .where( + and(eq(children.id, childId), eq(children.userId, session?.user.id!)), + ); + return { child: c }; +}; diff --git a/src/lib/api/devices/mutations.ts b/src/lib/api/devices/mutations.ts new file mode 100644 index 0000000..64cca10 --- /dev/null +++ b/src/lib/api/devices/mutations.ts @@ -0,0 +1,58 @@ +import { db } from "@/server/db/index"; +import { and, eq } from "drizzle-orm"; +import { + DeviceId, + NewDeviceParams, + UpdateDeviceParams, + updateDeviceSchema, + insertDeviceSchema, + devices, + deviceIdSchema +} from "@/server/db/schema/devices"; +import { getUserAuth } from "@/lib/auth/utils"; + +export const createDevice = async (device: NewDeviceParams) => { + const { session } = await getUserAuth(); + const newDevice = insertDeviceSchema.parse({ ...device, userId: session?.user.id! }); + try { + const [d] = await db.insert(devices).values(newDevice).returning(); + return { device: d }; + } catch (err) { + const message = (err as Error).message ?? "Error, please try again"; + console.error(message); + throw { error: message }; + } +}; + +export const updateDevice = async (id: DeviceId, device: UpdateDeviceParams) => { + const { session } = await getUserAuth(); + const { id: deviceId } = deviceIdSchema.parse({ id }); + const newDevice = updateDeviceSchema.parse({ ...device, userId: session?.user.id! }); + try { + const [d] = await db + .update(devices) + .set(newDevice) + .where(and(eq(devices.id, deviceId!), eq(devices.userId, session?.user.id!))) + .returning(); + return { device: d }; + } catch (err) { + const message = (err as Error).message ?? "Error, please try again"; + console.error(message); + throw { error: message }; + } +}; + +export const deleteDevice = async (id: DeviceId) => { + const { session } = await getUserAuth(); + const { id: deviceId } = deviceIdSchema.parse({ id }); + try { + const [d] = await db.delete(devices).where(and(eq(devices.id, deviceId!), eq(devices.userId, session?.user.id!))) + .returning(); + return { device: d }; + } catch (err) { + const message = (err as Error).message ?? "Error, please try again"; + console.error(message); + throw { error: message }; + } +}; + diff --git a/src/lib/api/devices/queries.ts b/src/lib/api/devices/queries.ts new file mode 100644 index 0000000..7396e15 --- /dev/null +++ b/src/lib/api/devices/queries.ts @@ -0,0 +1,19 @@ +import { db } from "@/server/db/index"; +import { eq, and } from "drizzle-orm"; +import { getUserAuth } from "@/lib/auth/utils"; +import { type DeviceId, deviceIdSchema, devices } from "@/server/db/schema/devices"; +import { children } from "@/server/db/schema/children"; + +export const getDevices = async () => { + const { session } = await getUserAuth(); + const d = await db.select({ device: devices, child: children }).from(devices).leftJoin(children, eq(devices.childId, children.id)).where(eq(devices.userId, session?.user.id!)); + return { devices: d }; +}; + +export const getDeviceById = async (id: DeviceId) => { + const { session } = await getUserAuth(); + const { id: deviceId } = deviceIdSchema.parse({ id }); + const [d] = await db.select().from(devices).where(and(eq(devices.id, deviceId), eq(devices.userId, session?.user.id!))).leftJoin(children, eq(devices.childId, children.id)); + return { device: d }; +}; + diff --git a/src/lib/auth/Provider.tsx b/src/lib/auth/Provider.tsx new file mode 100644 index 0000000..b2ab516 --- /dev/null +++ b/src/lib/auth/Provider.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { SessionProvider } from "next-auth/react"; + +type Props = { + children?: React.ReactNode; +}; + +export default function NextAuthProvider({ children }: Props) { + return {children}; +}; \ No newline at end of file diff --git a/src/lib/auth/utils.ts b/src/lib/auth/utils.ts index 2fecf1f..5eb205b 100644 --- a/src/lib/auth/utils.ts +++ b/src/lib/auth/utils.ts @@ -1,5 +1,17 @@ +import { db } from "@/server/db/index"; +import { DrizzleAdapter } from "@auth/drizzle-adapter"; +import { DefaultSession, getServerSession, NextAuthOptions } from "next-auth"; import { redirect } from "next/navigation"; -import { getServerAuthSession } from "@/server/auth"; +import { env } from "@/env.mjs" +import GoogleProvider from "next-auth/providers/google"; + +declare module "next-auth" { + interface Session { + user: DefaultSession["user"] & { + id: string; + }; + } +} export type AuthSession = { session: { @@ -7,13 +19,29 @@ export type AuthSession = { id: string; name?: string; email?: string; - username?: string; }; } | null; }; +export const authOptions: NextAuthOptions = { + adapter: DrizzleAdapter(db), + callbacks: { + session: ({ session, user }) => { + session.user.id = user.id; + return session; + }, + }, + providers: [ + GoogleProvider({ + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }) + ], +}; + + export const getUserAuth = async () => { - const session = await getServerAuthSession(); + const session = await getServerSession(authOptions); return { session } as AuthSession; }; @@ -21,3 +49,4 @@ export const checkAuth = async () => { const { session } = await getUserAuth(); if (!session) redirect("/api/auth/signin"); }; + diff --git a/src/lib/db/migrations/meta/_journal.json b/src/lib/db/migrations/meta/_journal.json new file mode 100644 index 0000000..5dcbf6a --- /dev/null +++ b/src/lib/db/migrations/meta/_journal.json @@ -0,0 +1 @@ +{"version":"5","dialect":"pg","entries":[]} \ No newline at end of file diff --git a/src/lib/hooks/useValidatedForm.tsx b/src/lib/hooks/useValidatedForm.tsx new file mode 100644 index 0000000..b5c8595 --- /dev/null +++ b/src/lib/hooks/useValidatedForm.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { FormEvent, useState } from "react"; +import { ZodSchema } from "zod"; + +type EntityZodErrors = Partial>; + +export function useValidatedForm(insertEntityZodSchema: ZodSchema) { + const [errors, setErrors] = useState | null>(null); + const hasErrors = + errors !== null && + Object.values(errors).some((error) => error !== undefined); + + const handleChange = (event: FormEvent) => { + const target = event.target as EventTarget; + if ( + target instanceof HTMLInputElement || + target instanceof HTMLSelectElement || + target instanceof HTMLTextAreaElement + ) { + if (!(target instanceof HTMLInputElement && target.type === "submit")) { + const field = target.name as keyof Entity; + const result = insertEntityZodSchema.safeParse({ + [field]: target.value, + }); + const fieldError = result.success + ? undefined + : result.error.flatten().fieldErrors[field]; + + setErrors((prev) => ({ + ...prev, + [field]: fieldError, + })); + } + } + }; + return { errors, setErrors, handleChange, hasErrors }; +} diff --git a/src/lib/models/child.ts b/src/lib/models/child.ts deleted file mode 100644 index bc3785f..0000000 --- a/src/lib/models/child.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type DeviceModel from './device'; - -export default interface ChildModel { - id: string; - name: string; - avatar: string | null; - devices: DeviceModel[]; - // recentLocations: Location[]; -} diff --git a/src/lib/models/device.ts b/src/lib/models/device.ts deleted file mode 100644 index 0d4eb96..0000000 --- a/src/lib/models/device.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type PingModel from './ping'; - -export default interface DeviceModel { - id: string; - deviceName: string; - pings: PingModel[]; -} diff --git a/src/lib/models/location-update.ts b/src/lib/models/location-update.ts index 8501475..713c053 100644 --- a/src/lib/models/location-update.ts +++ b/src/lib/models/location-update.ts @@ -1,4 +1,4 @@ -import type Location from './location'; +import type Location from "./location"; export default interface LocationUpdate { childId: string; diff --git a/src/lib/models/ping.ts b/src/lib/models/ping.ts deleted file mode 100644 index 2d3384b..0000000 --- a/src/lib/models/ping.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface ChildModel { - id: string; - latitude: number; - longitude: number; - timestamp: Date; -} diff --git a/src/lib/services/auth/api.ts b/src/lib/services/auth/api.ts deleted file mode 100644 index 6a78e1b..0000000 --- a/src/lib/services/auth/api.ts +++ /dev/null @@ -1,6 +0,0 @@ -import generateApiKey from 'generate-api-key'; - -const createApiKey = () => - generateApiKey({ method: 'string', length: 256 }) as string; - -export { createApiKey }; diff --git a/src/lib/services/auth/provider.tsx b/src/lib/services/auth/provider.tsx deleted file mode 100644 index b8176c8..0000000 --- a/src/lib/services/auth/provider.tsx +++ /dev/null @@ -1,12 +0,0 @@ -"use client"; - -import { SessionProvider } from "next-auth/react"; -import { type ReactNode } from "react"; - -export default function NextAuthProvider({ - children, -}: { - children: ReactNode; -}) { - return {children}; -} diff --git a/src/lib/types/nav.ts b/src/lib/types/nav.ts deleted file mode 100644 index b19758e..0000000 --- a/src/lib/types/nav.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type NavItem = { - title: string; - href?: string; - disabled?: boolean; - external?: boolean; -}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index f1410e8..bd0c391 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,14 +1,6 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)) } - - -export type Action = "create" | "update" | "delete"; - -export type OptimisticAction = { - action: Action; - data: T; -}; diff --git a/src/lib/validations/auth.ts b/src/lib/validations/auth.ts deleted file mode 100644 index 218890b..0000000 --- a/src/lib/validations/auth.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as z from 'zod' - -export const userAuthSchema = z.object({ - email: z.string().email(), -}) diff --git a/src/lib/validations/child.ts b/src/lib/validations/child.ts deleted file mode 100644 index 2354154..0000000 --- a/src/lib/validations/child.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as z from 'zod'; - -export const newChildSchema = z.object({ - name: z.string().max(50), -}); diff --git a/src/lib/validations/connect-device.ts b/src/lib/validations/connect-device.ts deleted file mode 100644 index cae18e4..0000000 --- a/src/lib/validations/connect-device.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as z from 'zod'; - -export const connectDeviceSchema = z.object({ - childId: z.string().max(50), -}); diff --git a/src/server/api/root.ts b/src/server/api/root.ts index b7ae28c..9f67ced 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -1,5 +1,7 @@ -import { childRouter } from "@/server/api/routers/child"; +import { postRouter } from "@/server/api/routers/post"; import { createTRPCRouter } from "@/server/api/trpc"; +import { childrenRouter } from "./routers/children"; +import { devicesRouter } from "./routers/devices"; /** * This is the primary router for your server. @@ -7,7 +9,9 @@ import { createTRPCRouter } from "@/server/api/trpc"; * All routers added in /api/routers should be manually added here. */ export const appRouter = createTRPCRouter({ - child: childRouter, + post: postRouter, + children: childrenRouter, + devices: devicesRouter, }); // export type definition of API diff --git a/src/server/api/routers/child.ts b/src/server/api/routers/child.ts deleted file mode 100644 index 20b081b..0000000 --- a/src/server/api/routers/child.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from 'zod'; - -import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc'; -import { child } from '@/server/db/schema'; -import { eq } from 'drizzle-orm'; - -export const childRouter = createTRPCRouter({ - create: protectedProcedure - .input(z.object({ name: z.string().min(1) })) - .mutation(async ({ ctx, input }) => { - console.log('Child', 'Create', ctx.session); - const c = { - parentId: ctx.session.user.id, - name: input.name, - }; - const result = await ctx.db.insert(child).values(c).returning(); - return result[0]; - }), - - mine: protectedProcedure.query(async ({ ctx }) => { - return ctx.db.query.child.findMany({ - orderBy: (child, { asc }) => [asc(child.name)], - where: eq(child.parentId, ctx.session.user.id), - with: { - devices: { - with: { pings: true }, - }, - }, - }); - }), -}); diff --git a/src/server/api/routers/children.ts b/src/server/api/routers/children.ts new file mode 100644 index 0000000..26657c5 --- /dev/null +++ b/src/server/api/routers/children.ts @@ -0,0 +1,42 @@ +import { getChildById, getChildren } from "@/lib/api/children/queries"; +import { + publicProcedure, + createTRPCRouter, + protectedProcedure, +} from "@/server/api/trpc"; +import { + childIdSchema, + insertChildParams, + updateChildParams, +} from "@/server/db/schema/children"; +import { + createChild, + deleteChild, + updateChild, +} from "@/lib/api/children/mutations"; + +export const childrenRouter = createTRPCRouter({ + getChildren: publicProcedure.query(async () => { + return getChildren(); + }), + getChildById: publicProcedure + .input(childIdSchema) + .query(async ({ input }) => { + return getChildById(input.id); + }), + createChild: publicProcedure + .input(insertChildParams) + .mutation(async ({ input }) => { + return createChild(input); + }), + updateChild: publicProcedure + .input(updateChildParams) + .mutation(async ({ input }) => { + return updateChild(input.id, input); + }), + deleteChild: publicProcedure + .input(childIdSchema) + .mutation(async ({ input }) => { + return deleteChild(input.id); + }), +}); diff --git a/src/server/api/routers/devices.ts b/src/server/api/routers/devices.ts new file mode 100644 index 0000000..16dd03f --- /dev/null +++ b/src/server/api/routers/devices.ts @@ -0,0 +1,32 @@ +import { getDeviceById, getDevices } from "@/lib/api/devices/queries"; +import { publicProcedure, createTRPCRouter } from "@/server/api/trpc"; +import { + deviceIdSchema, + insertDeviceParams, + updateDeviceParams, +} from "@/server/db/schema/devices"; +import { createDevice, deleteDevice, updateDevice } from "@/lib/api/devices/mutations"; + +export const devicesRouter = createTRPCRouter({ + getDevices: publicProcedure.query(async () => { + return getDevices(); + }), + getDeviceById: publicProcedure.input(deviceIdSchema).query(async ({ input }) => { + return getDeviceById(input.id); + }), + createDevice: publicProcedure + .input(insertDeviceParams) + .mutation(async ({ input }) => { + return createDevice(input); + }), + updateDevice: publicProcedure + .input(updateDeviceParams) + .mutation(async ({ input }) => { + return updateDevice(input.id, input); + }), + deleteDevice: publicProcedure + .input(deviceIdSchema) + .mutation(async ({ input }) => { + return deleteDevice(input.id); + }), +}); diff --git a/src/server/api/routers/post.ts b/src/server/api/routers/post.ts new file mode 100644 index 0000000..88f2fc7 --- /dev/null +++ b/src/server/api/routers/post.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; + +import { createTRPCRouter, publicProcedure } from "@/server/api/trpc"; + +let post = { + id: 1, + name: "Hello World", +}; + +export const postRouter = createTRPCRouter({ + hello: publicProcedure + .input(z.object({ text: z.string() })) + .query(({ input }) => { + return { + greeting: `Hello ${input.text}`, + }; + }), + + create: publicProcedure + .input(z.object({ name: z.string().min(1) })) + .mutation(async ({ input }) => { + // simulate a slow db call + await new Promise((resolve) => setTimeout(resolve, 1000)); + + post = { id: post.id + 1, name: input.name }; + return post; + }), + + getLatest: publicProcedure.query(() => { + return post; + }), +}); diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 8b2d57d..213e30b 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -6,14 +6,10 @@ * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will * need to use are documented accordingly near the end. */ - -import { initTRPC, TRPCError } from "@trpc/server"; +import { initTRPC } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; -import { getServerAuthSession } from "@/server/auth"; -import { db } from "@/server/db"; - /** * 1. CONTEXT * @@ -27,11 +23,7 @@ import { db } from "@/server/db"; * @see https://trpc.io/docs/server/context */ export const createTRPCContext = async (opts: { headers: Headers }) => { - const session = await getServerAuthSession(); - return { - db, - session, ...opts, }; }; diff --git a/src/server/auth.ts b/src/server/auth.ts deleted file mode 100644 index c45c02b..0000000 --- a/src/server/auth.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { DrizzleAdapter } from "@auth/drizzle-adapter"; -import { - getServerSession, - type DefaultSession, - type NextAuthOptions, -} from "next-auth"; -import GoogleProvider from "next-auth/providers/google"; - -import { env } from "@/env"; -import { db } from "@/server/db"; -import { Adapter } from "next-auth/adapters"; -/** - * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` - * object and keep type safety. - * - * @see https://next-auth.js.org/getting-started/typescript#module-augmentation - */ -declare module "next-auth" { - interface Session extends DefaultSession { - user: { - id: string; - // ...other properties - // role: UserRole; - } & DefaultSession["user"]; - } - - // interface User { - // // ...other properties - // // role: UserRole; - // } -} - -/** - * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. - * - * @see https://next-auth.js.org/configuration/options - */ -export const authOptions: NextAuthOptions = { - adapter: DrizzleAdapter(db) as Adapter, - callbacks: { - session: async ({ session, token }) => { - if (session?.user) { - session.user.id = token.sub ?? ""; - } - return session; - }, - jwt: async ({ user, token }) => { - if (user) { - token.uid = user.id; - } - return token; - }, - }, - session: { - strategy: "jwt", - }, - providers: [ - GoogleProvider({ - clientId: env.GOOGLE_CLIENT_ID, - clientSecret: env.GOOGLE_CLIENT_SECRET, - }), - ], -}; - -/** - * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file. - * - * @see https://next-auth.js.org/configuration/nextjs - */ -export const getServerAuthSession = () => getServerSession(authOptions); diff --git a/src/server/db/index.ts b/src/server/db/index.ts index c21c95c..3353ef6 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -1,12 +1,23 @@ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; -import * as schema from "./schema"; -import * as extended from "~/server/db/schema/_root"; -import { env } from "@/env"; +import { env } from "@/env.mjs"; +import { + users, + accounts, + sessions, + verificationTokens, + children, + devices, +} from "@/server/db/schema/_root"; -const client = postgres(env.DATABASE_URL); -export const db = drizzle(client, { schema: { ...schema, ...extended } }); - -// console.log('DRIZZLE', 'migrating'); -// migrate(db, { migrationsFolder: 'drizzle' }) -// .then(() => console.log('DRIZZLE', 'migrated')); +export const client = postgres(env.DATABASE_URL); +export const db = drizzle(client, { + schema: { + devices, + children, + users, + accounts, + sessions, + verificationTokens, + }, +}); diff --git a/src/server/db/migrate.ts b/src/server/db/migrate.ts new file mode 100644 index 0000000..e3142de --- /dev/null +++ b/src/server/db/migrate.ts @@ -0,0 +1,36 @@ +import { env } from "@/env.mjs"; + +import { drizzle } from "drizzle-orm/postgres-js"; +import { migrate } from "drizzle-orm/postgres-js/migrator"; +import postgres from "postgres"; + + +const runMigrate = async () => { + if (!env.DATABASE_URL) { + throw new Error("DATABASE_URL is not defined"); + } + + +const connection = postgres(env.DATABASE_URL, { max: 1 }); + +const db = drizzle(connection); + + + console.log("⏳ Running migrations..."); + + const start = Date.now(); + + await migrate(db, { migrationsFolder: 'src/server/db/migrations' }); + + const end = Date.now(); + + console.log("βœ… Migrations completed in", end - start, "ms"); + + process.exit(0); +}; + +runMigrate().catch((err) => { + console.error("❌ Migration failed"); + console.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/drizzle/0000_wide_gravity.sql b/src/server/db/migrations/0000_slim_katie_power.sql similarity index 50% rename from drizzle/0000_wide_gravity.sql rename to src/server/db/migrations/0000_slim_katie_power.sql index e014db1..1b953c2 100644 --- a/drizzle/0000_wide_gravity.sql +++ b/src/server/db/migrations/0000_slim_katie_power.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS "account" ( - "userId" uuid NOT NULL, + "userId" text NOT NULL, "type" text NOT NULL, "provider" text NOT NULL, "providerAccountId" text NOT NULL, @@ -13,60 +13,33 @@ CREATE TABLE IF NOT EXISTS "account" ( CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId") ); --> statement-breakpoint -CREATE TABLE IF NOT EXISTS "child" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid - () NOT NULL, +CREATE TABLE IF NOT EXISTS "children" ( + "id" varchar(191) PRIMARY KEY NOT NULL, "name" varchar(256) NOT NULL, - "email" varchar(256), - "phone" varchar(256), - "avatar" varchar(256), - "key" varchar(256), - "parent_id" uuid NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "device" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid - () NOT NULL, - "device_id" varchar NOT NULL, - "child_id" uuid NOT NULL, - "device_name" varchar NOT NULL, - "api_key" varchar NOT NULL, - "pin" integer NOT NULL, - "expires" timestamp DEFAULT now - () + interval '1 hour', - CONSTRAINT "device_device_id_unique" UNIQUE("device_id"), - CONSTRAINT "device_api_key_unique" UNIQUE("api_key") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "ping" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid - () NOT NULL, - "device_id" uuid NOT NULL, - "latitude" double precision NOT NULL, - "longitude" double precision NOT NULL, - "timestamp" timestamp NOT NULL + "email" varchar(256) NOT NULL, + "avatar" varchar(256) NOT NULL, + "user_id" varchar(256) NOT NULL ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "session" ( "sessionToken" text PRIMARY KEY NOT NULL, - "userId" uuid NOT NULL, + "userId" text NOT NULL, "expires" timestamp NOT NULL ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "user" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid - () NOT NULL, + "id" text PRIMARY KEY NOT NULL, "name" text, "email" text NOT NULL, "emailVerified" timestamp, "image" text ); --> statement-breakpoint -CREATE TABLE IF NOT EXISTS "verification_token" ( - "identifier" varchar(255) NOT NULL, - "token" varchar(255) NOT NULL, +CREATE TABLE IF NOT EXISTS "verificationToken" ( + "identifier" text NOT NULL, + "token" text NOT NULL, "expires" timestamp NOT NULL, - CONSTRAINT "verification_token_identifier_token_pk" PRIMARY KEY("identifier","token") + CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token") ); --> statement-breakpoint DO $$ BEGIN @@ -75,6 +48,12 @@ EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "children" ADD CONSTRAINT "children_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint DO $$ BEGIN ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION diff --git a/src/server/db/migrations/0001_sticky_frightful_four.sql b/src/server/db/migrations/0001_sticky_frightful_four.sql new file mode 100644 index 0000000..75904a9 --- /dev/null +++ b/src/server/db/migrations/0001_sticky_frightful_four.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS "devices" ( + "id" varchar(191) PRIMARY KEY NOT NULL, + "name" varchar(256) NOT NULL, + "device_id" varchar(256) NOT NULL, + "child_id" varchar(256) NOT NULL, + "user_id" varchar(256) NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "devices" ADD CONSTRAINT "devices_child_id_children_id_fk" FOREIGN KEY ("child_id") REFERENCES "children"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "devices" ADD CONSTRAINT "devices_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/src/server/db/migrations/meta/0000_snapshot.json b/src/server/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..f09fad2 --- /dev/null +++ b/src/server/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,285 @@ +{ + "id": "76648d7a-0659-405a-92c4-4f0763229888", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "5", + "dialect": "pg", + "tables": { + "account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {} + }, + "children": { + "name": "children", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(191)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "children_user_id_user_id_fk": { + "name": "children_user_id_user_id_fk", + "tableFrom": "children", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/src/server/db/migrations/meta/0001_snapshot.json similarity index 68% rename from drizzle/meta/0000_snapshot.json rename to src/server/db/migrations/meta/0001_snapshot.json index 53ff92e..71ea5fb 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/src/server/db/migrations/meta/0001_snapshot.json @@ -1,6 +1,6 @@ { - "id": "c7d09662-b27f-4ac9-b546-1ef12bc6800a", - "prevId": "00000000-0000-0000-0000-000000000000", + "id": "3520a784-4841-418f-8cf4-7e58249c843f", + "prevId": "76648d7a-0659-405a-92c4-4f0763229888", "version": "5", "dialect": "pg", "tables": { @@ -10,7 +10,7 @@ "columns": { "userId": { "name": "userId", - "type": "uuid", + "type": "text", "primaryKey": false, "notNull": true }, @@ -102,16 +102,15 @@ }, "uniqueConstraints": {} }, - "child": { - "name": "child", + "children": { + "name": "children", "schema": "", "columns": { "id": { "name": "id", - "type": "uuid", + "type": "varchar(191)", "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid\n ()" + "notNull": true }, "name": { "name": "name", @@ -123,145 +122,104 @@ "name": "email", "type": "varchar(256)", "primaryKey": false, - "notNull": false - }, - "phone": { - "name": "phone", - "type": "varchar(256)", - "primaryKey": false, - "notNull": false + "notNull": true }, "avatar": { "name": "avatar", "type": "varchar(256)", "primaryKey": false, - "notNull": false + "notNull": true }, - "key": { - "name": "key", + "user_id": { + "name": "user_id", "type": "varchar(256)", "primaryKey": false, - "notNull": false - }, - "parent_id": { - "name": "parent_id", - "type": "uuid", - "primaryKey": false, "notNull": true } }, "indexes": {}, - "foreignKeys": {}, + "foreignKeys": { + "children_user_id_user_id_fk": { + "name": "children_user_id_user_id_fk", + "tableFrom": "children", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "device": { - "name": "device", + "devices": { + "name": "devices", "schema": "", "columns": { "id": { "name": "id", - "type": "uuid", + "type": "varchar(191)", "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid\n ()" + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true }, "device_id": { "name": "device_id", - "type": "varchar", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, "child_id": { "name": "child_id", - "type": "uuid", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "device_name": { - "name": "device_name", - "type": "varchar", - "primaryKey": false, - "notNull": true - }, - "api_key": { - "name": "api_key", - "type": "varchar", - "primaryKey": false, - "notNull": true - }, - "pin": { - "name": "pin", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now\n () + interval '1 hour'" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "device_device_id_unique": { - "name": "device_device_id_unique", - "nullsNotDistinct": false, - "columns": [ - "device_id" - ] - }, - "device_api_key_unique": { - "name": "device_api_key_unique", - "nullsNotDistinct": false, - "columns": [ - "api_key" - ] - } - } - }, - "ping": { - "name": "ping", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid\n ()" - }, - "device_id": { - "name": "device_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "latitude": { - "name": "latitude", - "type": "double precision", - "primaryKey": false, - "notNull": true - }, - "longitude": { - "name": "longitude", - "type": "double precision", - "primaryKey": false, - "notNull": true - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp", + "user_id": { + "name": "user_id", + "type": "varchar(256)", "primaryKey": false, "notNull": true } }, "indexes": {}, - "foreignKeys": {}, + "foreignKeys": { + "devices_child_id_children_id_fk": { + "name": "devices_child_id_children_id_fk", + "tableFrom": "devices", + "tableTo": "children", + "columnsFrom": [ + "child_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "devices_user_id_user_id_fk": { + "name": "devices_user_id_user_id_fk", + "tableFrom": "devices", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, "compositePrimaryKeys": {}, "uniqueConstraints": {} }, @@ -277,7 +235,7 @@ }, "userId": { "name": "userId", - "type": "uuid", + "type": "text", "primaryKey": false, "notNull": true }, @@ -313,10 +271,9 @@ "columns": { "id": { "name": "id", - "type": "uuid", + "type": "text", "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid\n ()" + "notNull": true }, "name": { "name": "name", @@ -348,19 +305,19 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, - "verification_token": { - "name": "verification_token", + "verificationToken": { + "name": "verificationToken", "schema": "", "columns": { "identifier": { "name": "identifier", - "type": "varchar(255)", + "type": "text", "primaryKey": false, "notNull": true }, "token": { "name": "token", - "type": "varchar(255)", + "type": "text", "primaryKey": false, "notNull": true }, @@ -374,8 +331,8 @@ "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": { - "verification_token_identifier_token_pk": { - "name": "verification_token_identifier_token_pk", + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", "columns": [ "identifier", "token" diff --git a/src/server/db/migrations/meta/_journal.json b/src/server/db/migrations/meta/_journal.json new file mode 100644 index 0000000..f46c363 --- /dev/null +++ b/src/server/db/migrations/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "5", + "dialect": "pg", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1705877584114, + "tag": "0000_slim_katie_power", + "breakpoints": true + }, + { + "idx": 1, + "version": "5", + "when": 1705877897654, + "tag": "0001_sticky_frightful_four", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts deleted file mode 100644 index 7cf7a52..0000000 --- a/src/server/db/schema.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { - timestamp, - text, - primaryKey, - integer, - pgTable, - uuid, - varchar, - pgSchema, - doublePrecision, -} from 'drizzle-orm/pg-core'; -import type { AdapterAccount } from '@auth/core/adapters'; -import { relations, sql } from 'drizzle-orm'; - -//#region auth -//TODO: use this schema once https://github.com/drizzle-team/drizzle-orm/issues/636 is fixed -const authSchema = pgSchema('auth'); -export const users = pgTable('user', { - id: uuid('id') - .notNull() - .primaryKey() - .default(sql`gen_random_uuid - ()`), - name: text('name'), - email: text('email').notNull(), - emailVerified: timestamp('emailVerified', { mode: 'date' }), - image: text('image'), -}); - -export const accounts = pgTable( - 'account', - { - userId: uuid('userId') - .notNull() - .references(() => users.id, { onDelete: 'cascade' }), - type: text('type').$type().notNull(), - provider: text('provider').notNull(), - providerAccountId: text('providerAccountId').notNull(), - refresh_token: text('refresh_token'), - access_token: text('access_token'), - expires_at: integer('expires_at'), - token_type: text('token_type'), - scope: text('scope'), - id_token: text('id_token'), - session_state: text('session_state'), - }, - (account) => ({ - compoundKey: primaryKey(account.provider, account.providerAccountId), - }), -); - -export const sessions = pgTable('session', { - sessionToken: text('sessionToken').notNull().primaryKey(), - userId: uuid('userId') - .notNull() - .references(() => users.id, { onDelete: 'cascade' }), - expires: timestamp('expires', { mode: 'date' }).notNull(), -}); - -export const verificationTokens = pgTable( - 'verification_token', - { - identifier: varchar('identifier', { length: 255 }).notNull(), - token: varchar('token', { length: 255 }).notNull(), - expires: timestamp('expires', { mode: 'date' }).notNull(), - }, - (vt) => ({ - compoundKey: primaryKey(vt.identifier, vt.token), - }), -); -//#endregion auth - -export const userRelations = relations(users, ({ many }) => ({ - children: many(child), -})); - -//#region child -export const child = pgTable('child', { - id: uuid('id') - .primaryKey() - .default(sql`gen_random_uuid - ()`), - name: varchar('name', { length: 256 }).notNull(), - email: varchar('email', { length: 256 }), - phone: varchar('phone', { length: 256 }), - avatar: varchar('avatar', { length: 256 }), - apiKey: varchar('key', { length: 256 }), - parentId: uuid('parent_id').notNull(), -}); -export const childRelations = relations(child, ({ one, many }) => ({ - parent: one(users, { - fields: [child.parentId], - references: [users.id], - }), - devices: many(device), -})); - -export const device = pgTable('device', { - id: uuid('id') - .primaryKey() - .default(sql`gen_random_uuid - ()`), - deviceId: varchar('device_id').notNull().unique(), - childId: uuid('child_id').notNull(), - deviceName: varchar('device_name').notNull(), - apiKey: varchar('api_key').notNull().unique(), - //TODO: make the device request/pin a separate table and enforce the expiry - pin: integer('pin').notNull(), - expires: timestamp('expires').default(sql`now - () + interval '1 hour'`), -}); -export const deviceRelations = relations(device, ({ one, many }) => ({ - child: one(child, { - fields: [device.childId], - references: [child.id], - }), - pings: many(ping), -})); - -export const ping = pgTable('ping', { - id: uuid('id') - .primaryKey() - .default(sql`gen_random_uuid - ()`), - deviceId: uuid('device_id').notNull(), - latitude: doublePrecision('latitude').notNull(), - longitude: doublePrecision('longitude').notNull(), - timestamp: timestamp('timestamp').notNull(), -}); -export const pingRelations = relations(ping, ({ one, many }) => ({ - device: one(device, { - fields: [ping.deviceId], - references: [device.id], - }), -})); diff --git a/src/server/db/schema/_root.ts b/src/server/db/schema/_root.ts new file mode 100644 index 0000000..39f5ff0 --- /dev/null +++ b/src/server/db/schema/_root.ts @@ -0,0 +1,5 @@ +import { users, accounts, sessions, verificationTokens } from "./auth" +import { children } from "./children"; +import { devices } from "./devices"; + +export { devices, children, users, accounts, sessions, verificationTokens } \ No newline at end of file diff --git a/src/server/db/schema/auth.ts b/src/server/db/schema/auth.ts new file mode 100644 index 0000000..f041d0a --- /dev/null +++ b/src/server/db/schema/auth.ts @@ -0,0 +1,58 @@ +import { + timestamp, + pgTable, + text, + primaryKey, + integer, +} from "drizzle-orm/pg-core"; +import type { AdapterAccount } from "@auth/core/adapters"; + +export const users = pgTable("user", { + id: text("id").notNull().primaryKey(), + name: text("name"), + email: text("email").notNull(), + emailVerified: timestamp("emailVerified", { mode: "date" }), + image: text("image"), +}); + +export const accounts = pgTable( + "account", + { + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + type: text("type").$type().notNull(), + provider: text("provider").notNull(), + providerAccountId: text("providerAccountId").notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: integer("expires_at"), + token_type: text("token_type"), + scope: text("scope"), + id_token: text("id_token"), + session_state: text("session_state"), + }, + (account) => ({ + compoundKey: primaryKey(account.provider, account.providerAccountId), + }) +); + +export const sessions = pgTable("session", { + sessionToken: text("sessionToken").notNull().primaryKey(), + userId: text("userId") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + expires: timestamp("expires", { mode: "date" }).notNull(), +}); + +export const verificationTokens = pgTable( + "verificationToken", + { + identifier: text("identifier").notNull(), + token: text("token").notNull(), + expires: timestamp("expires", { mode: "date" }).notNull(), + }, + (vt) => ({ + compoundKey: primaryKey(vt.identifier, vt.token), + }) +); diff --git a/src/server/db/schema/children.ts b/src/server/db/schema/children.ts new file mode 100644 index 0000000..f29fad9 --- /dev/null +++ b/src/server/db/schema/children.ts @@ -0,0 +1,48 @@ +import { varchar, pgTable } from "drizzle-orm/pg-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { type z } from "zod"; + +import { users } from "@/server/db/schema/auth"; +import { type getChildren } from "@/lib/api/children/queries"; + +import { randomUUID } from "crypto"; + +export const children = pgTable("children", { + id: varchar("id", { length: 191 }) + .primaryKey() + .$defaultFn(() => randomUUID()), + name: varchar("name", { length: 256 }).notNull(), + email: varchar("email", { length: 256 }).notNull(), + avatar: varchar("avatar", { length: 256 }).notNull(), + userId: varchar("user_id", { length: 256 }) + .references(() => users.id, { onDelete: "cascade" }) + .notNull(), +}); + +// Schema for children - used to validate API requests +export const insertChildSchema = createInsertSchema(children); + +export const insertChildParams = createSelectSchema(children, {}).omit({ + id: true, + userId: true, +}); + +export const updateChildSchema = createSelectSchema(children); + +export const updateChildParams = createSelectSchema(children, {}).omit({ + userId: true, +}); + +export const childIdSchema = updateChildSchema.pick({ id: true }); + +// Types for children - used to type API request params and within Components +export type Child = z.infer; +export type NewChild = z.infer; +export type NewChildParams = z.infer; +export type UpdateChildParams = z.infer; +export type ChildId = z.infer["id"]; + +// this type infers the return from getChildren() - meaning it will include any joins +export type CompleteChild = Awaited< + ReturnType +>["children"][number]; diff --git a/src/server/db/schema/devices.ts b/src/server/db/schema/devices.ts new file mode 100644 index 0000000..9a61ead --- /dev/null +++ b/src/server/db/schema/devices.ts @@ -0,0 +1,49 @@ +import { varchar, pgTable } from "drizzle-orm/pg-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { z } from "zod"; +import { children } from "./children" +import { users } from "@/server/db/schema/auth"; +import { type getDevices } from "@/lib/api/devices/queries"; + +import { randomUUID } from "crypto"; + + +export const devices = pgTable('devices', { + id: varchar("id", { length: 191 }).primaryKey().$defaultFn(() => randomUUID()), + name: varchar("name", { length: 256 }).notNull(), + deviceId: varchar("device_id", { length: 256 }).notNull(), + childId: varchar("child_id", { length: 256 }).references(() => children.id, { onDelete: "cascade" }).notNull(), + userId: varchar("user_id", { length: 256 }).references(() => users.id, { onDelete: "cascade" }).notNull(), +}); + + +// Schema for devices - used to validate API requests +export const insertDeviceSchema = createInsertSchema(devices); + +export const insertDeviceParams = createSelectSchema(devices, { + childId: z.coerce.string().min(1) +}).omit({ + id: true, + userId: true +}); + +export const updateDeviceSchema = createSelectSchema(devices); + +export const updateDeviceParams = createSelectSchema(devices,{ + childId: z.coerce.string().min(1) +}).omit({ + userId: true +}); + +export const deviceIdSchema = updateDeviceSchema.pick({ id: true }); + +// Types for devices - used to type API request params and within Components +export type Device = z.infer; +export type NewDevice = z.infer; +export type NewDeviceParams = z.infer; +export type UpdateDeviceParams = z.infer; +export type DeviceId = z.infer["id"]; + +// this type infers the return from getDevices() - meaning it will include any joins +export type CompleteDevice = Awaited>["devices"][number]; + diff --git a/src/server/db/scripts/auth.ts b/src/server/db/scripts/auth.ts deleted file mode 100644 index 276e6e0..0000000 --- a/src/server/db/scripts/auth.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { db } from ".."; -import * as schema from "../schema"; - -console.log("auth", "Seeding auth"); -await db - .insert(schema.accounts) - .values([ - { - userId: "2250f34e-997a-44de-ab8d-beddeda13525", - provider: "google", - type: "oauth", - providerAccountId: "112561477626832751929", - access_token: "FARTS", - expires_at: 9, - token_type: "Bearer", - scope: - "openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile", - id_token: "FARTS", - }, - ]) - .execute(); - -console.log("auth", "Seeded auth"); diff --git a/src/server/db/scripts/seed copy.ts b/src/server/db/scripts/seed copy.ts deleted file mode 100644 index 88077f7..0000000 --- a/src/server/db/scripts/seed copy.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ -import { db } from ".."; -import * as schema from "../schema"; -import { faker } from "@faker-js/faker"; - -const main = async () => { - console.log("seed", "Seeding"); - const seedUsers = [ - { - id: "2250f34e-997a-44de-ab8d-beddeda13525", - name: "Fergal Moran", - email: "fergal.moran@gmail.com", - emailVerified: new Date(), - }, - ]; - await db.insert(schema.users).values(seedUsers).execute(); - - const seedChildren = [ - { - id: "2250f34e-997a-44de-ab8d-beddeda13525", - name: "Lil Debuggles", - phone: "123 456 789", - email: "lildebuggles@kidarr.com", - avatar: faker.image.avatar(), - parentId: seedUsers[0]?.id!, - }, - ]; - await db.insert(schema.child).values(seedChildren).execute(); - - const seedDevices = [ - { - id: "5af79a30-df27-4646-9d9f-77e19b4191c1", - deviceId: "373791e3-afe3-49de-b0a2-842a44071585", - childId: seedChildren[0]?.id!, - deviceName: "Not an iPhone", - apiKey: "nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=", - pin: 1234, - expires: new Date(2065), - }, - ]; - await db.insert(schema.device).values(seedDevices).execute(); - - const seedPings = [ - { - deviceId: seedDevices[0]?.id!, - latitude: 51.903614, - longitude: -8.468399, - timestamp: new Date(), - }, - { - deviceId: seedDevices[0]?.id!, - latitude: 51.8985, - longitude: -8.4756, - timestamp: new Date(), - }, - { - deviceId: seedDevices[0]?.id!, - latitude: 51.93588161110811, - longitude: -8.495129534566756, - timestamp: new Date(), - }, - ]; - await db.insert(schema.ping).values(seedPings).execute(); - return; -}; - -main() - .then(() => { - console.log("seed", "Seeded"); - return; - }) - .catch((err) => { - console.error("seed", "Error seeding", err); - }); diff --git a/src/server/db/scripts/seed.ts b/src/server/db/scripts/seed.ts deleted file mode 100644 index 62f737e..0000000 --- a/src/server/db/scripts/seed.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ -import { db } from ".."; -import * as schema from "../schema"; -import { faker } from "@faker-js/faker"; - -console.log("seed", "Seeding"); -const seedUsers = [ - { - id: "2250f34e-997a-44de-ab8d-beddeda13525", - name: "Fergal Moran", - email: "fergal.moran@gmail.com", - emailVerified: new Date(), - }, -]; -await db.insert(schema.users).values(seedUsers).execute(); - -const seedChildren = [ - { - id: "2250f34e-997a-44de-ab8d-beddeda13525", - name: "Lil Debuggles", - phone: "123 456 789", - email: "lildebuggles@kidarr.com", - avatar: faker.image.avatar(), - parentId: seedUsers[0]?.id!, - }, -]; -await db.insert(schema.child).values(seedChildren).execute(); - -const seedDevices = [ - { - id: "5af79a30-df27-4646-9d9f-77e19b4191c1", - deviceId: "373791e3-afe3-49de-b0a2-842a44071585", - childId: seedChildren[0]?.id!, - deviceName: "Not an iPhone", - apiKey: "nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=", - pin: 1234, - expires: new Date(2065), - }, -]; -await db.insert(schema.device).values(seedDevices).execute(); - -const seedPings = [ - { - deviceId: seedDevices[0]?.id!, - latitude: 51.903614, - longitude: -8.468399, - timestamp: new Date(), - }, - { - deviceId: seedDevices[0]?.id!, - latitude: 51.8985, - longitude: -8.4756, - timestamp: new Date(), - }, - { - deviceId: seedDevices[0]?.id!, - latitude: 51.93588161110811, - longitude: -8.495129534566756, - timestamp: new Date(), - }, -]; -await db.insert(schema.ping).values(seedPings).execute(); -console.log("seed", "Seeded"); diff --git a/src/styles/globals.css b/src/styles/globals.css index cb3e8dc..77d3522 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,94 +1,76 @@ @tailwind base; @tailwind components; @tailwind utilities; - + @layer base { - :root { - --card: 283 36% 97%; - --ring: 283 93% 25%; - --input: 220 13% 91%; - --muted: 283 13% 92%; - --accent: 283 15% 81%; - --border: 220 13% 91%; - --popover: 283 36% 98%; - --primary: 283 93% 25%; - --secondary: 283 7% 90%; - --background: 283 36% 98%; - --foreground: 283 68% 2%; - --destructive: 4 84% 36%; - --card-foreground: 283 68% 1%; - --muted-foreground: 283 4% 37%; - --accent-foreground: 283 15% 21%; - --popover-foreground: 283 68% 2%; - --primary-foreground: 283 93% 85%; - --secondary-foreground: 283 7% 30%; - --destructive-foreground: 4 84% 96%; - --radius: 0.5rem; - } - - .dark { - --card: 283 47% 3%; - --ring: 283 93% 25%; - --input: 215 27.9% 16.9%; - --muted: 283 13% 8%; - --accent: 283 25% 17%; - --border: 215 27.9% 16.9%; - --popover: 283 47% 2%; - --primary: 283 93% 25%; - --secondary: 283 18% 12%; - --background: 283 47% 2%; - --foreground: 283 27% 98%; - --destructive: 4 84% 49%; - --card-foreground: 283 27% 99%; - --muted-foreground: 283 4% 63%; - --accent-foreground: 283 25% 77%; - --popover-foreground: 283 27% 98%; - --primary-foreground: 283 93% 85%; - --secondary-foreground: 283 18% 72%; - --destructive-foreground: 0 0% 100%; - } + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + + --radius: 0.5rem; } + + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + } +} + @layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - } -} - -.map { - width: 100%; - height: 40rem; -} - -img.leaflet-marker-icon { - border-radius: 50%; - border: 2px solid red; -} - -.leaflet-control { - z-index: 0 !important; -} - -.leaflet-pane { - z-index: 0 !important; -} - -.leaflet-top, -.leaflet-bottom { - z-index: 0 !important; -} - -.leaflet-popup-content-wrapper { - border-radius: 0px; - padding: 0px !important; -} - -.leaflet-popup-content { - padding: 0px !important; - border-radius: 0px !important; - margin-right: 0px !important; - margin-left: 0px !important; + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } } diff --git a/src/trpc/react.tsx b/src/trpc/react.tsx index 9f8d59d..a9b493a 100644 --- a/src/trpc/react.tsx +++ b/src/trpc/react.tsx @@ -10,10 +10,7 @@ import { getUrl, transformer } from "./shared"; export const api = createTRPCReact(); -export function TRPCReactProvider(props: { - children: React.ReactNode; - cookies: string; -}) { +export function TRPCReactProvider(props: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient()); const [trpcClient] = useState(() => @@ -27,15 +24,9 @@ export function TRPCReactProvider(props: { }), unstable_httpBatchStreamLink({ url: getUrl(), - headers() { - return { - cookie: props.cookies, - "x-trpc-source": "react", - }; - }, }), ], - }), + }) ); return ( diff --git a/src/trpc/server.ts b/src/trpc/server.ts index fa40ddb..e0ffd12 100644 --- a/src/trpc/server.ts +++ b/src/trpc/server.ts @@ -8,7 +8,7 @@ import { import { callProcedure } from "@trpc/server"; import { observable } from "@trpc/server/observable"; import { type TRPCErrorResponse } from "@trpc/server/rpc"; -import { cookies } from "next/headers"; +import { headers } from "next/headers"; import { cache } from "react"; import { appRouter, type AppRouter } from "@/server/api/root"; @@ -20,11 +20,11 @@ import { transformer } from "./shared"; * handling a tRPC call from a React Server Component. */ const createContext = cache(() => { + const heads = new Headers(headers()); + heads.set("x-trpc-source", "rsc"); + return createTRPCContext({ - headers: new Headers({ - cookie: cookies().toString(), - "x-trpc-source": "rsc", - }), + headers: heads, }); }); diff --git a/tailwind.config.ts b/tailwind.config.ts index 84287e8..d79e2ce 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,14 +1,8 @@ -import type { Config } from "tailwindcss" +import type { Config } from "tailwindcss"; const config = { darkMode: ["class"], - content: [ - './pages/**/*.{ts,tsx}', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './src/**/*.{ts,tsx}', - ], - prefix: "", + content: ["src/app/**/*.{ts,tsx}", "src/components/**/*.{ts,tsx}"], theme: { container: { center: true, @@ -54,10 +48,13 @@ const config = { }, }, borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, sm: "calc(var(--radius) - 4px)", }, + // fontFamily: { + // sans: ["var(--font-sans)", ...fontFamily.sans], + // }, keyframes: { "accordion-down": { from: { height: "0" }, @@ -75,6 +72,6 @@ const config = { }, }, plugins: [require("tailwindcss-animate")], -} satisfies Config +} satisfies Config; -export default config \ No newline at end of file +export default config; diff --git a/tsconfig.json b/tsconfig.json index c5eef6e..fbd998d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,35 @@ { "compilerOptions": { - /* Base Options: */ "esModuleInterop": true, "skipLibCheck": true, - "target": "es2022", + "target": "esnext", "allowJs": true, "resolveJsonModule": true, "moduleDetection": "force", "isolatedModules": true, - - /* Strictness */ "strict": true, "noUncheckedIndexedAccess": true, "checkJs": true, - - /* Bundled projects */ - "lib": ["dom", "dom.iterable", "ES2022"], + "lib": [ + "dom", + "dom.iterable", + "ES2022" + ], "noEmit": true, "module": "ESNext", "moduleResolution": "Bundler", "jsx": "preserve", - "plugins": [{ "name": "next" }], + "plugins": [ + { + "name": "next" + } + ], "incremental": true, - - /* Path Aliases */ - "baseUrl": ".", + "baseUrl": "./", "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, "include": [ @@ -38,5 +41,7 @@ "**/*.js", ".next/types/**/*.ts" ], - "exclude": ["node_modules"] -} + "exclude": [ + "node_modules" + ] +} \ No newline at end of file