Made a lot of the front end

This commit is contained in:
Gabriel Brown 2024-11-29 19:37:14 -06:00
parent 04a6322b55
commit 6ff62ca0a6
37 changed files with 1360 additions and 70 deletions

View File

@ -1,17 +0,0 @@
# When adding additional environment variables, the schema in "/src/env.js"
# should be updated accordingly.
# Examples:
# SERVERVAR="foo"
# NEXT_PUBLIC_CLIENTVAR="bar"
# Drizzle
## WAN
DATABASE_URL="postgresql://postgres:password@localhost:5432/example_db"
## LAN
DATABASE_URL="postgresql://postgres:password@localhost:5432/example_db"
# Auth.js
# openssl rand -base64 33
AUTH_SECRET=""
# Auth.js Providers:

View File

@ -27,7 +27,11 @@
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toast": "^1.2.1",
"@t3-oss/env-nextjs": "^0.10.1", "@t3-oss/env-nextjs": "^0.10.1",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",

270
pnpm-lock.yaml generated
View File

@ -38,9 +38,21 @@ importers:
'@radix-ui/react-popover': '@radix-ui/react-popover':
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-progress':
specifier: ^1.1.0
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-select':
specifier: ^2.1.2
version: 2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slider':
specifier: ^1.2.1
version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': '@radix-ui/react-slot':
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.3)(react@18.3.1) version: 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-tabs':
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-toast': '@radix-ui/react-toast':
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -778,6 +790,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
'@radix-ui/number@1.1.0':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
'@radix-ui/primitive@1.0.1': '@radix-ui/primitive@1.0.1':
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
@ -885,6 +900,15 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
'@radix-ui/react-context@1.1.1':
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dialog@1.0.5': '@radix-ui/react-dialog@1.0.5':
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
peerDependencies: peerDependencies:
@ -946,6 +970,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-dismissable-layer@1.1.1':
resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-dropdown-menu@2.1.1': '@radix-ui/react-dropdown-menu@2.1.1':
resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==} resolution: {integrity: sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==}
peerDependencies: peerDependencies:
@ -977,6 +1014,15 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
'@radix-ui/react-focus-guards@1.1.1':
resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-focus-scope@1.0.4': '@radix-ui/react-focus-scope@1.0.4':
resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==}
peerDependencies: peerDependencies:
@ -1112,6 +1158,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-portal@1.1.2':
resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-presence@1.0.1': '@radix-ui/react-presence@1.0.1':
resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies: peerDependencies:
@ -1138,6 +1197,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-presence@1.1.1':
resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@1.0.3': '@radix-ui/react-primitive@1.0.3':
resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
peerDependencies: peerDependencies:
@ -1164,6 +1236,19 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-progress@1.1.0':
resolution: {integrity: sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.0': '@radix-ui/react-roving-focus@1.1.0':
resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==}
peerDependencies: peerDependencies:
@ -1177,6 +1262,32 @@ packages:
'@types/react-dom': '@types/react-dom':
optional: true optional: true
'@radix-ui/react-select@2.1.2':
resolution: {integrity: sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slider@1.2.1':
resolution: {integrity: sha512-bEzQoDW0XP+h/oGbutF5VMWJPAl/UU8IJjr7h02SOHDIIIxq+cep8nItVNoBV+OMmahCdqdF38FTpmXoqQUGvw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.0.2': '@radix-ui/react-slot@1.0.2':
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies: peerDependencies:
@ -1195,6 +1306,19 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
'@radix-ui/react-tabs@1.1.1':
resolution: {integrity: sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-toast@1.2.1': '@radix-ui/react-toast@1.2.1':
resolution: {integrity: sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==} resolution: {integrity: sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==}
peerDependencies: peerDependencies:
@ -2959,6 +3083,16 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
react-remove-scroll@2.6.0:
resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
react-style-singleton@2.2.1: react-style-singleton@2.2.1:
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -3759,6 +3893,8 @@ snapshots:
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
'@radix-ui/number@1.1.0': {}
'@radix-ui/primitive@1.0.1': '@radix-ui/primitive@1.0.1':
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
@ -3854,6 +3990,12 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@radix-ui/react-context@1.1.1(@types/react@18.3.3)(react@18.3.1)':
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.3
'@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
@ -3932,6 +4074,19 @@ snapshots:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@types/react-dom': 18.3.0 '@types/react-dom': 18.3.0
'@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-dropdown-menu@2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@radix-ui/primitive': 1.1.0 '@radix-ui/primitive': 1.1.0
@ -3960,6 +4115,12 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.3)(react@18.3.1)':
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.3
'@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
@ -4116,6 +4277,16 @@ snapshots:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@types/react-dom': 18.3.0 '@types/react-dom': 18.3.0
'@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-presence@1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
@ -4137,6 +4308,16 @@ snapshots:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@types/react-dom': 18.3.0 '@types/react-dom': 18.3.0
'@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
@ -4156,6 +4337,16 @@ snapshots:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@types/react-dom': 18.3.0 '@types/react-dom': 18.3.0
'@radix-ui/react-progress@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@radix-ui/primitive': 1.1.0 '@radix-ui/primitive': 1.1.0
@ -4173,6 +4364,54 @@ snapshots:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@types/react-dom': 18.3.0 '@types/react-dom': 18.3.0
'@radix-ui/react-select@2.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-popper': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-visually-hidden': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
aria-hidden: 1.2.4
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-remove-scroll: 2.6.0(@types/react@18.3.3)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-slider@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1)': '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1)':
dependencies: dependencies:
'@babel/runtime': 7.25.6 '@babel/runtime': 7.25.6
@ -4188,6 +4427,22 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.3.3 '@types/react': 18.3.3
'@radix-ui/react-tabs@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-toast@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@radix-ui/react-toast@1.2.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@radix-ui/primitive': 1.1.0 '@radix-ui/primitive': 1.1.0
@ -5105,7 +5360,7 @@ snapshots:
debug: 4.3.6 debug: 4.3.6
enhanced-resolve: 5.17.1 enhanced-resolve: 5.17.1
eslint: 8.57.0 eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)
fast-glob: 3.3.2 fast-glob: 3.3.2
get-tsconfig: 4.7.6 get-tsconfig: 4.7.6
@ -5127,7 +5382,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
optionalDependencies: optionalDependencies:
@ -6120,6 +6375,17 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.3.3 '@types/react': 18.3.3
react-remove-scroll@2.6.0(@types/react@18.3.3)(react@18.3.1):
dependencies:
react: 18.3.1
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
tslib: 2.6.3
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.3.1): react-style-singleton@2.2.1(@types/react@18.3.3)(react@18.3.1):
dependencies: dependencies:
get-nonce: 1.0.1 get-nonce: 1.0.1

