dep bumps, add pages feature, bug fixes, type improvements
This commit is contained in:
parent
e21d896669
commit
fec58f2465
96 changed files with 645 additions and 384 deletions
|
@ -3,6 +3,7 @@ module.exports = {
|
||||||
preset: "ts-jest",
|
preset: "ts-jest",
|
||||||
testEnvironment: "node",
|
testEnvironment: "node",
|
||||||
setupFiles: ["<rootDir>/test/setup-tests.ts"],
|
setupFiles: ["<rootDir>/test/setup-tests.ts"],
|
||||||
|
// TODO: update to app dir
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
"@lib/(.*)": "<rootDir>/src/lib/$1",
|
"@lib/(.*)": "<rootDir>/src/lib/$1",
|
||||||
"@routes/(.*)": "<rootDir>/src/routes/$1"
|
"@routes/(.*)": "<rootDir>/src/routes/$1"
|
||||||
|
|
|
@ -26,6 +26,9 @@ const nextConfig = {
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
NEXT_PUBLIC_DRIFT_URL: process.env.DRIFT_URL
|
NEXT_PUBLIC_DRIFT_URL: process.env.DRIFT_URL
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
// ignoreBuildErrors: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
package.json
10
package.json
|
@ -14,8 +14,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next-auth/prisma-adapter": "^1.0.5",
|
"@next-auth/prisma-adapter": "^1.0.5",
|
||||||
"@next/eslint-plugin-next": "13.1.7-canary.23",
|
"@next/eslint-plugin-next": "13.2.1-canary.0",
|
||||||
"@next/font": "13.1.7-canary.23",
|
"@next/font": "13.2.1-canary.0",
|
||||||
"@prisma/client": "^4.9.0",
|
"@prisma/client": "^4.9.0",
|
||||||
"@radix-ui/react-dialog": "^1.0.2",
|
"@radix-ui/react-dialog": "^1.0.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
"client-zip": "2.3.0",
|
"client-zip": "2.3.0",
|
||||||
"jest": "^29.4.1",
|
"jest": "^29.4.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"next": "13.1.7-canary.23",
|
"next": "13.2.1-canary.0",
|
||||||
"next-auth": "^4.19.2",
|
"next-auth": "^4.19.2",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/bundle-analyzer": "13.1.6",
|
"@next/bundle-analyzer": "13.1.7-canary.26",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"csstype": "^3.1.1",
|
"csstype": "^3.1.1",
|
||||||
"eslint": "8.33.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-next": "13.1.6",
|
"eslint-config-next": "13.1.7-canary.26",
|
||||||
"next-unused": "0.0.6",
|
"next-unused": "0.0.6",
|
||||||
"prettier": "2.8.3",
|
"prettier": "2.8.3",
|
||||||
"prisma": "^4.9.0",
|
"prisma": "^4.9.0",
|
||||||
|
|
157
pnpm-lock.yaml
157
pnpm-lock.yaml
|
@ -2,9 +2,9 @@ lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@next-auth/prisma-adapter': ^1.0.5
|
'@next-auth/prisma-adapter': ^1.0.5
|
||||||
'@next/bundle-analyzer': 13.1.6
|
'@next/bundle-analyzer': 13.1.7-canary.26
|
||||||
'@next/eslint-plugin-next': 13.1.7-canary.23
|
'@next/eslint-plugin-next': 13.2.1-canary.0
|
||||||
'@next/font': 13.1.7-canary.23
|
'@next/font': 13.2.1-canary.0
|
||||||
'@prisma/client': ^4.9.0
|
'@prisma/client': ^4.9.0
|
||||||
'@radix-ui/react-dialog': ^1.0.2
|
'@radix-ui/react-dialog': ^1.0.2
|
||||||
'@radix-ui/react-dropdown-menu': ^2.0.2
|
'@radix-ui/react-dropdown-menu': ^2.0.2
|
||||||
|
@ -28,10 +28,10 @@ specifiers:
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
csstype: ^3.1.1
|
csstype: ^3.1.1
|
||||||
eslint: 8.33.0
|
eslint: 8.33.0
|
||||||
eslint-config-next: 13.1.6
|
eslint-config-next: 13.1.7-canary.26
|
||||||
jest: ^29.4.1
|
jest: ^29.4.1
|
||||||
lodash.debounce: ^4.0.8
|
lodash.debounce: ^4.0.8
|
||||||
next: 13.1.7-canary.23
|
next: 13.2.1-canary.0
|
||||||
next-auth: ^4.19.2
|
next-auth: ^4.19.2
|
||||||
next-themes: ^0.2.1
|
next-themes: ^0.2.1
|
||||||
next-unused: 0.0.6
|
next-unused: 0.0.6
|
||||||
|
@ -55,8 +55,8 @@ specifiers:
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next-auth/prisma-adapter': 1.0.5_77bhi65b6v5jbrzr36rs2ojwe4
|
'@next-auth/prisma-adapter': 1.0.5_77bhi65b6v5jbrzr36rs2ojwe4
|
||||||
'@next/eslint-plugin-next': 13.1.7-canary.23
|
'@next/eslint-plugin-next': 13.2.1-canary.0
|
||||||
'@next/font': 13.1.7-canary.23
|
'@next/font': 13.2.1-canary.0
|
||||||
'@prisma/client': 4.9.0_prisma@4.9.0
|
'@prisma/client': 4.9.0_prisma@4.9.0
|
||||||
'@radix-ui/react-dialog': 1.0.2_5ndqzdd6t4rivxsukjv3i3ak2q
|
'@radix-ui/react-dialog': 1.0.2_5ndqzdd6t4rivxsukjv3i3ak2q
|
||||||
'@radix-ui/react-dropdown-menu': 2.0.2_5ndqzdd6t4rivxsukjv3i3ak2q
|
'@radix-ui/react-dropdown-menu': 2.0.2_5ndqzdd6t4rivxsukjv3i3ak2q
|
||||||
|
@ -69,9 +69,9 @@ dependencies:
|
||||||
client-zip: 2.3.0
|
client-zip: 2.3.0
|
||||||
jest: 29.4.1_@types+node@18.11.18
|
jest: 29.4.1_@types+node@18.11.18
|
||||||
lodash.debounce: 4.0.8
|
lodash.debounce: 4.0.8
|
||||||
next: 13.1.7-canary.23_biqbaboplfbrettd7655fr4n2y
|
next: 13.2.1-canary.0_biqbaboplfbrettd7655fr4n2y
|
||||||
next-auth: 4.19.2_de7upave4ddivcfp3gf7tpswk4
|
next-auth: 4.19.2_avkzbs57los6fogzead7rr4u74
|
||||||
next-themes: 0.2.1_de7upave4ddivcfp3gf7tpswk4
|
next-themes: 0.2.1_avkzbs57los6fogzead7rr4u74
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
react-datepicker: 4.8.0_biqbaboplfbrettd7655fr4n2y
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
@ -89,7 +89,7 @@ optionalDependencies:
|
||||||
sharp: 0.31.3
|
sharp: 0.31.3
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@next/bundle-analyzer': 13.1.6
|
'@next/bundle-analyzer': 13.1.7-canary.26
|
||||||
'@types/bcrypt': 5.0.0
|
'@types/bcrypt': 5.0.0
|
||||||
'@types/lodash.debounce': 4.0.7
|
'@types/lodash.debounce': 4.0.7
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
|
@ -103,7 +103,7 @@ devDependencies:
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
csstype: 3.1.1
|
csstype: 3.1.1
|
||||||
eslint: 8.33.0
|
eslint: 8.33.0
|
||||||
eslint-config-next: 13.1.6_zkdaqh7it7uc4cvz2haft7rc6u
|
eslint-config-next: 13.1.7-canary.26_zkdaqh7it7uc4cvz2haft7rc6u
|
||||||
next-unused: 0.0.6
|
next-unused: 0.0.6
|
||||||
prettier: 2.8.3
|
prettier: 2.8.3
|
||||||
prisma: 4.9.0
|
prisma: 4.9.0
|
||||||
|
@ -819,11 +819,11 @@ packages:
|
||||||
next-auth: ^4
|
next-auth: ^4
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/client': 4.9.0_prisma@4.9.0
|
'@prisma/client': 4.9.0_prisma@4.9.0
|
||||||
next-auth: 4.19.2_de7upave4ddivcfp3gf7tpswk4
|
next-auth: 4.19.2_avkzbs57los6fogzead7rr4u74
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/bundle-analyzer/13.1.6:
|
/@next/bundle-analyzer/13.1.7-canary.26:
|
||||||
resolution: {integrity: sha512-rJS9CtLoGT58mL+v2ISKANosFFWP/0YKYByHQ3vTaZrbQP8b1rYRxd2QVMJmnSXaFkiP9URt1XJ6OdGyVq5b6g==}
|
resolution: {integrity: sha512-oRAFBr3qEsOC8cwnTWjodckrx1df1pKDSIumvvj9xcp4QeymwDpFdjz2Dp3Manh1Q/kWX+adqNu6fyJUnVDI4A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
webpack-bundle-analyzer: 4.7.0
|
webpack-bundle-analyzer: 4.7.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -831,28 +831,28 @@ packages:
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/env/13.1.7-canary.23:
|
/@next/env/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-7AHpOQHk0xQlLVFjvIaIkAhIsQ5QbIkZRySRigZBWJJ8ycyf6clZe8ileK/sbKdRDKT8O0YeuxtD/kOO8cRMXQ==}
|
resolution: {integrity: sha512-6e5sszeQUWRFZpy/HXVWs18DKbjErcF/pMKS8pIsknejN1DQPRxNFEI+QuXIf76N+r87K1qR/qWQx4WKQof8Rw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next/13.1.6:
|
/@next/eslint-plugin-next/13.1.7-canary.26:
|
||||||
resolution: {integrity: sha512-o7cauUYsXjzSJkay8wKjpKJf2uLzlggCsGUkPu3lP09Pv97jYlekTC20KJrjQKmSv5DXV0R/uks2ZXhqjNkqAw==}
|
resolution: {integrity: sha512-0Cs2jSO+4vmyiWPL3ryMRGIl2+UZ+rV9TFaxzGLZUfN2yQKodR2Ilt3CWmRBFNRJr6MV2N+BIdIQ5MPy/TBeRg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
glob: 7.1.7
|
glob: 7.1.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/eslint-plugin-next/13.1.7-canary.23:
|
/@next/eslint-plugin-next/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-8Se+LIQ4iOKxelhq0Va14PXkOm2UFgPQPJJqmN8GojiiY3gVKB2WJHsqDDu6o9dPNt7OgemLtQePcJH2barw5Q==}
|
resolution: {integrity: sha512-OwJgZrTGj/WovColJHOUd2RB+2uju12cYL2oL5CnyNTz999GJ/z/esghfCAqL5fh4YaiQqlZCRJEbbymTsiU0Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
glob: 7.1.7
|
glob: 7.1.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/font/13.1.7-canary.23:
|
/@next/font/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-y5pZjah3b5n2X1yMMAImPwAMi2hFY6Maip76R6+VzLjak+pNMB5jF72ieZoRsibGxt+efCoWOWIkh5s7ZO8YrA==}
|
resolution: {integrity: sha512-ByXy8uwtXMihrIKmkcDZxvLgfE5i5Y0kowcg+vATw+b7ukzUzlesI+65VWTo2AHVdtrBPGAdAjyJJTNvr/q4nQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/swc-android-arm-eabi/13.1.7-canary.23:
|
/@next/swc-android-arm-eabi/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-LxaoUF1bqi0hPrOci7RChuOg4oJA2IHiC6SviAlSTzWUgi3eJuigvfptTylO1ElBX4MMSJ5mqpq1h5fNQIg5/A==}
|
resolution: {integrity: sha512-irsC3EdtmroNaHrKeqZDJTcQ15wrTWp5JJ8IIMJ6GvW/vhVENk+unnVoxD4BQLmgelCbzi7udusISDCkUUWCkg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -860,8 +860,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-android-arm64/13.1.7-canary.23:
|
/@next/swc-android-arm64/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-gsJyRGzJlBLZNNfpPwn1T1MgeT3p/Ztx3lMuc37+v+sMjgegWZy7h5HbtTp7IT+y4DAwknNcSyLIidH88qj9lA==}
|
resolution: {integrity: sha512-3AyZ4THkuamCyOhFkrqFY38IMI+YFmxomOcFHK+67hrqfHQRkH0boL15SnHAzURlAeruI/Mw9/PuYshYXn3Usg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -869,8 +869,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-arm64/13.1.7-canary.23:
|
/@next/swc-darwin-arm64/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-29HDi1EQkNaiNU0yJDikIV4jAJvs52TeGqTDf9tIkHFOAvmAF50mGbaL2urcRhy1fFvubLEx3JHWV6pAobjq3g==}
|
resolution: {integrity: sha512-CN5e/C9WLGa8EdAmnndGzKh14ALfMHwjoRZYHBfFmM1Z+SArz89VrXeOmAMVgpcANBNC4ShvXp4rIUD9PveDQA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -878,8 +878,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64/13.1.7-canary.23:
|
/@next/swc-darwin-x64/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-iWU/5+myA89I25AtLHeZP03jqUh9OfUF67oQ1ZiDNcKZAEDGqYgwqVeiUbU6rBhX483YwZOAF+iUpYefV9FLQQ==}
|
resolution: {integrity: sha512-eP7hKzI84MrG54M3207pynGmJHqf2V6Ex0CXpht0WSMrrv7G6W9iHyLPqk18AgxzgjA5QH4lDP+FpTaoDmpdkg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -887,8 +887,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-freebsd-x64/13.1.7-canary.23:
|
/@next/swc-freebsd-x64/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-IuJljewmj7BNJI9qsLXzJZBC1qU7adif0Pcg2ww5Al6l8RviGy6aBWdMgt65tHTGlUqxHhLg7mW5jT2bUw1Bcw==}
|
resolution: {integrity: sha512-rloppA9VtxDFruDxMQJqrISwKzOip9/1hksljlXTBg+/fu2dJyufuARwhfksDNH3fBT0fQC10esFO+WjhLEp7g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
@ -896,8 +896,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm-gnueabihf/13.1.7-canary.23:
|
/@next/swc-linux-arm-gnueabihf/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-d1I0WY/L4RW4pQcDihWjMo+s90hv9c83lzDi1fsjhzKMJnHKyOcZKc2tXB6OvHW67y6PL6bgpPcqysmS4txwjA==}
|
resolution: {integrity: sha512-Fb0d7znHY3RlaJFCc07X+x3NGQw3Sp0UOsCOtalG2LSgMKFTt0dMc47WBZez+ratWI7dc6B3H8kmsgntBd4Skw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -905,8 +905,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu/13.1.7-canary.23:
|
/@next/swc-linux-arm64-gnu/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-zMaepi3ZZvPserWz34ZU9JKmZPnUn/1HKEe6oNuxkHSDIqR6RM2DcUIj5mwsUdQzCAHJs8ZbLpoAtiOj7x+Ipg==}
|
resolution: {integrity: sha512-ATkCdBfHpTtB+7VWgsib2O98sUAa9vyB7uyHTFDWPMB9qTnTXMUfPoYxFJmYXrjwB/xoW97AKrB3FGxMM+swHw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -914,8 +914,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl/13.1.7-canary.23:
|
/@next/swc-linux-arm64-musl/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-4bMTuoCQ5cf+6O7tIP//I4FF5o4crfRq7DdUTXoZvlF81xjlnI+gCRiHU8AllUryRhci+r4kM9/2m3GQkGQdqA==}
|
resolution: {integrity: sha512-2x2i0dgcyhE9wycHYLAZiiHkQlzambV0qdjsG7DqQhc23UhVvkqG6dB9PHPFrkxMFPq2QDLpYGlKF9sdnnEQbw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -923,8 +923,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu/13.1.7-canary.23:
|
/@next/swc-linux-x64-gnu/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-plovQsBsXdJUaQYx7aQHiUlYtxgawlp+1a9VX6CPJPM15HR91KmJsmdfI+JW1fVbV4x+N0TDARzdGSHKQNLAHQ==}
|
resolution: {integrity: sha512-nRQfotbWl4V9sIS+nx4ZXtJxe8/+naeUrJSoD+Wi9PrIyF3uuLE+xi1WMHS5RhGydyduvFSyIjQO2qGzZ0Lhsw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -932,8 +932,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl/13.1.7-canary.23:
|
/@next/swc-linux-x64-musl/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-0b9BiJ2eOFypCa41gEhURxD9borWGfXBb3AnhxwmUnMAanhZZSVutBCGjxqvki3qbTPCMUv6dVUaKXw+26ylPg==}
|
resolution: {integrity: sha512-hdM2Cds8yppGSzql2A4tCYdzjVheDh/Ei45GOz94gshO95u7SSS9UQNTaOKZTWOUfRnZYZrEbAVMhDxz10qqdQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -941,8 +941,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc/13.1.7-canary.23:
|
/@next/swc-win32-arm64-msvc/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-psVpG4pmJHrqjXMfKcXyRNO5t18tpee4pZAuTesu8DfRd+fPqg1CwB1Hp8qBxG9c80loCoQMlKFiH5nPFAvv+g==}
|
resolution: {integrity: sha512-2SkgCWZbDLBgKbgJ+slVpqQLNwOzp2/ja2C8nNzwUbDB4cbD9fTyWrUngZBgg8J1OaP5KR7qHOQ8JQoHqiTOjA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -950,8 +950,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc/13.1.7-canary.23:
|
/@next/swc-win32-ia32-msvc/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-5ZW5+QoI9yibjUPcHfh/uBjB5ubcsKoD45LNbxsxWGOl9c/tXp4zvhqIEtZ57SWajdZZiuKZ0I92MGxgiDwYfw==}
|
resolution: {integrity: sha512-1j1BJ2wtzDnbn6uOUPsPhOgjIWESr8TE7aLUAlxtTYgsrYdPYxe4uowa9M04mEw6UxZHatrrOA6zvTVoFUtB8g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -959,8 +959,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc/13.1.7-canary.23:
|
/@next/swc-win32-x64-msvc/13.2.1-canary.0:
|
||||||
resolution: {integrity: sha512-+90NgEZmVqLtQ+adKtZxz67XJvjF/gESaSH8Zt96ccNH9lzoZsSyX/SYHwNJQFikiBCNn0SMeAZHIlsY5dEbqQ==}
|
resolution: {integrity: sha512-87QjJxWWYIPKBx/FoaqEd2bxPFBdTUZ6OdEnCeuBW2XjN6qZpwL1H2dIEvh6EXJMjQYW/DAhOqgr9cvpw9fu+Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -1955,7 +1955,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.0.27
|
'@types/react': 18.0.27
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
tslib: 2.4.1
|
tslib: 2.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/aria-hidden/1.2.2_3stiutgnnbnfnf3uowm5cip22i:
|
/aria-hidden/1.2.2_3stiutgnnbnfnf3uowm5cip22i:
|
||||||
|
@ -2970,8 +2970,8 @@ packages:
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-config-next/13.1.6_zkdaqh7it7uc4cvz2haft7rc6u:
|
/eslint-config-next/13.1.7-canary.26_zkdaqh7it7uc4cvz2haft7rc6u:
|
||||||
resolution: {integrity: sha512-0cg7h5wztg/SoLAlxljZ0ZPUQ7i6QKqRiP4M2+MgTZtxWwNKb2JSwNc18nJ6/kXBI6xYvPraTbQSIhAuVw6czw==}
|
resolution: {integrity: sha512-pKLrCNiKO4uekzZJaIXthsasvlEzSiGQLsO2I2FPr+sQAjY18UhY3jl2HZznU/vbk5GHVRKs5ibrye/29KDtbQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.23.0 || ^8.0.0
|
eslint: ^7.23.0 || ^8.0.0
|
||||||
typescript: '>=3.3.1'
|
typescript: '>=3.3.1'
|
||||||
|
@ -2979,7 +2979,7 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 13.1.6
|
'@next/eslint-plugin-next': 13.1.7-canary.26
|
||||||
'@rushstack/eslint-patch': 1.2.0
|
'@rushstack/eslint-patch': 1.2.0
|
||||||
'@typescript-eslint/parser': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
|
'@typescript-eslint/parser': 5.49.0_zkdaqh7it7uc4cvz2haft7rc6u
|
||||||
eslint: 8.33.0
|
eslint: 8.33.0
|
||||||
|
@ -3367,7 +3367,7 @@ packages:
|
||||||
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
|
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.4.1
|
tslib: 2.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/filing-cabinet/3.3.0:
|
/filing-cabinet/3.3.0:
|
||||||
|
@ -5473,7 +5473,7 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/next-auth/4.19.2_de7upave4ddivcfp3gf7tpswk4:
|
/next-auth/4.19.2_avkzbs57los6fogzead7rr4u74:
|
||||||
resolution: {integrity: sha512-6V2YG3IJQVhgCAH7mvT3yopTW92gMdUrcwGX7NQ0dCreT/+axGua/JmVdarjec0C/oJukKpIYRgjMlV+L5ZQOQ==}
|
resolution: {integrity: sha512-6V2YG3IJQVhgCAH7mvT3yopTW92gMdUrcwGX7NQ0dCreT/+axGua/JmVdarjec0C/oJukKpIYRgjMlV+L5ZQOQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: ^12.2.5 || ^13
|
next: ^12.2.5 || ^13
|
||||||
|
@ -5488,7 +5488,7 @@ packages:
|
||||||
'@panva/hkdf': 1.0.2
|
'@panva/hkdf': 1.0.2
|
||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
jose: 4.11.0
|
jose: 4.11.0
|
||||||
next: 13.1.7-canary.23_biqbaboplfbrettd7655fr4n2y
|
next: 13.2.1-canary.0_biqbaboplfbrettd7655fr4n2y
|
||||||
oauth: 0.9.15
|
oauth: 0.9.15
|
||||||
openid-client: 5.3.0
|
openid-client: 5.3.0
|
||||||
preact: 10.11.2
|
preact: 10.11.2
|
||||||
|
@ -5498,14 +5498,14 @@ packages:
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/next-themes/0.2.1_de7upave4ddivcfp3gf7tpswk4:
|
/next-themes/0.2.1_avkzbs57los6fogzead7rr4u74:
|
||||||
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: '*'
|
next: '*'
|
||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 13.1.7-canary.23_biqbaboplfbrettd7655fr4n2y
|
next: 13.2.1-canary.0_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -5521,17 +5521,20 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next/13.1.7-canary.23_biqbaboplfbrettd7655fr4n2y:
|
/next/13.2.1-canary.0_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-kUgSfsX+f09h8ULeqitJmUff6RPkhZq2xA2X6hSnFMrZEAjxeuAiGDdK6dZXNiuuQDHTXnZOvovupq05pf2z2w==}
|
resolution: {integrity: sha512-4dXz9jinbUmxOCnXxDPxcFHmwhR+YDCLRP0kiYUpu1GiTlTYbfHAb0HosmA58ZL76W1tmqS6x8/3qkiDNtZWrg==}
|
||||||
engines: {node: '>=14.6.0'}
|
engines: {node: '>=14.6.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
'@opentelemetry/api': ^1.4.0
|
||||||
fibers: '>= 3.1.0'
|
fibers: '>= 3.1.0'
|
||||||
node-sass: ^6.0.0 || ^7.0.0
|
node-sass: ^6.0.0 || ^7.0.0
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
sass: ^1.3.0
|
sass: ^1.3.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
|
'@opentelemetry/api':
|
||||||
|
optional: true
|
||||||
fibers:
|
fibers:
|
||||||
optional: true
|
optional: true
|
||||||
node-sass:
|
node-sass:
|
||||||
|
@ -5539,7 +5542,7 @@ packages:
|
||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 13.1.7-canary.23
|
'@next/env': 13.2.1-canary.0
|
||||||
'@swc/helpers': 0.4.14
|
'@swc/helpers': 0.4.14
|
||||||
caniuse-lite: 1.0.30001449
|
caniuse-lite: 1.0.30001449
|
||||||
postcss: 8.4.14
|
postcss: 8.4.14
|
||||||
|
@ -5547,19 +5550,19 @@ packages:
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
styled-jsx: 5.1.1_react@18.2.0
|
styled-jsx: 5.1.1_react@18.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-android-arm-eabi': 13.1.7-canary.23
|
'@next/swc-android-arm-eabi': 13.2.1-canary.0
|
||||||
'@next/swc-android-arm64': 13.1.7-canary.23
|
'@next/swc-android-arm64': 13.2.1-canary.0
|
||||||
'@next/swc-darwin-arm64': 13.1.7-canary.23
|
'@next/swc-darwin-arm64': 13.2.1-canary.0
|
||||||
'@next/swc-darwin-x64': 13.1.7-canary.23
|
'@next/swc-darwin-x64': 13.2.1-canary.0
|
||||||
'@next/swc-freebsd-x64': 13.1.7-canary.23
|
'@next/swc-freebsd-x64': 13.2.1-canary.0
|
||||||
'@next/swc-linux-arm-gnueabihf': 13.1.7-canary.23
|
'@next/swc-linux-arm-gnueabihf': 13.2.1-canary.0
|
||||||
'@next/swc-linux-arm64-gnu': 13.1.7-canary.23
|
'@next/swc-linux-arm64-gnu': 13.2.1-canary.0
|
||||||
'@next/swc-linux-arm64-musl': 13.1.7-canary.23
|
'@next/swc-linux-arm64-musl': 13.2.1-canary.0
|
||||||
'@next/swc-linux-x64-gnu': 13.1.7-canary.23
|
'@next/swc-linux-x64-gnu': 13.2.1-canary.0
|
||||||
'@next/swc-linux-x64-musl': 13.1.7-canary.23
|
'@next/swc-linux-x64-musl': 13.2.1-canary.0
|
||||||
'@next/swc-win32-arm64-msvc': 13.1.7-canary.23
|
'@next/swc-win32-arm64-msvc': 13.2.1-canary.0
|
||||||
'@next/swc-win32-ia32-msvc': 13.1.7-canary.23
|
'@next/swc-win32-ia32-msvc': 13.2.1-canary.0
|
||||||
'@next/swc-win32-x64-msvc': 13.1.7-canary.23
|
'@next/swc-win32-x64-msvc': 13.2.1-canary.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
@ -6288,7 +6291,7 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-remove-scroll-bar: 2.3.4_3stiutgnnbnfnf3uowm5cip22i
|
react-remove-scroll-bar: 2.3.4_3stiutgnnbnfnf3uowm5cip22i
|
||||||
react-style-singleton: 2.2.1_3stiutgnnbnfnf3uowm5cip22i
|
react-style-singleton: 2.2.1_3stiutgnnbnfnf3uowm5cip22i
|
||||||
tslib: 2.4.1
|
tslib: 2.5.0
|
||||||
use-callback-ref: 1.3.0_3stiutgnnbnfnf3uowm5cip22i
|
use-callback-ref: 1.3.0_3stiutgnnbnfnf3uowm5cip22i
|
||||||
use-sidecar: 1.1.2_3stiutgnnbnfnf3uowm5cip22i
|
use-sidecar: 1.1.2_3stiutgnnbnfnf3uowm5cip22i
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -7143,10 +7146,6 @@ packages:
|
||||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tslib/2.4.1:
|
|
||||||
resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/tslib/2.5.0:
|
/tslib/2.5.0:
|
||||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { startTransition, Suspense, useState } from "react"
|
import { startTransition, Suspense, useState } from "react"
|
||||||
import styles from "./auth.module.css"
|
import styles from "./auth.module.css"
|
||||||
import Link from "../../components/link"
|
import Link from "../../../components/link"
|
||||||
import { signIn } from "next-auth/react"
|
import { signIn } from "next-auth/react"
|
||||||
import Input from "@components/input"
|
import Input from "@components/input"
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
|
@ -9,7 +9,7 @@ export function ErrorQueryParamsHandler() {
|
||||||
const { setToast } = useToasts()
|
const { setToast } = useToasts()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryParams.get("error")) {
|
if (queryParams?.get("error")) {
|
||||||
setToast({
|
setToast({
|
||||||
message: queryParams.get("error") as string,
|
message: queryParams.get("error") as string,
|
||||||
type: "error"
|
type: "error"
|
|
@ -59,7 +59,10 @@ function FileDropdown({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<Popover.Trigger className={buttonStyles.button}>
|
<Popover.Trigger
|
||||||
|
className={buttonStyles.button}
|
||||||
|
style={{ height: 40, padding: 10 }}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={clsx(buttonStyles.icon, styles.chevron)}
|
className={clsx(buttonStyles.icon, styles.chevron)}
|
||||||
style={{ marginRight: 6 }}
|
style={{ marginRight: 6 }}
|
|
@ -1,7 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as RadixTabs from "@radix-ui/react-tabs"
|
import * as RadixTabs from "@radix-ui/react-tabs"
|
||||||
import FormattingIcons from "src/app/(posts)/new/components/edit-document-list/edit-document/formatting-icons"
|
import FormattingIcons from "src/app/(drift)/(posts)/new/components/edit-document-list/edit-document/formatting-icons"
|
||||||
import { ChangeEvent, ClipboardEvent, useRef } from "react"
|
import { ChangeEvent, ClipboardEvent, useRef } from "react"
|
||||||
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
|
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
|
||||||
import Preview, { StaticPreview } from "../preview"
|
import Preview, { StaticPreview } from "../preview"
|
|
@ -17,7 +17,7 @@ function Description({ onChange, description }: props) {
|
||||||
label="Description"
|
label="Description"
|
||||||
maxLength={256}
|
maxLength={256}
|
||||||
width="100%"
|
width="100%"
|
||||||
placeholder="An optional description of your post"
|
placeholder="An optional description"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
|
@ -2,7 +2,7 @@ import { ChangeEvent, ClipboardEvent, useCallback } from "react"
|
||||||
import styles from "./document.module.css"
|
import styles from "./document.module.css"
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
import Input from "@components/input"
|
import Input from "@components/input"
|
||||||
import DocumentTabs from "src/app/(posts)/components/tabs"
|
import DocumentTabs from "src/app/(drift)/(posts)/components/tabs"
|
||||||
import { Trash } from "react-feather"
|
import { Trash } from "react-feather"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
|
@ -6,10 +6,10 @@ import generateUUID from "@lib/generate-uuid"
|
||||||
import styles from "./post.module.css"
|
import styles from "./post.module.css"
|
||||||
import EditDocumentList from "./edit-document-list"
|
import EditDocumentList from "./edit-document-list"
|
||||||
import { ChangeEvent } from "react"
|
import { ChangeEvent } from "react"
|
||||||
import getTitleForPostCopy from "@lib/get-title-for-post-copy"
|
import getTitleForPostCopy from "src/app/lib/get-title-for-post-copy"
|
||||||
import Description from "./description"
|
import Description from "./description"
|
||||||
import { PostWithFiles } from "@lib/server/prisma"
|
import { PostWithFiles } from "@lib/server/prisma"
|
||||||
import PasswordModal from "../../../components/password-modal"
|
import PasswordModal from "../../../../components/password-modal"
|
||||||
import Title from "./title"
|
import Title from "./title"
|
||||||
import FileDropzone from "./drag-and-drop"
|
import FileDropzone from "./drag-and-drop"
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
|
@ -35,16 +35,14 @@ export type Document = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Post({
|
function Post({
|
||||||
initialPost: stringifiedInitialPost,
|
initialPost,
|
||||||
newPostParent
|
newPostParent
|
||||||
}: {
|
}: {
|
||||||
initialPost?: string
|
initialPost?: PostWithFiles
|
||||||
newPostParent?: string
|
newPostParent?: string
|
||||||
}): JSX.Element | null {
|
}): JSX.Element | null {
|
||||||
const { isAuthenticated } = useSessionSWR()
|
const { isAuthenticated } = useSessionSWR()
|
||||||
|
|
||||||
const parsedPost = JSON.parse(stringifiedInitialPost || "{}") as PostWithFiles
|
|
||||||
const initialPost = parsedPost?.id ? parsedPost : null
|
|
||||||
const { setToast } = useToasts()
|
const { setToast } = useToasts()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [title, setTitle] = useState(
|
const [title, setTitle] = useState(
|
|
@ -1,6 +1,10 @@
|
||||||
import NewPost from "../../components/new"
|
import NewPost from "../../components/new"
|
||||||
import { notFound, redirect } from "next/navigation"
|
import { notFound, redirect } from "next/navigation"
|
||||||
import { getPostById } from "@lib/server/prisma"
|
import {
|
||||||
|
getPostById,
|
||||||
|
serverPostToClientPost,
|
||||||
|
ServerPostWithFiles
|
||||||
|
} from "@lib/server/prisma"
|
||||||
import { getSession } from "@lib/server/session"
|
import { getSession } from "@lib/server/session"
|
||||||
|
|
||||||
async function NewFromExisting({
|
async function NewFromExisting({
|
||||||
|
@ -21,7 +25,7 @@ async function NewFromExisting({
|
||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const post = await getPostById(id, {
|
const post = (await getPostById(id, {
|
||||||
select: {
|
select: {
|
||||||
authorId: true,
|
authorId: true,
|
||||||
title: true,
|
title: true,
|
||||||
|
@ -35,11 +39,11 @@ async function NewFromExisting({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})) as ServerPostWithFiles
|
||||||
|
|
||||||
const serialized = JSON.stringify(post)
|
const clientPost = post ? serverPostToClientPost(post) : undefined
|
||||||
|
|
||||||
return <NewPost initialPost={serialized} newPostParent={id} />
|
return <NewPost initialPost={clientPost} newPostParent={id} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewFromExisting
|
export default NewFromExisting
|
3
src/app/(drift)/(posts)/new/layout.tsx
Normal file
3
src/app/(drift)/(posts)/new/layout.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function NewLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import NewPost from "src/app/(posts)/new/components/new"
|
import NewPost from "src/app/(drift)/(posts)/new/components/new"
|
||||||
import "./react-datepicker.css"
|
import "./react-datepicker.css"
|
||||||
|
|
||||||
export default function New() {
|
export default function New() {
|
|
@ -2,28 +2,21 @@
|
||||||
|
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
import ButtonGroup from "@components/button-group"
|
import ButtonGroup from "@components/button-group"
|
||||||
import FileDropdown from "src/app/(posts)/components/file-dropdown"
|
import FileDropdown from "src/app/(drift)/(posts)/components/file-dropdown"
|
||||||
import { Edit, ArrowUpCircle, Archive } from "react-feather"
|
import { Edit, ArrowUpCircle, Archive } from "react-feather"
|
||||||
import styles from "./post-buttons.module.css"
|
import styles from "./post-buttons.module.css"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { PostWithFiles } from "@lib/server/prisma"
|
import { PostWithFiles } from "@lib/server/prisma"
|
||||||
|
|
||||||
export const PostButtons = ({
|
export const PostButtons = ({
|
||||||
title,
|
post,
|
||||||
files,
|
loading
|
||||||
loading,
|
|
||||||
postId,
|
|
||||||
parentId
|
|
||||||
}: {
|
}: {
|
||||||
title: string
|
post?: PostWithFiles
|
||||||
files?: Pick<PostWithFiles, "files">["files"]
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
postId?: string
|
|
||||||
parentId?: string
|
|
||||||
visibility?: string
|
|
||||||
authorId?: string
|
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { files, id: postId, parentId, title } = post || {}
|
||||||
const downloadClick = async () => {
|
const downloadClick = async () => {
|
||||||
if (!files?.length) return
|
if (!files?.length) return
|
||||||
const downloadZip = (await import("client-zip")).downloadZip
|
const downloadZip = (await import("client-zip")).downloadZip
|
|
@ -2,27 +2,19 @@ import CreatedAgoBadge from "@components/badges/created-ago-badge"
|
||||||
import ExpirationBadge from "@components/badges/expiration-badge"
|
import ExpirationBadge from "@components/badges/expiration-badge"
|
||||||
import VisibilityBadge from "@components/badges/visibility-badge"
|
import VisibilityBadge from "@components/badges/visibility-badge"
|
||||||
import Skeleton from "@components/skeleton"
|
import Skeleton from "@components/skeleton"
|
||||||
|
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||||
import styles from "./title.module.css"
|
import styles from "./title.module.css"
|
||||||
|
|
||||||
type TitleProps = {
|
type TitleProps = {
|
||||||
title: string
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
displayName?: string
|
post?: PostWithFilesAndAuthor
|
||||||
visibility?: string
|
|
||||||
createdAt?: string
|
|
||||||
expiresAt?: string
|
|
||||||
authorId?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PostTitle = ({
|
export const PostTitle = ({ post, loading }: TitleProps) => {
|
||||||
title,
|
const { title, author, visibility, createdAt, expiresAt } = post || {}
|
||||||
displayName,
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
visibility,
|
// @ts-ignore displayName should be present
|
||||||
createdAt,
|
const displayName = author?.displayName
|
||||||
expiresAt,
|
|
||||||
loading
|
|
||||||
}: // authorId
|
|
||||||
TitleProps) => {
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.title}>
|
<span className={styles.title}>
|
||||||
<h1
|
<h1
|
|
@ -8,16 +8,12 @@ import PasswordModalWrapper from "./password-modal-wrapper"
|
||||||
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: string | PostWithFilesAndAuthor
|
post: PostWithFilesAndAuthor
|
||||||
isProtected?: boolean
|
isProtected?: boolean
|
||||||
isAuthor?: boolean
|
isAuthor?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostFiles = ({ post: _initialPost }: Props) => {
|
const PostFiles = ({ post: initialPost }: Props) => {
|
||||||
const initialPost =
|
|
||||||
typeof _initialPost === "string"
|
|
||||||
? (JSON.parse(_initialPost) as PostWithFilesAndAuthor)
|
|
||||||
: _initialPost
|
|
||||||
const [post, setPost] = useState<PostWithFilesAndAuthor>(initialPost)
|
const [post, setPost] = useState<PostWithFilesAndAuthor>(initialPost)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -63,15 +59,13 @@ const PostFiles = ({ post: _initialPost }: Props) => {
|
||||||
gap: "var(--gap-double)"
|
gap: "var(--gap-double)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{post?.files?.map(({ id, content, title, html }) => (
|
{post?.files?.map((file) => (
|
||||||
<DocumentComponent
|
<DocumentComponent
|
||||||
skeleton={false}
|
skeleton={false}
|
||||||
key={id}
|
key={post.id}
|
||||||
title={title}
|
|
||||||
initialTab={"preview"}
|
initialTab={"preview"}
|
||||||
id={id}
|
file={file}
|
||||||
content={content}
|
post={post}
|
||||||
preview={html}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</main>
|
</main>
|
|
@ -1,6 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Post, PostWithFilesAndAuthor } from "@lib/server/prisma"
|
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
||||||
import PasswordModal from "@components/password-modal"
|
import PasswordModal from "@components/password-modal"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { useCallback, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
@ -10,8 +10,8 @@ import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setPost: (post: PostWithFilesAndAuthor) => void
|
setPost: (post: PostWithFilesAndAuthor) => void
|
||||||
postId: Post["id"]
|
postId: PostWithFilesAndAuthor["id"]
|
||||||
authorId: Post["authorId"]
|
authorId: PostWithFilesAndAuthor["authorId"]
|
||||||
}
|
}
|
||||||
|
|
||||||
const PasswordModalWrapper = ({ setPost, postId, authorId }: Props) => {
|
const PasswordModalWrapper = ({ setPost, postId, authorId }: Props) => {
|
||||||
|
@ -20,7 +20,9 @@ const PasswordModalWrapper = ({ setPost, postId, authorId }: Props) => {
|
||||||
const { session, isLoading } = useSessionSWR()
|
const { session, isLoading } = useSessionSWR()
|
||||||
const isAuthor = isLoading
|
const isAuthor = isLoading
|
||||||
? undefined
|
? undefined
|
||||||
: session?.user && session?.user?.id === authorId
|
: session?.user
|
||||||
|
? session?.user?.id === authorId
|
||||||
|
: false
|
||||||
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false)
|
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false)
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (password: string) => {
|
async (password: string) => {
|
||||||
|
@ -64,14 +66,14 @@ const PasswordModalWrapper = ({ setPost, postId, authorId }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAuthor) {
|
if (isAuthor === true) {
|
||||||
onSubmit("author")
|
onSubmit("author")
|
||||||
setToast({
|
setToast({
|
||||||
message:
|
message:
|
||||||
"You're the author of this post, so you automatically have access to it.",
|
"You're the author of this post, so you automatically have access to it.",
|
||||||
type: "default"
|
type: "default"
|
||||||
})
|
})
|
||||||
} else {
|
} else if (isAuthor === false) {
|
||||||
setIsPasswordModalOpen(true)
|
setIsPasswordModalOpen(true)
|
||||||
}
|
}
|
||||||
}, [isAuthor, onSubmit, setToast])
|
}, [isAuthor, onSubmit, setToast])
|
|
@ -1,21 +1,20 @@
|
||||||
"use client"
|
|
||||||
|
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
import ButtonGroup from "@components/button-group"
|
import ButtonGroup from "@components/button-group"
|
||||||
import Skeleton from "@components/skeleton"
|
import Skeleton from "@components/skeleton"
|
||||||
import Tooltip from "@components/tooltip"
|
import Tooltip from "@components/tooltip"
|
||||||
import DocumentTabs from "src/app/(posts)/components/tabs"
|
import DocumentTabs from "src/app/(drift)/(posts)/components/tabs"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { Download, ExternalLink } from "react-feather"
|
import { Download, ExternalLink, Globe } from "react-feather"
|
||||||
import styles from "./document.module.css"
|
import styles from "./document.module.css"
|
||||||
|
import { getURLFriendlyTitle } from "src/app/lib/get-url-friendly-title"
|
||||||
|
import { PostWithFiles, ServerPost } from "@lib/server/prisma"
|
||||||
|
import { isAllowedVisibilityForWebpage } from "@lib/constants"
|
||||||
|
|
||||||
type SharedProps = {
|
type SharedProps = {
|
||||||
title?: string
|
|
||||||
initialTab: "edit" | "preview"
|
initialTab: "edit" | "preview"
|
||||||
id?: string
|
file?: PostWithFiles["files"][0]
|
||||||
content?: string
|
post?: Pick<ServerPost, "id" | "title" | "visibility">
|
||||||
preview?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = (
|
type Props = (
|
||||||
|
@ -28,7 +27,13 @@ type Props = (
|
||||||
) &
|
) &
|
||||||
SharedProps
|
SharedProps
|
||||||
|
|
||||||
const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
|
const DownloadButtons = ({
|
||||||
|
rawLink,
|
||||||
|
siteLink
|
||||||
|
}: {
|
||||||
|
rawLink?: string
|
||||||
|
siteLink?: string
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Tooltip content="Download">
|
<Tooltip content="Download">
|
||||||
|
@ -44,15 +49,28 @@ const DownloadButtons = ({ rawLink }: { rawLink?: string }) => {
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Open raw in new tab">
|
{rawLink ? (
|
||||||
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
|
<Tooltip content="Open raw in new tab">
|
||||||
<Button
|
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
|
||||||
iconLeft={<ExternalLink color="var(--fg)" />}
|
<Button
|
||||||
aria-label="Open raw file in new tab"
|
iconLeft={<ExternalLink color="var(--fg)" />}
|
||||||
style={{ border: "none", background: "transparent" }}
|
aria-label="Open raw file in new tab"
|
||||||
/>
|
style={{ border: "none", background: "transparent" }}
|
||||||
</Link>
|
/>
|
||||||
</Tooltip>
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
{siteLink ? (
|
||||||
|
<Tooltip content="Open as webpage">
|
||||||
|
<Link href={siteLink || ""} target="_blank" rel="noopener noreferrer">
|
||||||
|
<Button
|
||||||
|
iconLeft={<Globe color="var(--fg)" />}
|
||||||
|
aria-label="Open as webpage"
|
||||||
|
style={{ border: "none", background: "transparent" }}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -78,14 +96,14 @@ const Document = ({ skeleton, ...props }: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, initialTab, id, content = "", preview } = props
|
const { file, post } = props
|
||||||
|
|
||||||
// if the query has our title, we can use it to scroll to the file.
|
// if the query has our title, we can use it to scroll to the file.
|
||||||
// we can't use the browsers implementation because the data isn't loaded yet
|
// we can't use the browsers implementation because the data isn't loaded yet
|
||||||
if (title && typeof window !== "undefined") {
|
if (file?.title && typeof window !== "undefined") {
|
||||||
const hash = window.location.hash
|
const hash = window.location.hash
|
||||||
if (hash && hash === `#${title}`) {
|
if (file && hash && hash === `#${file?.title}`) {
|
||||||
const element = document.getElementById(title)
|
const element = document.getElementById(file.title)
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView()
|
element.scrollIntoView()
|
||||||
}
|
}
|
||||||
|
@ -95,28 +113,35 @@ const Document = ({ skeleton, ...props }: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<header id={title}>
|
<header id={file?.title}>
|
||||||
<Link
|
<Link
|
||||||
href={`#${title}`}
|
href={`#${file?.title}`}
|
||||||
aria-label="File"
|
aria-label="File"
|
||||||
style={{
|
style={{
|
||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
color: "var(--fg)"
|
color: "var(--fg)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{file?.title}
|
||||||
</Link>
|
</Link>
|
||||||
{/* TODO: switch to api once next.js bug is fixed */}
|
{/* TODO: switch to api once next.js bug is fixed */}
|
||||||
{/* Not /api/ because of rewrites defined in next.config.mjs */}
|
{/* Not /api/ because of rewrites defined in next.config.mjs */}
|
||||||
<DownloadButtons rawLink={`/api/file/raw/${id}`} />
|
<DownloadButtons
|
||||||
|
rawLink={`/api/file/raw/${file?.id}`}
|
||||||
|
siteLink={
|
||||||
|
file && post && isAllowedVisibilityForWebpage(post.visibility)
|
||||||
|
? `/pages/${file.id}/${getURLFriendlyTitle(file?.title || "")}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
<div className={styles.documentContainer}>
|
<div className={styles.documentContainer}>
|
||||||
<DocumentTabs
|
<DocumentTabs
|
||||||
defaultTab={initialTab}
|
defaultTab={props.initialTab}
|
||||||
staticPreview={preview}
|
staticPreview={file?.html}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
>
|
>
|
||||||
{content}
|
{file?.content || ""}
|
||||||
</DocumentTabs>
|
</DocumentTabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
20
src/app/(drift)/(posts)/post/[id]/context.tsx
Normal file
20
src/app/(drift)/(posts)/post/[id]/context.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { createContext, useContext } from "react"
|
||||||
|
import { getPost } from "./get-post"
|
||||||
|
|
||||||
|
const PostContext = createContext<
|
||||||
|
Awaited<ReturnType<typeof getPost>> | null
|
||||||
|
>(null)
|
||||||
|
|
||||||
|
export const PostProvider = PostContext.Provider
|
||||||
|
|
||||||
|
export const usePost = () => {
|
||||||
|
const post = useContext(PostContext)
|
||||||
|
|
||||||
|
if (!post) {
|
||||||
|
throw new Error("usePost must be used within a PostProvider")
|
||||||
|
}
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
|
@ -41,26 +41,24 @@ export const getPost = cache(async (id: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.visibility === "public" || post.visibility === "unlisted") {
|
if (post.visibility === "public" || post.visibility === "unlisted") {
|
||||||
return { post }
|
return post
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.visibility === "private") {
|
if (post.visibility === "private") {
|
||||||
const user = await getCurrentUser()
|
const user = await getCurrentUser()
|
||||||
if (user?.id === post.authorId || user?.role === "admin") {
|
if (user?.id === post.authorId || user?.role === "admin") {
|
||||||
return { post }
|
return post
|
||||||
}
|
}
|
||||||
return redirect("/new")
|
return redirect("/new")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.visibility === "protected") {
|
if (post.visibility === "protected") {
|
||||||
return {
|
return {
|
||||||
post: {
|
|
||||||
visibility: "protected",
|
visibility: "protected",
|
||||||
authorId: post.authorId,
|
authorId: post.authorId,
|
||||||
id: post.id
|
id: post.id
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { post }
|
return post
|
||||||
})
|
})
|
40
src/app/(drift)/(posts)/post/[id]/layout.tsx
Normal file
40
src/app/(drift)/(posts)/post/[id]/layout.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
PostWithFilesAndAuthor,
|
||||||
|
serverPostToClientPost,
|
||||||
|
ServerPostWithFilesAndAuthor
|
||||||
|
} from "@lib/server/prisma"
|
||||||
|
import ScrollToTop from "@components/scroll-to-top"
|
||||||
|
import { PostButtons } from "./components/header/post-buttons"
|
||||||
|
import styles from "./layout.module.css"
|
||||||
|
import { PostTitle } from "./components/header/title"
|
||||||
|
import { getPost } from "./get-post"
|
||||||
|
|
||||||
|
export default async function PostLayout({
|
||||||
|
children,
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
params: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const post = (await getPost(params.id)) as ServerPostWithFilesAndAuthor
|
||||||
|
|
||||||
|
// TODO: type-safe
|
||||||
|
const clientPost = serverPostToClientPost(post) as PostWithFilesAndAuthor
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
{post.visibility !== "protected" && <PostButtons post={clientPost} />}
|
||||||
|
{post.visibility !== "protected" && <PostTitle post={clientPost} />}
|
||||||
|
</div>
|
||||||
|
{post.description && (
|
||||||
|
<div>
|
||||||
|
<p>{post.description}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ScrollToTop />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
import VisibilityControl from "@components/badges/visibility-control"
|
import VisibilityControl from "@components/badges/visibility-control"
|
||||||
|
import {
|
||||||
|
PostWithFilesAndAuthor,
|
||||||
|
serverPostToClientPost,
|
||||||
|
ServerPostWithFilesAndAuthor
|
||||||
|
} from "@lib/server/prisma"
|
||||||
import PostFiles from "./components/post-files"
|
import PostFiles from "./components/post-files"
|
||||||
import { getPost } from "./get-post"
|
import { getPost } from "./get-post"
|
||||||
|
|
||||||
|
@ -9,11 +14,12 @@ export default async function PostPage({
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const { post } = await getPost(params.id)
|
const post = (await getPost(params.id)) as ServerPostWithFilesAndAuthor
|
||||||
const stringifiedPost = JSON.stringify(post)
|
const clientPost = serverPostToClientPost(post) as PostWithFilesAndAuthor
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PostFiles post={stringifiedPost} />
|
<PostFiles post={clientPost} />
|
||||||
<VisibilityControl
|
<VisibilityControl
|
||||||
authorId={post.authorId}
|
authorId={post.authorId}
|
||||||
postId={post.id}
|
postId={post.id}
|
|
@ -3,7 +3,7 @@
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
import { Spinner } from "@components/spinner"
|
import { Spinner } from "@components/spinner"
|
||||||
import { useToasts } from "@components/toasts"
|
import { useToasts } from "@components/toasts"
|
||||||
import { Post, User } from "@lib/server/prisma"
|
import { ServerPostWithFilesAndAuthor, UserWithPosts } from "@lib/server/prisma"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
|
@ -14,7 +14,7 @@ export function UserTable({
|
||||||
}: {
|
}: {
|
||||||
users?: {
|
users?: {
|
||||||
createdAt: string
|
createdAt: string
|
||||||
posts?: Post[]
|
posts?: ServerPostWithFilesAndAuthor[]
|
||||||
id: string
|
id: string
|
||||||
email: string | null
|
email: string | null
|
||||||
role: string | null
|
role: string | null
|
||||||
|
@ -95,7 +95,7 @@ export function PostTable({
|
||||||
posts?: {
|
posts?: {
|
||||||
createdAt: string
|
createdAt: string
|
||||||
id: string
|
id: string
|
||||||
author?: User | null
|
author?: UserWithPosts | null
|
||||||
title: string
|
title: string
|
||||||
visibility: string
|
visibility: string
|
||||||
}[]
|
}[]
|
|
@ -1,10 +1,11 @@
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
import { PropsWithChildren } from "react"
|
|
||||||
|
|
||||||
export default async function AdminLayout({
|
export default async function AdminLayout({
|
||||||
children
|
children
|
||||||
}: PropsWithChildren<unknown>) {
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
const user = await getCurrentUser()
|
const user = await getCurrentUser()
|
||||||
const isAdmin = user?.role === "admin"
|
const isAdmin = user?.role === "admin"
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { getAllPosts, getAllUsers } from "@lib/server/prisma"
|
import {
|
||||||
|
getAllPosts,
|
||||||
|
getAllUsers,
|
||||||
|
PostWithFiles,
|
||||||
|
serverPostToClientPost,
|
||||||
|
ServerPostWithFiles
|
||||||
|
} from "@lib/server/prisma"
|
||||||
import { PostTable, UserTable } from "./components/tables"
|
import { PostTable, UserTable } from "./components/tables"
|
||||||
|
|
||||||
export default async function AdminPage() {
|
export default async function AdminPage() {
|
||||||
|
@ -25,15 +31,9 @@ export default async function AdminPage() {
|
||||||
|
|
||||||
const [users, posts] = await Promise.all([usersPromise, postsPromise])
|
const [users, posts] = await Promise.all([usersPromise, postsPromise])
|
||||||
|
|
||||||
const serializedPosts = posts.map((post) => {
|
const serializedPosts = posts.map((post) =>
|
||||||
return {
|
serverPostToClientPost(post as ServerPostWithFiles)
|
||||||
...post,
|
) as PostWithFiles[]
|
||||||
createdAt: post.createdAt.toISOString(),
|
|
||||||
updatedAt: post.updatedAt.toISOString(),
|
|
||||||
expiresAt: post.expiresAt?.toISOString(),
|
|
||||||
deletedAt: post.deletedAt?.toISOString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const serializedUsers = users.map((user) => {
|
const serializedUsers = users.map((user) => {
|
||||||
return {
|
return {
|
||||||
|
@ -46,7 +46,8 @@ export default async function AdminPage() {
|
||||||
<div>
|
<div>
|
||||||
<h1>Admin</h1>
|
<h1>Admin</h1>
|
||||||
<h2>Users</h2>
|
<h2>Users</h2>
|
||||||
<UserTable users={serializedUsers} />
|
{/* @ts-expect-error Type 'unknown' is not assignable to type */}
|
||||||
|
<UserTable users={serializedUsers as unknown} />
|
||||||
<h2>Posts</h2>
|
<h2>Posts</h2>
|
||||||
<PostTable posts={serializedPosts} />
|
<PostTable posts={serializedPosts} />
|
||||||
</div>
|
</div>
|
|
@ -1,5 +1,9 @@
|
||||||
import PostList from "@components/post-list"
|
import PostList from "@components/post-list"
|
||||||
import { getPostsByUser, getUserById } from "@lib/server/prisma"
|
import {
|
||||||
|
getPostsByUser,
|
||||||
|
getUserById,
|
||||||
|
serverPostToClientPost
|
||||||
|
} from "@lib/server/prisma"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
import { User } from "react-feather"
|
import { User } from "react-feather"
|
||||||
|
@ -11,15 +15,10 @@ async function PostListWrapper({
|
||||||
posts: ReturnType<typeof getPostsByUser>
|
posts: ReturnType<typeof getPostsByUser>
|
||||||
userId: string
|
userId: string
|
||||||
}) {
|
}) {
|
||||||
const data = (await posts).filter((post) => post.visibility === "public")
|
const data = (await posts)
|
||||||
return (
|
.filter((post) => post.visibility === "public")
|
||||||
<PostList
|
.map(serverPostToClientPost)
|
||||||
userId={userId}
|
return <PostList userId={userId} initialPosts={data} hideSearch hideActions />
|
||||||
initialPosts={JSON.stringify(data)}
|
|
||||||
hideSearch
|
|
||||||
hideActions
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function UserPage({
|
export default async function UserPage({
|
|
@ -4,14 +4,16 @@ import Layout from "@components/layout"
|
||||||
import { Toasts } from "@components/toasts"
|
import { Toasts } from "@components/toasts"
|
||||||
import Header from "@components/header"
|
import Header from "@components/header"
|
||||||
import { Inter } from "@next/font/google"
|
import { Inter } from "@next/font/google"
|
||||||
import { PropsWithChildren, Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
import { Spinner } from "@components/spinner"
|
import { Spinner } from "@components/spinner"
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children
|
children
|
||||||
}: PropsWithChildren<unknown>) {
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
// suppressHydrationWarning is required because of next-themes
|
// suppressHydrationWarning is required because of next-themes
|
||||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
|
@ -1,5 +1,5 @@
|
||||||
import { redirect } from "next/navigation"
|
import { redirect } from "next/navigation"
|
||||||
import { getPostsByUser } from "@lib/server/prisma"
|
import { getPostsByUser, serverPostToClientPost } from "@lib/server/prisma"
|
||||||
import PostList from "@components/post-list"
|
import PostList from "@components/post-list"
|
||||||
import { getCurrentUser } from "@lib/server/session"
|
import { getCurrentUser } from "@lib/server/session"
|
||||||
import { authOptions } from "@lib/server/auth"
|
import { authOptions } from "@lib/server/auth"
|
||||||
|
@ -12,14 +12,12 @@ export default async function Mine() {
|
||||||
return redirect(authOptions.pages?.signIn || "/new")
|
return redirect(authOptions.pages?.signIn || "/new")
|
||||||
}
|
}
|
||||||
|
|
||||||
const posts = await getPostsByUser(userId, true)
|
const posts = (await getPostsByUser(userId, true)).map(serverPostToClientPost)
|
||||||
|
|
||||||
const stringifiedPosts = JSON.stringify(posts)
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<PostList skeleton={true} initialPosts={[]} />}>
|
<Suspense fallback={<PostList skeleton={true} initialPosts={[]} />}>
|
||||||
<PostList
|
<PostList
|
||||||
userId={userId}
|
userId={userId}
|
||||||
initialPosts={stringifiedPosts}
|
initialPosts={posts}
|
||||||
isOwner={true}
|
isOwner={true}
|
||||||
hideSearch={false}
|
hideSearch={false}
|
||||||
/>
|
/>
|
|
@ -2,7 +2,11 @@ import Image from "next/image"
|
||||||
import Card from "@components/card"
|
import Card from "@components/card"
|
||||||
import { getWelcomeContent } from "src/pages/api/welcome"
|
import { getWelcomeContent } from "src/pages/api/welcome"
|
||||||
import DocumentTabs from "./(posts)/components/tabs"
|
import DocumentTabs from "./(posts)/components/tabs"
|
||||||
import { getAllPosts } from "@lib/server/prisma"
|
import {
|
||||||
|
getAllPosts,
|
||||||
|
serverPostToClientPost,
|
||||||
|
ServerPostWithFilesAndAuthor
|
||||||
|
} from "@lib/server/prisma"
|
||||||
import PostList, { NoPostsFound } from "@components/post-list"
|
import PostList, { NoPostsFound } from "@components/post-list"
|
||||||
import { cache, Suspense } from "react"
|
import { cache, Suspense } from "react"
|
||||||
import ErrorBoundary from "@components/error/fallback"
|
import ErrorBoundary from "@components/error/fallback"
|
||||||
|
@ -34,12 +38,7 @@ export default async function Page() {
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<PostList
|
<PostList skeleton hideActions hideSearch initialPosts={[]} />
|
||||||
skeleton
|
|
||||||
hideActions
|
|
||||||
hideSearch
|
|
||||||
initialPosts={JSON.stringify({})}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* @ts-expect-error because of async RSC */}
|
{/* @ts-expect-error because of async RSC */}
|
||||||
|
@ -67,14 +66,14 @@ async function WelcomePost() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function PublicPostList() {
|
async function PublicPostList() {
|
||||||
const posts = await getAllPosts({
|
const posts = (await getAllPosts({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
title: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
author: {
|
author: {
|
||||||
select: {
|
select: {
|
||||||
name: true
|
displayName: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
visibility: true,
|
visibility: true,
|
||||||
|
@ -92,13 +91,13 @@ async function PublicPostList() {
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: "desc"
|
createdAt: "desc"
|
||||||
}
|
}
|
||||||
})
|
})) as unknown as ServerPostWithFilesAndAuthor[]
|
||||||
|
|
||||||
if (posts.length === 0) {
|
if (posts.length === 0) {
|
||||||
return <NoPostsFound />
|
return <NoPostsFound />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const clientPosts = posts.map((post) => serverPostToClientPost(post))
|
||||||
<PostList initialPosts={JSON.stringify(posts)} hideActions hideSearch />
|
|
||||||
)
|
return <PostList initialPosts={clientPosts} hideActions hideSearch />
|
||||||
}
|
}
|
|
@ -6,11 +6,7 @@ import { ThemeProvider } from "next-themes"
|
||||||
import { PropsWithChildren } from "react"
|
import { PropsWithChildren } from "react"
|
||||||
import { SWRConfig } from "swr"
|
import { SWRConfig } from "swr"
|
||||||
|
|
||||||
export type ChildrenProps = {
|
export function Providers({ children }: PropsWithChildren<unknown>) {
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Providers({ children }: ChildrenProps) {
|
|
||||||
return (
|
return (
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<RadixTooltip.Provider delayDuration={200}>
|
<RadixTooltip.Provider delayDuration={200}>
|
|
@ -1,6 +1,6 @@
|
||||||
import SettingsGroup from "../components/settings-group"
|
import SettingsGroup from "../../components/settings-group"
|
||||||
import Profile from "src/app/settings/components/sections/profile"
|
|
||||||
import APIKeys from "./components/sections/api-keys"
|
import APIKeys from "./components/sections/api-keys"
|
||||||
|
import Profile from "./components/sections/profile"
|
||||||
|
|
||||||
export default async function SettingsPage() {
|
export default async function SettingsPage() {
|
||||||
return (
|
return (
|
|
@ -1,5 +0,0 @@
|
||||||
import { ChildrenProps } from "src/app/providers"
|
|
||||||
|
|
||||||
export default function NewLayout({ children }: ChildrenProps) {
|
|
||||||
return <>{children}</>
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { PostWithFilesAndAuthor } from "@lib/server/prisma"
|
|
||||||
import ScrollToTop from "@components/scroll-to-top"
|
|
||||||
import { title } from "process"
|
|
||||||
import { PostButtons } from "./components/header/post-buttons"
|
|
||||||
import styles from "./layout.module.css"
|
|
||||||
import { PostTitle } from "./components/header/title"
|
|
||||||
import { getPost } from "./get-post"
|
|
||||||
import { PropsWithChildren } from "react"
|
|
||||||
|
|
||||||
export default async function PostLayout({
|
|
||||||
children,
|
|
||||||
params
|
|
||||||
}: PropsWithChildren<{
|
|
||||||
params: {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
}>) {
|
|
||||||
const { post } = (await getPost(params.id)) as {
|
|
||||||
post: PostWithFilesAndAuthor
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.root}>
|
|
||||||
<div className={styles.header}>
|
|
||||||
{/* post.title is not set when the post is protected */}
|
|
||||||
{post.title && (
|
|
||||||
<PostButtons
|
|
||||||
parentId={post.parentId || undefined}
|
|
||||||
postId={post.id}
|
|
||||||
files={post.files}
|
|
||||||
title={title}
|
|
||||||
authorId={post.authorId}
|
|
||||||
visibility={post.visibility}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{post.title && (
|
|
||||||
<PostTitle
|
|
||||||
title={post.title}
|
|
||||||
createdAt={post.createdAt.toString()}
|
|
||||||
expiresAt={post.expiresAt?.toString()}
|
|
||||||
// displayName is an optional param
|
|
||||||
displayName={post.author?.displayName || undefined}
|
|
||||||
visibility={post.visibility}
|
|
||||||
authorId={post.authorId}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{post.description && (
|
|
||||||
<div>
|
|
||||||
<p>{post.description}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ScrollToTop />
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -8,11 +8,11 @@ import Badge from "../badge"
|
||||||
const ExpirationBadge = ({
|
const ExpirationBadge = ({
|
||||||
postExpirationDate
|
postExpirationDate
|
||||||
}: {
|
}: {
|
||||||
postExpirationDate: Date | string | null
|
postExpirationDate: Date | string | undefined
|
||||||
onExpires?: () => void
|
onExpires?: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const expirationDate = useMemo(
|
const expirationDate = useMemo(
|
||||||
() => (postExpirationDate ? new Date(postExpirationDate) : null),
|
() => (postExpirationDate ? new Date(postExpirationDate) : undefined),
|
||||||
[postExpirationDate]
|
[postExpirationDate]
|
||||||
)
|
)
|
||||||
const [timeUntilString, setTimeUntil] = useState<string | null>(
|
const [timeUntilString, setTimeUntil] = useState<string | null>(
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Spinner } from "@components/spinner"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { useSessionSWR } from "@lib/use-session-swr"
|
import { useSessionSWR } from "@lib/use-session-swr"
|
||||||
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
|
import FadeIn from "@components/fade-in"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
authorId: string
|
authorId: string
|
||||||
|
@ -87,7 +88,7 @@ function VisibilityControl({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<FadeIn>
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
style={{
|
style={{
|
||||||
maxWidth: 600,
|
maxWidth: 600,
|
||||||
|
@ -128,7 +129,7 @@ function VisibilityControl({
|
||||||
onClose={onClosePasswordModal}
|
onClose={onClosePasswordModal}
|
||||||
onSubmit={submitPassword}
|
onSubmit={submitPassword}
|
||||||
/>
|
/>
|
||||||
</>
|
</FadeIn>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
/* padding: var(--gap-half) var(--gap); */
|
|
||||||
color: var(--darker-gray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover,
|
.button:hover,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
/* @media (prefers-reduced-motion: no-preference) { */
|
||||||
.fadeIn {
|
.fadeIn {
|
||||||
animation-name: fadeInAnimation;
|
animation-name: fadeInAnimation;
|
||||||
animation-fill-mode: backwards;
|
animation-fill-mode: backwards;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
/* } */
|
||||||
|
|
||||||
@keyframes fadeInAnimation {
|
@keyframes fadeInAnimation {
|
||||||
from {
|
from {
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { PropsWithChildren } from "react"
|
import clsx from "clsx"
|
||||||
import styles from "./page.module.css"
|
import styles from "./page.module.css"
|
||||||
|
|
||||||
export default function Layout({ children }: PropsWithChildren<unknown>) {
|
export default function Layout({
|
||||||
return <div className={styles.page}>{children}</div>
|
children,
|
||||||
|
forSites
|
||||||
|
}: {
|
||||||
|
forSites?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={clsx(styles.page, forSites && styles.forSites)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forSites {
|
||||||
|
margin-top: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
/* 55rem == --main-content */
|
/* 55rem == --main-content */
|
||||||
@media screen and (max-width: 55rem) {
|
@media screen and (max-width: 55rem) {
|
||||||
.page {
|
.page {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { fetchWithUser } from "src/app/lib/fetch-with-user"
|
||||||
import { Stack } from "@components/stack"
|
import { Stack } from "@components/stack"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialPosts: string | PostWithFiles[]
|
initialPosts: PostWithFiles[]
|
||||||
morePosts?: boolean
|
morePosts?: boolean
|
||||||
hideSearch?: boolean
|
hideSearch?: boolean
|
||||||
hideActions?: boolean
|
hideActions?: boolean
|
||||||
|
@ -24,17 +24,13 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostList = ({
|
const PostList = ({
|
||||||
initialPosts: initialPostsMaybeJSON,
|
initialPosts,
|
||||||
hideSearch,
|
hideSearch,
|
||||||
hideActions,
|
hideActions,
|
||||||
isOwner,
|
isOwner,
|
||||||
skeleton,
|
skeleton,
|
||||||
userId
|
userId
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const initialPosts =
|
|
||||||
typeof initialPostsMaybeJSON === "string"
|
|
||||||
? JSON.parse(initialPostsMaybeJSON)
|
|
||||||
: initialPostsMaybeJSON
|
|
||||||
const [searchValue, setSearchValue] = useState("")
|
const [searchValue, setSearchValue] = useState("")
|
||||||
const [searching, setSearching] = useState(false)
|
const [searching, setSearching] = useState(false)
|
||||||
const [posts, setPosts] = useState<PostWithFiles[]>(initialPosts)
|
const [posts, setPosts] = useState<PostWithFiles[]>(initialPosts)
|
||||||
|
|
|
@ -8,6 +8,12 @@ import styles from "./scroll.module.css"
|
||||||
|
|
||||||
const ScrollToTop = () => {
|
const ScrollToTop = () => {
|
||||||
const [shouldShow, setShouldShow] = useState(false)
|
const [shouldShow, setShouldShow] = useState(false)
|
||||||
|
|
||||||
|
const isReducedMotion =
|
||||||
|
typeof window !== "undefined"
|
||||||
|
? window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||||
|
: false
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if user is scrolled, set visible
|
// if user is scrolled, set visible
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
|
@ -17,13 +23,8 @@ const ScrollToTop = () => {
|
||||||
return () => window.removeEventListener("scroll", handleScroll)
|
return () => window.removeEventListener("scroll", handleScroll)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const isReducedMotion =
|
|
||||||
typeof window !== "undefined"
|
|
||||||
? window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
||||||
: false
|
|
||||||
const onClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
const onClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.currentTarget.blur()
|
e.currentTarget.blur()
|
||||||
|
|
||||||
window.scrollTo({ top: 0, behavior: isReducedMotion ? "auto" : "smooth" })
|
window.scrollTo({ top: 0, behavior: isReducedMotion ? "auto" : "smooth" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Card from "@components/card"
|
"use client"
|
||||||
|
|
||||||
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
||||||
import styles from "./tooltip.module.css"
|
import styles from "./tooltip.module.css"
|
||||||
|
|
||||||
|
@ -17,8 +18,9 @@ const Tooltip = ({
|
||||||
<RadixTooltip.Trigger asChild className={className}>
|
<RadixTooltip.Trigger asChild className={className}>
|
||||||
{children}
|
{children}
|
||||||
</RadixTooltip.Trigger>
|
</RadixTooltip.Trigger>
|
||||||
|
|
||||||
<RadixTooltip.Content>
|
<RadixTooltip.Content>
|
||||||
<Card className={styles.tooltip}>{content}</Card>
|
<div className={styles.tooltip}>{content}</div>
|
||||||
</RadixTooltip.Content>
|
</RadixTooltip.Content>
|
||||||
</RadixTooltip.Root>
|
</RadixTooltip.Root>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,24 +1,32 @@
|
||||||
.tooltip {
|
.tooltip {
|
||||||
animation: fadein 300ms;
|
animation: fadein 300ms;
|
||||||
|
background: var(--bg);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0 var(--gap);
|
||||||
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-side='top'] .tooltip{
|
[data-side="top"] .tooltip {
|
||||||
margin-bottom: var(--gap-quarter);
|
margin-bottom: var(--gap-quarter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip[data-side='bottom'] {
|
.tooltip[data-side="bottom"] {
|
||||||
margin-top: var(--gap-quarter);
|
margin-top: var(--gap-quarter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip[data-side='left'] {
|
.tooltip[data-side="left"] {
|
||||||
margin-right: var(--gap-quarter);
|
margin-right: var(--gap-quarter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip[data-side='right'] {
|
.tooltip[data-side="right"] {
|
||||||
margin-left: var(--gap-quarter);
|
margin-left: var(--gap-quarter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from { opacity: 0; }
|
from {
|
||||||
to { opacity: 1; }
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
3
src/app/lib/get-url-friendly-title.tsx
Normal file
3
src/app/lib/get-url-friendly-title.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function getURLFriendlyTitle(title: string) {
|
||||||
|
return title.replace(/\s/g, "-")
|
||||||
|
}
|
25
src/app/pages/[fileId]/[fileTitle]/layout.tsx
Normal file
25
src/app/pages/[fileId]/[fileTitle]/layout.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import "@styles/globals.css"
|
||||||
|
import "@styles/markdown.css"
|
||||||
|
|
||||||
|
import Layout from "@components/layout"
|
||||||
|
import { Inter } from "@next/font/google"
|
||||||
|
import ThemeProvider from "./theme-provider"
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"], variable: "--inter-font" })
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
// suppressHydrationWarning is required because of next-themes
|
||||||
|
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||||
|
<body>
|
||||||
|
<ThemeProvider>
|
||||||
|
<Layout forSites>{children}</Layout>
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
89
src/app/pages/[fileId]/[fileTitle]/page.tsx
Normal file
89
src/app/pages/[fileId]/[fileTitle]/page.tsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import {
|
||||||
|
ALLOWED_VISIBILITIES_FOR_WEBPAGE,
|
||||||
|
isAllowedVisibilityForWebpage
|
||||||
|
} from "@lib/constants"
|
||||||
|
import {
|
||||||
|
getAllPosts,
|
||||||
|
getFileById,
|
||||||
|
getPostById,
|
||||||
|
ServerPostWithFiles
|
||||||
|
} from "@lib/server/prisma"
|
||||||
|
import { Metadata } from "next"
|
||||||
|
import { notFound } from "next/navigation"
|
||||||
|
import { getURLFriendlyTitle } from "src/app/lib/get-url-friendly-title"
|
||||||
|
|
||||||
|
export default async function FilePage({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: {
|
||||||
|
fileId: string
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const { fileId: id } = params
|
||||||
|
|
||||||
|
const file = await getFileById(id)
|
||||||
|
|
||||||
|
if (!file || !isAllowedVisibilityForWebpage(file.post.visibility)) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 style={{ color: "var(--gray)" }}>{file.title}</h1>
|
||||||
|
<hr />
|
||||||
|
<article
|
||||||
|
dangerouslySetInnerHTML={{ __html: file.html.toString("utf-8") }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
const posts = (await getAllPosts({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
files: {
|
||||||
|
// include where visibility is public or unlisted
|
||||||
|
// and title ends with .md
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
endsWith: ".md"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
visibility: {
|
||||||
|
in: ALLOWED_VISIBILITIES_FOR_WEBPAGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})) as ServerPostWithFiles[]
|
||||||
|
|
||||||
|
return posts.flatMap((post) => {
|
||||||
|
return post.files.map((file) => ({
|
||||||
|
fileId: file.id,
|
||||||
|
fileTitle: getURLFriendlyTitle(file.title)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: {
|
||||||
|
fileId: string
|
||||||
|
}
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { fileId: postId } = params
|
||||||
|
const post = await getPostById(postId, {
|
||||||
|
select: {
|
||||||
|
description: true,
|
||||||
|
title: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: post?.title || "",
|
||||||
|
description: post?.description || ""
|
||||||
|
}
|
||||||
|
}
|
14
src/app/pages/[fileId]/[fileTitle]/theme-provider.tsx
Normal file
14
src/app/pages/[fileId]/[fileTitle]/theme-provider.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
|
|
||||||
|
export default function ThemeProvider({
|
||||||
|
children
|
||||||
|
}: PropsWithChildren<unknown>) {
|
||||||
|
return (
|
||||||
|
<NextThemesProvider enableSystem defaultTheme="dark">
|
||||||
|
{children}
|
||||||
|
</NextThemesProvider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -122,6 +122,10 @@ table th {
|
||||||
|
|
||||||
table th,
|
table th,
|
||||||
table td {
|
table td {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.35rem 0.75rem;
|
||||||
border: 1px solid var(--light-gray);
|
border: 1px solid var(--light-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article > :not(:first-child) {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
|
@ -74,7 +74,9 @@ export const config = (env: Environment): Config => {
|
||||||
is_production,
|
is_production,
|
||||||
enable_admin: stringToBoolean(env.ENABLE_ADMIN),
|
enable_admin: stringToBoolean(env.ENABLE_ADMIN),
|
||||||
registration_password: env.REGISTRATION_PASSWORD ?? "",
|
registration_password: env.REGISTRATION_PASSWORD ?? "",
|
||||||
welcome_content: env.WELCOME_CONTENT ?? "Welcome to Drift.",
|
welcome_content:
|
||||||
|
env.WELCOME_CONTENT ??
|
||||||
|
"## Drift is a self-hostable clone of GitHub Gist.\n\nIt is a simple way to save and share code and text snippets, with support for the following:\n\n- Render GitHub Extended Markdown\n- User authentication\n- Private, public, and password protected posts\n- Syntax highlighting and language detection\n- Drag-and-drop file uploading \n\n You can find the source code and sponsor development on [GitHub](https://github.com/MaxLeiter/drift).",
|
||||||
welcome_title: env.WELCOME_TITLE ?? "Drift",
|
welcome_title: env.WELCOME_TITLE ?? "Drift",
|
||||||
url:
|
url:
|
||||||
throwIfUndefined("DRIFT_URL", true) ||
|
throwIfUndefined("DRIFT_URL", true) ||
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
import { ServerPost } from "./server/prisma"
|
||||||
|
|
||||||
|
// Visibilties for the webpages feature
|
||||||
|
export const ALLOWED_VISIBILITIES_FOR_WEBPAGE = ["public", "unlisted"]
|
||||||
|
export function isAllowedVisibilityForWebpage(visibility: ServerPost["visibility"]) {
|
||||||
|
return ALLOWED_VISIBILITIES_FOR_WEBPAGE.includes(visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code files for uploading with drag and drop and syntax highlighting
|
||||||
export const allowedFileTypes = [
|
export const allowedFileTypes = [
|
||||||
"application/json",
|
"application/json",
|
||||||
"application/x-javascript",
|
"application/x-javascript",
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { Gist } from "./types"
|
import { Gist } from "./types"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import type { Post } from "@lib/server/prisma"
|
|
||||||
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file"
|
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file"
|
||||||
import { prisma } from "@lib/server/prisma"
|
import { prisma, ServerPost } from "@lib/server/prisma"
|
||||||
|
|
||||||
export type AdditionalPostInformation = Pick<
|
export type AdditionalPostInformation = Pick<
|
||||||
Post,
|
ServerPost,
|
||||||
"visibility" | "password" | "expiresAt"
|
"visibility" | "password" | "expiresAt"
|
||||||
> & {
|
> & {
|
||||||
userId: string
|
userId: string
|
||||||
|
@ -14,7 +13,7 @@ export type AdditionalPostInformation = Pick<
|
||||||
export async function createPostFromGist(
|
export async function createPostFromGist(
|
||||||
{ userId, visibility, password, expiresAt }: AdditionalPostInformation,
|
{ userId, visibility, password, expiresAt }: AdditionalPostInformation,
|
||||||
gist: Gist
|
gist: Gist
|
||||||
): Promise<Post> {
|
): Promise<ServerPost> {
|
||||||
const files = Object.values(gist.files)
|
const files = Object.values(gist.files)
|
||||||
const [title, description] = gist.description.split("\n", 1)
|
const [title, description] = gist.description.split("\n", 1)
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,20 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
import config from "@lib/config"
|
import config from "@lib/config"
|
||||||
import { Post, PrismaClient, User, Prisma } from "@prisma/client"
|
import {
|
||||||
|
Post as ServerPost,
|
||||||
|
PrismaClient,
|
||||||
|
User as ServerUser,
|
||||||
|
Prisma,
|
||||||
|
File as ServerFile
|
||||||
|
} from "@prisma/client"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import { cache } from "react"
|
import { cache } from "react"
|
||||||
export type { User, File, Post } from "@prisma/client"
|
export type {
|
||||||
|
User as ServerUser,
|
||||||
|
File as ServerFile,
|
||||||
|
Post as ServerPost
|
||||||
|
} from "@prisma/client"
|
||||||
export const prisma =
|
export const prisma =
|
||||||
global.prisma ||
|
global.prisma ||
|
||||||
new PrismaClient({
|
new PrismaClient({
|
||||||
|
@ -42,29 +51,91 @@ const postWithFilesAndAuthor = Prisma.validator<Prisma.PostArgs>()({
|
||||||
})
|
})
|
||||||
|
|
||||||
export type ServerPostWithFiles = Prisma.PostGetPayload<typeof postWithFiles>
|
export type ServerPostWithFiles = Prisma.PostGetPayload<typeof postWithFiles>
|
||||||
export type PostWithAuthor = Prisma.PostGetPayload<typeof postWithAuthor>
|
export type ServerPostWithAuthor = Prisma.PostGetPayload<typeof postWithAuthor>
|
||||||
export type ServerPostWithFilesAndAuthor = Prisma.PostGetPayload<
|
export type ServerPostWithFilesAndAuthor = Prisma.PostGetPayload<
|
||||||
typeof postWithFilesAndAuthor
|
typeof postWithFilesAndAuthor
|
||||||
>
|
>
|
||||||
|
|
||||||
export type PostWithFiles = Omit<ServerPostWithFiles, "files"> & {
|
export type PostWithFiles = Omit<
|
||||||
files: (Omit<ServerPostWithFiles["files"][number], "content" | "html"> & {
|
ServerPostWithFiles,
|
||||||
|
"files" | "updatedAt" | "createdAt" | "deletedAt" | "expiresAt"
|
||||||
|
> & {
|
||||||
|
files: (Omit<
|
||||||
|
ServerPostWithFiles["files"][number],
|
||||||
|
"content" | "html" | "updatedAt" | "createdAt" | "deletedAt"
|
||||||
|
> & {
|
||||||
content: string
|
content: string
|
||||||
html: string
|
html: string
|
||||||
|
updatedAt?: string
|
||||||
|
createdAt: string
|
||||||
|
deletedAt?: string
|
||||||
})[]
|
})[]
|
||||||
|
updatedAt?: string
|
||||||
|
createdAt: string
|
||||||
|
deletedAt?: string
|
||||||
|
expiresAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PostWithFilesAndAuthor = Omit<
|
export type PostWithFilesAndAuthor = Omit<
|
||||||
ServerPostWithFilesAndAuthor,
|
ServerPostWithFilesAndAuthor,
|
||||||
"files"
|
"files" | "updatedAt" | "createdAt" | "deletedAt" | "expiresAt" | "author"
|
||||||
> & {
|
> & {
|
||||||
files: (Omit<
|
files: (Omit<
|
||||||
ServerPostWithFilesAndAuthor["files"][number],
|
ServerPostWithFilesAndAuthor["files"][number],
|
||||||
"content" | "html"
|
"content" | "html" | "updatedAt" | "createdAt" | "deletedAt"
|
||||||
> & {
|
> & {
|
||||||
content: string
|
content: string
|
||||||
html: string
|
html: string
|
||||||
|
updatedAt?: string
|
||||||
|
createdAt: string
|
||||||
|
deletedAt?: string
|
||||||
})[]
|
})[]
|
||||||
|
|
||||||
|
author: Omit<
|
||||||
|
ServerPostWithFilesAndAuthor["author"],
|
||||||
|
"createdAt" | "updatedAt"
|
||||||
|
> & {
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAt?: string
|
||||||
|
createdAt: string
|
||||||
|
deletedAt?: string
|
||||||
|
expiresAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serverPostToClientPost(
|
||||||
|
post: ServerPostWithFiles | ServerPostWithFilesAndAuthor
|
||||||
|
): PostWithFilesAndAuthor | PostWithFiles {
|
||||||
|
let result: PostWithFiles | PostWithFilesAndAuthor = {
|
||||||
|
...post,
|
||||||
|
files: post.files?.map((file) => ({
|
||||||
|
...file,
|
||||||
|
content: file.content?.toString("utf-8"),
|
||||||
|
html: file.html?.toString("utf-8"),
|
||||||
|
updatedAt: file.updatedAt?.toISOString(),
|
||||||
|
createdAt: file.createdAt?.toISOString(),
|
||||||
|
deletedAt: file.deletedAt?.toISOString()
|
||||||
|
})),
|
||||||
|
updatedAt: post.updatedAt?.toISOString(),
|
||||||
|
createdAt: post.createdAt?.toISOString(),
|
||||||
|
deletedAt: post.deletedAt?.toISOString(),
|
||||||
|
expiresAt: post.expiresAt?.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("author" in post && post.author) {
|
||||||
|
result = {
|
||||||
|
...result,
|
||||||
|
author: {
|
||||||
|
...post.author,
|
||||||
|
createdAt: post.author.createdAt?.toISOString(),
|
||||||
|
updatedAt: post.author.updatedAt?.toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFilesForPost = async (postId: string) => {
|
export const getFilesForPost = async (postId: string) => {
|
||||||
|
@ -87,12 +158,15 @@ export async function getFilesByPost(postId: string) {
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPostsByUser(userId: string): Promise<Post[]>
|
export async function getPostsByUser(userId: string): Promise<ServerPost[]>
|
||||||
export async function getPostsByUser(
|
export async function getPostsByUser(
|
||||||
userId: string,
|
userId: string,
|
||||||
includeFiles: true
|
includeFiles: true
|
||||||
): Promise<ServerPostWithFiles[]>
|
): Promise<ServerPostWithFiles[]>
|
||||||
export async function getPostsByUser(userId: User["id"], withFiles?: boolean) {
|
export async function getPostsByUser(
|
||||||
|
userId: ServerUser["id"],
|
||||||
|
withFiles?: boolean
|
||||||
|
) {
|
||||||
const posts = await prisma.post.findMany({
|
const posts = await prisma.post.findMany({
|
||||||
where: {
|
where: {
|
||||||
authorId: userId
|
authorId: userId
|
||||||
|
@ -123,7 +197,7 @@ export async function getPostsByUser(userId: User["id"], withFiles?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUserById = async (
|
export const getUserById = async (
|
||||||
userId: User["id"],
|
userId: ServerUser["id"],
|
||||||
selects?: Prisma.UserFindUniqueArgs["select"]
|
selects?: Prisma.UserFindUniqueArgs["select"]
|
||||||
) => {
|
) => {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
|
@ -143,7 +217,7 @@ export const getUserById = async (
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isUserAdmin = async (userId: User["id"]) => {
|
export const isUserAdmin = async (userId: ServerUser["id"]) => {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: userId
|
id: userId
|
||||||
|
@ -185,36 +259,24 @@ type GetPostByIdOptions = Pick<
|
||||||
"include" | "rejectOnNotFound" | "select"
|
"include" | "rejectOnNotFound" | "select"
|
||||||
>
|
>
|
||||||
|
|
||||||
export const getPostById = async (
|
export const getPostById = cache(
|
||||||
postId: Post["id"],
|
async (postId: ServerPost["id"], options?: GetPostByIdOptions) => {
|
||||||
options?: GetPostByIdOptions
|
const post = await prisma.post.findUnique({
|
||||||
): Promise<Post | PostWithFiles | PostWithFilesAndAuthor | null> => {
|
where: {
|
||||||
const post = await prisma.post.findUnique({
|
id: postId
|
||||||
where: {
|
},
|
||||||
id: postId
|
...options
|
||||||
},
|
})
|
||||||
...options
|
|
||||||
})
|
|
||||||
|
|
||||||
if (post) {
|
return post
|
||||||
if ("files" in post) {
|
|
||||||
// @ts-expect-error TODO: fix types so files can exist
|
|
||||||
post.files = post.files.map((file) => ({
|
|
||||||
...file,
|
|
||||||
content: file.content ? file.content.toString() : undefined,
|
|
||||||
html: file.html ? file.html.toString() : undefined
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
return post
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getAllPosts = cache(
|
export const getAllPosts = cache(
|
||||||
async (
|
async (
|
||||||
options?: Prisma.PostFindManyArgs
|
options?: Prisma.PostFindManyArgs
|
||||||
): Promise<
|
): Promise<
|
||||||
Post[] | ServerPostWithFiles[] | ServerPostWithFilesAndAuthor[]
|
ServerPost[] | ServerPostWithFiles[] | ServerPostWithFilesAndAuthor[]
|
||||||
> => {
|
> => {
|
||||||
const posts = await prisma.post.findMany(options)
|
const posts = await prisma.post.findMany(options)
|
||||||
return posts
|
return posts
|
||||||
|
@ -231,7 +293,7 @@ export type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
|
||||||
|
|
||||||
export const getAllUsers = async (
|
export const getAllUsers = async (
|
||||||
options?: Prisma.UserFindManyArgs
|
options?: Prisma.UserFindManyArgs
|
||||||
): Promise<User[] | UserWithPosts[]> => {
|
): Promise<ServerUser[] | UserWithPosts[]> => {
|
||||||
const users = (await prisma.user.findMany({
|
const users = (await prisma.user.findMany({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -242,7 +304,7 @@ export const getAllUsers = async (
|
||||||
createdAt: true
|
createdAt: true
|
||||||
},
|
},
|
||||||
...options
|
...options
|
||||||
})) as User[] | UserWithPosts[]
|
})) as ServerUser[] | UserWithPosts[]
|
||||||
|
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
@ -252,7 +314,7 @@ export const searchPosts = async (
|
||||||
{
|
{
|
||||||
userId
|
userId
|
||||||
}: {
|
}: {
|
||||||
userId?: User["id"]
|
userId?: ServerUser["id"]
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<ServerPostWithFiles[]> => {
|
): Promise<ServerPostWithFiles[]> => {
|
||||||
const posts = await prisma.post.findMany({
|
const posts = await prisma.post.findMany({
|
||||||
|
@ -287,7 +349,10 @@ function generateApiToken() {
|
||||||
return crypto.randomBytes(32).toString("hex")
|
return crypto.randomBytes(32).toString("hex")
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createApiToken = async (userId: User["id"], name: string) => {
|
export const createApiToken = async (
|
||||||
|
userId: ServerUser["id"],
|
||||||
|
name: string
|
||||||
|
) => {
|
||||||
const apiToken = await prisma.apiToken.create({
|
const apiToken = await prisma.apiToken.create({
|
||||||
data: {
|
data: {
|
||||||
token: generateApiToken(),
|
token: generateApiToken(),
|
||||||
|
@ -301,3 +366,19 @@ export const createApiToken = async (userId: User["id"], name: string) => {
|
||||||
|
|
||||||
return apiToken
|
return apiToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFileById(fileId: ServerFile["id"]) {
|
||||||
|
return prisma.file.findUnique({
|
||||||
|
where: {
|
||||||
|
id: fileId
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
post: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
visibility: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { withMethods } from "@lib/api-middleware/with-methods"
|
||||||
|
|
||||||
import { prisma } from "@lib/server/prisma"
|
import { prisma } from "@lib/server/prisma"
|
||||||
import { NextApiRequest, NextApiResponse } from "next"
|
import { NextApiRequest, NextApiResponse } from "next"
|
||||||
import { File } from "@lib/server/prisma"
|
import { ServerFile } from "@lib/server/prisma"
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file"
|
import { getHtmlFromFile } from "@lib/server/get-html-from-drift-file"
|
||||||
import { verifyApiUser } from "@lib/server/verify-api-user"
|
import { verifyApiUser } from "@lib/server/verify-api-user"
|
||||||
|
@ -14,7 +14,7 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse<unknown>) {
|
||||||
return res.status(401).json({ error: "Unauthorized" })
|
return res.status(401).json({ error: "Unauthorized" })
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = req.body.files as (Omit<File, "content" | "html"> & {
|
const files = req.body.files as (Omit<ServerFile, "content" | "html"> & {
|
||||||
content: string
|
content: string
|
||||||
html: string
|
html: string
|
||||||
})[]
|
})[]
|
||||||
|
|
Loading…
Reference in a new issue