chore: use prettier

This commit is contained in:
DIYgod 2022-08-06 18:49:04 +01:00
parent 5ea3e3465d
commit 34105452f9
No known key found for this signature in database
GPG Key ID: D159328F47A80DCA
57 changed files with 1151 additions and 595 deletions

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
.next
e2e
pnpm-lock.yaml
dist

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -12,7 +12,11 @@
"db-generate": "prisma generate",
"start": "node server.js",
"db-seed": "node -r esbuild-register prisma/seed.ts",
"uno-generate": "unocss \"src/**/*.{ts,tsx}\" -o src/generated/uno.css"
"uno-generate": "unocss \"src/**/*.{ts,tsx}\" -o src/generated/uno.css",
"prepare": "husky install"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@codemirror/commands": "^0.20.0",
@ -106,7 +110,10 @@
"esbuild-register": "^3.3.2",
"eslint": "^8.15.0",
"eslint-config-next": "^12.1.6",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"postcss-import": "^14.1.0",
"prettier": "^2.7.1",
"prisma": "^3.14.0",
"tailwindcss": "^3.0.24",
"typescript": "^4.5.5",

View File

@ -55,8 +55,10 @@ specifiers:
ethers: ^5.6.9
fast-deep-equal: ^3.1.3
form-data: ^4.0.0
husky: ^8.0.1
idb-keyval: ^6.2.0
ioredis: ^5.2.2
lint-staged: ^13.0.3
mailgun.js: ^5.2.2
mdast-util-to-string: ^3.1.0
nanoid: ^3.3.3
@ -64,6 +66,7 @@ specifiers:
pica: ^9.0.1
postcss-import: ^14.1.0
posthtml: ^0.16.6
prettier: ^2.7.1
prisma: ^3.14.0
prismjs: ^1.28.0
react: ^18.1.0
@ -189,7 +192,10 @@ devDependencies:
esbuild-register: 3.3.3_esbuild@0.14.47
eslint: 8.18.0
eslint-config-next: 12.1.6_ngoqluhis4kr6sqtxgtlos2xtq
husky: 8.0.1
lint-staged: 13.0.3
postcss-import: 14.1.0
prettier: 2.7.1
prisma: 3.15.2
tailwindcss: 3.1.4
typescript: 4.7.4
@ -2283,6 +2289,14 @@ packages:
resolution: {integrity: sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==}
dev: false
/aggregate-error/3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
dependencies:
clean-stack: 2.2.0
indent-string: 4.0.0
dev: true
/ajv/6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
dependencies:
@ -2292,6 +2306,13 @@ packages:
uri-js: 4.4.1
dev: true
/ansi-escapes/4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
engines: {node: '>=8'}
dependencies:
type-fest: 0.21.3
dev: true
/ansi-regex/4.1.1:
resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==}
engines: {node: '>=6'}
@ -2301,6 +2322,11 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
/ansi-regex/6.0.1:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
engines: {node: '>=12'}
dev: true
/ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@ -2314,6 +2340,11 @@ packages:
dependencies:
color-convert: 2.0.1
/ansi-styles/6.1.0:
resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==}
engines: {node: '>=12'}
dev: true
/any-signal/2.1.2:
resolution: {integrity: sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ==}
dependencies:
@ -2409,6 +2440,11 @@ packages:
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
dev: true
/astral-regex/2.0.0:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'}
dev: true
/async-busboy/1.1.0:
resolution: {integrity: sha512-6PKzCbx//XQLMwi8IitM/kBcrw9RuUL7T5rvHWHwudZzcNp+dJPVnH2FmYIKO/8x//iCHunkDjxtEomfjRwjSQ==}
dependencies:
@ -2917,6 +2953,34 @@ packages:
resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==}
dev: false
/clean-stack/2.2.0:
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
engines: {node: '>=6'}
dev: true
/cli-cursor/3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-truncate/2.1.0:
resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==}
engines: {node: '>=8'}
dependencies:
slice-ansi: 3.0.0
string-width: 4.2.3
dev: true
/cli-truncate/3.1.0:
resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
slice-ansi: 5.0.0
string-width: 5.1.2
dev: true
/cliui/5.0.0:
resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}
dependencies:
@ -2996,6 +3060,11 @@ packages:
resolution: {integrity: sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==}
dev: false
/commander/9.4.0:
resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==}
engines: {node: ^12.20.0 || >=14}
dev: true
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
@ -3361,6 +3430,10 @@ packages:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
dev: true
/eastasianwidth/0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
/eip1193-provider/1.0.1:
resolution: {integrity: sha512-kSuqwQ26d7CzuS/t3yRXo2Su2cVH0QfvyKbr2H7Be7O5YDyIq4hQGCNTo5wRdP07bt+E2R/8nPCzey4ojBHf7g==}
dependencies:
@ -3403,7 +3476,6 @@ packages:
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: false
/emoji-regex/9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
@ -4240,6 +4312,21 @@ packages:
strip-final-newline: 2.0.0
dev: true
/execa/6.1.0:
resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
cross-spawn: 7.0.3
get-stream: 6.0.1
human-signals: 3.0.1
is-stream: 3.0.0
merge-stream: 2.0.0
npm-run-path: 5.1.0
onetime: 6.0.0
signal-exit: 3.0.7
strip-final-newline: 3.0.0
dev: true
/ext/1.6.0:
resolution: {integrity: sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==}
dependencies:
@ -4759,6 +4846,17 @@ packages:
engines: {node: '>=10.17.0'}
dev: true
/human-signals/3.0.1:
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
engines: {node: '>=12.20.0'}
dev: true
/husky/8.0.1:
resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==}
engines: {node: '>=14'}
hasBin: true
dev: true
/iconv-lite/0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
@ -4797,7 +4895,6 @@ packages:
/indent-string/4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
dev: false
/inflight/1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
@ -5105,7 +5202,11 @@ packages:
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: false
/is-fullwidth-code-point/4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
dev: true
/is-generator-function/1.0.10:
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
@ -5209,6 +5310,11 @@ packages:
engines: {node: '>=8'}
dev: true
/is-stream/3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
/is-string/1.0.7:
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
engines: {node: '>= 0.4'}
@ -5515,6 +5621,48 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: false
/lint-staged/13.0.3:
resolution: {integrity: sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==}
engines: {node: ^14.13.1 || >=16.0.0}
hasBin: true
dependencies:
cli-truncate: 3.1.0
colorette: 2.0.19
commander: 9.4.0
debug: 4.3.4
execa: 6.1.0
lilconfig: 2.0.5
listr2: 4.0.5
micromatch: 4.0.5
normalize-path: 3.0.0
object-inspect: 1.12.2
pidtree: 0.6.0
string-argv: 0.3.1
yaml: 2.1.1
transitivePeerDependencies:
- enquirer
- supports-color
dev: true
/listr2/4.0.5:
resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==}
engines: {node: '>=12'}
peerDependencies:
enquirer: '>= 2.3.0 < 3'
peerDependenciesMeta:
enquirer:
optional: true
dependencies:
cli-truncate: 2.1.0
colorette: 2.0.19
log-update: 4.0.0
p-map: 4.0.0
rfdc: 1.3.0
rxjs: 7.5.6
through: 2.3.8
wrap-ansi: 7.0.0
dev: true
/local-pkg/0.4.1:
resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==}
engines: {node: '>=14'}
@ -5574,6 +5722,16 @@ packages:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/log-update/4.0.0:
resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
engines: {node: '>=10'}
dependencies:
ansi-escapes: 4.3.2
cli-cursor: 3.1.0
slice-ansi: 4.0.0
wrap-ansi: 6.2.0
dev: true
/long/4.0.0:
resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==}
dev: false
@ -6106,6 +6264,11 @@ packages:
engines: {node: '>=6'}
dev: true
/mimic-fn/4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
dev: true
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
@ -6375,6 +6538,13 @@ packages:
path-key: 3.1.1
dev: true
/npm-run-path/5.1.0:
resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
path-key: 4.0.0
dev: true
/number-to-bn/1.7.0:
resolution: {integrity: sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==}
engines: {node: '>=6.5.0', npm: '>=3'}
@ -6467,6 +6637,13 @@ packages:
mimic-fn: 2.1.0
dev: true
/onetime/6.0.0:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
dependencies:
mimic-fn: 4.0.0
dev: true
/optionator/0.9.1:
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
engines: {node: '>= 0.8.0'}
@ -6544,6 +6721,13 @@ packages:
p-limit: 3.1.0
dev: true
/p-map/4.0.0:
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
engines: {node: '>=10'}
dependencies:
aggregate-error: 3.1.0
dev: true
/p-retry/4.6.2:
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
engines: {node: '>=8'}
@ -6621,6 +6805,11 @@ packages:
engines: {node: '>=8'}
dev: true
/path-key/4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@ -6680,6 +6869,12 @@ packages:
engines: {node: '>=8.6'}
dev: true
/pidtree/0.6.0:
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
engines: {node: '>=0.10'}
hasBin: true
dev: true
/pify/2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@ -6835,6 +7030,12 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prettier/2.7.1:
resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
/prisma/3.15.2:
resolution: {integrity: sha512-nMNSMZvtwrvoEQ/mui8L/aiCLZRCj5t6L3yujKpcDhIPk7garp8tL4nMx2+oYsN0FWBacevJhazfXAbV1kfBzA==}
engines: {node: '>=12.6'}
@ -7331,6 +7532,14 @@ packages:
supports-preserve-symlinks-flag: 1.0.0
dev: true
/restore-cursor/3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
dev: true
/retimer/2.0.0:
resolution: {integrity: sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg==}
dev: false
@ -7345,6 +7554,10 @@ packages:
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
/rfdc/1.3.0:
resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
dev: true
/rimraf/3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@ -7386,6 +7599,12 @@ packages:
tslib: 1.14.1
dev: false
/rxjs/7.5.6:
resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==}
dependencies:
tslib: 2.4.0
dev: true
/sade/1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
@ -7516,6 +7735,32 @@ packages:
engines: {node: '>=8'}
dev: true
/slice-ansi/3.0.0:
resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
engines: {node: '>=8'}
dependencies:
ansi-styles: 4.3.0
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
dev: true
/slice-ansi/4.0.0:
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
dev: true
/slice-ansi/5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
dependencies:
ansi-styles: 6.1.0
is-fullwidth-code-point: 4.0.0
dev: true
/sortablejs/1.15.0:
resolution: {integrity: sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==}
dev: false
@ -7600,6 +7845,11 @@ packages:
engines: {node: '>=4'}
dev: false
/string-argv/0.3.1:
resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==}
engines: {node: '>=0.6.19'}
dev: true
/string-width/3.1.0:
resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
engines: {node: '>=6'}
@ -7616,7 +7866,15 @@ packages:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: false
/string-width/5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.0.1
dev: true
/string.prototype.matchall/4.0.7:
resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==}
@ -7671,6 +7929,13 @@ packages:
dependencies:
ansi-regex: 5.0.1
/strip-ansi/7.0.1:
resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
engines: {node: '>=12'}
dependencies:
ansi-regex: 6.0.1
dev: true
/strip-bom/3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
@ -7681,6 +7946,11 @@ packages:
engines: {node: '>=6'}
dev: true
/strip-final-newline/3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
dev: true
/strip-hex-prefix/1.0.0:
resolution: {integrity: sha1-DF8VX+8RUTczd96du1iNoFUA428=}
engines: {node: '>=6.5.0', npm: '>=3'}
@ -7781,6 +8051,10 @@ packages:
resolution: {integrity: sha512-YGdk8sdmr4ge3g+doFj/7RLF5kLM+Mi7DEciu9PHxnMJZMeVuZeTj31g4VE7ekUffx/IdbvrtOCiz62afg0mkg==}
dev: false
/through/2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true
/timeout-abort-controller/1.1.1:
resolution: {integrity: sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ==}
dependencies:
@ -7859,7 +8133,6 @@ packages:
/tslib/2.4.0:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
dev: false
/tsutils/3.21.0_typescript@4.7.4:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
@ -7893,6 +8166,11 @@ packages:
engines: {node: '>=10'}
dev: true
/type-fest/0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
dev: true
/type-fest/0.6.0:
resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}
engines: {node: '>=8'}
@ -8566,7 +8844,15 @@ packages:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: false
/wrap-ansi/7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@ -8631,6 +8917,11 @@ packages:
engines: {node: '>= 6'}
dev: true
/yaml/2.1.1:
resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==}
engines: {node: '>= 14'}
dev: true
/yargs-parser/13.1.2:
resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==}
dependencies:

View File

@ -1,7 +1,7 @@
import { ConnectButton as RainbowConnectButton } from "@rainbow-me/rainbowkit"
import { useGetUserSites } from "~/queries/site"
import { useAccount, useDisconnect } from "wagmi"
import { Avatar } from "~/components/ui/Avatar";
import { Avatar } from "~/components/ui/Avatar"
import type { HeaderLinkType } from "~/components/site/SiteHeader"
import { DashboardIcon } from "../icons/DashboardIcon"
import { LogoutIcon } from "../icons/LogoutIcon"
@ -13,7 +13,7 @@ import { useEffect, useState } from "react"
export const ConnectButton: React.FC<{
left?: boolean
}> = ({ left, }) => {
}> = ({ left }) => {
const { address } = useAccount()
const userSites = useGetUserSites(address)
const { disconnect } = useDisconnect()
@ -61,67 +61,88 @@ export const ConnectButton: React.FC<{
openConnectModal,
mounted,
}) => {
return (
<div
{...(!mounted && {
'aria-hidden': true,
'style': {
"aria-hidden": true,
style: {
opacity: 0,
pointerEvents: 'none',
userSelect: 'none',
pointerEvents: "none",
userSelect: "none",
},
})}
>
{(() => {
if (!mounted || !account || !chain) {
return (
<button className="text-indigo-600" onClick={openConnectModal} type="button" style={{ height: "30px" }}>
<button
className="text-indigo-600"
onClick={openConnectModal}
type="button"
style={{ height: "30px" }}
>
Connect Wallet
</button>
);
)
}
return (
<div className="flex relative group" style={{ gap: 12, height: "30px" }}>
{userSites.isSuccess ?
<>
<button className="flex items-center" type="button">
<Avatar
className="align-middle mr-2"
images={userSites.data?.[0]?.avatars || []}
name={userSites.data?.[0]?.name}
size={30}
/>
<div className="flex flex-col">
<span className="text-left leading-none text-gray-600 font-bold" style={{ "marginBottom": "0.15rem" }}>{userSites.data?.[0]?.name || account.displayName}</span>
<span className="text-left leading-none text-xs text-gray-400">{"@" + userSites.data?.[0]?.username || account.displayName}</span>
<div
className="flex relative group"
style={{ gap: 12, height: "30px" }}
>
{userSites.isSuccess ? (
<>
<button className="flex items-center" type="button">
<Avatar
className="align-middle mr-2"
images={userSites.data?.[0]?.avatars || []}
name={userSites.data?.[0]?.name}
size={30}
/>
<div className="flex flex-col">
<span
className="text-left leading-none text-gray-600 font-bold"
style={{ marginBottom: "0.15rem" }}
>
{userSites.data?.[0]?.name || account.displayName}
</span>
<span className="text-left leading-none text-xs text-gray-400">
{"@" + userSites.data?.[0]?.username ||
account.displayName}
</span>
</div>
</button>
<div
className={`absolute hidden ${
left ? "left" : "right"
}-0 pt-2 group-hover:block top-full z-10 text-gray-600`}
>
<div className="bg-white rounded-lg ring-1 ring-zinc-100 min-w-[140px] shadow-md py-2 text-sm">
{dropdownLinks.map((link, i) => {
return (
<UniLink
key={i}
href={link.url}
onClick={link.onClick}
className="px-4 h-8 flex items-center w-full whitespace-nowrap hover:bg-zinc-100"
>
<span className="mr-2">{link.icon}</span>
{link.label}
</UniLink>
)
})}
</div>
</div>
</button>
<div className={`absolute hidden ${left ? "left" : "right"}-0 pt-2 group-hover:block top-full z-10 text-gray-600`}>
<div className="bg-white rounded-lg ring-1 ring-zinc-100 min-w-[140px] shadow-md py-2 text-sm">
{dropdownLinks.map((link, i) => {
return (
<UniLink
key={i}
href={link.url}
onClick={link.onClick}
className="px-4 h-8 flex items-center w-full whitespace-nowrap hover:bg-zinc-100"
>
<span className="mr-2">{link.icon}</span>
{link.label}
</UniLink>
)
})}
</div>
</div>
</>
: ""}
</>
) : (
""
)}
</div>
);
)
})()}
</div>
);
)
}}
</RainbowConnectButton.Custom>
);
};
)
}

View File