View File

@ -1,7 +1,7 @@
"use server" "use server"
import { auth } from "~/auth" import { auth } from "~/auth"
import BreadCrumbBillTracker from "~/components/home/breadcrumb/BreadCrumbBillTracker" import BreadCrumbBillTracker from "~/components/portal/home/breadcrumb/BreadCrumbBillTracker"
import BillTrackerCalendar from "~/components/billtracker/BillTrackerCalendar" import BillTrackerCalendar from "~/components/portal/billtracker/BillTrackerCalendar"
export default async function HomePage() { export default async function HomePage() {
const session = await auth() const session = await auth()

View File

View File

@ -0,0 +1,59 @@
import Link from 'next/link'
import { Button } from "~/components/ui/button"
import { Card, CardContent } from "~/components/ui/card"
import { CreditCard, FileText, MessageSquare, PenToolIcon as Tool, BarChart } from 'lucide-react'
export default function AccountLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8 text-primary">My Account</h1>
<div className="flex flex-col md:flex-row gap-8">
<Card className="w-full md:w-64 h-fit">
<CardContent className="p-4">
<nav className="space-y-2">
<Link href="/account" passHref>
<Button variant="ghost" className="w-full justify-start">
<BarChart className="mr-2 h-4 w-4" /> Dashboard
</Button>
</Link>
<Link href="/account/payments" passHref>
<Button variant="ghost" className="w-full justify-start">
<CreditCard className="mr-2 h-4 w-4" /> Payments
</Button>
</Link>
<Link href="/account/workorders" passHref>
<Button variant="ghost" className="w-full justify-start">
<Tool className="mr-2 h-4 w-4" /> Work Orders
</Button>
</Link>
<Link href="/account/messages" passHref>
<Button variant="ghost" className="w-full justify-start">
<MessageSquare className="mr-2 h-4 w-4" /> Messages
</Button>
</Link>
<Link href="/account/documents" passHref>
<Button variant="ghost" className="w-full justify-start">
<FileText className="mr-2 h-4 w-4" /> Documents
</Button>
</Link>
<Link href="/account/billtracker" passHref>
<Button variant="ghost" className="w-full justify-start">
<BarChart className="mr-2 h-4 w-4" /> Bill Tracker
</Button>
</Link>
</nav>
</CardContent>
</Card>
<main className="flex-1">
{children}
</main>
</div>
</div>
)
}

View File

112
src/app/account/page.tsx Normal file
View File