@ -20,7 +20,7 @@ const AvatarEditorModal: React.FC<{
const updateSite = useUpdateSite()
const useCropAndSave = async () => {
let key;
let key
// Get cropped image
if (editorRef.current) {
const fromCanvas = editorRef.current?.getImage()
@ -74,7 +74,11 @@ const AvatarEditorModal: React.FC<{
</div>
<div className="h-16 border-t flex items-center px-5">
<Button isBlock onClick={useCropAndSave} isLoading={updateSite.isLoading}>
<Button
isBlock
onClick={useCropAndSave}
isLoading={updateSite.isLoading}
>
Crop and Save
</Button>
</div>

View File

@ -10,7 +10,7 @@ import { UniLink } from "../ui/UniLink"
import { DashboardSidebar } from "./DashboardSidebar"
import { SiteSwitcher } from "./SiteSwitcher"
import { useGetUserSites } from "~/queries/site"
import { useAccount } from 'wagmi'
import { useAccount } from "wagmi"
import { ConnectButton } from "~/components/common/ConnectButton"
export function DashboardLayout({
@ -25,7 +25,7 @@ export function DashboardLayout({
const { address } = useAccount()
const userSite = useGetUserSites(address);
const userSite = useGetUserSites(address)
useEffect(() => {
if (!address) {

View File

@ -8,7 +8,9 @@ export const DashboardMain: React.FC<{
<div className="md:pl-sidebar w-full">
<div
className={clsx(
fullWidth ? "relative" : "max-w-screen-xl relative px-5 py-5 md:px-10"
fullWidth
? "relative"
: "max-w-screen-xl relative px-5 py-5 md:px-10",
)}
>
{children}

View File

@ -25,7 +25,7 @@ export const PagesManager: React.FC<{
router.query.visibility
? (router.query.visibility as PageVisibilityEnum)
: PageVisibilityEnum.All,
[router.query.visibility]
[router.query.visibility],
)
const deletePage = useDeletePage()
@ -128,7 +128,7 @@ export const PagesManager: React.FC<{
onClick() {
deletePage.mutate({
site: subdomain,
id: page.id
id: page.id,
})
},
},
@ -146,11 +146,11 @@ export const PagesManager: React.FC<{
className={clsx(
`space-x-2 px-3 h-9 inline-flex items-center justify-center text-white rounded-lg text-sm`,
isPost ? `bg-pink-500` : `bg-blue-500`
isPost ? `bg-pink-500` : `bg-blue-500`,
)}
onClick={() =>
router.push(
`/dashboard/${subdomain}/editor?type=${isPost ? "post" : "page"}`
`/dashboard/${subdomain}/editor?type=${isPost ? "post" : "page"}`,
)
}
>
@ -182,14 +182,18 @@ export const PagesManager: React.FC<{
<a className="group relative hover:bg-zinc-100 rounded-lg py-3 px-3 transition-colors -mx-3 flex justify-between">
<div>
<div className="flex items-center">
<span>{page.title || 'Untitled'}</span>
<span>{page.title || "Untitled"}</span>
</div>
<div className="text-zinc-400 text-xs mt-1">
<span className="capitalize">
{getPageVisibility(page).toLowerCase()}
</span>
<span className="mx-2">·</span>
<span>{getPageVisibility(page) === PageVisibilityEnum.Draft ? formatDate(page.date_updated) : formatDate(page.date_published)}</span>
<span>
{getPageVisibility(page) === PageVisibilityEnum.Draft
? formatDate(page.date_updated)
: formatDate(page.date_published)}
</span>
</div>
</div>
<div className="w-10 flex-shrink-0">
@ -200,7 +204,7 @@ export const PagesManager: React.FC<{
<button
className={clsx(
`text-gray-400 relative z-50 w-8 h-8 rounded inline-flex invisible group-hover:visible justify-center items-center`,
open ? `bg-gray-200` : `hover:bg-gray-200`
open ? `bg-gray-200` : `hover:bg-gray-200`,
)}
onClick={(e) => {
e.stopPropagation()

View File

@ -16,9 +16,7 @@ export const SiteSwitcher: React.FC<Props> = ({
subscriptions,
viewer,
}) => {
const activeSubscription = subscriptions.find(
(s) => s.username === subdomain
)
const activeSubscription = subscriptions.find((s) => s.username === subdomain)
return (
<div className="px-3 pt-3 pb-2 text-sm">
@ -63,7 +61,8 @@ export const SiteSwitcher: React.FC<Props> = ({
<span className="truncate w-8/12">
{subscription.name}
</span>
{activeSubscription?.username === subscription.username && (
{activeSubscription?.username ===
subscription.username && (
<span className="text-accent">
<svg
className="w-5 h-5"

View File

@ -1,9 +1,25 @@
import React from "react"
export const BlockchainIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg width="1em" height="1em" {...props} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M128 320v286.208a106.666667 106.666667 0 1 0 106.965333 182.912l256.128 144.085333a42.666667 42.666667 0 0 0 41.813334 0l256.128-144.085333A106.666667 106.666667 0 1 0 896 606.208V320a42.666667 42.666667 0 0 0-21.76-37.205333l-256.085333-144.042667a106.666667 106.666667 0 0 0-212.309334 0L149.76 282.794667A42.666667 42.666667 0 0 0 128 320z m362.666667-170.666667a21.333333 21.333333 0 1 1 42.666666 0 21.333333 21.333333 0 0 1-42.666666 0zM469.333333 247.125333v91.050667l-106.837333 62.293333A42.666667 42.666667 0 0 0 341.333333 437.333333v113.92l-111.744 63.829334c-5.12-3.413333-10.538667-6.4-16.256-8.874667V344.96l219.904-123.733333c10.026667 11.008 22.314667 19.882667 36.096 25.898666z m341.333334 359.082667a106.325333 106.325333 0 0 0-16.256 8.874667L682.666667 551.253333v-113.92a42.666667 42.666667 0 0 0-21.162667-36.864L554.666667 338.176V247.125333c13.781333-6.016 26.069333-14.933333 36.096-25.856L810.666667 344.96v261.248z m-62.634667 80.64a107.946667 107.946667 0 0 0-0.853333 27.904L512 847.018667l-235.221333-132.266667a107.946667 107.946667 0 0 0-0.853334-27.861333l98.602667-56.32 115.968 67.626666a42.666667 42.666667 0 0 0 43.008 0l115.968-67.669333 98.56 56.32zM597.333333 461.866667v100.352l-85.333333 49.749333-85.333333-49.749333v-100.352l85.333333-49.749334 85.333333 49.749334zM170.666667 682.666667a21.333333 21.333333 0 1 0 0 42.666666 21.333333 21.333333 0 0 0 0-42.666666z m661.333333 21.333333a21.333333 21.333333 0 1 1 42.666667 0 21.333333 21.333333 0 0 1-42.666667 0z" p-id="1166"></path><path d="M512 128a21.333333 21.333333 0 1 0 0 42.666667 21.333333 21.333333 0 0 0 0-42.666667zM597.333333 562.176v-100.352l-85.333333-49.749333-85.333333 49.749333v100.352l85.333333 49.749333 85.333333-49.749333zM149.333333 704a21.333333 21.333333 0 1 1 42.666667 0 21.333333 21.333333 0 0 1-42.666667 0zM853.333333 682.666667a21.333333 21.333333 0 1 0 0 42.666666 21.333333 21.333333 0 0 0 0-42.666666z" p-id="1167"></path></svg>
<svg
width="1em"
height="1em"
{...props}
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M128 320v286.208a106.666667 106.666667 0 1 0 106.965333 182.912l256.128 144.085333a42.666667 42.666667 0 0 0 41.813334 0l256.128-144.085333A106.666667 106.666667 0 1 0 896 606.208V320a42.666667 42.666667 0 0 0-21.76-37.205333l-256.085333-144.042667a106.666667 106.666667 0 0 0-212.309334 0L149.76 282.794667A42.666667 42.666667 0 0 0 128 320z m362.666667-170.666667a21.333333 21.333333 0 1 1 42.666666 0 21.333333 21.333333 0 0 1-42.666666 0zM469.333333 247.125333v91.050667l-106.837333 62.293333A42.666667 42.666667 0 0 0 341.333333 437.333333v113.92l-111.744 63.829334c-5.12-3.413333-10.538667-6.4-16.256-8.874667V344.96l219.904-123.733333c10.026667 11.008 22.314667 19.882667 36.096 25.898666z m341.333334 359.082667a106.325333 106.325333 0 0 0-16.256 8.874667L682.666667 551.253333v-113.92a42.666667 42.666667 0 0 0-21.162667-36.864L554.666667 338.176V247.125333c13.781333-6.016 26.069333-14.933333 36.096-25.856L810.666667 344.96v261.248z m-62.634667 80.64a107.946667 107.946667 0 0 0-0.853333 27.904L512 847.018667l-235.221333-132.266667a107.946667 107.946667 0 0 0-0.853334-27.861333l98.602667-56.32 115.968 67.626666a42.666667 42.666667 0 0 0 43.008 0l115.968-67.669333 98.56 56.32zM597.333333 461.866667v100.352l-85.333333 49.749333-85.333333-49.749333v-100.352l85.333333-49.749334 85.333333 49.749334zM170.666667 682.666667a21.333333 21.333333 0 1 0 0 42.666666 21.333333 21.333333 0 0 0 0-42.666666z m661.333333 21.333333a21.333333 21.333333 0 1 1 42.666667 0 21.333333 21.333333 0 0 1-42.666667 0z"
p-id="1166"
></path>
<path
d="M512 128a21.333333 21.333333 0 1 0 0 42.666667 21.333333 21.333333 0 0 0 0-42.666667zM597.333333 562.176v-100.352l-85.333333-49.749333-85.333333 49.749333v100.352l85.333333 49.749333 85.333333-49.749333zM149.333333 704a21.333333 21.333333 0 1 1 42.666667 0 21.333333 21.333333 0 0 1-42.666667 0zM853.333333 682.666667a21.333333 21.333333 0 1 0 0 42.666666 21.333333 21.333333 0 0 0 0-42.666666z"
p-id="1167"
></path>
</svg>
)
}

View File

@ -1,9 +1,22 @@
import React from "react"
export const CopyIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg width="1em" height="1em" {...props} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5253"><path d="M832 128c-12.8-76.8-76.8-128-160-128h-448C102.4 0 0 102.4 0 224v448c0 76.8 57.6 140.8 128 160 12.8 76.8 76.8 128 160 128h512c89.6 0 160-70.4 160-160v-512c0-76.8-57.6-140.8-128-160zM64 672v-448C64 134.4 134.4 64 224 64h448c44.8 0 76.8 25.6 89.6 64H288C198.4 128 128 198.4 128 288v473.6c-38.4-12.8-64-44.8-64-89.6z m832 128c0 51.2-44.8 96-96 96h-512c-51.2 0-96-44.8-96-96v-512C192 236.8 236.8 192 288 192h512c51.2 0 96 44.8 96 96v512z" p-id="5254"></path></svg>
<svg
width="1em"
height="1em"
{...props}
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5253"
>
<path
d="M832 128c-12.8-76.8-76.8-128-160-128h-448C102.4 0 0 102.4 0 224v448c0 76.8 57.6 140.8 128 160 12.8 76.8 76.8 128 160 128h512c89.6 0 160-70.4 160-160v-512c0-76.8-57.6-140.8-128-160zM64 672v-448C64 134.4 134.4 64 224 64h448c44.8 0 76.8 25.6 89.6 64H288C198.4 128 128 198.4 128 288v473.6c-38.4-12.8-64-44.8-64-89.6z m832 128c0 51.2-44.8 96-96 96h-512c-51.2 0-96-44.8-96-96v-512C192 236.8 236.8 192 288 192h512c51.2 0 96 44.8 96 96v512z"
p-id="5254"
></path>
</svg>
)
}

View File

@ -1,9 +1,18 @@
import React from "react"
export const CrossbellIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg width="1em" height="1em" {...props} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 1024c70.64 0 127.94-57.3 127.94-128H384.06c0 70.7 57.3 128 127.94 128z m430.78-299.42c-38.64-41.52-110.94-103.98-110.94-308.58 0-155.4-108.96-279.8-255.88-310.32V64c0-35.34-28.64-64-63.96-64s-63.96 28.66-63.96 64v41.68C301.12 136.2 192.16 260.6 192.16 416c0 204.6-72.3 267.06-110.94 308.58-12 12.9-17.32 28.32-17.22 43.42 0.22 32.8 25.96 64 64.2 64h767.6c38.24 0 64-31.2 64.2-64 0.1-15.1-5.22-30.54-17.22-43.42z"></path></svg>
<svg
width="1em"
height="1em"
{...props}
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M512 1024c70.64 0 127.94-57.3 127.94-128H384.06c0 70.7 57.3 128 127.94 128z m430.78-299.42c-38.64-41.52-110.94-103.98-110.94-308.58 0-155.4-108.96-279.8-255.88-310.32V64c0-35.34-28.64-64-63.96-64s-63.96 28.66-63.96 64v41.68C301.12 136.2 192.16 260.6 192.16 416c0 204.6-72.3 267.06-110.94 308.58-12 12.9-17.32 28.32-17.22 43.42 0.22 32.8 25.96 64 64.2 64h767.6c38.24 0 64-31.2 64.2-64 0.1-15.1-5.22-30.54-17.22-43.42z"></path>
</svg>
)
}

View File

@ -1,7 +1,7 @@
import React from "react"
export const DashboardIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg width="1em" height="1em" viewBox="0 0 24 24" {...props}>

View File

@ -1,9 +1,23 @@
import React from "react"
export const LogoutIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg width="1em" height="1em" {...props} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2564"><path d="M896 512c0-14.16-6.128-26.896-15.888-35.68l-160-144a48 48 0 0 0-64.224 71.36L722.912 464H432a48 48 0 1 0 0 96h290.912l-67.008 60.32a48 48 0 1 0 64.208 71.36l160-144A47.84 47.84 0 0 0 896 512zM416 832H224V192h192a48 48 0 1 0 0-96H176a48 48 0 0 0-48 48v736a48 48 0 0 0 48 48h240a48 48 0 1 0 0-96z" fill="" p-id="2565"></path></svg>
<svg
width="1em"
height="1em"
{...props}
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2564"
>
<path
d="M896 512c0-14.16-6.128-26.896-15.888-35.68l-160-144a48 48 0 0 0-64.224 71.36L722.912 464H432a48 48 0 1 0 0 96h290.912l-67.008 60.32a48 48 0 1 0 64.208 71.36l160-144A47.84 47.84 0 0 0 896 512zM416 832H224V192h192a48 48 0 1 0 0-96H176a48 48 0 0 0-48 48v736a48 48 0 0 0 48 48h240a48 48 0 1 0 0-96z"
fill=""
p-id="2565"
></path>
</svg>
)
}

View File

@ -1,7 +1,7 @@
import React from "react"
export const MoreIcon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg

View File

@ -1,9 +1,26 @@
import React from "react"
export const RSS3Icon: React.FC<React.ComponentPropsWithoutRef<"svg">> = (
props
props,
) => {
return (
<svg width="1em" height="1em" {...props} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6994"><path d="M139.093333 974.336a96.426667 96.426667 0 1 0 0-192.853333 96.426667 96.426667 0 0 0 0 192.853333zM926.122667 971.264c-26.453333 0-46.634667-20.224-46.634667-46.677333 0-433.962667-353.066667-788.608-788.608-788.608-26.453333 0-46.634667-20.224-46.634667-46.634667C44.202667 62.890667 64.426667 42.666667 90.88 42.666667 577.706667 42.666667 972.8 439.296 972.8 924.586667c0 26.453333-20.224 46.634667-46.677333 46.634666z" p-id="6995"></path><path d="M601.045333 972.8c-26.453333 0-46.634667-20.224-46.634666-46.677333 0-255.061333-208.426667-463.488-463.530667-463.488-26.453333 0-46.634667-20.224-46.634667-46.677334 0-26.453333 20.181333-46.634667 46.634667-46.634666 307.968 0 556.842667 250.410667 556.842667 556.8 0 24.917333-21.76 46.677333-46.677334 46.677333z" p-id="6996"></path></svg>
<svg
width="1em"
height="1em"
{...props}
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6994"
>
<path
d="M139.093333 974.336a96.426667 96.426667 0 1 0 0-192.853333 96.426667 96.426667 0 0 0 0 192.853333zM926.122667 971.264c-26.453333 0-46.634667-20.224-46.634667-46.677333 0-433.962667-353.066667-788.608-788.608-788.608-26.453333 0-46.634667-20.224-46.634667-46.634667C44.202667 62.890667 64.426667 42.666667 90.88 42.666667 577.706667 42.666667 972.8 439.296 972.8 924.586667c0 26.453333-20.224 46.634667-46.677333 46.634666z"
p-id="6995"
></path>
<path
d="M601.045333 972.8c-26.453333 0-46.634667-20.224-46.634666-46.677333 0-255.061333-208.426667-463.488-463.530667-463.488-26.453333 0-46.634667-20.224-46.634667-46.677334 0-26.453333 20.181333-46.634667 46.634667-46.634666 307.968 0 556.842667 250.410667 556.842667 556.8 0 24.917333-21.76 46.677333-46.677334 46.677333z"
p-id="6996"
></path>
</svg>
)
}

View File

@ -37,7 +37,15 @@ export function MainLayout({
<footer className="mt-10 text-sm font-medium text-zinc-500">
<div className="max-w-screen-md px-5 mx-auto flex justify-between">
<div>
<span className="align-middle">&copy; <UniLink href={`https://blog.${OUR_DOMAIN}`} className="hover:text-indigo-500">{APP_NAME}</UniLink></span>
<span className="align-middle">
&copy;{" "}
<UniLink
href={`https://blog.${OUR_DOMAIN}`}
className="hover:text-indigo-500"
>
{APP_NAME}
</UniLink>
</span>
<span className="text-zinc-400 ml-2 inline-flex items-center space-x-1 align-middle">
<UniLink href={DISCORD_LINK}>
<span className="inline-block i-mdi-discord text-xl hover:text-indigo-500"></span>

View File

@ -12,34 +12,93 @@ export const SiteFooter: React.FC<{
return (
<footer className="text-zinc-500 border-t">
<div className="max-w-screen-md mx-auto px-5 py-10 text-xs">
<p className="mb-4">🔔 This {page ? (page.tags?.includes("post") ? "post" : "page") : "blog"} has been permanently stored on the blockchain and signed by its creator.</p>
<p className="mb-4">
🔔 This{" "}
{page ? (page.tags?.includes("post") ? "post" : "page") : "blog"} has
been permanently stored on the blockchain and signed by its creator.
</p>
<ul className="mb-6">
<li className="mt-2">
<div className="font-bold">Blockchain Transaction</div>
<div>
{page ?
page?.related_urls?.filter((url) => url.startsWith("https://scan.crossbell.io/tx/")).map((url) => {
return <a target="_blank" rel="noreferrer" className="inline-block mr-4 break-all" href={url} key={url}>{url.replace("https://scan.crossbell.io/tx/", "").slice(0, 10)}...{url.replace("https://scan.crossbell.io/tx/", "").slice(-10)}</a>
}) :
site?.metadata?.transactions.map((hash: string) => {
return <a target="_blank" rel="noreferrer" className="inline-block mr-4 break-all" href={`https://scan.crossbell.io/tx/${hash}`} key={hash}>{hash.slice(0, 10)}...{hash.slice(-10)}</a>
})
}
{page
? page?.related_urls
?.filter((url) =>
url.startsWith("https://scan.crossbell.io/tx/"),
)
.map((url) => {
return (
<a
target="_blank"
rel="noreferrer"
className="inline-block mr-4 break-all"
href={url}
key={url}
>
{url
.replace("https://scan.crossbell.io/tx/", "")
.slice(0, 10)}
...
{url
.replace("https://scan.crossbell.io/tx/", "")
.slice(-10)}
</a>
)
})
: site?.metadata?.transactions.map((hash: string) => {
return (
<a
target="_blank"
rel="noreferrer"
className="inline-block mr-4 break-all"
href={`https://scan.crossbell.io/tx/${hash}`}
key={hash}
>
{hash.slice(0, 10)}...{hash.slice(-10)}
</a>
)
})}
</div>
</li>
{
page &&
{page && (
<li className="mt-2">
<div className="font-bold">IPFS Address</div>
<div>{page?.related_urls?.filter((url) => url.startsWith("https://gateway.ipfs.io/ipfs/")).map((url) => {
return <a target="_blank" rel="noreferrer" className="inline-block mr-4 break-all" href={url} key={url}>{url.replace("https://gateway.ipfs.io/ipfs/", "ipfs://")}</a>
})}</div>
<div>
{page?.related_urls
?.filter((url) =>
url.startsWith("https://gateway.ipfs.io/ipfs/"),
)
.map((url) => {
return (
<a
target="_blank"
rel="noreferrer"
className="inline-block mr-4 break-all"
href={url}
key={url}
>
{url.replace(
"https://gateway.ipfs.io/ipfs/",
"ipfs://",
)}
</a>
)
})}
</div>
</li>
}
)}
<li className="mt-2">
<div className="font-bold">Author Address</div>
<div>
<a target="_blank" rel="noreferrer" className="inline-block mr-4 break-all" href={`https://scan.crossbell.io/address/${site?.metadata?.owner}`} key={site?.metadata?.owner}>{site?.metadata?.owner}</a>
<a
target="_blank"
rel="noreferrer"
className="inline-block mr-4 break-all"
href={`https://scan.crossbell.io/address/${site?.metadata?.owner}`}
key={site?.metadata?.owner}
>
{site?.metadata?.owner}
</a>
</div>
</li>
</ul>

View File

@ -17,8 +17,12 @@ import { useAccount } from "wagmi"
import unidata from "~/lib/unidata"
import { Fragment, useEffect, useState } from "react"
import toast from "react-hot-toast"
import { useGetSubscription, useSubscribeToSite, useUnsubscribeFromSite } from "~/queries/site"
import { useConnectModal } from '@rainbow-me/rainbowkit';
import {
useGetSubscription,
useSubscribeToSite,
useUnsubscribeFromSite,
} from "~/queries/site"
import { useConnectModal } from "@rainbow-me/rainbowkit"
import { Menu } from "@headlessui/react"
import { MoreIcon } from "~/components/icons/MoreIcon"
import { RSS3Icon } from "~/components/icons/RSS3Icon"
@ -79,19 +83,32 @@ export const SiteHeader: React.FC<{
}
const subscription = useGetSubscription({
userId: address || '',
siteId: site?.username || '',
userId: address || "",
siteId: site?.username || "",
})
useEffect(() => {
if (followProgress && address && subscription.isSuccess && !subscription.data && site?.username) {
if (
followProgress &&
address &&
subscription.isSuccess &&
!subscription.data &&
site?.username
) {
subscribeToSite.mutate({
userId: address,
siteId: site.username,
})
setFollowProgress(false)
}
}, [followProgress, address, subscription.isSuccess, subscription.data, site?.username, subscribeToSite])
}, [
followProgress,
address,
subscription.isSuccess,
subscription.data,
site?.username,
subscribeToSite,
])
const leftLinks: HeaderLinkType[] = [
{ label: "Home", url: "/" },
@ -134,14 +151,16 @@ export const SiteHeader: React.FC<{
<div className="text-gray-500 text-sm">{site?.bio}</div>
)}
<div className="mt-3 text-sm">
{subscription.data ?
{subscription.data ? (
<Button
rounded="full"
size="sm"
variant="secondary"
onClick={handleClickSubscribe}
className="space-x-1 group align-middle"
isLoading={unsubscribeFromSite.isLoading || subscribeToSite.isLoading}
isLoading={
unsubscribeFromSite.isLoading || subscribeToSite.isLoading
}
>
<span className="pl-1">
<CrossbellIcon />
@ -152,23 +171,26 @@ export const SiteHeader: React.FC<{
<span className="pr-1 hidden group-hover:block w-16">
Unfollow
</span>
</Button> :
</Button>
) : (
<Button
rounded="full"
size="sm"
variant="crossbell"
onClick={handleClickSubscribe}
className="space-x-1 align-middle"
isLoading={unsubscribeFromSite.isLoading || subscribeToSite.isLoading || subscription.isLoading}
isLoading={
unsubscribeFromSite.isLoading ||
subscribeToSite.isLoading ||
subscription.isLoading
}
>
<span className="pl-1">
<CrossbellIcon />
</span>
<span className="pr-1">
Follow
</span>
<span className="pr-1">Follow</span>
</Button>
}
)}
<div className="relative inline-block align-middle h-7">
<Menu>
{() => (
@ -194,7 +216,9 @@ export const SiteHeader: React.FC<{
href={item.url}
className="h-10 flex w-full space-x-2 items-center px-3 hover:bg-gray-100"
>
<span className="fill-gray-500">{item.icon}</span>
<span className="fill-gray-500">
{item.icon}
</span>
<span>{item.text}</span>
</UniLink>
)

View File

@ -44,10 +44,8 @@ export const SiteLayout: React.FC<SiteLayoutProps> = ({
description={ogDescription ?? site?.bio}
image={getUserContentsUrl(site?.avatars?.[0])}
/>
<SiteHeader
site={site}
/>
<style>{ site?.css }</style>
<SiteHeader site={site} />
<style>{site?.css}</style>
<div className={clsx(`max-w-screen-md mx-auto px-5 pb-12`, `pt-12`)}>
{children}
</div>

View File

@ -5,7 +5,7 @@ import { PostMeta } from "./PostMeta"
import { Profile, Note } from "~/lib/types"
export const SitePage: React.FC<{
site?: Profile,
site?: Profile
page?: Note
}> = ({ site, page }) => {
return (
@ -19,18 +19,19 @@ export const SitePage: React.FC<{
{page?.tags?.includes("post") && (
<PostMeta
publishedAt={page.date_published}
authors={[{
id: site?.username || "",
name: site?.name || "",
avatar: site?.avatars?.[0] || null,
}] || []}
authors={
[
{
id: site?.username || "",
name: site?.name || "",
avatar: site?.avatars?.[0] || null,
},
] || []
}
/>
)}
</div>
<PageContent
contentHTML={page?.body?.content || ""}
className="my-10"
/>
<PageContent contentHTML={page?.body?.content || ""} className="my-10" />
</>
)
}

View File

@ -34,7 +34,7 @@ export const Avatar: React.FC<
className={clsx(
`inline-flex text-white bg-gray-400 items-center justify-center text-xl font-medium uppercase flex-shrink-0`,
borderRadius,
className
className,
)}
style={{
width: `${size}px`,
@ -53,7 +53,7 @@ export const Avatar: React.FC<
className={clsx(
`inline-flex text-zinc-500 bg-gray-400 flex-shrink-0 items-center justify-center text-xl font-medium uppercase`,
borderRadius,
className
className,
)}
style={{
width: `${size}px`,

View File

@ -36,7 +36,7 @@ export const Button = React.forwardRef<
isAutoWidth,
...props
},
ref
ref,
) {
return (
<button
@ -54,7 +54,7 @@ export const Button = React.forwardRef<
isAutoWidth && `is-auto-width`,
size && `is-${size}`,
`is-${variant || "primary"}`,
rounded === "full" ? "rounded-full" : "rounded-lg"
rounded === "full" ? "rounded-full" : "rounded-lg",
)}
>
{children}

View File

@ -1,29 +1,27 @@
import { useCallback } from "react"
import { Web3Storage } from 'web3.storage'
import { Web3Storage } from "web3.storage"
export const useUploadFile = () => {
const uploadFile = useCallback<UploadFile>(
async (blob, filename) => {
const file = new File([blob], filename)
const web3Storage = new Web3Storage({
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDAyMDIwODZmRjU5OUU0Y0YyMzM4MkUzNjg1Y0NmZUEyOGNBODBCOTAiLCJpc3MiOiJ3ZWIzLXN0b3JhZ2UiLCJpYXQiOjE2NTIzNjM1Njk3NDUsIm5hbWUiOiJVbmlkYXRhIn0.XmsAuXvbTj4BFhZlJK4xXfbd0ltVZJCEhqdYcW_kLOo',
} as any)
const cid = await web3Storage.put([file], {
name: file.name,
maxRetries: 3,
wrapWithDirectory: false,
})
return {
key: `https://gateway.ipfs.io/ipfs/${cid}`
}
},
[]
)
const uploadFile = useCallback<UploadFile>(async (blob, filename) => {
const file = new File([blob], filename)
const web3Storage = new Web3Storage({
token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDAyMDIwODZmRjU5OUU0Y0YyMzM4MkUzNjg1Y0NmZUEyOGNBODBCOTAiLCJpc3MiOiJ3ZWIzLXN0b3JhZ2UiLCJpYXQiOjE2NTIzNjM1Njk3NDUsIm5hbWUiOiJVbmlkYXRhIn0.XmsAuXvbTj4BFhZlJK4xXfbd0ltVZJCEhqdYcW_kLOo",
} as any)
const cid = await web3Storage.put([file], {
name: file.name,
maxRetries: 3,
wrapWithDirectory: false,
})
return {
key: `https://gateway.ipfs.io/ipfs/${cid}`,
}
}, [])
return uploadFile
}
export type UploadFile = (
blob: Blob,
filename: string
filename: string,
) => Promise<{ key: string }>

View File

@ -7,7 +7,7 @@ export const logout = () => {
if (location.host !== OUR_DOMAIN) {
url.searchParams.set(
"next",
`${location.protocol}//${location.host}/api/logout`
`${location.protocol}//${location.host}/api/logout`,
)
}
location.href = url.href

View File

@ -41,7 +41,7 @@ const findUserFromToken = async (token: string) => {
export const getAuthUser = async <TRequireAuth extends boolean | undefined>(
request: IncomingMessage,
requireAuth?: TRequireAuth
requireAuth?: TRequireAuth,
) => {
const token = getAuthTokenFromRequest(request)
@ -80,18 +80,18 @@ export const getAuthCookieOptions = ({
export const generateCookie = (
options:
| { type: "clear"; domain?: string }
| { type: "auth"; token: string; domain?: string }
| { type: "auth"; token: string; domain?: string },
) => {
if (options.type === "clear") {
return Cookie.serialize(
AUTH_COOKIE_NAME,
"",
getAuthCookieOptions({ domain: options.domain, clearCookie: true })
getAuthCookieOptions({ domain: options.domain, clearCookie: true }),
)
}
return Cookie.serialize(
AUTH_COOKIE_NAME,
options.token,
getAuthCookieOptions({ domain: options.domain })
getAuthCookieOptions({ domain: options.domain }),
)
}

View File

@ -53,11 +53,11 @@ const createPrisma = (readonly: boolean) => {
}
export const prismaPrimary = /* @__PURE__ */ singleton("prisma-primary", () =>
createPrisma(false)
createPrisma(false),
)
export const prismaRead = /* @__PURE__ */ singleton("prisma-read", () =>
createPrisma(true)
createPrisma(true),
)
export * from "@prisma/client"

View File

@ -7,4 +7,4 @@ export const SITE_URL = `${IS_PROD ? "https" : "http"}://${OUR_DOMAIN}`
export const R2_URL = process.env.NEXT_PUBLIC_R2_URL
export const APP_DESCRIPTION = process.env.APP_DESCRIPTION
export const DISCORD_LINK = process.env.NEXT_PUBLIC_DISCORD_LINK
export const GITHUB_LINK = process.env.NEXT_PUBLIC_GITHUB_LINK
export const GITHUB_LINK = process.env.NEXT_PUBLIC_GITHUB_LINK

View File

@ -20,12 +20,12 @@ export const getPageVisibility = ({
date_published,
}: {
date_published?: string
}) => {
}) => {
if (!date_published) {
return PageVisibilityEnum.Draft
} else if (dayjs(date_published).isBefore(new Date())) {
return PageVisibilityEnum.Published
} else if (dayjs(date_published).isBefore(new Date('9999-01-01'))) {
} else if (dayjs(date_published).isBefore(new Date("9999-01-01"))) {
return PageVisibilityEnum.Scheduled
}
return PageVisibilityEnum.Draft

View File

@ -1,5 +1,8 @@
import { get, set, del } from "idb-keyval";
import { PersistedClient, Persister } from "@tanstack/react-query-persist-client";
import { get, set, del } from "idb-keyval"
import {
PersistedClient,
Persister,
} from "@tanstack/react-query-persist-client"
/**
* Creates an Indexed DB persister
@ -8,13 +11,13 @@ import { PersistedClient, Persister } from "@tanstack/react-query-persist-client
export function createIDBPersister(idbValidKey: IDBValidKey = "reactQuery") {
return {
persistClient: async (client: PersistedClient) => {
set(idbValidKey, client);
set(idbValidKey, client)
},
restoreClient: async () => {
return await get<PersistedClient>(idbValidKey);
return await get<PersistedClient>(idbValidKey)
},
removeClient: async () => {
await del(idbValidKey);
await del(idbValidKey)
},
} as Persister;
}
} as Persister
}

View File

@ -1,18 +1,21 @@
import { PersistedClient, Persister } from "@tanstack/react-query-persist-client";
import {
PersistedClient,
Persister,
} from "@tanstack/react-query-persist-client"
import Redis from "ioredis"
let redis: Redis
if (process.env.REDIS_URL) {
redis = new Redis(process.env.REDIS_URL)
redis.on('error', (error: any) => {
console.error('Redis error: ', error)
redis.on("error", (error: any) => {
console.error("Redis error: ", error)
})
redis.on('end', () => {
console.log('Redis end')
redis.on("end", () => {
console.log("Redis end")
})
redis.on('connect', () => {
console.log('Redis connected.')
redis.on("connect", () => {
console.log("Redis connected.")
})
}
@ -23,7 +26,7 @@ export function createRedisPersister(redisValidKey = "reactQuery") {
redis.set(redisValidKey, JSON.stringify(client))
},
restoreClient: async () => {
const data = await redis.get(redisValidKey);
const data = await redis.get(redisValidKey)
if (data) {
return JSON.parse(data)
} else {
@ -31,10 +34,10 @@ export function createRedisPersister(redisValidKey = "reactQuery") {
}
},
removeClient: async () => {
await redis.del(redisValidKey);
await redis.del(redisValidKey)
},
} as Persister;
} as Persister
} else {
return null
}
}
}

View File

@ -1,6 +1,6 @@
import { QueryClient } from '@tanstack/react-query'
import { QueryClient } from "@tanstack/react-query"
import { createRedisPersister } from "~/lib/persister.server"
import { persistQueryClient } from '@tanstack/react-query-persist-client'
import { persistQueryClient } from "@tanstack/react-query-persist-client"
const cacheTime = 30 * 60 * 60 * 1000 // 30 days

View File

@ -19,14 +19,14 @@ export const notFound = (message?: string) => new NotFound(message)
export const isNotFoundError = (error: unknown) => error instanceof NotFound
export const serverSidePropsHandler = <
TProps extends { [key: string]: any } = { [key: string]: any }
TProps extends { [key: string]: any } = { [key: string]: any },
>(
fn: (
ctx: GetServerSidePropsContext
) => Promise<{ props: TProps } | Redirect | NotFound>
ctx: GetServerSidePropsContext,
) => Promise<{ props: TProps } | Redirect | NotFound>,
) => {
return async (
ctx: GetServerSidePropsContext
ctx: GetServerSidePropsContext,
): Promise<GetServerSidePropsResult<TProps>> => {
try {
const result = await fn(ctx)

View File

@ -11,7 +11,7 @@ export const singleton = <T>(id: string, factory: () => T): T => {
export const singletonAsync = <T>(
id: string,
factory: () => Promise<T>,
enableSingleton = true
enableSingleton = true,
): { readonly value: T; wait: Promise<void> } => {
if (globalThis._singletons[id] && enableSingleton) {
return {

View File

@ -76,8 +76,8 @@ export type Note = UniNote & {
}
export type Notes = {
total: number,
list: Note[],
total: number
list: Note[]
}
export type Profile = UniProfile & {
@ -86,6 +86,6 @@ export type Profile = UniProfile & {
}
export type Profiles = {
total: number,
list: Note[],
}
total: number
list: Note[]
}

View File

@ -3,7 +3,7 @@ import { R2_URL } from "./env"
export function getUserContentsUrl(filename: string): string
export function getUserContentsUrl(filename: undefined | null): undefined
export function getUserContentsUrl<T extends string | undefined | null>(
filename: T
filename: T,
): T
export function getUserContentsUrl(filename: string | undefined | null) {
if (!filename) return undefined

View File

@ -44,7 +44,7 @@ export const getMemberships = async ({
export const updateMembership = async (
membershipId: string,
input: { lastSwitchedTo?: Date }
input: { lastSwitchedTo?: Date },
) => {
await prismaPrimary.membership.update({
where: {

View File

@ -26,7 +26,6 @@ const checkPageSlug = async ({
// if (!slug) {
// throw new Error("Missing page slug")
// }
// const page = await prismaPrimary.page.findFirst({
// where: {
// siteId,
@ -37,7 +36,6 @@ const checkPageSlug = async ({
// },
// })
// if (!page) return
// if (page.deletedAt) {
// await prismaPrimary.page.delete({
// where: {
@ -46,48 +44,53 @@ const checkPageSlug = async ({
// })
// return
// }
// throw new Error("Page slug already used")
}
export async function createOrUpdatePage(
input: {
pageId?: string
siteId: string
slug?: string
title?: string
content?: string
published?: boolean
publishedAt?: string
excerpt?: string
/** Only needed when creating page */
isPost?: boolean
},
) {
return await unidata.notes.set({
source: 'Crossbell Note',
identity: input.siteId,
platform: 'Crossbell',
action: input.pageId ? 'update' : 'add',
}, {
...(input.pageId && { id: input.pageId }),
...(input.title && { title: input.title }),
...(input.content && {
body: {
content: input.content,
mime_type: 'text/markdown',
}}),
...(input.publishedAt && { date_published: input.published ? input.publishedAt : new Date('9999-01-01').toISOString() }),
...(input.excerpt && {
summary: {
content: input.excerpt,
mime_type: 'text/markdown',
}
}),
tags: [input.isPost ? "post" : "page"],
applications: ["xlog"],
...(input.slug && {_xlog_slug: input.slug})
})
export async function createOrUpdatePage(input: {
pageId?: string
siteId: string
slug?: string
title?: string
content?: string
published?: boolean
publishedAt?: string
excerpt?: string
/** Only needed when creating page */
isPost?: boolean
}) {
return await unidata.notes.set(
{
source: "Crossbell Note",
identity: input.siteId,
platform: "Crossbell",
action: input.pageId ? "update" : "add",
},
{
...(input.pageId && { id: input.pageId }),
...(input.title && { title: input.title }),
...(input.content && {
body: {
content: input.content,
mime_type: "text/markdown",
},
}),
...(input.publishedAt && {
date_published: input.published
? input.publishedAt
: new Date("9999-01-01").toISOString(),
}),
...(input.excerpt && {
summary: {
content: input.excerpt,
mime_type: "text/markdown",
},
}),
tags: [input.isPost ? "post" : "page"],
applications: ["xlog"],
...(input.slug && { _xlog_slug: input.slug }),
},
)
}
export async function scheduleEmailForPost(
@ -95,15 +98,12 @@ export async function scheduleEmailForPost(
input: { pageId: string; emailSubject?: string },
) {
// const page = await getPage(gate, { page: input.pageId })
// if (!gate.allows({ type: "can-update-page", siteId: page.siteId })) {
// throw gate.permissionError()
// }
// if (page.emailStatus) {
// throw new Error("Email already scheduled or sent")
// }
// await prismaPrimary.page.update({
// where: {
// id: page.id,
@ -113,24 +113,21 @@ export async function scheduleEmailForPost(
// emailSubject: input.emailSubject,
// },
// })
// await notifySubscribersForNewPost(gate, {
// pageId: page.id,
// })
}
export async function getPagesBySite(
input: {
site?: string
type: "post" | "page"
visibility?: PageVisibilityEnum | null
take?: number | null
cursor?: string | null
includeContent?: boolean
includeExcerpt?: boolean
render?: boolean
},
) {
export async function getPagesBySite(input: {
site?: string
type: "post" | "page"
visibility?: PageVisibilityEnum | null
take?: number | null
cursor?: string | null
includeContent?: boolean
includeExcerpt?: boolean
render?: boolean
}) {
if (!input.site) {
return {
total: 0,
@ -140,94 +137,122 @@ export async function getPagesBySite(
const visibility = input.visibility || PageVisibilityEnum.All
let pages: Notes = await unidata.notes.get({
source: 'Crossbell Note',
let pages: Notes = (await unidata.notes.get({
source: "Crossbell Note",
identity: input.site,
platform: 'Crossbell',
platform: "Crossbell",
limit: input.take || 1000,
}) || {
})) || {
total: 0,
list: [],
};
}
if (pages?.list) {
switch (visibility) {
case PageVisibilityEnum.Published:
pages.list = pages.list.filter(page => +new Date(page.date_published) <= +new Date())
pages.list = pages.list.filter(
(page) => +new Date(page.date_published) <= +new Date(),
)
break
case PageVisibilityEnum.Draft:
pages.list = pages.list.filter(page => page.date_published === new Date('9999-01-01').toISOString())
pages.list = pages.list.filter(
(page) =>
page.date_published === new Date("9999-01-01").toISOString(),
)
break
case PageVisibilityEnum.Scheduled:
pages.list = pages.list.filter(page => +new Date(page.date_published) > +new Date() && page.date_published !== new Date('9999-01-01').toISOString())
pages.list = pages.list.filter(
(page) =>
+new Date(page.date_published) > +new Date() &&
page.date_published !== new Date("9999-01-01").toISOString(),
)
break
}
pages.list = pages.list.filter(page => page.applications?.includes("Crosslog") || page.applications?.includes("xlog")).filter(page => page.tags?.includes(input.type))
pages.list = pages.list
.filter(
(page) =>
page.applications?.includes("Crosslog") ||
page.applications?.includes("xlog"),
)
.filter((page) => page.tags?.includes(input.type))
pages.total = pages.list.length
pages.list = await Promise.all(pages?.list.map(async (page) => {
if (page.body?.content && page.body?.mime_type === 'text/markdown' && input.render) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: 'text/html'
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: 'text/html'
pages.list = await Promise.all(
pages?.list.map(async (page) => {
if (
page.body?.content &&
page.body?.mime_type === "text/markdown" &&
input.render
) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: "text/html",
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: "text/html",
}
}
}
}
page.slug = page.metadata?.raw?._xlog_slug || page.metadata?.raw?._crosslog_slug || page.id
return page
}))
page.slug =
page.metadata?.raw?._xlog_slug ||
page.metadata?.raw?._crosslog_slug ||
page.id
return page
}),
)
}
return pages
}
export async function deletePage({ site, id }: { site: string, id: string }) {
return await unidata.notes.set({
source: 'Crossbell Note',
identity: site,
platform: 'Crossbell',
action: 'remove',
}, {
id,
})
export async function deletePage({ site, id }: { site: string; id: string }) {
return await unidata.notes.set(
{
source: "Crossbell Note",
identity: site,
platform: "Crossbell",
action: "remove",
},
{
id,
},
)
}
export async function getPage<TRender extends boolean = false>(
input: {
/** page slug or id, `site` is needed when `page` is a slug */
page?: string
pageId?: string
site?: string
render?: TRender
includeAuthors?: boolean
},
) {
export async function getPage<TRender extends boolean = false>(input: {
/** page slug or id, `site` is needed when `page` is a slug */
page?: string
pageId?: string
site?: string
render?: TRender
includeAuthors?: boolean
}) {
if (!input.site || !(input.page || input.pageId)) {
return undefined
}
const pages: Notes | null = await unidata.notes.get({
source: 'Crossbell Note',
source: "Crossbell Note",
identity: input.site,
platform: 'Crossbell',
platform: "Crossbell",
limit: 1000,
...(input.pageId && {
filter: {
id: input.pageId,
}
})
},
}),
})
let page;
let page
if (input.page) {
page = pages?.list.find((item) => {
item.slug = item.metadata?.raw?._xlog_slug || item.metadata?.raw?._crosslog_slug || item.id
item.slug =
item.metadata?.raw?._xlog_slug ||
item.metadata?.raw?._crosslog_slug ||
item.id
return item.slug === input.page
})
} else {
@ -239,35 +264,48 @@ export async function getPage<TRender extends boolean = false>(
}
if (pages?.list) {
pages.list = await Promise.all(pages?.list.map(async (page) => {
if (page.body?.content && page.body?.mime_type === 'text/markdown' && input.render) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: 'text/html'
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: 'text/html'
pages.list = await Promise.all(
pages?.list.map(async (page) => {
if (
page.body?.content &&
page.body?.mime_type === "text/markdown" &&
input.render
) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: "text/html",
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: "text/html",
}
}
}
}
page.slug = page.metadata?.raw?._xlog_slug || page.metadata?.raw?._crosslog_slug || page.id
return page
}))
page.slug =
page.metadata?.raw?._xlog_slug ||
page.metadata?.raw?._crosslog_slug ||
page.id
return page
}),
)
}
if (page.body?.content && page.body?.mime_type === 'text/markdown' && input.render) {
if (
page.body?.content &&
page.body?.mime_type === "text/markdown" &&
input.render
) {
const rendered = await renderPageContent(page.body.content)
page.body = {
content: rendered.contentHTML,
mime_type: 'text/html'
mime_type: "text/html",
}
if (!page.summary) {
page.summary = {
content: rendered.excerpt,
mime_type: 'text/html'
mime_type: "text/html",
}
}
}
@ -283,23 +321,18 @@ export const notifySubscribersForNewPost = async (
) => {
// const page = await getPage(gate, { page: input.pageId, render: true })
// const site = await getSite(page.siteId)
// if (page.emailStatus !== PageEmailStatus.PENDING) {
// return
// }
// if (!page.published || page.publishedAt > new Date()) {
// return
// }
// if (page.type === "PAGE") {
// throw new Error("You can only notify subscribers for post updates")
// }
// if (!gate.allows({ type: "can-notify-site-subscribers", site })) {
// throw gate.permissionError()
// }
// await prismaPrimary.page.update({
// where: {
// id: page.id,
@ -308,7 +341,6 @@ export const notifySubscribersForNewPost = async (
// emailStatus: PageEmailStatus.RUNNING,
// },
// })
// const memberships = await prismaPrimary.membership.findMany({
// where: {
// role: MembershipRole.SUBSCRIBER,
@ -318,11 +350,9 @@ export const notifySubscribersForNewPost = async (
// user: true,
// },
// })
// const emailSubscribers = memberships
// .filter((member) => (member.config as any).email)
// .map((member) => member.user)
// try {
// await sendEmailForNewPost({
// post: page,

View File

@ -43,24 +43,26 @@ export const getUserSites = async (address?: string) => {
return null
}
const profiles = await unidata.profiles.get({
source: 'Crossbell Profile',
source: "Crossbell Profile",
identity: address,
platform: 'Ethereum',
});
const sites = profiles.list?.sort((a, b) => {
if (a.metadata?.primary) {
return -1
} else if (b.metadata?.primary) {
return 1
} else {
return +new Date(b.date_updated || 0) - +new Date(a.date_updated || 0)
}
}).map((profile) => {
profile.name = profile.name || profile.username;
return profile
platform: "Ethereum",
})
const sites = profiles.list
?.sort((a, b) => {
if (a.metadata?.primary) {
return -1
} else if (b.metadata?.primary) {
return 1
} else {
return +new Date(b.date_updated || 0) - +new Date(a.date_updated || 0)
}
})
.map((profile) => {
profile.name = profile.name || profile.username
return profile
})
if (!sites || !sites.length) return null
return sites
@ -68,14 +70,22 @@ export const getUserSites = async (address?: string) => {
export const getSite = async (input: string) => {
const profiles = await unidata.profiles.get({
source: 'Crossbell Profile',
source: "Crossbell Profile",
identity: input,
platform: 'Crossbell',
});
platform: "Crossbell",
})
const site: Profile = profiles.list?.sort((a, b) => +new Date(b.date_updated || 0) - +new Date(a.date_updated || 0))?.[0]
site.navigation = site.metadata?.raw?.['_xlog_navigation'] || site.metadata?.raw?.['_crosslog_navigation'] || [{ id: nanoid(), label: "Archives", url: "/archives" }]
site.css = site.metadata?.raw?.['_xlog_css'] || site.metadata?.raw?.['_crosslog_css'] || ''
const site: Profile = profiles.list?.sort(
(a, b) => +new Date(b.date_updated || 0) - +new Date(a.date_updated || 0),
)?.[0]
site.navigation = site.metadata?.raw?.["_xlog_navigation"] ||
site.metadata?.raw?.["_crosslog_navigation"] || [
{ id: nanoid(), label: "Archives", url: "/archives" },
]
site.css =
site.metadata?.raw?.["_xlog_css"] ||
site.metadata?.raw?.["_crosslog_css"] ||
""
site.name = site.name || site.username
return site
@ -86,94 +96,103 @@ export const getSubscription = async (data: {
siteId: string
}) => {
const links = await unidata.links.get({
source: 'Crossbell Link',
source: "Crossbell Link",
identity: data.userId,
platform: 'Ethereum',
platform: "Ethereum",
filter: {
to: data.siteId
}
to: data.siteId,
},
})
return !!links?.list?.length
}
export async function updateSite(
payload: {
site: string
name?: string
description?: string
icon?: string | null
subdomain?: string
navigation?: SiteNavigationItem[]
css?: string
},
) {
return await unidata.profiles.set({
source: 'Crossbell Profile',
identity: payload.site,
platform: 'Crossbell',
action: 'update',
}, {
...(payload.name && { name: payload.name }),
...(payload.description && { bio: payload.description }),
...(payload.icon && { avatars: [payload.icon] }),
...(payload.subdomain && { username: payload.subdomain }),
...(payload.navigation && { _xlog_navigation: payload.navigation }),
...(payload.css && { _xlog_css: payload.css }),
})
export async function updateSite(payload: {
site: string
name?: string
description?: string
icon?: string | null
subdomain?: string
navigation?: SiteNavigationItem[]
css?: string
}) {
return await unidata.profiles.set(
{
source: "Crossbell Profile",
identity: payload.site,
platform: "Crossbell",
action: "update",
},
{
...(payload.name && { name: payload.name }),
...(payload.description && { bio: payload.description }),
...(payload.icon && { avatars: [payload.icon] }),
...(payload.subdomain && { username: payload.subdomain }),
...(payload.navigation && { _xlog_navigation: payload.navigation }),
...(payload.css && { _xlog_css: payload.css }),
},
)
}
export async function createSite(
address: string,
payload: { name: string; subdomain: string },
) {
return await unidata.profiles.set({
source: 'Crossbell Profile',
identity: address,
platform: 'Ethereum',
action: 'add',
}, {
username: payload.subdomain,
name: payload.name,
tags: ['navigation:' + JSON.stringify([
{
id: nanoid(),
label: "Archives",
url: "/archives",
},
])],
})
return await unidata.profiles.set(
{
source: "Crossbell Profile",
identity: address,
platform: "Ethereum",
action: "add",
},
{
username: payload.subdomain,
name: payload.name,
tags: [
"navigation:" +
JSON.stringify([
{
id: nanoid(),
label: "Archives",
url: "/archives",
},
]),
],
},
)
}
export async function subscribeToSite(
input: {
userId: string
siteId: string
},
) {
return unidata.links.set({
source: 'Crossbell Link',
identity: input.userId,
platform: 'Ethereum',
action: 'add',
}, {
to: input.siteId,
type: 'follow',
})
export async function subscribeToSite(input: {
userId: string
siteId: string
}) {
return unidata.links.set(
{
source: "Crossbell Link",
identity: input.userId,
platform: "Ethereum",
action: "add",
},
{
to: input.siteId,
type: "follow",
},
)
}
export async function unsubscribeFromSite(
input: {
userId: string
siteId: string
},
) {
return unidata.links.set({
source: 'Crossbell Link',
identity: input.userId,
platform: 'Ethereum',
action: 'remove',
}, {
to: input.siteId,
type: 'follow',
})
export async function unsubscribeFromSite(input: {
userId: string
siteId: string
}) {
return unidata.links.set(
{
source: "Crossbell Link",
identity: input.userId,
platform: "Ethereum",
action: "remove",
},
{
to: input.siteId,
type: "follow",
},
)
}

View File

@ -11,7 +11,7 @@ export const userModel = {
avatar?: string
bio?: string
email?: string
}
},
) {
const user = gate.getUser(true)

View File

@ -2,62 +2,60 @@ import { Toaster } from "react-hot-toast"
import "~/css/main.css"
import "~/generated/uno.css"
import { StoreProvider, createStore } from "~/lib/store"
import { getDefaultWallets, RainbowKitProvider } from "@rainbow-me/rainbowkit"
import { configureChains, createClient, WagmiConfig } from "wagmi"
import { jsonRpcProvider } from "wagmi/providers/jsonRpc"
import {
getDefaultWallets,
RainbowKitProvider,
} from '@rainbow-me/rainbowkit';
import {
configureChains,
createClient,
WagmiConfig,
} from 'wagmi'
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'
import { Hydrate, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import { createIDBPersister } from '~/lib/persister.client'
import { connectorsForWallets, wallet } from '@rainbow-me/rainbowkit'
Hydrate,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"
import { createIDBPersister } from "~/lib/persister.client"
import { connectorsForWallets, wallet } from "@rainbow-me/rainbowkit"
import '@rainbow-me/rainbowkit/styles.css'
import "@rainbow-me/rainbowkit/styles.css"
const { chains, provider } = configureChains(
[{
id: 3737,
name: 'Crossbell',
network: 'crossbell',
rpcUrls: {
default: 'https://rpc.crossbell.io',
},
iconUrl: 'https://gateway.ipfs.io/ipfs/QmS8zEetTb6pwdNpVjv5bz55BXiSMGP9BjTJmNcjcUT91t',
nativeCurrency: {
decimals: 18,
name: 'Crossbell Token',
symbol: 'CSB',
},
blockExplorers: {
default: { name: 'Crossbell Explorer', url: 'https://scan.crossbell.io' },
},
testnet: false,
} as any],
[
jsonRpcProvider({ rpc: chain => ({ http: chain.rpcUrls.default }) })
]
{
id: 3737,
name: "Crossbell",
network: "crossbell",
rpcUrls: {
default: "https://rpc.crossbell.io",
},
iconUrl:
"https://gateway.ipfs.io/ipfs/QmS8zEetTb6pwdNpVjv5bz55BXiSMGP9BjTJmNcjcUT91t",
nativeCurrency: {
decimals: 18,
name: "Crossbell Token",
symbol: "CSB",
},
blockExplorers: {
default: {
name: "Crossbell Explorer",
url: "https://scan.crossbell.io",
},
},
testnet: false,
} as any,
],
[jsonRpcProvider({ rpc: (chain) => ({ http: chain.rpcUrls.default }) })],
)
const connectors = connectorsForWallets([
{
groupName: 'Recommended',
wallets: [
wallet.metaMask({ chains }),
wallet.walletConnect({ chains }),
],
groupName: "Recommended",
wallets: [wallet.metaMask({ chains }), wallet.walletConnect({ chains })],
},
])
const wagmiClient = createClient({
autoConnect: true,
connectors,
provider
provider,
})
const queryClient = new QueryClient({
@ -66,7 +64,7 @@ const queryClient = new QueryClient({
cacheTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
});
})
const persister = createIDBPersister()
@ -75,7 +73,10 @@ function MyApp({ Component, pageProps }: any) {
<WagmiConfig client={wagmiClient}>
<RainbowKitProvider chains={chains}>
<StoreProvider createStore={createStore}>
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<Hydrate state={pageProps.dehydratedState}>
<ReactQueryDevtools />
<Component {...pageProps} />

View File

@ -10,7 +10,7 @@ import { Viewer, Profile, Note } from "~/lib/types"
import { queryClientServer } from "~/lib/query-client.server"
import { prefetchGetSite } from "~/queries/site.server"
import { useGetSite } from "~/queries/site"
import { dehydrate, QueryClient } from '@tanstack/react-query'
import { dehydrate, QueryClient } from "@tanstack/react-query"
import { fetchGetPage } from "~/queries/page.server"
import { useGetPage } from "~/queries/page"
import { PageVisibilityEnum } from "~/lib/types"
@ -20,7 +20,7 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
async (ctx) => {
const domainOrSubdomain = ctx.params!.site as string
const pageSlug = ctx.params!.page as string
await prefetchGetSite(domainOrSubdomain)
const page = await fetchGetPage({
site: domainOrSubdomain,
@ -61,11 +61,7 @@ function SitePagePage({
const site = useGetSite(domainOrSubdomain)
return (
<SiteLayout
site={site.data}
ogDescription={ogDescription}
page={page.data}
>
<SiteLayout site={site.data} ogDescription={ogDescription} page={page.data}>
<SitePage site={site.data} page={page.data} />
</SiteLayout>
)

View File

@ -10,7 +10,7 @@ import { Viewer, Profile, Notes } from "~/lib/types"
import { queryClientServer } from "~/lib/query-client.server"
import { prefetchGetSite } from "~/queries/site.server"
import { useGetSite } from "~/queries/site"
import { dehydrate, QueryClient } from '@tanstack/react-query'
import { dehydrate, QueryClient } from "@tanstack/react-query"
import { useGetPagesBySite } from "~/queries/page"
import { prefetchGetPagesBySite } from "~/queries/page.server"
import { PageVisibilityEnum } from "~/lib/types"
@ -18,7 +18,7 @@ import { PageVisibilityEnum } from "~/lib/types"
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
async (ctx) => {
const domainOrSubdomain = ctx.params!.site as string
await prefetchGetSite(domainOrSubdomain)
await prefetchGetPagesBySite({
site: domainOrSubdomain,
@ -33,14 +33,14 @@ export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
domainOrSubdomain,
},
}
}
},
)
function SiteArchivesPage({
domainOrSubdomain,
}: {
site: Profile,
posts: Notes,
site: Profile
posts: Notes
domainOrSubdomain: string
}) {
const site = useGetSite(domainOrSubdomain)
@ -52,10 +52,7 @@ function SiteArchivesPage({
})
return (
<SiteLayout
site={site.data}
title="Archives"
>
<SiteLayout site={site.data} title="Archives">
<SiteArchives posts={posts.data} />
</SiteLayout>
)

View File

@ -5,11 +5,11 @@ import { createSSGHelpers } from "@trpc/react/ssg"
import { getTRPCContext } from "~/lib/trpc.server"
import { Viewer, Profile, Notes } from "~/lib/types"
import { getViewer } from "~/lib/viewer"
import { useEffect, useState } from 'react'
import { useEffect, useState } from "react"
import { queryClientServer } from "~/lib/query-client.server"
import { prefetchGetSite } from "~/queries/site.server"
import { useGetSite } from "~/queries/site"
import { dehydrate, QueryClient } from '@tanstack/react-query'
import { dehydrate, QueryClient } from "@tanstack/react-query"
import { useGetPagesBySite } from "~/queries/page"
import { prefetchGetPagesBySite } from "~/queries/page.server"
import { PageVisibilityEnum } from "~/lib/types"
@ -33,11 +33,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
}
}
function SiteIndexPage({
domainOrSubdomain,
}: {
domainOrSubdomain: string
}) {
function SiteIndexPage({ domainOrSubdomain }: { domainOrSubdomain: string }) {
const site = useGetSite(domainOrSubdomain)
const posts = useGetPagesBySite({
site: domainOrSubdomain,

View File

@ -38,10 +38,13 @@ export default function SubdomainEditor() {
const page = useGetPage({
site: subdomain!,
pageId: pageId,
render: false
render: false,
})
const pageContent = useMemo(() => page.data?.body?.content, [page.data?.body?.content])
const pageContent = useMemo(
() => page.data?.body?.content,
[page.data?.body?.content],
)
const visibility = getPageVisibility({
date_published: page.data?.date_published,
@ -137,9 +140,13 @@ export default function SubdomainEditor() {
if (!page.data) return
setValues({
title: page.data.title || '',
publishedAt: page.data.date_published === new Date('9999-01-01').toISOString() ? new Date().toISOString() : page.data.date_published,
published: page.data.date_published !== new Date('9999-01-01').toISOString(),
title: page.data.title || "",
publishedAt:
page.data.date_published === new Date("9999-01-01").toISOString()
? new Date().toISOString()
: page.data.date_published,
published:
page.data.date_published !== new Date("9999-01-01").toISOString(),
excerpt: page.data.summary?.content || "",
slug: page.data.slug!,
})

View File

@ -57,11 +57,9 @@ export default function SettingsDomainsPage() {
id="subdomain"
label="Custom CSS"
className="w-full"
style={
{
height: "400px",
}
}
style={{
height: "400px",
}}
multiline
{...form.register("css")}
/>

View File

@ -34,7 +34,7 @@ export default function SettingsDomainsPage() {
if (updateSite.data?.code === 0) {
toast.success("Saved!")
router.replace(
`/dashboard/${updateSite.variables?.subdomain}/settings/domains`
`/dashboard/${updateSite.variables?.subdomain}/settings/domains`,
)
} else {
toast.error("Failed to update site" + ": " + updateSite.data.message)

View File

@ -1,8 +1,8 @@
import { redirect } from "~/lib/server-side-props"
import { useGetUserSites } from "~/queries/site"
import { useAccount } from 'wagmi'
import { useAccount } from "wagmi"
import { useEffect } from "react"
import { useRouter } from 'next/router'
import { useRouter } from "next/router"
export default function Dashboard() {
const { address } = useAccount()
@ -19,5 +19,9 @@ export default function Dashboard() {
}
}, [userSites, router])
return <div className="flex items-center justify-center w-full h-60">Loading...</div>
return (
<div className="flex items-center justify-center w-full h-60">
Loading...
</div>
)
}

View File

@ -14,12 +14,12 @@ export default function NewSitePage() {
const router = useRouter()
const createSite = useCreateSite()
const [addressIn, setAddressIn] = useState<string>('')
const [addressIn, setAddressIn] = useState<string>("")
const { address } = useAccount()
useEffect(() => {
if (address) {
setAddressIn(address || '')
setAddressIn(address || "")
} else {
router.push("/")
}

View File

@ -6,7 +6,7 @@ import { UniLink } from "~/components/ui/UniLink"
import { getAuthUser } from "~/lib/auth.server"
import { FLY_REGION } from "~/lib/env.server"
import { useStore } from "~/lib/store"
import { useAccount } from 'wagmi'
import { useAccount } from "wagmi"
import { ConnectButton } from "~/components/common/ConnectButton"
export const getServerSideProps: GetServerSideProps = async (ctx) => {
@ -17,19 +17,15 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
}
}
export default function Home({
region,
}: {
region: string | null
}) {
export default function Home({ region }: { region: string | null }) {
const setLoginModalOpened = useStore((store) => store.setLoginModalOpened)
const [addressIn, setAddressIn] = useState<string>('')
const [addressIn, setAddressIn] = useState<string>("")
const { address } = useAccount()
useEffect(() => {
console.log("-> region", region)
setAddressIn(address || '')
setAddressIn(address || "")
}, [region, address])
return (
@ -43,7 +39,8 @@ export default function Home({
for Absolutely Everyone
</h2>
<h3 className="mt-5 text-zinc-500">
Meet xlog, the on-chain and open-source blogging platform for everyone.
Meet xlog, the on-chain and open-source blogging platform for
everyone.
<br />
Permanently stored on the blockchain and signed by you.
</h3>

View File

@ -1,9 +1,12 @@
import * as pageModel from "~/models/page.model"
import { queryClientServer } from "~/lib/query-client.server"
export const fetchGetPage = async (input: Parameters<typeof pageModel.getPage>[0]) => {
const key = ['getPage', input.page, input]
const data: ReturnType<typeof pageModel.getPage> | undefined = queryClientServer.getQueryData(key)
export const fetchGetPage = async (
input: Parameters<typeof pageModel.getPage>[0],
) => {
const key = ["getPage", input.page, input]
const data: ReturnType<typeof pageModel.getPage> | undefined =
queryClientServer.getQueryData(key)
if (!data) {
return await queryClientServer.fetchQuery(key, async () => {
return pageModel.getPage(input)
@ -16,8 +19,10 @@ export const fetchGetPage = async (input: Parameters<typeof pageModel.getPage>[0
}
}
export const prefetchGetPagesBySite = async (input: Parameters<typeof pageModel.getPagesBySite>[0]) => {
const key = ['getPagesBySite', input.site, input]
export const prefetchGetPagesBySite = async (
input: Parameters<typeof pageModel.getPagesBySite>[0],
) => {
const key = ["getPagesBySite", input.site, input]
const data = queryClientServer.getQueryData(key)
if (!data) {
await queryClientServer.prefetchQuery(key, async () => {

View File

@ -1,41 +1,48 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import * as pageModel from "~/models/page.model"
import { PageVisibilityEnum } from "~/lib/types"
export const useGetPagesBySite = (input: Parameters<typeof pageModel.getPagesBySite>[0]) => {
return useQuery(['getPagesBySite', input.site, input], async () => {
export const useGetPagesBySite = (
input: Parameters<typeof pageModel.getPagesBySite>[0],
) => {
return useQuery(["getPagesBySite", input.site, input], async () => {
return pageModel.getPagesBySite(input)
})
}
export const useGetPage = (input: Parameters<typeof pageModel.getPage>[0]) => {
return useQuery(['getPage', input.page, input], async () => {
return useQuery(["getPage", input.page, input], async () => {
return pageModel.getPage(input)
})
}
export function useCreateOrUpdatePage() {
const queryClient = useQueryClient();
const mutation = useMutation(async (
payload: Parameters<typeof pageModel.createOrUpdatePage>[0]) => {
return pageModel.createOrUpdatePage(payload)
}, {
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['getPagesBySite', variables.siteId])
queryClient.invalidateQueries(['getPage', variables.pageId])
const queryClient = useQueryClient()
const mutation = useMutation(
async (payload: Parameters<typeof pageModel.createOrUpdatePage>[0]) => {
return pageModel.createOrUpdatePage(payload)
},
})
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries(["getPagesBySite", variables.siteId])
queryClient.invalidateQueries(["getPage", variables.pageId])
},
},
)
return mutation
}
export function useDeletePage() {
const queryClient = useQueryClient();
return useMutation(async (input: Parameters<typeof pageModel.deletePage>[0]) => {
return pageModel.deletePage(input)
}, {
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['getPagesBySite', variables.site])
queryClient.invalidateQueries(['getPage', variables.id])
const queryClient = useQueryClient()
return useMutation(
async (input: Parameters<typeof pageModel.deletePage>[0]) => {
return pageModel.deletePage(input)
},
})
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries(["getPagesBySite", variables.site])
queryClient.invalidateQueries(["getPage", variables.id])
},
},
)
}

View File

@ -2,15 +2,15 @@ import * as siteModel from "~/models/site.model"
import { queryClientServer } from "~/lib/query-client.server"
export const prefetchGetSite = async (input: string) => {
const key = ['getSite', input]
const key = ["getSite", input]
const data = queryClientServer.getQueryData(key)
if (!data) {
await queryClientServer.prefetchQuery(['getSite', input], async () => {
await queryClientServer.prefetchQuery(["getSite", input], async () => {
return siteModel.getSite(input)
})
} else {
queryClientServer.prefetchQuery(['getSite', input], async () => {
queryClientServer.prefetchQuery(["getSite", input], async () => {
return siteModel.getSite(input)
})
}
}
}

View File

@ -1,9 +1,9 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import * as siteModel from "~/models/site.model"
import { SiteNavigationItem, Profile } from "~/lib/types"
export const useGetUserSites = (address?: string) => {
return useQuery(['getUserSites', address], async () => {
return useQuery(["getUserSites", address], async () => {
if (!address) {
return []
}
@ -12,7 +12,7 @@ export const useGetUserSites = (address?: string) => {
}
export const useGetSite = (input: string) => {
return useQuery(['getSite', input], async () => {
return useQuery(["getSite", input], async () => {
return siteModel.getSite(input)
})
}
@ -21,7 +21,7 @@ export const useGetSubscription = (data: {
userId: string
siteId: string
}) => {
return useQuery(['getSubscription', data], async () => {
return useQuery(["getSubscription", data], async () => {
if (!data.userId || !data.siteId) {
return false
}
@ -30,51 +30,62 @@ export const useGetSubscription = (data: {
}
export function useUpdateSite() {
const queryClient = useQueryClient();
const mutation = useMutation(async (
payload: Parameters<typeof siteModel.updateSite>[0]) => {
return siteModel.updateSite(payload)
}, {
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['getUserSites'])
queryClient.invalidateQueries(['getSite'])
const queryClient = useQueryClient()
const mutation = useMutation(
async (payload: Parameters<typeof siteModel.updateSite>[0]) => {
return siteModel.updateSite(payload)
},
})
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries(["getUserSites"])
queryClient.invalidateQueries(["getSite"])
},
},
)
return mutation
}
export function useCreateSite() {
const queryClient = useQueryClient();
return useMutation(async (input: {
address: string,
payload: { name: string; subdomain: string },
}) => {
return siteModel.createSite(input.address, input.payload)
}, {
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['getUserSites', variables.address])
const queryClient = useQueryClient()
return useMutation(
async (input: {
address: string
payload: { name: string; subdomain: string }
}) => {
return siteModel.createSite(input.address, input.payload)
},
})
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries(["getUserSites", variables.address])
},
},
)
}
export function useSubscribeToSite() {
const queryClient = useQueryClient();
return useMutation(async (input: Parameters<typeof siteModel.subscribeToSite>[0]) => {
return siteModel.subscribeToSite(input)
}, {
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['getSubscription', variables])
const queryClient = useQueryClient()
return useMutation(
async (input: Parameters<typeof siteModel.subscribeToSite>[0]) => {
return siteModel.subscribeToSite(input)
},
})
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries(["getSubscription", variables])
},
},
)
}
export function useUnsubscribeFromSite() {
const queryClient = useQueryClient();
return useMutation(async (input: Parameters<typeof siteModel.subscribeToSite>[0]) => {
return siteModel.unsubscribeFromSite(input)
}, {
onSuccess: (data, variables) => {
queryClient.invalidateQueries(['getSubscription', variables])
const queryClient = useQueryClient()
return useMutation(
async (input: Parameters<typeof siteModel.subscribeToSite>[0]) => {
return siteModel.unsubscribeFromSite(input)
},
})
}
{
onSuccess: (data, variables) => {
queryClient.invalidateQueries(["getSubscription", variables])
},
},
)
}

View File

@ -1,10 +1,6 @@
{
"compilerOptions": {
"lib": [
"DOM",
"DOM.Iterable",
"esnext"
],
"lib": ["DOM", "DOM.Iterable", "esnext"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "preserve",
@ -14,9 +10,7 @@
"strict": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./src/*"
]
"~/*": ["./src/*"]
},
"noEmit": true,
"forceConsistentCasingInFileNames": true,
@ -25,12 +19,6 @@
"incremental": true,
"module": "esnext"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}