@ -0,0 +1,112 @@
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"
import { Button } from "~/components/ui/button"
import { Progress } from "~/components/ui/progress"
import { CreditCard, AlertTriangle, CheckCircle } from 'lucide-react'
export default function AccountHomePage() {
// This data would typically come from your backend
const accountData = {
balance: 1200,
nextPaymentDue: "2023-07-01",
recentPayment: {
amount: 1200,
date: "2023-06-01"
},
workOrders: [
{ id: 1, title: "Leaky faucet", status: "In Progress" },
{ id: 2, title: "Broken AC", status: "Scheduled" }
],
documents: [
{ id: 1, title: "Lease Agreement", date: "2023-01-15" },
{ id: 2, title: "Move-in Checklist", date: "2023-01-15" }
]
}
return (
<div className="space-y-8">
<Card>
<CardHeader>
<CardTitle>Account Overview</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Current Balance</CardTitle>
<CreditCard className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">${accountData.balance}</div>
<p className="text-xs text-muted-foreground">
Next payment due: {accountData.nextPaymentDue}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Recent Payment</CardTitle>
<CheckCircle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">${accountData.recentPayment.amount}</div>
<p className="text-xs text-muted-foreground">
Paid on: {accountData.recentPayment.date}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Lease Progress</CardTitle>
<AlertTriangle className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<Progress value={33} className="w-full" />
<p className="text-xs text-muted-foreground mt-2">
4 months remaining
</p>
</CardContent>
</Card>
</div>
</CardContent>
</Card>
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Recent Work Orders</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-4">
{accountData.workOrders.map(order => (
<li key={order.id} className="flex justify-between items-center">
<span>{order.title}</span>
<span className="text-sm text-muted-foreground">{order.status}</span>
</li>
))}
</ul>
<Button className="w-full mt-4">View All Work Orders</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Recent Documents</CardTitle>
</CardHeader>
<CardContent>
<ul className="space-y-4">
{accountData.documents.map(doc => (
<li key={doc.id} className="flex justify-between items-center">
<span>{doc.title}</span>
<span className="text-sm text-muted-foreground">{doc.date}</span>
</li>
))}
</ul>
<Button className="w-full mt-4">View All Documents</Button>
</CardContent>
</Card>
</div>
</div>
)
}

View File

View File

83
src/app/contact/page.tsx Normal file
View File

@ -0,0 +1,83 @@
import type { Metadata } from 'next'
import ContactForm from '~/components/contact/contact-form'
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"
import { MapPin, Phone, Mail, Clock } from 'lucide-react'
export const metadata: Metadata = {
title: 'Contact Us | PropertyPro',
description: 'Get in touch with PropertyPro for any inquiries about our properties or services.',
}
export default function ContactPage() {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8 text-primary">Contact Us</h1>
<div className="grid md:grid-cols-2 gap-8">
<div>
<ContactForm />
</div>
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Our Office</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<p className="flex items-center">
<MapPin className="mr-2 h-4 w-4 text-primary" />
123 Property Street, Cityville, State 12345
</p>
<p className="flex items-center">
<Phone className="mr-2 h-4 w-4 text-primary" />
(123) 456-7890
</p>
<p className="flex items-center">
<Mail className="mr-2 h-4 w-4 text-primary" />
info@propertypro.com
</p>
<p className="flex items-center">
<Clock className="mr-2 h-4 w-4 text-primary" />
Mon-Fri: 9am-5pm, Sat: 10am-3pm, Sun: Closed
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>FAQ</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold mb-1">How do I schedule a property viewing?</h3>
<p className="text-sm text-muted-foreground">You can schedule a viewing by contacting our office or using the form on this page.</p>
</div>
<div>
<h3 className="font-semibold mb-1">What documents do I need to apply for a rental?</h3>
<p className="text-sm text-muted-foreground">Typically, you'll need proof of income, identification, and references. Specific requirements may vary.</p>
</div>
<div>
<h3 className="font-semibold mb-1">How do I report maintenance issues?</h3>
<p className="text-sm text-muted-foreground">Tenants can report maintenance issues through their online account or by contacting our office directly.</p>
</div>
</CardContent>
</Card>
<div className="aspect-w-16 aspect-h-9">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3022.1422937950147!2d-73.98731968459391!3d40.74844797932681!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89c259a9b3117469%3A0xd134e199a405a163!2sEmpire%20State%20Building!5e0!3m2!1sen!2sus!4v1616562308246!5m2!1sen!2sus"
width="600"
height="450"
style={{border:0}}
allowFullScreen={true}
loading="lazy"
className="w-full h-full rounded-lg"
title="PropertyPro Office Location"
></iframe>
</div>
</div>
</div>
</div>
)
}

View File

@ -8,6 +8,7 @@ import { type Metadata } from "next";
import Theme_Toggle from '~/components/theme/theme_toggle' import Theme_Toggle from '~/components/theme/theme_toggle'
import { Button } from "~/components/ui/button" import { Button } from "~/components/ui/button"
import Link from 'next/link' import Link from 'next/link'
import Avatar_Popover from "~/components/auth/AvatarPopover";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Tenant Portal", title: "Tenant Portal",
@ -25,7 +26,75 @@ export default async function RootLayout({
}: Readonly<{ children: React.ReactNode }>) { }: Readonly<{ children: React.ReactNode }>) {
const session = await auth(); const session = await auth();
if (!session?.user) { if (!session?.user) {
} return (
<html lang="en">
<body
className={cn(
"min-h-screen bg-background font-sans antialiased",
fontSans.variable)}
>
<Theme_Provider
attribute="class"
defaultTheme="system"
enableSystem={true}
disableTransitionOnChange={true}
>
<SessionProvider>
<div className="flex flex-col min-h-screen">
<header className="bg-background shadow-sm">
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
<Link href="/" className="md:text-2xl font-bold text-primary">Magnolia Coast Properties</Link>
<nav className="hidden md:flex space-x-2 lg:space-x-4 text-sm lg:text-lg">
<Link href="/properties" className="text-gray-600 hover:text-primary">Properties</Link>
<Link href="/services" className="text-gray-600 hover:text-primary">Services</Link>
<Link href="/about" className="text-gray-600 hover:text-primary">About</Link>
<Link href="/contact" className="text-gray-600 hover:text-primary">Contact</Link>
</nav>
<div className="flex space-x-2 md:space-x-4">
<Theme_Toggle/>
<Link href="/login">
<Button variant="outline">Login</Button>
</Link>
</div>
</div>
</header>
{children}
<footer className="bg-background text-primary py-8">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 className="text-xl font-bold mb-4">Magnolia Coast Property Management</h3>
<p>Your trusted partner in property management</p>
</div>
<div>
<h4 className="text-lg font-semibold mb-4">Quick Links</h4>
<ul className="space-y-2">
<li><Link href="/properties" className="hover:text-primary-foreground">Properties</Link></li>
<li><Link href="/services" className="hover:text-primary-foreground">Services</Link></li>
<li><Link href="/about" className="hover:text-primary-foreground">About Us</Link></li>
<li><Link href="/contact" className="hover:text-primary-foreground">Contact</Link></li>
</ul>
</div>
<div>
<h4 className="text-lg font-semibold mb-4">Contact Us</h4>
<p>123 Property Street</p>
<p>City, State 12345</p>
<p>Phone: (123) 456-7890</p>
<p>Email: info@propertypro.com</p>
</div>
</div>
<div className="mt-8 text-center">
<p>&copy; 2024 Magnolia Coast Property Management LLC. All rights reserved.</p>
</div>
</div>
</footer>
</div>
</SessionProvider>
</Theme_Provider>
</body>
</html>
);
} else {
return ( return (
<html lang="en"> <html lang="en">
<body <body
@ -44,18 +113,17 @@ export default async function RootLayout({
<div className="flex flex-col min-h-screen"> <div className="flex flex-col min-h-screen">
<header className="bg-background shadow-sm"> <header className="bg-background shadow-sm">
<div className="container mx-auto px-4 py-4 flex justify-between items-center"> <div className="container mx-auto px-4 py-4 flex justify-between items-center">
<Link href="/" className="text-2xl:md font-bold text-primary">Magnolia Coast Properties</Link> <Link href="/" className="md:text-2xl font-bold text-primary">Magnolia Coast Properties</Link>
<nav className="hidden md:flex space-x-2 lg:space-x-4 text-sm lg:text-lg"> <nav className="hidden md:flex space-x-2 lg:space-x-4 text-sm lg:text-lg">
<Link href="#" className="text-gray-600 hover:text-primary">Home</Link> <Link href="/account" className="text-gray-600 hover:text-primary">My Account</Link>
<Link href="#" className="text-gray-600 hover:text-primary">Properties</Link> <Link href="/properties" className="text-gray-600 hover:text-primary">Properties</Link>
<Link href="#" className="text-gray-600 hover:text-primary">Services</Link> <Link href="/services" className="text-gray-600 hover:text-primary">Services</Link>
<Link href="#" className="text-gray-600 hover:text-primary">About</Link> <Link href="/about" className="text-gray-600 hover:text-primary">About</Link>
<Link href="#" className="text-gray-600 hover:text-primary">Contact</Link> <Link href="/contact" className="text-gray-600 hover:text-primary">Contact</Link>
</nav> </nav>
<div className="flex space-x-2"> <div className="flex space-x-2 md:space-x-4">
<Theme_Toggle/> <Theme_Toggle/>
<Button variant="outline">Login</Button> <Avatar_Popover/>
<Button>Register</Button>
</div> </div>
</div> </div>
</header> </header>
@ -70,11 +138,11 @@ export default async function RootLayout({
<div> <div>
<h4 className="text-lg font-semibold mb-4">Quick Links</h4> <h4 className="text-lg font-semibold mb-4">Quick Links</h4>
<ul className="space-y-2"> <ul className="space-y-2">
<li><Link href="#" className="hover:text-primary-foreground">Home</Link></li> <li><Link href="/account" className="hover:text-primary-foreground">My Account</Link></li>
<li><Link href="#" className="hover:text-primary-foreground">Properties</Link></li> <li><Link href="/properties" className="hover:text-primary-foreground">Properties</Link></li>
<li><Link href="#" className="hover:text-primary-foreground">Services</Link></li> <li><Link href="/services" className="hover:text-primary-foreground">Services</Link></li>
<li><Link href="#" className="hover:text-primary-foreground">About Us</Link></li> <li><Link href="/about" className="hover:text-primary-foreground">About Us</Link></li>
<li><Link href="#" className="hover:text-primary-foreground">Contact</Link></li> <li><Link href="/contact" className="hover:text-primary-foreground">Contact</Link></li>
</ul> </ul>
</div> </div>
<div> <div>
@ -97,3 +165,4 @@ export default async function RootLayout({
</html> </html>
); );
} }
}

127
src/app/login/page.tsx Normal file
View File

@ -0,0 +1,127 @@
'use client'
import { useState } from 'react'
import { Button } from "~/components/ui/button"
import { Input } from "~/components/ui/input"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"
import { Label } from "~/components/ui/label"
import { Eye, EyeOff } from 'lucide-react'
import Sign_In from "~/components/auth/client/SignInAppleButton"
export default function AuthPage() {
const [showPassword, setShowPassword] = useState(false)
const togglePasswordVisibility = () => setShowPassword(!showPassword)
return (
<div className="flex items-center justify-center min-h-screen bg-background">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl font-bold text-center text-primary">
Welcome to Magnolia Coast Property Management
</CardTitle>
<CardDescription className="text-center text-muted-foreground">
Login or create an account to get started
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="login" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="register">Register</TabsTrigger>
</TabsList>
<TabsContent value="login">
<form>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" placeholder="Enter your email" type="email" required />
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Input
id="password"
placeholder="Enter your password"
type={showPassword ? "text" : "password"}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={togglePasswordVisibility}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
</div>
</div>
<div className="flex flex-col justify-center">
<Button className="w-full mt-6 mb-4 text-md" type="submit">Login</Button>
<Sign_In/>
</div>
</form>
</TabsContent>
<TabsContent value="register">
<form>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="register-name">Full Name</Label>
<Input id="register-name" placeholder="Enter your full name" required />
</div>
<div className="space-y-2">
<Label htmlFor="register-email">Email</Label>
<Input id="register-email" placeholder="Enter your email" type="email" required />
</div>
<div className="space-y-2">
<Label htmlFor="register-password">Password</Label>
<div className="relative">
<Input
id="register-password"
placeholder="Create a password"
type={showPassword ? "text" : "password"}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={togglePasswordVisibility}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
</div>
</div>
<div className="flex flex-col justify-center">
<Button className="w-full mt-6 mb-4 text-md" type="submit">Register</Button>
<Sign_In/>
</div>
</form>
</TabsContent>
</Tabs>
</CardContent>
<CardFooter className="flex justify-center">
<p className="text-sm text-muted-foreground">
By continuing, you agree to our{' '}
<a href="#" className="underline text-primary">Terms of Service</a> and{' '}
<a href="#" className="underline text-primary">Privacy Policy</a>
</p>
</CardFooter>
</Card>
</div>
);
}

View File

@ -34,7 +34,9 @@ export default async function HomePage() {
<p className="text-gray-600">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p> <p className="text-gray-600">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</CardContent> </CardContent>
<CardFooter> <CardFooter>
<Link href={`/properties/${i}`} passHref>
<Button className="w-full">View Details</Button> <Button className="w-full">View Details</Button>
</Link>
</CardFooter> </CardFooter>
</Card> </Card>
))} ))}

View File

@ -0,0 +1,86 @@
import Link from 'next/link'
import { Button } from "~/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"
import { Home, Bed, Bath, Square, MapPin, Calendar, DollarSign } from 'lucide-react'
// This would typically come from your database
const property = {
id: 1,
title: "Luxurious Family Home",
type: "House",
bedrooms: 4,
bathrooms: 3,
area: 2500,
price: 750000,
address: "123 Main St, Anytown, USA",
description: "This beautiful family home features spacious living areas, a modern kitchen, and a large backyard perfect for entertaining. Located in a quiet neighborhood with easy access to schools and shopping.",
amenities: ["Central Air", "Garage", "Fireplace", "Hardwood Floors", "Swimming Pool"],
yearBuilt: 2015,
}
export default function PropertyPage({ params }: { params: { id: string } }) {
return (
<div className="container mx-auto px-4 py-8">
<Link href="/properties" className="text-primary hover:underline mb-4 inline-block">
&larr; Back to Properties
</Link>
<div className="grid md:grid-cols-2 gap-8">
<div>
<img src="/placeholder.svg?height=400&width=600" alt={property.title} className="w-full h-[400px] object-cover rounded-lg" />
<div className="mt-4 grid grid-cols-2 gap-4">
<img src="/placeholder.svg?height=150&width=250" alt="Additional view 1" className="w-full h-[150px] object-cover rounded" />
<img src="/placeholder.svg?height=150&width=250" alt="Additional view 2" className="w-full h-[150px] object-cover rounded" />
</div>
</div>
<div>
<h1 className="text-3xl font-bold mb-4 text-primary">{property.title}</h1>
<p className="text-xl font-semibold mb-4 text-primary">${property.price.toLocaleString()}</p>
<div className="grid grid-cols-2 gap-4 mb-6">
<div className="flex items-center text-muted-foreground">
<Home className="mr-2" /> {property.type}
</div>
<div className="flex items-center text-muted-foreground">
<Bed className="mr-2" /> {property.bedrooms} Bedrooms
</div>
<div className="flex items-center text-muted-foreground">
<Bath className="mr-2" /> {property.bathrooms} Bathrooms
</div>
<div className="flex items-center text-muted-foreground">
<Square className="mr-2" /> {property.area} sqft
</div>
<div className="flex items-center text-muted-foreground">
<MapPin className="mr-2" /> {property.address}
</div>
<div className="flex items-center text-muted-foreground">
<Calendar className="mr-2" /> Built in {property.yearBuilt}
</div>
</div>
<p className="mb-6 text-muted-foreground">{property.description}</p>
<Card className="mb-6">
<CardHeader>
<CardTitle>Amenities</CardTitle>
</CardHeader>
<CardContent>
<ul className="grid grid-cols-2 gap-2">
{property.amenities.map((amenity, index) => (
<li key={index} className="flex items-center text-muted-foreground">
<DollarSign className="mr-2 h-4 w-4" /> {amenity}
</li>
))}
</ul>
</CardContent>
</Card>
<Button className="w-full">Schedule a Viewing</Button>
</div>
</div>
</div>
)
}

116
src/app/properties/page.tsx Normal file
View File

@ -0,0 +1,116 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { Button } from "~/components/ui/button"
import { Input } from "~/components/ui/input"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"
import { Slider } from "~/components/ui/slider"
import { Search, Home, Bed, Bath, Square } from 'lucide-react'
// Mock data for properties
const properties = Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
title: `Property ${i + 1}`,
type: i % 3 === 0 ? 'House' : i % 3 === 1 ? 'Apartment' : 'Condo',
bedrooms: Math.floor(Math.random() * 5) + 1,
bathrooms: Math.floor(Math.random() * 3) + 1,
area: Math.floor(Math.random() * 1000) + 500,
price: Math.floor(Math.random() * 500000) + 100000,
}))
export default function PropertiesPage() {
const [sortBy, setSortBy] = useState('price')
const [filterType, setFilterType] = useState('All')
const [priceRange, setPriceRange] = useState([0, 1000000])
const [searchTerm, setSearchTerm] = useState('')
const filteredAndSortedProperties = properties
.filter(property =>
(filterType === 'All' || property.type === filterType) &&
property.price >= priceRange[0] && property.price <= priceRange[1] &&
property.title.toLowerCase().includes(searchTerm.toLowerCase())
)
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price
if (sortBy === 'bedrooms') return b.bedrooms - a.bedrooms
return 0
})
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8 text-primary">Available Properties</h1>
<div className="mb-8 flex flex-wrap gap-4">
<Input
placeholder="Search properties..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="max-w-xs"
/>
<Select onValueChange={setSortBy} defaultValue={sortBy}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="price">Price</SelectItem>
<SelectItem value="bedrooms">Bedrooms</SelectItem>
</SelectContent>
</Select>
<Select onValueChange={setFilterType} defaultValue={filterType}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Property type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="All">All</SelectItem>
<SelectItem value="House">House</SelectItem>
<SelectItem value="Apartment">Apartment</SelectItem>
<SelectItem value="Condo">Condo</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2 text-primary">Price Range</h2>
<Slider
min={0}
max={1000000}
step={10000}
value={priceRange}
onValueChange={setPriceRange}
className="max-w-md"
/>
<div className="mt-2 text-sm text-muted-foreground">
${priceRange[0].toLocaleString()} - ${priceRange[1].toLocaleString()}
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredAndSortedProperties.map((property) => (
<Card key={property.id}>
<CardHeader>
<CardTitle>{property.title}</CardTitle>
</CardHeader>
<CardContent>
<img src={`/placeholder.svg?height=200&width=300`} alt={property.title} className="w-full h-48 object-cover mb-4 rounded" />
<div className="flex justify-between text-muted-foreground">
<span><Home className="inline mr-1" /> {property.type}</span>
<span><Bed className="inline mr-1" /> {property.bedrooms}</span>
<span><Bath className="inline mr-1" /> {property.bathrooms}</span>
<span><Square className="inline mr-1" /> {property.area} sqft</span>
</div>
<p className="mt-4 text-xl font-bold text-primary">${property.price.toLocaleString()}</p>
</CardContent>
<CardFooter>
<Link href={`/properties/${property.id}`} passHref>
<Button className="w-full">View Details</Button>
</Link>
</CardFooter>
</Card>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,9 @@
const No_Session = () => {
return (
<div className="w-2/3 mx-auto text-center pt-10">
<h1 className="text-3xl font-semibold pb-4">Unauthorized Access</h1>
<p className="text-xl">Please sign in to view this page.</p>
</div>
);
}
export default No_Session;

View File

@ -4,12 +4,12 @@ import Image from "next/image"
export default function Sign_In() { export default function Sign_In() {
return ( return (
<div className="flex flex-row bg-primary py-3 px-10 rounded-xl text-lg font-semibold <Button onClick={() => signIn("apple")} className="flex flex-row bg-primary py-3
mt-10 text-background my-auto"> px-10 rounded-md text-md font-semibold text-background">
<Image src="/logos/Apple_logo_black.svg" alt="Apple logo" width={20} height={20} <Image src="/logos/Apple_logo_black.svg" alt="Apple logo" width={16} height={16}
className="mr-4 my-auto" className="mr-4"
/> />
<Button onClick={() => signIn("apple")}>Sign in with Apple</Button> Sign in with Apple
</div> </Button>
); );
} }

View File

@ -2,5 +2,13 @@ import { signOut } from "next-auth/react"
import { Button } from "~/components/ui/button" import { Button } from "~/components/ui/button"
export default function Sign_Out() { export default function Sign_Out() {
return <Button onClick={() => signOut()}>Sign Out</Button> return (
<Button
onClick={() => signOut()}
className="flex flex-row bg-yellow py-3
px-10 rounded-md text-md font-semibold text-background"
>
Sign out
</Button>
);
} }

View File

@ -1,3 +1,4 @@
import { Button } from "~/components/ui/button"
import { signOut } from "~/auth" import { signOut } from "~/auth"
export default function Sign_Out() { export default function Sign_Out() {
@ -9,7 +10,10 @@ export default function Sign_Out() {
await signOut() await signOut()
}} }}
> >
<button type="submit" className="w-full">Sign Out</button> <Button type="submit" className="w-full"
>
Sign Out
</Button>
</form> </form>
) )
} }

View File

@ -0,0 +1,82 @@
'use client'
import { useState } from 'react'
import { Button } from "~/components/ui/button"
import { Input } from "~/components/ui/input"
import { Textarea } from "~/components/ui/textarea"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
import { Label } from "~/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"
export default function ContactForm() {
const [formStatus, setFormStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle')
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
setFormStatus('submitting')
// Here you would typically send the form data to your server
// For this example, we'll just simulate a submission
setTimeout(() => {
setFormStatus('success')
}, 2000)
}
return (
<Card>
<CardHeader>
<CardTitle>Send us a message</CardTitle>
<CardDescription>We&apos;ll get back to you as soon as possible.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit}>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Name</Label>
<Input id="name" name="name" placeholder="Your name" required />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="email">Email</Label>
<Input id="email" name="email" placeholder="Your email" type="email" required />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="phone">Phone</Label>
<Input id="phone" name="phone" placeholder="Your phone number" type="tel" />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="subject">Subject</Label>
<Select name="subject" required>
<SelectTrigger>
<SelectValue placeholder="Select a subject" />
</SelectTrigger>
<SelectContent>
<SelectItem value="general">General Inquiry</SelectItem>
<SelectItem value="viewing">Property Viewing</SelectItem>
<SelectItem value="application">Rental Application</SelectItem>
<SelectItem value="maintenance">Maintenance Request</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="message">Message</Label>
<Textarea id="message" name="message" placeholder="Your message" required />
</div>
</div>
<CardFooter className="flex justify-between mt-4 px-0">
<Button type="submit" disabled={formStatus === 'submitting'}>
{formStatus === 'submitting' ? 'Sending...' : 'Send Message'}
</Button>
{formStatus === 'success' && (
<p className="text-sm text-green-600">Message sent successfully!</p>
)}
{formStatus === 'error' && (
<p className="text-sm text-red-600">There was an error sending your message. Please try again.</p>
)}
</CardFooter>
</form>
</CardContent>
</Card>
)
}

View File

@ -1,7 +1,7 @@
"use client" "use client"
import * as React from "react" import * as React from "react"
import { Calendar } from "~/components/ui/BillTrackerCalendar" import { Calendar } from "~/components/ui/BillTrackerCalendar"
import CreateBillForm from "~/components/billtracker/CreateBillForm" import CreateBillForm from "~/components/portal/billtracker/CreateBillForm"
export default function BillTrackerCalendar() { export default function BillTrackerCalendar() {
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(undefined) const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(undefined)

View File

@ -3,7 +3,7 @@ import {
Drawer, Drawer,
DrawerTrigger, DrawerTrigger,
} from "~/components/ui/drawer" } from "~/components/ui/drawer"
import CreateBillDrawer from "~/components/billtracker/CreateBillDrawer" import CreateBillDrawer from "~/components/portal/billtracker/CreateBillDrawer"
import { Button } from "~/components/ui/button" import { Button } from "~/components/ui/button"
type CreateBillFormProps = { type CreateBillFormProps = {

View File

@ -7,7 +7,7 @@ import {
DrawerHeader, DrawerHeader,
DrawerTitle, DrawerTitle,
} from "~/components/ui/drawer" } from "~/components/ui/drawer"
import BillForm from "~/components/billtracker/BillForm" import BillForm from "~/components/portal/billtracker/BillForm"
type CreateBillDrawerProps = { type CreateBillDrawerProps = {
date: Date; date: Date;

View File

@ -13,28 +13,13 @@ const fontSans = FontSans({
export default function Nav_Bar() { export default function Nav_Bar() {
return ( return (
<div className={cn("flex flex-col justify-start items-start " + <div className={cn("flex flex-col justify-start items-start h-5/6 my-auto" +
"py-6 text-lg md:text-xl lg:text-2xl font-semibold font-sans antialiased", fontSans.variable)} "py-6 text-lg md:text-xl lg:text-2xl font-semibold font-sans antialiased", fontSans.variable)}
> >
<Card className="md:p-4"> <Card className="md:p-4">
<CardContent className="py-4"> <CardContent className="py-4">
<Link href="/"> <Link href="/">
Make Payment Payments
</Link>
</CardContent>
<CardContent className="py-4">
<Link href="/">
Auto-Payment
</Link>
</CardContent>
<CardContent className="py-4">
<Link href="/">
Payment Methods
</Link>
</CardContent>
<CardContent className="py-4">
<Link href="/">
Payment History
</Link> </Link>
</CardContent> </CardContent>
<CardContent className="py-4"> <CardContent className="py-4">
@ -53,7 +38,7 @@ export default function Nav_Bar() {
</Link> </Link>
</CardContent> </CardContent>
<CardContent className="pt-4"> <CardContent className="pt-4">
<Link href="/billtracker"> <Link href="/account/billtracker">
Bill Tracker Bill Tracker
</Link> </Link>
</CardContent> </CardContent>

View File

@ -10,7 +10,7 @@ export default function Breadcrumb_Home() {
<Breadcrumb className="w-full m-auto flex flex-row justify-center items-center"> <Breadcrumb className="w-full m-auto flex flex-row justify-center items-center">
<BreadcrumbList> <BreadcrumbList>
<BreadcrumbItem> <BreadcrumbItem>
<Link href="/"> <Link href="/account">
<h1 className="text-3xl font-bold text-center font-sans antialiased"> <h1 className="text-3xl font-bold text-center font-sans antialiased">
Dashboard Dashboard
</h1> </h1>

View File

@ -13,6 +13,7 @@ import {
export default function Theme_Toggle() { export default function Theme_Toggle() {
const { setTheme } = useTheme() const { setTheme } = useTheme()
return ( return (
<div className="mx-2">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="border-none"> <Button variant="outline" size="icon" className="border-none">
@ -33,5 +34,6 @@ export default function Theme_Toggle() {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div>
) )
} }

View File

@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "~/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@ -0,0 +1,160 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "~/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "~/lib/utils"
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
))
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }

View File

@ -0,0 +1,55 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "~/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "~/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }