264 Commits

Author SHA1 Message Date
1d7ad49058 Update dependency tailwindcss to v3.4.14 2024-10-15 11:01:06 +00:00
Lee
96ab998031 Merge pull request 'Update dependency react-use-websocket to v4.9.0' (#68) from renovate/react-use-websocket-4.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 2m52s
Reviewed-on: #68
2024-10-10 02:17:12 +00:00
Lee
7de2848f45 Merge pull request 'Update dependency lucide-react to ^0.451.0' (#70) from renovate/lucide-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #70
2024-10-10 02:17:07 +00:00
Lee
63fead44c3 Merge pull request 'Update nextjs monorepo to v14.2.15' (#72) from renovate/nextjs-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #72
2024-10-10 02:16:48 +00:00
f8aa41ed05 Update nextjs monorepo to v14.2.15 2024-10-10 02:01:40 +00:00
50e6506fc4 Update dependency lucide-react to ^0.451.0 2024-10-08 10:01:35 +00:00
2a6488d4e5 Update dependency react-use-websocket to v4.9.0 2024-10-01 20:03:34 +00:00
Lee
7be126fe00 Merge pull request 'Update dependency lucide-react to ^0.446.0' (#64) from renovate/lucide-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m44s
Reviewed-on: #64
2024-09-25 23:49:23 +00:00
0ef741381c Update dependency lucide-react to ^0.446.0 2024-09-25 06:01:24 +00:00
Lee
51ac278329 Merge pull request 'Update dependency postcss to v8.4.41' (#61) from renovate/postcss-8.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 44s
Reviewed-on: #61
2024-08-07 07:46:28 +00:00
Lee
9996df2f92 Merge pull request 'Update dependency @sentry/nextjs to v8.24.0' (#60) from renovate/sentry-javascript-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #60
2024-08-07 07:46:19 +00:00
c1c74ffef7 Update dependency postcss to v8.4.41 2024-08-06 00:03:26 +00:00
cf0e595cfc Update dependency @sentry/nextjs to v8.23.0 2024-08-05 15:01:21 +00:00
Lee
2efcb4a780 Merge pull request 'Update dependency lucide-react to ^0.424.0' (#58) from renovate/lucide-react-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m12s
Reviewed-on: #58
2024-08-02 17:51:33 +00:00
Lee
50037d9090 Merge pull request 'Update dependency @types/node to v20.14.14' (#59) from renovate/node-20.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #59
2024-08-02 17:51:22 +00:00
8a1263a1dd Update dependency lucide-react to ^0.424.0 2024-08-02 11:01:18 +00:00
137de19133 Update dependency @types/node to v20.14.14 2024-08-02 09:01:18 +00:00
Lee
c9c79c1f29 Merge pull request 'Update dependency lucide-react to ^0.419.0' (#57) from renovate/lucide-react-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 45s
Reviewed-on: #57
2024-08-01 17:28:56 +00:00
65d0cc7807 Update dependency lucide-react to ^0.419.0 2024-08-01 14:01:26 +00:00
Lee
d44ace3f20 Merge pull request 'Update dependency @sentry/nextjs to v8.22.0' (#56) from renovate/sentry-javascript-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 48s
Reviewed-on: #56
2024-08-01 13:17:37 +00:00
5b251e07ad Update dependency @sentry/nextjs to v8.22.0 2024-08-01 09:01:21 +00:00
Lee
636b0dde29 Merge pull request 'Update dependency lucide-react to ^0.418.0' (#55) from renovate/lucide-react-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m51s
Reviewed-on: #55
2024-08-01 01:26:18 +00:00
634cf8877a Update dependency lucide-react to ^0.418.0 2024-07-31 17:00:57 +00:00
Lee
6ee34eaf44 Merge pull request 'Update dependency @sentry/nextjs to v8.21.0' (#54) from renovate/sentry-javascript-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m8s
Reviewed-on: #54
2024-07-31 15:59:16 +00:00
d51215a781 Update dependency @sentry/nextjs to v8.21.0 2024-07-31 11:01:06 +00:00
Lee
22dba0bc90 Merge pull request 'Update radix-ui-primitives monorepo' (#53) from renovate/radix-ui-primitives-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 2m46s
Reviewed-on: #53
2024-07-31 00:36:04 +00:00
78ff92eed3 Update radix-ui-primitives monorepo 2024-07-30 23:01:11 +00:00
Lee
7e73cc3e8c Merge pull request 'Update dependency typescript to v5.5.4' (#52) from renovate/typescript-5.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 6s
Reviewed-on: #52
2024-07-30 22:15:34 +00:00
Lee
49cae6930b Merge pull request 'Update dependency mcutils-library to v1.3.4' (#51) from renovate/mcutils-library-1.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #51
2024-07-30 22:15:21 +00:00
33ab92f579 Update dependency typescript to v5.5.4 2024-07-30 22:01:09 +00:00
db6af18475 Update dependency mcutils-library to v1.3.4 2024-07-30 22:01:00 +00:00
Lee
a2cd3d0cf5 Merge pull request 'Update dependency tailwind-merge to v2.4.0' (#50) from renovate/tailwind-merge-2.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #50
2024-07-30 21:05:08 +00:00
Lee
916ee81302 Merge pull request 'Update dependency lucide-react to ^0.417.0' (#49) from renovate/lucide-react-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #49
2024-07-30 21:05:03 +00:00
Lee
28aa81036f Merge pull request 'Update dependency postcss to v8.4.40' (#43) from renovate/postcss-8.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #43
2024-07-30 21:04:58 +00:00
Lee
5fde0f46c5 Merge pull request 'Update dependency @eslint/eslintrc to v3.1.0' (#40) from renovate/eslint-eslintrc-3.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #40
2024-07-30 21:04:53 +00:00
Lee
5a4056f113 Merge pull request 'Update dependency @types/node to v20.14.13' (#32) from renovate/node-20.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #32
2024-07-30 21:04:47 +00:00
08e270da6f Update dependency tailwind-merge to v2.4.0 2024-07-30 21:02:27 +00:00
b2fbcb6ebf Update dependency lucide-react to ^0.417.0 2024-07-30 21:02:18 +00:00
7b602d64f8 Update dependency @types/node to v20.14.13 2024-07-30 21:02:13 +00:00
4026b08b42 Update dependency @eslint/eslintrc to v3.1.0 2024-07-30 21:02:07 +00:00
d5b48e8bc9 Update dependency postcss to v8.4.40 2024-07-30 21:01:53 +00:00
d3886e4a39 fix
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m26s
2024-07-30 21:32:54 +01:00
94b81e0d69 Merge branch 'master' of https://git.fascinated.cc/MinecraftUtilities/Frontend
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 10s
2024-07-30 21:29:51 +01:00
35e786d9a5 sentry 2024-07-30 21:29:32 +01:00
Lee
9278a23f5a Merge pull request 'Update dependency @types/react-syntax-highlighter to v15.5.13' (#35) from renovate/react-syntax-highlighter-15.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 2m3s
Reviewed-on: #35
2024-07-30 20:23:07 +00:00
Lee
2954dd4955 Merge pull request 'Update dependency @types/react to v18.3.3' (#36) from renovate/react-monorepo into master
Some checks are pending
Deploy App / docker (ubuntu-latest) (push) Waiting to run
Reviewed-on: #36
2024-07-30 20:22:58 +00:00
Lee
0ce04fbc37 Merge pull request 'Update dependency @heroicons/react to v2.1.5' (#42) from renovate/heroicons-react-2.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #42
2024-07-30 20:22:45 +00:00
Lee
9930b8387f Merge pull request 'Update dependency unified to v11.0.5' (#44) from renovate/unified-11.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #44
2024-07-30 20:22:36 +00:00
Lee
9be11a628b Merge pull request 'Update dependency tailwindcss to v3.4.7' (#48) from renovate/tailwindcss-3.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #48
2024-07-30 20:22:25 +00:00
Lee
0afcd08f91 Merge pull request 'Update dependency mcutils-library to v1.3.3' (#47) from renovate/mcutils-library-1.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #47
2024-07-30 20:21:42 +00:00
d1b3b7e1fe Update dependency tailwindcss to v3.4.7 2024-07-30 20:21:19 +00:00
0ae2c2956a Update dependency mcutils-library to v1.3.3 2024-07-30 20:21:10 +00:00
35ad8b458b Update dependency @types/react-syntax-highlighter to v15.5.13 2024-07-30 20:21:04 +00:00
c7365df0bd Update dependency @types/react to v18.3.3 2024-07-30 20:20:58 +00:00
307551dc05 Update dependency @heroicons/react to v2.1.5 2024-07-30 20:20:49 +00:00
Lee
fc90fdd59b Merge pull request 'Update nextjs monorepo to v14.2.5' (#45) from renovate/nextjs-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m17s
Reviewed-on: #45
2024-07-30 20:17:53 +00:00
829a0afea0 Update nextjs monorepo to v14.2.5 2024-07-30 20:17:40 +00:00
Lee
266ac15bb1 Merge pull request 'Update dependency react-spinners to ^0.14.0' (#46) from renovate/react-spinners-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 57s
Reviewed-on: #46
2024-07-30 20:10:14 +00:00
Lee
78987e0c55 Merge pull request 'Update dependency @hookform/resolvers to v3.9.0' (#39) from renovate/hookform-resolvers-3.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m13s
Reviewed-on: #39
2024-07-30 20:06:01 +00:00
65a154a5dc Update dependency @hookform/resolvers to v3.9.0 2024-07-09 14:01:42 +00:00
Lee
179b5eb9d2 revert 2a17ac2d3d6389170a60ce4782bb733f32413874
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m32s
revert Merge pull request 'Update dependency @sentry/nextjs to v8' (#38) from renovate/major-sentry-javascript-monorepo into master

Reviewed-on: #38
2024-07-09 13:02:48 +00:00
24f34ecd03 Update dependency react-spinners to ^0.14.0 2024-07-08 11:01:05 +00:00
8e655681a7 Update dependency unified to v11.0.5 2024-07-08 10:01:09 +00:00
Lee
91867bb718 Delete sentry.edge.config.ts
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 23s
2024-07-08 09:37:33 +00:00
Lee
2a17ac2d3d Merge pull request 'Update dependency @sentry/nextjs to v8' (#38) from renovate/major-sentry-javascript-monorepo into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 1m10s
Reviewed-on: #38
2024-07-08 09:32:03 +00:00
840711604b Update dependency @sentry/nextjs to v8 2024-07-08 09:31:22 +00:00
Lee
260dadda6d Merge pull request 'Update dependency remote-mdx to ^0.0.8' (#37) from renovate/remote-mdx-0.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m58s
Reviewed-on: #37
2024-07-08 09:26:35 +00:00
Lee
5370fd7cad Merge pull request 'Update dependency lucide-react to ^0.402.0' (#33) from renovate/lucide-react-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 6s
Reviewed-on: #33
2024-07-08 09:25:32 +00:00
Lee
725e0cd25d Merge pull request 'Update dependency tailwindcss to v3.4.4' (#41) from renovate/tailwindcss-3.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #41
2024-07-08 09:23:24 +00:00
649141ece3 Update dependency lucide-react to ^0.402.0 2024-07-08 08:00:57 +00:00
390f1ffd64 Update dependency tailwindcss to v3.4.4 2024-06-05 18:01:39 +00:00
eb32d3786d Update dependency remote-mdx to ^0.0.8 2024-05-11 14:01:34 +00:00
Lee
bc27ed78b1 Merge pull request 'Update react monorepo to v18.3.1' (#31) from renovate/react-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m11s
Reviewed-on: #31
2024-04-28 04:28:31 +00:00
11e2efef2e Update react monorepo to v18.3.1 2024-04-27 10:00:58 +00:00
Lee
68e0599159 Merge pull request 'Update dependency lucide-react to ^0.376.0' (#30) from renovate/lucide-react-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 4s
Reviewed-on: #30
2024-04-27 09:50:30 +00:00
Lee
bc04ac8e82 Merge pull request 'Update dependency remote-mdx to ^0.0.7' (#29) from renovate/remote-mdx-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #29
2024-04-27 09:49:27 +00:00
fe0a0d19f9 Update dependency lucide-react to ^0.376.0 2024-04-27 03:01:02 +00:00
cfdc69a078 Update dependency remote-mdx to ^0.0.7 2024-04-27 03:00:56 +00:00
Lee
9abf21e890 Merge pull request 'Update react monorepo to v18.3.1' (#28) from renovate/react-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m2s
Reviewed-on: #28
2024-04-27 02:09:39 +00:00
2d3db00551 Update react monorepo to v18.3.0 2024-04-25 17:01:01 +00:00
Lee
73441e0898 Merge pull request 'Update nextjs monorepo to v14.2.3' (#27) from renovate/nextjs-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m48s
Reviewed-on: #27
2024-04-24 20:28:55 +00:00
7bee4c1611 Update nextjs monorepo to v14.2.3 2024-04-24 20:02:16 +00:00
Lee
5fd3d2f822 Merge pull request 'Update dependency remark-mdx to v3.0.1' (#25) from renovate/mdx-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m12s
Reviewed-on: #25
2024-04-24 16:29:12 +00:00
Lee
3156916b4f Merge pull request 'Update dependency @sentry/nextjs to v7.112.2' (#26) from renovate/sentry-javascript-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m9s
Reviewed-on: #26
2024-04-24 16:24:43 +00:00
Lee
a29f0c41ac Merge pull request 'Update dependency lucide-react to ^0.373.0' (#24) from renovate/lucide-react-0.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m7s
Reviewed-on: #24
2024-04-24 16:20:42 +00:00
dbcba6682b Update dependency @sentry/nextjs to v7.112.2 2024-04-24 13:01:04 +00:00
cf1f2bce26 Update dependency remark-mdx to v3.0.1 2024-04-24 09:01:50 +00:00
795f02e0e0 Update dependency lucide-react to ^0.373.0 2024-04-24 08:01:01 +00:00
239d2f2078 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m52s
2024-04-23 17:52:42 +01:00
30e55d43c7 use my paste server 2024-04-23 17:52:37 +01:00
Lee
62919e1b93 Merge pull request 'Update dependency @sentry/nextjs to v7.112.1' (#23) from renovate/sentry-javascript-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m41s
Reviewed-on: #23
2024-04-23 14:56:22 +00:00
Lee
0ccc90851a Merge pull request 'Update dependency remote-mdx to ^0.0.5' (#22) from renovate/remote-mdx-0.x into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #22
2024-04-23 14:56:07 +00:00
Lee
fb70dd6ab7 Merge pull request 'Update dependency clsx to v2.1.1' (#21) from renovate/clsx-2.x-lockfile into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m19s
Reviewed-on: #21
2024-04-23 14:51:07 +00:00
4198176b69 Update dependency @sentry/nextjs to v7.112.0 2024-04-23 10:00:59 +00:00
8001ad7a11 Update dependency remote-mdx to ^0.0.5 2024-04-23 06:01:05 +00:00
89805d0442 Update dependency clsx to v2.1.1 2024-04-23 06:00:57 +00:00
Lee
58f4a6edf8 Merge pull request 'Update dependency mcutils-library to v1.3.2' (#20) from renovate/mcutils-library-1.x-lockfile into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 4m39s
Reviewed-on: #20
2024-04-23 02:39:29 +00:00
a0b8777a94 fix the code in the code dialog falling off the dialog
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m32s
2024-04-22 22:56:34 +01:00
1e96ed1b1c add json upload button and add page reload button to server and player pages
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m29s
2024-04-22 22:50:13 +01:00
63e4eedc37 deadmau5
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m59s
2024-04-22 21:14:07 +01:00
c2790054db start work on cleaning up types and add try a player and server to their pages
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m29s
2024-04-22 21:08:44 +01:00
1ab27f69be Update dependency mcutils-library to v1.3.2 2024-04-22 20:01:02 +00:00
52fea3da68 use the server preview from the api in the server route for embeds
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m25s
2024-04-22 20:15:56 +01:00
Lee
4ffd10e9c3 Merge pull request 'Update dependency mcutils-library to v1.3.1' (#19) from renovate/mcutils-library-1.x-lockfile into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m2s
Reviewed-on: #19
2024-04-22 19:11:51 +00:00
9e17145e90 change docs and docs page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m30s
2024-04-22 20:07:53 +01:00
782b5efd4b clear pages when clicking on a page in the command dialog
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m29s
2024-04-22 17:20:54 +01:00
e277645333 update the command menu
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m30s
2024-04-22 17:06:50 +01:00
9ba13d1160 testing the test
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 41s
2024-04-22 14:21:09 +01:00
40029b9839 ci
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m29s
2024-04-22 02:47:08 +01:00
ffa2db120f update error page
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
2024-04-22 02:41:42 +01:00
525b4981fe fix spacing on docs page and remove postbuild task in docker
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m25s
2024-04-22 02:38:06 +01:00
Lee
ce55389cba Delete public/sitemap.xml
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m34s
2024-04-22 01:31:17 +00:00
Lee
656eefa2e5 Delete public/robots.txt
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
2024-04-22 01:31:14 +00:00
230e5d6864 ci
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
2024-04-22 02:31:02 +01:00
69fd6483de xi
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 51s
2024-04-22 02:29:22 +01:00
a6650af640 add the sitemap
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 9s
2024-04-22 02:28:35 +01:00
f0eaaaad5e fix command menu keyboard shortcuts
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m22s
2024-04-22 02:13:30 +01:00
96dc8de92f add language indicator to the code highlighter
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m32s
2024-04-22 02:11:41 +01:00
00a5febf66 fix code highlighter colors and centre the breadcrumbs on docs
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m23s
2024-04-22 01:56:01 +01:00
49daf6f1a4 impl command menu and change theme colors
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 3m26s
2024-04-22 01:21:04 +01:00
c2d7f5f33c Update dependency mcutils-library to v1.3.1 2024-04-21 23:01:03 +00:00
a200fa045c fix Dockerfile
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m15s
2024-04-21 20:22:17 +01:00
d26c70f507 fix Dockerfile
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 11s
2024-04-21 20:20:44 +01:00
0b9a112c5b maybe fix?
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 40s
2024-04-21 20:18:01 +01:00
23c4284d8a add a release version to Sentry
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m25s
2024-04-21 20:08:17 +01:00
5c05493ed1 maybe it'll push release data, who knows
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m24s
2024-04-21 19:48:24 +01:00
Lee
32364ab58f Delete .env
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m25s
2024-04-21 18:42:37 +00:00
Lee
81e6580eed Merge pull request 'Update dependency @sentry/nextjs to v7.111.0' (#18) from renovate/sentry-javascript-monorepo into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m4s
Reviewed-on: #18
2024-04-21 18:05:39 +00:00
735d6532c2 Update dependency @sentry/nextjs to v7.111.0 2024-04-21 18:00:57 +00:00
2a06a4d338 attempt to impl sentry
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m1s
2024-04-21 18:23:29 +01:00
8d35e7ab0f cleanup imports 2024-04-21 17:39:56 +01:00
ec27cd9f29 add github icon and display breadcrumb on docs home page 2024-04-21 17:39:00 +01:00
738fcb8e24 add search icon to the docs search button 2024-04-21 17:13:46 +01:00
ab2716a8c9 fix not found page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m27s
2024-04-21 13:50:02 +01:00
130444e3e2 update docs link 2024-04-21 13:49:52 +01:00
Lee
e791c20ba7 Update documentation/server/favicon.md
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m13s
2024-04-21 05:27:01 +00:00
96abad164a add padding spacing to the bottom of the doc page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m14s
2024-04-21 06:05:25 +01:00
e6a28ed268 mostly completed docs
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m38s
2024-04-21 05:47:52 +01:00
3b1872a9dc update js lib doc 2024-04-21 02:44:47 +01:00
4b336e4456 add a continue typing indicator
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m6s
2024-04-21 02:38:16 +01:00
cb6814d47b update placeholder text
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-21 02:24:16 +01:00
389c004d16 fix mobile responsiveness for search dialog
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m11s
2024-04-21 02:23:41 +01:00
213cdb1e5c Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m54s
2024-04-21 02:17:58 +01:00
ebdaf623d9 add docs searching 2024-04-21 02:17:51 +01:00
Lee
e7ef6a1bd8 Merge pull request 'Update dependency mcutils-library to v1.3.0' (#17) from renovate/mcutils-library-1.x-lockfile into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m34s
Reviewed-on: #17
2024-04-20 22:09:56 +00:00
a5cab7766e Update dependency mcutils-library to v1.3.0 2024-04-20 22:00:59 +00:00
dbed53efe4 fix the fixy zone 2024-04-20 22:30:33 +01:00
24170a5a66 fix the fixy zone
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m6s
2024-04-20 22:29:54 +01:00
b5198b5807 fix the fixy zone
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m13s
2024-04-20 22:24:45 +01:00
025233d837 update path for docs
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m16s
2024-04-20 22:18:50 +01:00
f7a3bb00a5 add breadcrumb and cleanup docs loader
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m8s
2024-04-20 22:16:30 +01:00
325fe62569 use the new server preview
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m33s
2024-04-20 21:26:27 +01:00
fcba215522 change the styling slightly for the get started button
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-20 15:00:52 +01:00
a17ce202d8 make the get started button on the landing more fancy
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m7s
2024-04-20 14:43:37 +01:00
7553a35b79 remove postman button from landing page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-20 14:35:36 +01:00
74618c8cce change the stat cards to use the same card background as the rest of the site
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m11s
2024-04-20 14:28:26 +01:00
9b0e5bfcf7 use title components in docs
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-20 03:22:10 +01:00
3ca0b5be81 fix title 2024-04-20 03:04:23 +01:00
400f878939 fix mdx renderer return types 2024-04-20 03:00:36 +01:00
fe43779a67 make the markdown list slighly more to the right
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-20 02:59:07 +01:00
fa530d3168 cleanup markdown renderer
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-20 02:54:04 +01:00
18a782243e move common dir
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m9s
2024-04-20 02:35:52 +01:00
6356be7ac3 fix spacing on lists
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m8s
2024-04-20 02:29:28 +01:00
c124e5750b add api section to the docs
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
2024-04-20 02:28:26 +01:00
8df02ab62c fix descriptions
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m13s
2024-04-20 02:17:39 +01:00
a0e08ff437 fix descriptions
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m11s
2024-04-20 02:15:27 +01:00
4827125f73 update readme 2024-04-20 02:12:29 +01:00
b4076c850a add libraries to the docs (will probably re-design later)
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m36s
2024-04-20 02:04:42 +01:00
57a45a4c05 generate metadata for the documentation page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m10s
2024-04-20 00:50:59 +01:00
e8e74a1a8d hide api link in navbar on mobile
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m11s
2024-04-20 00:44:40 +01:00
9399cf9efd don't open docs in a new tab
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m26s
2024-04-20 00:40:08 +01:00
7edfc31e82 don't open docs in a new tab
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m28s
2024-04-20 00:37:44 +01:00
9a5cc8642d add documentation dir to the Dockerfile
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m55s
2024-04-20 00:35:16 +01:00
8169c08faa add veryyyy basic docs renderer 2024-04-20 00:34:42 +01:00
d0f926f330 update the config.json 2024-04-19 23:13:14 +01:00
6e2fc9e13b make star size slightly bigger on star us button 2024-04-19 22:58:01 +01:00
31b831f97b switch to local assets for the logo
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m6s
2024-04-19 22:39:16 +01:00
a3e9411c97 fix theme toggle button 2024-04-19 22:37:25 +01:00
3be59f97c3 fix autocomplete filling in fields 2024-04-19 22:32:11 +01:00
1ed0e72316 add a title component
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-19 22:08:20 +01:00
78390a3bc9 make pages more consistent on mobile
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-19 21:53:11 +01:00
95fef83dcf fix server page for mobile
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-19 21:43:32 +01:00
97c1b88370 add cache info buttons
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m27s
2024-04-19 21:19:14 +01:00
Lee
2b6d8964b3 Merge pull request 'Update dependency mcutils-library to v1.2.5' (#16) from renovate/mcutils-library-1.x-lockfile into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m25s
Reviewed-on: #16
2024-04-19 20:02:29 +00:00
b587a1072e Update dependency mcutils-library to v1.2.5 2024-04-19 20:00:59 +00:00
19f52d8575 fix status page embed
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-19 20:05:55 +01:00
03cdef9d23 cleanup imports
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m32s
2024-04-19 18:50:30 +01:00
93ea24762c new mojang status page, and add color to the embed based on if there was any offline or degraded 2024-04-19 18:50:12 +01:00
7305714b12 i never committed the files, i actually have brain damage
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m21s
2024-04-19 17:03:24 +01:00
cdb75aae87 fix the Dockerfile - copy public dir
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 40s
2024-04-19 17:01:18 +01:00
617a57fb3f fix the Dockerfile - copy public dir
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 7s
2024-04-19 16:58:54 +01:00
c62ce2bc6f fix the Dockerfile - copy public dir
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 40s
2024-04-19 16:57:39 +01:00
2eedafb72d fix card
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m2s
2024-04-19 16:55:00 +01:00
78709ed060 make the server page show a minecraft style server
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 28s
2024-04-19 16:51:58 +01:00
0708def9a1 make the landing page description smaller and make the title semibold
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-19 15:58:13 +01:00
9fb8bc7b1d fix ring color
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-19 15:56:05 +01:00
0641d6722c fix mobile responsiveness on landing page buttons
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m1s
2024-04-19 15:53:24 +01:00
777d7bebd3 update code formatter background color
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m2s
2024-04-19 15:51:17 +01:00
7df6e93f74 make the background less dark
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m1s
2024-04-19 15:46:49 +01:00
d90a3985cb update the github link
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m2s
2024-04-19 15:43:36 +01:00
69502c7709 fix navbar
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-19 15:41:35 +01:00
c2ca3beb05 update accent color on github button
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m18s
2024-04-19 15:33:36 +01:00
f06b47823c remove fleet dir
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m1s
2024-04-19 15:31:09 +01:00
6ffdff96d6 Merge remote-tracking branch 'origin/master'
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
2024-04-19 15:29:44 +01:00
67a8cff0fd fix navbar centering 2024-04-19 15:29:26 +01:00
bcf6c4867d update star us button 2024-04-19 15:22:03 +01:00
Lee
d014f87d07 Merge pull request 'Update dependency tailwind-merge to v2.3.0' (#14) from renovate/tailwind-merge-2.x-lockfile into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m24s
Reviewed-on: #14
2024-04-19 12:36:27 +00:00
cfbc9a082c Update dependency tailwind-merge to v2.3.0 2024-04-19 12:00:54 +00:00
Lee
7ca580b54e Merge pull request 'Update dependency lucide-react to ^0.372.0' (#13) from renovate/lucide-react-0.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m49s
Reviewed-on: #13
2024-04-19 11:19:51 +00:00
268a607143 Update dependency lucide-react to ^0.372.0 2024-04-19 09:01:27 +00:00
Lee
f92453e1b4 Merge pull request 'Configure Renovate' (#11) from renovate/configure into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
Reviewed-on: #11
2024-04-18 20:17:00 +00:00
a82f92f376 Add renovate.json 2024-04-18 20:16:29 +00:00
Lee
ced3054ec3 Delete renovate.json
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 29s
2024-04-18 20:13:35 +00:00
Lee
7eebb04e1a Update renovate.json
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 19:58:42 +00:00
Lee
776782287c Merge pull request 'Configure Renovate' (#9) from renovate/configure into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Failing after 5s
Reviewed-on: #9
2024-04-18 19:55:37 +00:00
328b62718f Add renovate.json 2024-04-18 19:55:10 +00:00
Lee
685fee9689 Delete renovate.json
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
2024-04-18 19:55:06 +00:00
1540daf269 fix infinite loading spinner when player not found
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m2s
2024-04-18 19:16:49 +01:00
78dc37f9b7 update stat ids
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m1s
2024-04-18 18:13:57 +01:00
cfb0411fc1 Merge branch 'master' of https://git.fascinated.cc/MinecraftUtilities/Frontend
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m5s
2024-04-18 12:49:40 +01:00
081c0c9f92 fix metrics 2024-04-18 12:49:38 +01:00
Lee
5129e843de Merge pull request 'Configure Renovate' (#8) from renovate/configure into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 29s
Reviewed-on: #8
2024-04-18 11:03:25 +00:00
8b4d228c11 Add renovate.json 2024-04-18 11:02:12 +00:00
Lee
ec984db31a Delete renovate.json
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m8s
2024-04-18 11:01:28 +00:00
Lee
aefd0d5e3a Merge pull request 'Update dependency lucide-react to ^0.371.0' (#4) from renovate/lucide-react-0.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m52s
Reviewed-on: #4
2024-04-18 10:58:46 +00:00
Lee
f2177b6c2f Merge pull request 'Update dependency eslint to v9' (#5) from renovate/eslint-9.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m28s
Reviewed-on: #5
2024-04-18 10:57:05 +00:00
Lee
d1db008589 Merge pull request 'Update dependency react-use-websocket to v4' (#6) from renovate/react-use-websocket-4.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m49s
Reviewed-on: #6
2024-04-18 10:55:01 +00:00
Lee
50e9875e9a Merge pull request 'Update node Docker tag to v21' (#7) from renovate/node-21.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 2m22s
Reviewed-on: #7
2024-04-18 10:45:35 +00:00
2053f7baef highlight the current page in the navbar
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m0s
2024-04-18 11:22:43 +01:00
ef1abcf162 Update node Docker tag to v21 2024-04-18 10:00:46 +00:00
380c50760d Merge branch 'master' of https://git.fascinated.cc/MinecraftUtilities/Frontend
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 10:56:58 +01:00
2040710f05 custom row renderer to allow for clickable links 2024-04-18 10:56:57 +01:00
e245eea213 Update dependency react-use-websocket to v4 2024-04-18 09:01:01 +00:00
15fadd38e5 Update dependency eslint to v9 2024-04-18 09:00:53 +00:00
Lee
6b3851ef16 Merge pull request 'Update dependency eslint-config-next to v14.2.2' (#3) from renovate/eslint-config-next-14.x into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m58s
Reviewed-on: #3
2024-04-18 08:42:16 +00:00
Lee
a7e8f8a3cc Merge pull request 'Update dependency @types/react to v18.2.79' (#2) from renovate/react-18.x-lockfile into master
Some checks failed
Deploy App / docker (ubuntu-latest) (push) Has been cancelled
Reviewed-on: #2
2024-04-18 08:41:45 +00:00
2945caa2d7 Update dependency lucide-react to ^0.371.0 2024-04-18 08:41:10 +00:00
48db8c4a7a Update dependency eslint-config-next to v14.2.2 2024-04-18 08:38:19 +00:00
339f94dcef Update dependency @types/react to v18.2.79 2024-04-18 08:38:11 +00:00
Lee
c5247806e4 Merge pull request 'Configure Renovate' (#1) from renovate/configure into master
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m8s
Reviewed-on: #1
2024-04-18 08:37:25 +00:00
7c8044e6e7 Add renovate.json 2024-04-18 08:34:03 +00:00
b4d3e4002d cleanup depends
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m26s
2024-04-18 09:21:52 +01:00
b99313e008 bump mcutils-library
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m25s
2024-04-18 09:20:22 +01:00
2c80edb56a move json button and add json button to the server page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 09:11:19 +01:00
57144c5c91 cleanup
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 09:04:54 +01:00
6e46d613a4 format player count on server embed
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 09:03:06 +01:00
2a080e28a1 update server embed
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 09:01:10 +01:00
f61e46cb27 update server embed
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 08:59:13 +01:00
21e1a0e596 fix embed color
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 08:54:13 +01:00
e793d0eb39 fix styling on mojang status page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 08:51:52 +01:00
f69d69373c cleanup
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 08:45:57 +01:00
ccd0e0851c add background
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 08:41:54 +01:00
3f8204ddd3 make code dialog look much nicer
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 08:37:43 +01:00
413b52b499 use small text in code dialogs
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 08:29:22 +01:00
587c1bca82 fix mobile support on player page
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 08:26:02 +01:00
54911efd0d actually make the open in new tab button in the skin part dialog open in a new tab
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m2s
2024-04-18 08:22:57 +01:00
4739297ba5 make the skin part dialog imags slightly smaller on mobile
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m3s
2024-04-18 08:21:03 +01:00
93477561a0 add mobile support to skin part dialogs
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m15s
2024-04-18 08:18:46 +01:00
2c3b938765 add with overlays skin part image to the skin part view
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m5s
2024-04-18 08:14:09 +01:00
f9257bb184 fix player head size
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 08:05:10 +01:00
211807f494 fix infinite loading icon when searching for an invalid player
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m5s
2024-04-18 07:59:03 +01:00
0c4a1ebda9 tooltip and click for player head too
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m4s
2024-04-18 07:55:50 +01:00
ec47715c44 add dialog to skin parts
All checks were successful
Deploy App / docker (ubuntu-latest) (push) Successful in 1m5s
2024-04-18 07:46:22 +01:00
ff017c6bad remove debug 2024-04-18 07:33:48 +01:00
105 changed files with 11077 additions and 4304 deletions

View File

@ -1,4 +0,0 @@
{
"toolchains": [],
"nodejs.editor.formatOnSave.prettier.mode": "Enabled"
}

16
.gitignore vendored
View File

@ -37,3 +37,19 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# Sentry Config File
.sentryclirc
# Sentry Config File
.sentryclirc
# Environment variables
.env
# Sitemap & Robots
/public/sitemap*
/public/robots.txt
# Sentry Config File
.env.sentry-build-plugin

View File

@ -1,4 +1,4 @@
FROM node:18-alpine AS base
FROM node:21-alpine AS base
# Install dependencies only when needed
FROM base AS deps
@ -22,11 +22,18 @@ WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
# Get the git commit hash
ARG GIT_REV
ENV GIT_REV ${GIT_REV}
# Sentry Auth Token
ARG SENTRY_AUTH_TOKEN
ENV SENTRY_AUTH_TOKEN ${SENTRY_AUTH_TOKEN}
# Disable telemetry during build
ENV NEXT_TELEMETRY_DISABLED 1
# Build the frontend
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
@ -39,12 +46,17 @@ FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
# Disable telemetry during runtime
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy the public folder
COPY --from=builder /app/public ./public
COPY --from=builder /app/documentation ./documentation
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

View File

@ -1,36 +1,3 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# Minecraft Utilities - Frontend
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
See [The Website](https://mcutils.xyz) or [Minecraft Utilities Documentation](https://mcutils.xyz/docs) for more information.

View File

@ -1,6 +1,6 @@
{
"siteName": "Minecraft Utilities",
"siteDescription": "Minecraft Utilities offers you many endpoints to get information about a minecraft server or a player.",
"siteUrl": "https://mcutils.xyz/",
"apiUrl": "https://api.mcutils.xyz"
"name": "Minecraft Utilities",
"description": "Minecraft Utilities offers you many endpoints to get information about a minecraft server or a player.",
"publicUrl": "https://mcutils.xyz/",
"apiEndpoint": "https://api.mcutils.xyz"
}

34
documentation/home.md Normal file
View File

@ -0,0 +1,34 @@
---
title: Home
summary: Welcome to the Minecraft Utilities documentation!
---
# Getting Started
Welcome to the Minecraft Utilities documentation! You can find information on how to use the various features of the API.
## Features
See below for a list of features that are currently available in the API.
| Feature | Description |
|----------------------------------------------------------|----------------------------------------|
| [Player Lookup](/docs/player/player-lookup) | Get a player's information |
| [Player Username To Uuid](/docs/player/username-to-uuid) | Get a player's skin parts |
| [Player Skin Parts](/docs/player/skin-parts) | Get a player's skin parts |
| [Server Lookup](/docs/server/server-lookup) | Get a server's information |
| [Server Icon](/docs/server/favicon) | Get a server's icon |
| [Server Preview](/docs/server/preview) | View the server as if you were in-game |
| [Mojang Status](/docs/mojang/endpoint-status) | Get the status of Mojang's services |
## Libraries
We offer a few different libraries for different languages to help you get started with the Minecraft Utilities plugin.
- [Java](/docs/libraries/java)
- [JavaScript](/docs/libraries/javascript)
## API
You can view our Postman collection [here](https://www.postman.com/imfascinated/workspace/minecraft-utilities) to see all the available endpoints and how to use them.

View File

@ -0,0 +1,42 @@
---
title: Java Library
summary: The Java library for Minecraft Utilities is a simple way to interact with the API using Java!
---
# Java Library
This is the Java library for Minecraft Utilities.
## Installation
To use the Java library, you can add the following to your `pom.xml` file:
```xml
<repositories>
<repository>
<id>fascinated-repo-public</id>
<name>Fascinated's Repository</name>
<url>https://repo.fascinated.cc/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>xyz.mcutils</groupId>
<artifactId>mcutils-java-library</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
```
## Usage
This is a simple example of how to use the Java library to get a player's information.
```java
public class Main {
public static void main(String[] args) {
System.out.println(McUtilsAPI.getPlayer("Notch"));
}
}
```

View File

@ -0,0 +1,28 @@
---
title: Javascript Library
summary: The Javascript library for Minecraft Utilities is a simple way to interact with the API using Javascript!
---
# Javascript Library
This is the Javascript library for Minecraft Utilities.
## Installation
To use the Javascript library, you can run the following command:
```bash
npm install mcutils-library
```
## Usage
This is a simple example of how to use the Javascript library to get a player's information.
```javascript
import { getPlayer, CachedPlayer } from "mcutils-library";
const cachedPlayer: CachedPlayer = await getPlayer(playerId);
console.log(player);
```

View File

@ -0,0 +1,63 @@
---
title: Mojang Endpoint Status
summary: Get the status of the Mojang APIs.
---
# Overview
The Mojang endpoint status endpoint allows you to get the status of the Mojang APIs.
## Endpoint
```
GET /mojang/status
```
## Example
```bash
curl -X GET "https://api.mcutils.xyz/mojang/status" -H "accept: application/json"
```
## Response
```json
{
"cache": {
"cached": false,
"cachedTime": -1
},
"endpoints": [
{
"name": "Minecraft Textures",
"hostname": "textures.minecraft.net",
"status": "ONLINE"
},
{
"name": "Minecraft Libraries",
"hostname": "libraries.minecraft.net",
"status": "ONLINE"
},
{
"name": "Minecraft Services",
"hostname": "api.minecraftservices.com",
"status": "ONLINE"
},
{
"name": "Mojang Assets",
"hostname": "assets.mojang.com",
"status": "ONLINE"
},
{
"name": "Mojang API",
"hostname": "api.mojang.com",
"status": "ONLINE"
},
{
"name": "Mojang Session Server",
"hostname": "sessionserver.mojang.com",
"status": "ONLINE"
}
]
}
```

View File

@ -0,0 +1,68 @@
---
title: Player Lookup
summary: Get information about a player.
---
# Overview
The player lookup endpoint allows you to get information about a player. This includes their UUID, username, and any other information that is available.
## Endpoint
```
GET /player/:query
```
## Parameters
| Parameter | Description | Required |
|-----------|---------------------------------------------------------|----------|
| query | The username or uuid of the player you want to look up. | Yes |
## Example
```bash
curl -X GET "https://api.mcutils.xyz/player/imfascinated" -H "accept: application/json"
```
## Response
```json
{
"cache": {
"cached": false,
"cachedTime": -1
},
"uniqueId": "eeab5f8a-18dd-4d58-af78-2b3c4543da48",
"trimmedUniqueId": "eeab5f8a18dd4d58af782b3c4543da48",
"username": "ImFascinated",
"skin": {
"url": "http://textures.minecraft.net/texture/ba1e0c9e21983c06e45614642316cd7029a297bc246bc6d236a41388c3ff9a09",
"model": "SLIM",
"legacy": false,
"parts": {
"head": "https://api.mcutils.xyz/player/head/eeab5f8a-18dd-4d58-af78-2b3c4543da48",
"face": "https://api.mcutils.xyz/player/face/eeab5f8a-18dd-4d58-af78-2b3c4543da48",
"body": "https://api.mcutils.xyz/player/body/eeab5f8a-18dd-4d58-af78-2b3c4543da48"
}
},
"cape": {
"url": "http://textures.minecraft.net/texture/2340c0e03dd24a11b15a8b33c2a7e9e32abb2051b2481d0ba7defd635ca7a933"
},
"rawProperties": [
{
"name": "textures",
"value": "ewogICJ0aW1lc3RhbXAiIDogMTcxMzY3MDc4MTM0NSwKICAicHJvZmlsZUlkIiA6ICJlZWFiNWY4YTE4ZGQ0ZDU4YWY3ODJiM2M0NTQzZGE0OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJJbUZhc2NpbmF0ZWQiLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmExZTBjOWUyMTk4M2MwNmU0NTYxNDY0MjMxNmNkNzAyOWEyOTdiYzI0NmJjNmQyMzZhNDEzODhjM2ZmOWEwOSIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9LAogICAgIkNBUEUiIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzIzNDBjMGUwM2RkMjRhMTFiMTVhOGIzM2MyYTdlOWUzMmFiYjIwNTFiMjQ4MWQwYmE3ZGVmZDYzNWNhN2E5MzMiCiAgICB9CiAgfQp9",
"signed": false
}
]
}
```
## Errors
| Status Code | Description |
|-------------|-----------------------------------------------|
| 400 | The username is invalid. |
| 404 | The player was not found. |
| 429 | The Mojang API rate limit has been exhausted. |

View File

@ -0,0 +1,47 @@
---
title: Player Skin Parts
summary: Get a specific part of a player's skin.
---
# Overview
The player skin parts endpoint allows you to get a specific part of a player's skin.
## Endpoint
```
GET /player/:part/:query
```
## Parts
| Part | Description |
|------|---------------------------------|
| head | Get the player's isometric head |
| face | Get the player's face |
| body | Get the player's body |
## Parameters
| Parameter | Description | Required |
|-----------|---------------------------------------------------------|----------|
| part | The part of the skin you want to get. | Yes |
| query | The username or uuid of the player you want to look up. | Yes |
## Example
```bash
curl -X GET "https://api.mcutils.xyz/player/head/imfascinated" -H "accept: image/png"
```
## Response
![Skin Part](https://api.mcutils.xyz/player/head/eeab5f8a-18dd-4d58-af78-2b3c4543da48 "Player's Head")
## Errors
| Status Code | Description |
|-------------|-----------------------------------------------|
| 400 | The username is invalid. |
| 404 | The player was not found. |
| 429 | The Mojang API rate limit has been exhausted. |

View File

@ -0,0 +1,46 @@
---
title: Player Username to UUID
summary: Get a player's UUID from their username.
---
# Overview
The player username to UUID endpoint allows you to get a player's UUID from their username.
## Endpoint
```
GET /player/uuid/:query
```
## Parameters
| Parameter | Description | Required |
|-----------|---------------------------------------------------------|----------|
| query | The username of the player you want to look up. | Yes |
## Example
```bash
curl -X GET "https://api.mcutils.xyz/player/uuid/imfascinated" -H "accept: application/json"
```
## Response
```json
{
"cache": {
"cached": false,
"cachedTime": -1
},
"uniqueId": "eeab5f8a-18dd-4d58-af78-2b3c4543da48",
"username": "ImFascinated"
}
```
## Errors
| Status Code | Description |
|-------------|-----------------------------------------------|
| 400 | The username is invalid. |
| 429 | The Mojang API rate limit has been exhausted. |

View File

@ -0,0 +1,45 @@
---
title: Server Blocked Status
summary: Get the Mojang blocked status of a Minecraft server.
---
# Overview
The server blocked status endpoint allows you to get the Mojang blocked status of a Minecraft server.
## Endpoint
```
GET /server/blocked/:query
```
## Platforms
This endpoint is only available for Java servers.
## Parameters
| Parameter | Description | Required |
|-----------|---------------------------------------------------------------|----------|
| query | The IP address or hostname of the server you want to look up. | Yes |
## Example
```bash
curl -X GET "https://api.mcutils.xyz/server/blocked/play.hypixel.net"
```
## Response
```json
{
"blocked": false
}
```
## Errors
| Status Code | Description |
|-------------|-----------------------------------------------|
| 400 | The server was not found. |
| 429 | The Mojang API rate limit has been exhausted. |

View File

@ -0,0 +1,41 @@
---
title: Server Favicon
summary: Get the favicon of a Minecraft server.
---
# Overview
The server favicon endpoint allows you to get the favicon of a Minecraft server.
## Endpoint
```
GET /server/icon/:query
```
## Platforms
This endpoint is only available for Java servers.
## Parameters
| Parameter | Description | Required |
|-----------|---------------------------------------------------------------------------|----------|
| query | The IP address or hostname of the server you want to get the favicon for. | Yes |
## Example
```bash
curl -X GET "https://api.mcutils.xyz/server/icon/hypixel.net" -H "accept: image/png"
```
## Response
![Server Icon](https://api.mcutils.xyz/server/icon/play.hypixel.net)
## Errors
| Status Code | Description |
|-------------|-----------------------------------------------|
| 400 | The server was not found. |
| 429 | The Mojang API rate limit has been exhausted. |

View File

@ -0,0 +1,45 @@
---
title: Server Preview
summary: Get the server list preview of a Minecraft server.
---
# Overview
The server preview endpoint allows you to get the server list preview of a Minecraft server.
## Endpoint
```
GET /server/:platform/preview/:query
```
## Platforms
| Platform | Description |
|----------|-----------------|
| bedrock | Bedrock Edition |
| java | Java Edition |
## Parameters
| Parameter | Description | Required |
|-----------|---------------------------------------------------------------------------|----------|
| platform | The platform of the server you want to get the preview for. | Yes |
| query | The IP address or hostname of the server you want to get the preview for. | Yes |
## Example
```bash
curl -X GET "https://api.mcutils.xyz/server/java/preview/hypixel.net" -H "accept: image/png"
```
## Response
![Server Icon](https://api.mcutils.xyz/server/java/preview/play.hypixel.net)
## Errors
| Status Code | Description |
|-------------|-----------------------------------------------|
| 400 | The server was not found. |
| 429 | The Mojang API rate limit has been exhausted. |

File diff suppressed because one or more lines are too long

5
next-sitemap.config.js Normal file
View File

@ -0,0 +1,5 @@
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.SITE_URL || "https://mcutils.xyz",
generateRobotsTxt: true,
};

View File

@ -1,3 +1,17 @@
import {withSentryConfig} from "@sentry/nextjs";
import nextBuildId from "next-build-id";
import path from "path";
import { fileURLToPath } from "url";
/**
* The current git commit hash.
*
* @type {string|string} The current git commit hash.
*/
const buildId = (
process.env.GIT_REV || nextBuildId.sync({ dir: path.dirname(fileURLToPath(import.meta.url)) })
).substring(0, 7);
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
@ -15,6 +29,46 @@ const nextConfig = {
},
],
},
env: {
NEXT_PUBLIC_BUILD_ID: buildId,
},
experimental: {
mdxRs: true,
},
};
export default nextConfig;
export default withSentryConfig(nextConfig, {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
org: "minecraft-utilities",
project: "frontend",
sentryUrl: "https://glitchtip.fascinated.cc/",
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
// tunnelRoute: "/monitoring",
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
// See the following for more information:
// https://docs.sentry.io/product/crons/
// https://vercel.com/docs/cron-jobs
automaticVercelMonitors: true,
});

View File

@ -5,47 +5,66 @@
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"postbuild": "next-sitemap",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@eslint/eslintrc": "^3.0.2",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.3.4",
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@next/mdx": "^14.2.2",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"axios": "^1.6.8",
"@sentry/nextjs": "^8.20.0",
"@types/mdx": "^2.0.13",
"class-variance-authority": "^0.7.0",
"clipboard-copy": "^4.0.1",
"clsx": "^2.1.0",
"lucide-react": "^0.368.0",
"mcutils-library": "^1.2.1",
"cmdk": "^1.0.0",
"fuse.js": "^7.0.0",
"lucide-react": "^0.451.0",
"mcutils-library": "^1.3.1",
"moment": "^2.30.1",
"next": "14.2.2",
"next": "14.2.5",
"next-build-id": "^3.0.0",
"next-sitemap": "^4.2.3",
"next-themes": "^0.3.0",
"react": "^18",
"react-countup": "^6.5.3",
"react-dom": "^18",
"react-hook-form": "^7.51.3",
"react-spinners": "^0.13.8",
"react-spinners": "^0.14.0",
"react-syntax-highlighter": "^15.5.0",
"react-use-websocket": "3.0.0",
"react-use-websocket": "4.9.0",
"read-file": "^0.2.0",
"remark-gfm": "^4.0.0",
"remote-mdx": "^0.0.8",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4"
"tailwindcss-animate": "^1.0.7"
},
"pnpm": {
"overrides": {
"remark-mdx": "3.0.1",
"unified": "11.0.5",
"remark-parse": "11.0.0",
"mdast-util-frontmatter": "2.0.1"
}
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.11",
"eslint": "^8",
"eslint-config-next": "14.2.1",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"

11631
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/media/full-ping.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/media/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/media/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

6
renovate.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}

30
sentry.client.config.ts Normal file
View File

@ -0,0 +1,30 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://25aaa031240f4d659649d28e0a3fb0cb@glitchtip.fascinated.cc/1",
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
replaysOnErrorSampleRate: 1.0,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
integrations: [
Sentry.replayIntegration({
// Additional Replay configuration goes in here, for example:
maskAllText: true,
blockAllMedia: true,
}),
],
});

16
sentry.edge.config.ts Normal file
View File

@ -0,0 +1,16 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://25aaa031240f4d659649d28e0a3fb0cb@glitchtip.fascinated.cc/1",
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});

19
sentry.server.config.ts Normal file
View File

@ -0,0 +1,19 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: "https://25aaa031240f4d659649d28e0a3fb0cb@glitchtip.fascinated.cc/1",
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
// Uncomment the line below to enable Spotlight (https://spotlightjs.com)
// spotlight: process.env.NODE_ENV === 'development',
});

View File

@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { searchDocs } from "@/app/common/documentation";
export async function GET(request: NextRequest) {
// The query to search for
const query: string | null = request.nextUrl.searchParams.get("query");
// No query provided
if (!query) {
return new NextResponse(JSON.stringify({ error: "No query provided" }), { status: 400 });
}
// Don't allow queries less than 3 characters
if (query.length < 3) {
return new NextResponse(JSON.stringify({ error: "Query must be at least 3 characters" }), { status: 400 });
}
// Return the search results
return new NextResponse(JSON.stringify(searchDocs(query)));
}

View File

@ -0,0 +1,75 @@
import { CustomMDX } from "@/app/components/mdx-components";
import { Metadata } from "next";
import { generateEmbed } from "@/app/common/embed";
import { Title } from "@/app/components/title";
import { DocsContentMetadata, getDocContent, getDocsContent } from "@/app/common/documentation";
import { notFound } from "next/navigation";
import { GithubLink } from "@/app/components/docs/github-link";
import { DocsBreadcrumb } from "@/app/components/docs/breadcrumb";
type DocumentationPageParams = {
params: {
/**
* The slug for the documentation page.
*/
slug?: string[];
};
};
export async function generateStaticParams() {
let documentationPages: DocsContentMetadata[] = getDocsContent();
return documentationPages.map(page => ({
slug: [page.slug],
}));
}
export async function generateMetadata({ params: { slug } }: DocumentationPageParams): Promise<Metadata> {
const page: DocsContentMetadata | undefined = getDocContent(slug);
// Fallback to page not found
if (!page) {
return generateEmbed({
title: "Page not found",
description: "The documentation page was not found",
});
}
return generateEmbed({
title: `${page.title} - Documentation`,
description: `${page.summary}\n\nClick to view this page`,
});
}
export default function Page({ params: { slug } }: DocumentationPageParams) {
const page: DocsContentMetadata | undefined = getDocContent(slug);
// Page was not found, show an error page
if (!page) {
return notFound();
}
return (
<div className="w-full h-full px-4 flex flex-col gap-2">
<div className="flex justify-between items-center">
{/* The breadcrumb for the documentation page */}
<DocsBreadcrumb page={page} />
{/* The Git link for the documentation page */}
<div className="flex flex-row gap-2 items-center">
<GithubLink page={page} />
</div>
</div>
{/* The documentation page title and description */}
<div className="text-center mb-4">
<Title title={page.title} subtitle={page.summary} />
</div>
{/* The content of the documentation page */}
<div className="text-left w-full pb-[2rem]">
<CustomMDX source={page.content} />
</div>
</div>
);
}

View File

@ -1,18 +1,18 @@
import { Colors } from "@/app/common/colors";
import { generateEmbed } from "@/app/common/embed";
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import { cn } from "@/app/common/utils";
import { Card } from "@/app/components/card";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/app/components/ui/table";
import { generateEmbed } from "@/common/embed";
import { capitalizeFirstLetter } from "@/common/string-utils";
import { formatTime } from "@/common/time-utils";
import { cn } from "@/common/utils";
import { getMojangEndpointStatus } from "mcutils-library";
import { Metadata } from "next";
import { Title } from "@/app/components/title";
import { CachedEndpointStatus, getMojangEndpointStatus, Status } from "mcutils-library";
import { Metadata, Viewport } from "next";
import Link from "next/link";
import { ReactElement } from "react";
/**
* Force the page to be dynamic, so it will be regenerated on every request
*/
export const dynamic = "force-dynamic";
export const revalidate = 0;
/**
* Gets the color of the status
@ -43,15 +43,31 @@ function formatStatus(status: any): string {
return capitalizeFirstLetter(status.toLowerCase());
}
export async function generateViewport(): Promise<Viewport> {
const { endpoints } = await getMojangEndpointStatus();
let warning = false;
for (const endpoint of endpoints) {
if (endpoint.status != Status.ONLINE) {
warning = true;
break;
}
}
return {
themeColor: warning ? Colors.yellow : Colors.green,
};
}
export async function generateMetadata(): Promise<Metadata> {
const { endpoints } = await getMojangEndpointStatus();
let description = Object.entries(endpoints)
.map(([url, status]) => {
return `${url}: ${formatStatus(status)}`;
})
.join("\n");
description += `\n\nEmbed Cache: ${formatTime(new Date())}`;
let description = "Current Mojang API Status:\n";
for (const endpoint of endpoints) {
const { name, hostname, status } = endpoint;
description += `${name}: ${capitalizeFirstLetter(status.toLowerCase())}\n`;
}
return generateEmbed({
title: "Mojang Status",
@ -61,44 +77,39 @@ export async function generateMetadata(): Promise<Metadata> {
export default async function Page(): Promise<ReactElement> {
const { endpoints } = await getMojangEndpointStatus();
const endpointsSize = Object.entries(endpoints).length;
return (
<div>
<div className="text-center mb-4">
<h1 className="text-xl">Mojang Status</h1>
<p>The current status of Mojang Services</p>
<Title title="Mojang Status" subtitle="The current status of Mojang Services" />
</div>
<Card className="w-full xs:w-fit text-center">
<div>
{endpointsSize === 0 && <p>Unable to fetch endpoint statuses</p>}
{endpointsSize > 0 && (
<Table className="md:w-[500px] text-start">
<TableHeader>
<TableRow>
<TableHead className="pl-1 h-8">Service</TableHead>
<TableHead className="pl-1 h-8 text-center">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{Object.entries(endpoints).map(([url, status]) => {
<Card
classNameCard="py-0 pb-2 w-screen xs:w-[28rem] md:w-[35rem]"
classNameContent="text-left flex flex-col divide-y gap-2"
>
{endpoints.length == 0 && <p>Unable to fetch endpoint statuses</p>}
{endpoints.length > 0 &&
endpoints.map((server: CachedEndpointStatus) => {
const { name, endpoint, status } = server;
return (
<TableRow key={url}>
<TableCell className="p-[0.3rem]">
<Link className="hover:text-primary transition-all" href={url} target="_blank">
{url}
<div key={name} className="flex flex-row justify-between pt-2">
<div className="flex flex-col leading-[1.5rem]">
<p className="font-semibold">{name}</p>
<Link
href={endpoint}
className="text-sm text-primary hover:opacity-75 transition-all transform-gpu"
target="_blank"
>
<p>{endpoint}</p>
</Link>
</TableCell>
<TableCell className={cn(getColor(status), "p-[0.3rem] text-center")}>
{formatStatus(status)}
</TableCell>
</TableRow>
</div>
<div className={cn("flex items-center font-semibold", getColor(status))}>
<p>{formatStatus(status)}</p>
</div>
</div>
);
})}
</TableBody>
</Table>
)}
</div>
</Card>
</div>
);

View File

@ -4,65 +4,41 @@ import { ReactElement } from "react";
import { Button } from "../components/ui/button";
import { Separator } from "../components/ui/separator";
import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip";
import { Title } from "@/app/components/title";
import { LandingButton } from "@/app/types/landing/landing-button";
type Button = {
/**
* The title of the button.
*/
title: string;
/**
* The tooltip to display for this statistic.
*/
tooltip: string;
/**
* The URL to go to.
*/
url: string;
/**
* Whether clicking the button will
* open the link in a new tab.
*/
openInNewTab?: boolean;
};
const buttons: Button[] = [
const buttons: LandingButton[] = [
{
title: "Get Started",
tooltip: "Click to view the Postman Collection",
url: "https://www.postman.com/imfascinated/workspace/minecraft-utilities",
openInNewTab: true,
},
{
title: "Documentation",
tooltip: "Click to open the documentation",
url: "https://api.mcutils.xyz/swagger-ui.html",
openInNewTab: true,
tooltip: "Click to get started with the API",
url: "/docs",
className:
"bg-gradient-to-r from-indigo-600 to-emerald-600 px-7 hover:opacity-75 transition-all transform-gpu text-white",
},
];
export default function Home(): ReactElement {
return (
<div className="text-center flex flex-col gap-4">
<div className="p-4">
<div>
<h1 className="text-4xl mb-2">Minecraft Utilities</h1>
<div className="text-lg">
<div className="p-2">
<Title
title="Minecraft Utilities"
subtitle={
<>
<p>
Minecraft Utilities offers you many endpoints to get information about a minecraft server or a player.
</p>
<p>We offer you a simple and easy to use API.</p>
</div>
</div>
</>
}
/>
<div className="flex flex-row gap-2 justify-center mt-4">
<div className="flex flex-row gap-2 justify-center mt-4 flex-wrap">
{buttons.map((button, index) => {
return (
<Tooltip key={index}>
<TooltipTrigger asChild>
<Button key={index}>
<Button key={index} className={button.className ? button.className : ""}>
<Link href={button.url} target={button.openInNewTab ? "_blank" : ""}>
<p>{button.title}</p>
</Link>
@ -79,7 +55,7 @@ export default function Home(): ReactElement {
<Separator />
<div className="flex flex-col gap-4 items-center pt-4">
<div className="flex flex-col gap-4 items-center p-2">
<div className="text-center">
<p className="font-semibold text-xl">API Statistics</p>
<p>View the statistics for the API in real-time!</p>

View File

@ -4,28 +4,27 @@ import { ErrorCard } from "@/app/components/error-card";
import { LookupPlayer } from "@/app/components/player/lookup-player";
import { PlayerView } from "@/app/components/player/player-view";
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/app/components/ui/context-menu";
import { Colors } from "@/common/colors";
import { generateEmbed } from "@/common/embed";
import { isValidPlayer } from "@/common/player";
import { Colors } from "@/app/common/colors";
import { generateEmbed } from "@/app/common/embed";
import { isValidPlayer } from "@/app/common/player";
import config from "@root/config.json";
import { CachedPlayer, McUtilsAPIError, getPlayer } from "mcutils-library";
import { CachedPlayer, getPlayer, McUtilsAPIError } from "mcutils-library";
import { Metadata, Viewport } from "next";
import { ReactElement } from "react";
import { Title } from "@/app/components/title";
import { PlayerPageParams } from "@/app/types/player/page-params";
import { TryAPlayer } from "@/app/components/player/try-a-player";
type Params = {
params: {
id: string;
};
};
export const revalidate = 60;
export async function generateViewport({ params: { id } }: Params): Promise<Viewport> {
export async function generateViewport({ params: { id } }: PlayerPageParams): Promise<Viewport> {
const validPlayer = await isValidPlayer(id);
return {
themeColor: validPlayer ? Colors.green : Colors.red,
};
}
export async function generateMetadata({ params: { id } }: Params): Promise<Metadata> {
export async function generateMetadata({ params: { id } }: PlayerPageParams): Promise<Metadata> {
// No id provided
if (!id || id.length === 0) {
return generateEmbed({
@ -39,7 +38,7 @@ export async function generateMetadata({ params: { id } }: Params): Promise<Meta
const headPartUrl = skin.parts.head;
return generateEmbed({
title: `${username}`,
title: `${username}'s Profile`,
description: `UUID: ${uniqueId}\n\nClick to view more information about the player.`,
image: headPartUrl,
});
@ -52,7 +51,7 @@ export async function generateMetadata({ params: { id } }: Params): Promise<Meta
}
}
export default async function Page({ params: { id } }: Params): Promise<ReactElement> {
export default async function Page({ params: { id } }: PlayerPageParams): Promise<ReactElement> {
let error: string | undefined = undefined; // The error to display
let player: CachedPlayer | undefined = undefined; // The player to display
@ -66,10 +65,12 @@ export default async function Page({ params: { id } }: Params): Promise<ReactEle
return (
<div className="h-full flex flex-col items-center">
<div className="mb-4 text-center">
<h1 className="text-xl">Lookup a Player</h1>
<p>You can enter a players uuid or username to get information about the player.</p>
<Title
title="Player Lookup"
subtitle="You can enter a players uuid or username to get information about the player."
/>
<LookupPlayer currentPlayer={id[0]} />
<LookupPlayer currentPlayer={id && id[0]} />
</div>
{error && <ErrorCard message={error} />}
@ -87,12 +88,15 @@ export default async function Page({ params: { id } }: Params): Promise<ReactEle
<ContextMenuItem>Copy Player UUID</ContextMenuItem>
</CopyButton>
<CopyButton content={`${config.siteUrl}/player/${id}`}>
<CopyButton content={`${config.publicUrl}/player/${id}`}>
<ContextMenuItem>Copy Share URL</ContextMenuItem>
</CopyButton>
</ContextMenuContent>
</ContextMenu>
)}
{/* Try a Player */}
{player == null && !error && <TryAPlayer />}
</div>
);
}

View File

@ -3,27 +3,25 @@ import { ErrorCard } from "@/app/components/error-card";
import { LookupServer } from "@/app/components/server/lookup-server";
import { ServerView } from "@/app/components/server/server-view";
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/app/components/ui/context-menu";
import { Colors } from "@/common/colors";
import { generateEmbed } from "@/common/embed";
import { isValidServer } from "@/common/server";
import { capitalizeFirstLetter } from "@/common/string-utils";
import { Colors } from "@/app/common/colors";
import { generateEmbed } from "@/app/common/embed";
import { isValidServer } from "@/app/common/server";
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import config from "@root/config.json";
import {
CachedBedrockMinecraftServer,
CachedJavaMinecraftServer,
getServer,
McUtilsAPIError,
ServerPlatform,
getServer,
} from "mcutils-library";
import { Metadata, Viewport } from "next";
import { ReactElement } from "react";
import { Title } from "@/app/components/title";
import { ServerPageParams } from "@/app/types/server/page-params";
import { TryAServer } from "@/app/components/server/try-a-server";
type Params = {
params: {
platform: ServerPlatform;
hostname: string;
};
};
export const revalidate = 60;
/**
* Gets the favicon for a server
@ -37,7 +35,7 @@ function getFavicon(
server: CachedJavaMinecraftServer | CachedBedrockMinecraftServer | undefined,
): string | undefined {
if (server == null || platform === ServerPlatform.Bedrock) {
return config.apiUrl + "/server/icon/fallback";
return config.apiEndpoint + "/server/icon/fallback";
}
server = server as CachedJavaMinecraftServer;
return server.favicon && server.favicon.url;
@ -53,14 +51,14 @@ function checkPlatform(platform: ServerPlatform): boolean {
return platform === ServerPlatform.Java || platform === ServerPlatform.Bedrock;
}
export async function generateViewport({ params: { platform, hostname } }: Params): Promise<Viewport> {
export async function generateViewport({ params: { platform, hostname } }: ServerPageParams): Promise<Viewport> {
const validPlayer = await isValidServer(platform, hostname);
return {
themeColor: validPlayer ? Colors.green : Colors.red,
themeColor: validPlayer || !platform ? Colors.green : Colors.red,
};
}
export async function generateMetadata({ params: { platform, hostname } }: Params): Promise<Metadata> {
export async function generateMetadata({ params: { platform, hostname } }: ServerPageParams): Promise<Metadata> {
if (!checkPlatform(platform)) {
// Invalid platform
return generateEmbed({
@ -78,18 +76,14 @@ export async function generateMetadata({ params: { platform, hostname } }: Param
try {
const server = await getServer(platform, hostname);
const { hostname: serverHostname, players } = server as CachedJavaMinecraftServer | CachedBedrockMinecraftServer;
const favicon = getFavicon(platform, server);
let description = `Hostname: ${serverHostname}\n`;
description += `${players.online}/${players.max} players online\n\n`;
description += "Click to view more information about the server.";
const { hostname: serverHostname } = server;
return generateEmbed({
title: `${hostname}`,
description: description,
image: favicon,
title: `${serverHostname} ${capitalizeFirstLetter(platform)} Server`,
embedTitle: `${capitalizeFirstLetter(platform)} Server: ${serverHostname}`,
description: "Click to view more information about the server.",
image: `${config.apiEndpoint}/server/${platform}/preview/${serverHostname}`,
cardType: "summary_large_image",
});
} catch (err) {
// An error occurred
@ -100,7 +94,7 @@ export async function generateMetadata({ params: { platform, hostname } }: Param
}
}
export default async function Page({ params: { platform, hostname } }: Params): Promise<ReactElement> {
export default async function Page({ params: { platform, hostname } }: ServerPageParams): Promise<ReactElement> {
let error: string | undefined = undefined; // The error to display
let server: CachedJavaMinecraftServer | CachedBedrockMinecraftServer | undefined = undefined; // The server to display
let invalidPlatform: boolean = !checkPlatform(platform); // Whether the platform is invalid
@ -121,16 +115,21 @@ export default async function Page({ params: { platform, hostname } }: Params):
return (
<div className="h-full flex flex-col items-center">
<div className="mb-4 text-center">
<h1 className="text-xl">Lookup a {invalidPlatform ? "" : capitalizeFirstLetter(platform)} Server</h1>
<p>You can enter a server hostname to get information about the server.</p>
<Title
title={`Lookup a ${invalidPlatform ? "" : capitalizeFirstLetter(platform)} Server`}
subtitle="You can enter a server hostname to get information about the server."
/>
<LookupServer currentPlatform={platform.toLowerCase()} currentServer={hostname[0]} />
<LookupServer currentPlatform={platform.toLowerCase()} currentServer={hostname && hostname[0]} />
</div>
{/* An errored occurred when looking up the server */}
{error && <ErrorCard message={error} />}
{/* The server */}
{server != null && (
<ContextMenu>
<ContextMenuTrigger>
<ContextMenuTrigger asChild>
<ServerView server={server} favicon={favicon} />
</ContextMenuTrigger>
<ContextMenuContent className="flex flex-col">
@ -145,6 +144,9 @@ export default async function Page({ params: { platform, hostname } }: Params):
</ContextMenuContent>
</ContextMenu>
)}
{/* Try a Server */}
{server == null && !error && <TryAServer />}
</div>
);
}

View File

@ -4,4 +4,5 @@
export const Colors = {
green: "#0FFF50",
red: "#8B0000",
yellow: "#FFD700",
};

View File

@ -0,0 +1,194 @@
import * as fs from "node:fs";
import path from "node:path";
import Fuse from "fuse.js";
/**
* Metadata for documentation content.
*/
export type DocsContentMetadata = MDXMetadata & {
/**
* The title of this content.
*/
title: string;
/**
* The summary of this content.
*/
summary: string;
};
/**
* Metadata for an MDX file.
*/
type MDXMetadata = {
/**
* The slug of the file, defined once read.
*/
slug: string;
/**
* The metadata of the file.
*/
metadata: {
[key: string]: string;
};
/**
* The content of the file.
*/
content: string;
};
/**
* The regex to match for metadata.
*/
const METADATA_REGEX: RegExp = /---\s*([\s\S]*?)\s*---/;
/**
* The directory of the documentation.
*/
const docsDir = path.join(process.cwd(), "documentation");
/**
* The cached documentation content.
*/
const cachedDocs: DocsContentMetadata[] = getDocsContent();
/**
* The fuse index for searching
*/
const fuseIndex: Fuse<DocsContentMetadata> = new Fuse(cachedDocs, {
keys: ["title", "summary"],
includeScore: true,
threshold: 0.4,
});
/**
* Get the directories in the
* given directory.
*/
function getDocsDirectories(dir: string): string[] {
const directories: string[] = [dir];
const paths: string[] = fs.readdirSync(dir);
for (const sub of paths) {
const subPath: string = path.join(dir, sub);
const stat: fs.Stats = fs.statSync(subPath);
if (stat.isDirectory()) {
directories.push(...getDocsDirectories(subPath));
}
}
return directories;
}
/**
* Get the content to
* display in the docs.
*/
export function getDocsContent(): DocsContentMetadata[] {
const directories: string[] = getDocsDirectories(docsDir);
const page: DocsContentMetadata[] = [];
for (let directory of directories) {
page.push(...getMetadata<DocsContentMetadata>(directory));
}
return page;
}
/**
* Get the content of the
* documentation page.
*
* @param path the path to the content
*/
export function getDocContent(path?: string[]): DocsContentMetadata | undefined {
const slug: string = path ? path.join("/") : "home";
return process.env.NODE_ENV === "development"
? getDocsContent().find((doc: DocsContentMetadata) => doc.slug === slug)
: cachedDocs.find((doc: DocsContentMetadata) => doc.slug === slug);
}
/**
* Search the documentation
* for the given query.
*
* @param query the query to search
* @param limit the maximum number of results
*/
export function searchDocs(
query?: string,
limit?: number,
): {
title: string;
summary: string;
slug: string;
}[] {
if (!limit) {
limit = 5; // Default to 5 results
}
return fuseIndex.search(query || "", { limit }).map(result => {
return {
title: result.item.title,
summary: result.item.summary,
slug: result.item.slug,
};
});
}
/**
* Get the metadata of mdx
* files in the given directory.
*
* @param directory the directory to search
*/
export function getMetadata<T extends MDXMetadata>(directory: string): T[] {
const files: string[] = fs.readdirSync(directory).filter((file: string): boolean => {
const extension: string = path.extname(file); // The file extension
return extension === ".md" || extension === ".mdx";
}); // Read the MDX files
return files.map((file: string): T => {
const filePath: string = path.join(directory, file); // The path of the file
return {
...parseMetadata<T>(fs.readFileSync(filePath, "utf-8")),
slug: filePath
.replace(docsDir, "")
.replace(/\.mdx?$/, "")
.replace(/\\/g, "/")
.substring(1),
}; // Map each file to its metadata
});
}
/**
* Parse the metadata from
* the given content.
*
* @param content the content to parse
* @returns the metadata and content
* @template T the type of metadata
*/
function parseMetadata<T extends MDXMetadata>(content: string): T {
const metadataBlock: string = METADATA_REGEX.exec(content)![1]; // Get the block of metadata
content = content.replace(METADATA_REGEX, "").trim(); // Remove the metadata block from the content
let metadata: Partial<{
[key: string]: string;
}> = {}; // The metadata to return
// Parse the metadata block as a key-value pair
metadataBlock
.trim() // Trim any leading or trailing whitespace
.split("\n") // Get each line
.forEach((line: string): void => {
const split: string[] = line.split(": "); // Split the metadata by the colon
let value: string = split[1].trim(); // The value of the metadata
value = value.replace(/^['"](.*)['"]$/, "$1"); // Remove quotes
metadata[split[0].trim()] = value; // Add the metadata to the object
});
// Return the metadata and content. The initial
// slug is empty, and is defined later on.
return { ...metadata, content } as T;
}

View File

@ -6,6 +6,11 @@ type Embed = {
*/
title: string;
/**
* The title of the embed.
*/
embedTitle?: string;
/**
* The description of the embed.
*/
@ -15,16 +20,32 @@ type Embed = {
* The image to show as the thumbmail.
*/
image?: string;
/**
* The type of the card.
*/
cardType?: "summary" | "summary_large_image";
};
/**
* Generates metadata for a embed.
*
* @param title the title of the embed
* @param embedTitle the title of the embed
* @param description the description of the embed
* @param image the image to show as the thumbmail
* @param cardType the type of the card
* @returns the metadata for the embed
*/
export function generateEmbed({ title, description, image }: Embed): Metadata {
export function generateEmbed({ title, embedTitle, description, image, cardType }: Embed): Metadata {
// Fall back to the title
if (!embedTitle) {
embedTitle = title;
}
if (!cardType) {
cardType = "summary";
}
const metadata: Metadata = {
title: `${title}`,
openGraph: {
@ -32,7 +53,7 @@ export function generateEmbed({ title, description, image }: Embed): Metadata {
description: description,
},
twitter: {
card: "summary",
card: cardType,
},
};

View File

@ -0,0 +1,17 @@
const PASTE_URL: string = "https://paste.fascinated.cc";
/**
* Creates a new haste with the given content.
*
* @param content the content to create the haste with
* @returns the URL of the created haste
*/
export async function createHaste(content: string): Promise<string> {
const response = await fetch(`${PASTE_URL}/api/upload`, {
method: "POST",
body: content,
});
const { id } = await response.json();
return `${PASTE_URL}/${id}`;
}

View File

@ -11,6 +11,6 @@ export async function isValidPlayer(id: string): Promise<boolean> {
await getPlayer(id);
return true;
} catch {
return true;
return false;
}
}

View File

@ -1,4 +1,4 @@
import { ServerPlatform, getServer } from "mcutils-library";
import { getServer, ServerPlatform } from "mcutils-library";
/**
* Checks if the server is valid.
@ -12,6 +12,6 @@ export async function isValidServer(platform: ServerPlatform, query: string): Pr
await getServer(platform, query);
return true;
} catch {
return true;
return false;
}
}

View File

@ -1,78 +1,75 @@
"use client"
"use client";
// Inspired by react-hot-toast library
import * as React from "react"
import * as React from "react";
import type {
ToastActionElement,
ToastProps,
} from "@/app/components/ui/toast"
import type { ToastActionElement, ToastProps } from "@/app/components/ui/toast";
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
id: string;
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
};
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
} as const;
let count = 0
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString();
}
type ActionType = typeof actionTypes
type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
interface State {
toasts: ToasterToast[]
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
toastTimeouts.delete(toastId);
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout)
}
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
@ -80,77 +77,75 @@ export const reducer = (state: State, action: Action): State => {
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
};
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
toasts: state.toasts.map(t => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
};
case "DISMISS_TOAST": {
const { toastId } = action
const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
state.toasts.forEach(toast => {
addToRemoveQueue(toast.id);
});
}
return {
...state,
toasts: state.toasts.map((t) =>
toasts: state.toasts.map(t =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
: t,
),
}
};
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
toasts: state.toasts.filter(t => t.id !== action.toastId),
};
}
}
}
};
const listeners: Array<(state: State) => void> = []
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] }
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
memoryState = reducer(memoryState, action);
listeners.forEach(listener => {
listener(memoryState);
});
}
type Toast = Omit<ToasterToast, "id">
type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) {
const id = genId()
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
});
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({
type: "ADD_TOAST",
@ -158,37 +153,37 @@ function toast({ ...props }: Toast) {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
onOpenChange: open => {
if (!open) dismiss();
},
},
})
});
return {
id: id,
dismiss,
update,
}
};
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => {
listeners.push(setState)
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState)
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1)
listeners.splice(index, 1);
}
}
}, [state])
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
};
}
export { useToast, toast }
export { useToast, toast };

View File

@ -1,4 +1,4 @@
import { clsx, type ClassValue } from "clsx";
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
/**

View File

@ -0,0 +1,24 @@
import { Cache } from "mcutils-library";
import React, { ReactElement, ReactNode } from "react";
import { Popover, PopoverContent, PopoverTrigger } from "@/app/components/ui/popover";
import moment from "moment";
type CacheInformationProps = {
cache: Cache;
children?: ReactNode;
};
export function CacheInformation({ cache, children }: CacheInformationProps): ReactElement {
const isCached = cache.cached;
const cacheTime = cache.cachedTime;
return (
<Popover>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent>
<p className={isCached ? "text-green-400" : "text-red-400"}>{isCached ? "Cached" : "Not Cached"}</p>
{cacheTime !== -1 && <p>{moment(cacheTime).calendar()}</p>}
</PopoverContent>
</Popover>
);
}

View File

@ -1,5 +1,7 @@
import { cn } from "@/common/utils";
import { Card as ShadcnCard, CardContent } from "@/app/components/ui/card";
import { cn } from "@/app/common/utils";
import { ReactElement } from "react";
import { type ClassValue } from "clsx";
type CardProps = {
/**
@ -8,11 +10,20 @@ type CardProps = {
children: React.ReactNode;
/**
* The class names to append.
* The class names for the card.
*/
className?: string;
classNameCard?: ClassValue;
/**
* The class names for the content.
*/
classNameContent?: ClassValue;
};
export function Card({ children, className }: CardProps): ReactElement {
return <div className={cn("bg-secondary rounded-lg p-3", className)}>{children}</div>;
export function Card({ children, classNameCard, classNameContent }: CardProps): ReactElement {
return (
<ShadcnCard className={cn("p-1.5", classNameCard)}>
<CardContent className={cn("p-1.5", classNameContent)}>{children}</CardContent>
</ShadcnCard>
);
}

View File

@ -1,7 +1,15 @@
import { ReactElement } from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import { atelierSeasideDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
import { CodeHighlighter } from "./code-highlighter";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./ui/dialog";
import { CreateHasteButton } from "@/app/components/create-haste-button";
type CodeDialogProps = {
/**
@ -29,20 +37,15 @@ export function CodeDialog({ title, description, code, children }: CodeDialogPro
return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="max-h-[700px]">
<DialogContent className="text-sm">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<SyntaxHighlighter
language="javascript"
style={atelierSeasideDark}
customStyle={{
maxHeight: "600px",
}}
>
{code}
</SyntaxHighlighter>
<CodeHighlighter code={code} />
<DialogFooter>
<CreateHasteButton content={code} />
</DialogFooter>
</DialogContent>
</Dialog>
);

View File

@ -0,0 +1,92 @@
import { ReactElement } from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import createElement from "react-syntax-highlighter/dist/esm/create-element";
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
import { cn } from "@/app/common/utils";
import { capitalizeFirstLetter } from "@/app/common/string-utils";
type CodeHighlighterProps = {
/**
* The code to highlight.
*/
code: string;
/**
* The language of the code.
*/
language?: string;
/**
* Should the element be rounded?
*/
rounded?: boolean;
};
/**
* Render the rows with the ability to render links.
*
* @param rows the rows to render
* @param stylesheet the stylesheet to use
* @param useInlineStyles should inline styles be used
* @returns the rendered rows
*/
function rowRenderer({
rows,
stylesheet,
useInlineStyles,
}: {
rows: any;
stylesheet: { [key: string]: React.CSSProperties };
useInlineStyles: boolean;
}) {
return rows.map((node: any, i: number) => {
node.children = node.children.map((children: any) => {
const text = children?.children?.[0]?.value;
if (typeof text === "string" && text.startsWith('"http')) {
return {
...children,
tagName: "a",
properties: {
...children.properties,
href: text.slice(1, -1), // in JSON strings are enclosed with ", they need to be removed
target: "_blank",
// Tailwind CSS classes
class: "underline !text-primary hover:!text-muted-foreground transition-all",
},
};
}
return children;
});
return createElement({
node,
stylesheet,
useInlineStyles,
key: `code-segement${i}`,
});
});
}
export function CodeHighlighter({ code, language = "json", rounded = true }: CodeHighlighterProps): ReactElement {
return (
<div className="text-xs md:text-md relative">
{/* Language */}
<div className="absolute top-0 right-0 p-1 bg-muted rounded-bl-md rounded-tr-md">
<span className="text-xs text-muted-foreground">{capitalizeFirstLetter(language)}</span>
</div>
{/* Code */}
<SyntaxHighlighter
className={cn("max-h-[600px] !bg-secondary break-all rounded-md", rounded && "rounded-md")}
language={language}
style={atomOneDark}
wrapLongLines
renderer={rowRenderer}
>
{code}
</SyntaxHighlighter>
</div>
);
}

View File

@ -0,0 +1,117 @@
"use client";
import React, { ReactElement, useState } from "react";
import { DocsContentMetadata } from "@/app/common/documentation";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/app/components/ui/command";
import { Button, ButtonProps } from "@/app/components/ui/button";
import { useRouter } from "next/navigation";
import { cn } from "@/app/common/utils";
export function CommandMenu({ ...props }: ButtonProps): ReactElement {
const router = useRouter();
/**
* Whether to show the search
*/
const [open, setOpen] = useState<boolean>(false);
/**
* The pages that were found
*/
const [pages, setPages] = useState<DocsContentMetadata[] | undefined>(undefined);
// Handle keyboard shortcuts
React.useEffect(() => {
const down = (e: KeyboardEvent) => {
if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || e.key === "/") {
if (
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLSelectElement
) {
return;
}
e.preventDefault();
setOpen(open => !open);
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
/**
* Search the documentation
* for the given query.
*
* @param query the query to search for
*/
async function searchDocs(query: string): Promise<void> {
// Don't bother searching if the query is less than 3 characters
if (query.length < 3) {
setPages(undefined);
return;
}
// Attempt to search for the query
const response = await fetch(`/api/docs/search?query=${query}`);
const pages: DocsContentMetadata[] = await response.json();
setPages(pages);
}
return (
<>
<Button
variant="outline"
className={cn(
"relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-muted-foreground shadow-none sm:pr-12 md:w-40 lg:w-64",
props.className,
)}
onClick={() => setOpen(true)}
>
Search
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput
placeholder="Query..."
onValueChange={async search => {
await searchDocs(search);
}}
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{pages && pages.length > 1 && (
<CommandGroup heading="Suggestions">
{pages.map(page => {
return (
<CommandItem
key={page.slug}
onSelect={() => {
router.push(`/docs/${page.slug}`); // Go to the page
setOpen(false); // Close the dialog
setPages(undefined); // Clear the pages
}}
className="flex flex-col items-start gap-1"
>
<p className="font-semibold text-primary">{page.title}</p>
<p>{page.summary}</p>
</CommandItem>
);
})}
</CommandGroup>
)}
</CommandList>
</CommandDialog>
</>
);
}

View File

@ -10,9 +10,9 @@ type ContainerProps = {
export default function Container({ children }: ContainerProps): ReactElement {
return (
<div className="z-[9999] m-auto flex h-screen min-h-full flex-col items-center opacity-90 w-full xs:max-w-[1200px]">
<div className="z-[9999] m-auto flex h-screen flex-col items-center opacity-90 w-full xs:max-w-[1200px]">
<NavBar />
<div className="w-full flex mt-4 justify-center">{children}</div>
<div className="w-full flex mt-4 justify-center h-full">{children}</div>
</div>
);
}

View File

@ -1,6 +1,6 @@
"use client";
import { useToast } from "@/common/use-toast";
import { useToast } from "@/app/common/use-toast";
import copy from "clipboard-copy";
import { ReactElement } from "react";
@ -10,6 +10,11 @@ type CopyButtonProps = {
*/
content: string;
/**
* The message to display when the content is copied.
*/
message?: string | boolean;
/**
* The children for this element.
*/
@ -22,7 +27,7 @@ type CopyButtonProps = {
* @param props the properties for the button
* @returns the copy button
*/
export function CopyButton({ content, children }: CopyButtonProps): ReactElement {
export function CopyButton({ content, message, children }: CopyButtonProps): ReactElement {
const { toast } = useToast();
return (
@ -33,7 +38,7 @@ export function CopyButton({ content, children }: CopyButtonProps): ReactElement
title: "Copied!",
description: (
<p>
Copied <b>{content}</b> to your clipboard.
Copied <b>{!message ? content : message}</b> to your clipboard.
</p>
),
duration: 5000,

View File

@ -0,0 +1,21 @@
"use client";
import { ReactElement } from "react";
import { Button } from "@/app/components/ui/button";
import { CreateHasteButtonProps } from "@/app/types/create-haste-button";
import { createHaste } from "@/app/common/hastebin";
export function CreateHasteButton({ content }: CreateHasteButtonProps): ReactElement {
/**
* Uploads the content to Haste and opens the URL in a new tab.
*/
async function upload(): Promise<void> {
const url = await createHaste(content);
console.log(url);
// Open the URL in a new tab.
window.open(url, "_blank", "noopener,noreferrer");
}
return <Button onClick={() => upload()}>Create Haste</Button>;
}

View File

@ -0,0 +1,48 @@
import { ReactElement } from "react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from "@/app/components/ui/breadcrumb";
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import { DocsContentMetadata } from "@/app/common/documentation";
type DocsBreadcrumbProps = {
/**
* The page to render the breadcrumb for.
*/
page: DocsContentMetadata;
};
export function DocsBreadcrumb({ page }: DocsBreadcrumbProps): ReactElement {
const slugParts: string[] = page.slug.split("/");
const isHome: boolean = slugParts.length == 1;
return (
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href={`/docs`}>Home</BreadcrumbLink>
</BreadcrumbItem>
{!isHome &&
slugParts.map((slug, index, array) => {
const path: string = array.slice(0, index + 1).join("/");
const name: string = slug.includes("-")
? slug.split("-").map(capitalizeFirstLetter).join(" ")
: capitalizeFirstLetter(slug);
return (
<div key={slug} className="flex items-center ">
<BreadcrumbSeparator className="pr-1.5" />
<BreadcrumbItem>
<BreadcrumbLink href={`/docs/${path}`}>{capitalizeFirstLetter(name)}</BreadcrumbLink>
</BreadcrumbItem>
</div>
);
})}
</BreadcrumbList>
</Breadcrumb>
);
}

View File

@ -0,0 +1,22 @@
import { DocsContentMetadata } from "@/app/common/documentation";
import { ReactElement } from "react";
import Image from "next/image";
import Link from "next/link";
type GithubLink = {
/**
* The page to link to.
*/
page: DocsContentMetadata;
};
export function GithubLink({ page }: GithubLink): ReactElement {
return (
<Link
href={`https://git.fascinated.cc/MinecraftUtilities/Frontend/src/branch/master/documentation/${page.slug}.md`}
target="_blank"
>
<Image src="/media/github.png" alt="The GitHub logo" width={32} height={32} className="filter dark:invert" />
</Link>
);
}

View File

@ -0,0 +1,42 @@
"use client";
import { ReactElement, useEffect, useState } from "react";
import { Star } from "lucide-react";
import Link from "next/link";
import { cn } from "@/app/common/utils";
type GithubStarProps = {
/**
* The class name for this component.
*/
className?: string;
};
export function GithubStar({ className }: GithubStarProps): ReactElement {
const [starCount, setStarCount] = useState(0);
const getStarCount = async () => {
const res = await fetch("https://api.github.com/repos/RealFascinated/MinecraftUtilities");
const data = await res.json();
return data.stargazers_count;
};
useEffect(() => {
getStarCount().then(setStarCount);
}, []);
return (
<Link
className={cn(
"bg-github-green px-2 py-1 rounded-lg items-center gap-1 hover:opacity-85 transform-gpu transition-all hidden md:flex",
className,
)}
href="https://github.com/RealFascinated/MinecraftUtilities"
target="_blank"
>
<p className="text-white text-sm bg-github-green-accent py-[3px] px-[4px] rounded-lg leading-none">{starCount}</p>
<Star size={18} />
<p className="text-white text-sm leading-none">Star us!</p>
</Link>
);
}

View File

@ -1,3 +1,4 @@
import { cn } from "@/app/common/utils";
import Link from "next/link";
import { ReactElement } from "react";
@ -17,11 +18,16 @@ type ButtonProps = {
* open the link in a new tab.
*/
openInNewTab?: boolean;
/**
* The class names for the button.
*/
className?: string;
};
export function RedirectButton({ title, url, openInNewTab }: ButtonProps): ReactElement {
export function HrefButton({ title, url, openInNewTab, className }: ButtonProps): ReactElement {
return (
<div className="w-fit rounded-lg">
<div className={cn("w-fit", className)}>
<Link href={url} target={openInNewTab ? "_blank" : ""}>
<p className="hover:text-primary transition-all">{title}</p>
</Link>

View File

@ -11,7 +11,7 @@ type LogoProps = {
export default function Logo({ size = 30 }: LogoProps): ReactElement {
return (
<Image
src="https://git.fascinated.cc/MinecraftUtilities/Assets/raw/branch/master/logo.png"
src={`/media/logo.png`}
alt={"The Logo"}
width={size}
height={size}

View File

@ -0,0 +1,44 @@
import { MDXRemote } from "remote-mdx/rsc";
import { ReactElement } from "react";
import {
formatCode,
formatHeading,
formatLink,
formatList,
formatTable,
formatTableData,
formatTableHeader,
} from "@/app/components/mdx-renderer";
import remarkGfm from "remark-gfm";
/**
* The components to use in the MDX renderer.
*/
const components = {
h1: (props: any) => formatHeading(1, props),
h2: (props: any) => formatHeading(2, props),
h3: (props: any) => formatHeading(3, props),
h4: (props: any) => formatHeading(4, props),
h5: (props: any) => formatHeading(5, props),
h6: (props: any) => formatHeading(6, props),
code: (props: any) => formatCode(props),
ul: (props: any) => formatList(props),
a: (props: any) => formatLink(props),
table: (props: any) => formatTable(props),
th: (props: any) => formatTableHeader(props),
td: (props: any) => formatTableData(props),
};
export function CustomMDX(props: any): ReactElement {
return (
<MDXRemote
{...props}
components={{ ...components, ...(props.components || {}) }}
options={{
mdxOptions: {
remarkPlugins: [remarkGfm],
},
}}
/>
);
}

View File

@ -0,0 +1,90 @@
import { CodeHighlighter } from "@/app/components/code-highlighter";
import { Separator } from "@/app/components/ui/separator";
import Link from "next/link";
import { ReactElement } from "react";
/**
* Create a heading component.
*
* @param level The level of the heading.
* @param props The props to pass to the heading.
*/
export function formatHeading(level: number, props: any): ReactElement {
const Tag = `h${level}`;
const paddingBottom = level > 1 ? "pt-6" : "";
const textSize = 4 - level;
return (
<div className={`pb-4 ${paddingBottom}`}>
<Tag className={`text-${textSize}xl font-semibold pb-2`} {...props} />
<Separator />
</div>
);
}
/**
* Format a code block.
*
* @param props The props to pass to the code block.
*/
export function formatCode(props: any): ReactElement {
if (!props.className) {
return <code className="text-xs bg-secondary p-1 rounded-md leading-none" {...props} />;
}
const language = props.className.replace("language-", "");
return (
<div className="pt-4">
<CodeHighlighter language={props.className ? language : undefined} code={props.children} />
</div>
);
}
/**
* Format a list.
*
* @param props The props to pass to the list.
*/
export function formatList(props: any): ReactElement {
return <ul className="list-disc pl-4 ml-2 pt-2">{props.children}</ul>;
}
/**
* Format a link.
*
* @param props The props to pass to the link.
*/
export function formatLink(props: any): ReactElement {
return (
<Link href={props.href} className="text-primary hover:opacity-85 transition-all transform-gpu">
{props.children}
</Link>
);
}
/**
* Format a table.
*
* @param props The props to pass to the table.
*/
export function formatTable(props: any): ReactElement {
return <table className="table-auto divide-y divide-gray-200 mt-4">{props.children}</table>;
}
/**
* Format table header.
*
* @param props The props to pass to the table header.
*/
export function formatTableHeader(props: any): ReactElement {
return <th className="border-border border p-1.5">{props.children}</th>;
}
/**
* Format table data.
*
* @param props The props to pass to the table data.
*/
export function formatTableData(props: any): ReactElement {
return <td className="border-border border p-1.5">{props.children}</td>;
}

View File

@ -1,8 +1,15 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ReactElement } from "react";
import { HrefButton } from "./href-button";
import Logo from "./logo";
import { RedirectButton } from "./rediect-button";
import { ToggleThemeButton } from "./theme-toggle-button";
import { GithubStar } from "@/app/components/github-star";
import { Card } from "@/app/components/card";
import { cn } from "@/app/common/utils";
import { CommandMenu } from "@/app/components/command-menu";
type Page = {
/**
@ -15,6 +22,11 @@ type Page = {
*/
url: string;
/**
* Whether to hide the button on mobile.
*/
hideOnMobile?: boolean;
/**
* Whether clicking the button will
* open the link in a new tab.
@ -26,37 +38,53 @@ const pages: Page[] = [
{ name: "Player", url: "/player" },
{ name: "Server", url: "/server/java" },
{ name: "Mojang", url: "/mojang/status" },
{ name: "API", url: "https://api.mcutils.xyz", openInNewTab: true },
{ name: "API", url: "https://api.mcutils.xyz", hideOnMobile: true, openInNewTab: true },
{ name: "Docs", url: "/docs" },
];
export default function NavBar(): ReactElement {
const path: string = usePathname();
const isDocs: boolean = path ? path.includes("/docs") : false;
return (
<div className="bg-secondary w-full rounded-lg flex items-center gap-3 mt-2 bg-opacity-85 h-12">
<Link href="/" className="flex items-center gap-2 pl-3 pr-1">
<Card
classNameCard="w-full p-0 mt-2 border-none"
classNameContent="p-0 relative rounded-lg flex justify-between items-center gap-3 px-3 bg-opacity-85 h-12"
>
{/* Left */}
<div className={cn("flex flex-row items-center gap-2 z-50", isDocs ? "w-full md:w-fit" : "w-fit")}>
<Link href="/" className="flex items-center gap-2">
<Logo />
<p className="hidden md:block">Minecraft Utilities</p>
</Link>
<div className="flex-grow" />
{/* Command Menu */}
<CommandMenu className={cn(isDocs ? "" : "hidden md:inline-flex")} />
</div>
{/* Links */}
<div className={cn("absolute inset-x-0 justify-center", isDocs ? "hidden md:flex" : "flex")}>
<div className="flex gap-4">
{pages.map((page, index) => {
return <RedirectButton key={index} title={page.name} url={page.url} openInNewTab={page.openInNewTab} />;
const isActive: boolean = path ? path.includes(page.url) : false;
return (
<HrefButton
className={cn(isActive ? "text-primary" : "", page.hideOnMobile ? "hidden md:block" : "")}
key={index}
title={page.name}
url={page.url}
openInNewTab={page.openInNewTab}
/>
);
})}
</div>
<div className="flex-grow" />
<div className="mr-4 flex items-center gap-2">
<div className="hidden md:block">
<RedirectButton
title="Star us on Github!"
url="https://github.com/RealFascinated/minecraft-helper"
openInNewTab
/>
</div>
{/* Right */}
<div className="flex gap-4 items-center z-50">
<ToggleThemeButton />
<GithubStar className={isDocs ? "hidden md:flex" : "hidden"} />
</div>
</div>
</Card>
);
}

View File

@ -1,6 +1,6 @@
"use client";
import { useToast } from "@/common/use-toast";
import { useToast } from "@/app/common/use-toast";
import { getPlayer } from "mcutils-library";
import { useRouter } from "next/navigation";
import { ReactElement, useState } from "react";
@ -17,7 +17,6 @@ type PlayerLookupProps = {
};
export function LookupPlayer({ currentPlayer }: PlayerLookupProps): ReactElement {
console.log(currentPlayer);
const router = useRouter();
const { toast } = useToast();
const [loading, setLoading] = useState<boolean>(false);
@ -25,7 +24,6 @@ export function LookupPlayer({ currentPlayer }: PlayerLookupProps): ReactElement
/**
* Lookup a server based on the platform
*
* @param platform the server platform
* @param query the query to lookup
*/
const lookupPlayer = async (query: string) => {
@ -34,7 +32,7 @@ export function LookupPlayer({ currentPlayer }: PlayerLookupProps): ReactElement
}
// Ignore the same player
if (query.toLowerCase() == currentPlayer.toLowerCase()) {
if (currentPlayer !== undefined && query.toLowerCase() == currentPlayer.toLowerCase()) {
return;
}
@ -50,13 +48,14 @@ export function LookupPlayer({ currentPlayer }: PlayerLookupProps): ReactElement
description: (err as Error).message,
duration: 5000,
});
return;
setLoading(false);
}
};
return (
<form
className="flex flex-col gap-2 justify-center items-center mt-4 flex-wrap"
autoComplete="off"
action={(form: FormData) => {
lookupPlayer(form.get("query") as string);
}}

View File

@ -0,0 +1,27 @@
import { CachedPlayer, SkinPart } from "mcutils-library";
import { ReactElement } from "react";
import { SkinPartImage } from "@/app/components/player/skin-part-image";
type PlayerSkinProps = {
/**
* The player to get the skin from.
*/
player: CachedPlayer;
};
export function PlayerSkin({ player }: PlayerSkinProps): ReactElement {
const skin = player.skin;
return (
<div className="flex flex-col gap-2">
<p className="text-lg">Skin Parts</p>
<div className="flex gap-2">
{Object.entries(skin.parts)
.filter(([part]) => part !== SkinPart.HEAD) // Don't show the head part again
.map(([part, url]) => {
return <SkinPartImage key={part} playerName={player.username} part={part as SkinPart} url={url} />;
})}
</div>
</div>
);
}

View File

@ -1,12 +1,14 @@
/* eslint-disable @next/next/no-img-element */
import { CachedPlayer, SkinPart } from "mcutils-library";
import Image from "next/image";
import Link from "next/link";
import { ReactElement } from "react";
import { Card } from "../card";
import { CodeDialog } from "../code-dialog";
import { Button } from "../ui/button";
import { Separator } from "../ui/separator";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { SkinPartImage } from "./skin-part-image";
import { CacheInformation } from "@/app/components/cache-information";
import { PlayerSkin } from "@/app/components/player/player-skin";
import { ReloadPageButton } from "@/app/components/reload-page-button";
type PlayerViewProps = {
/**
@ -17,29 +19,11 @@ type PlayerViewProps = {
export function PlayerView({ player }: PlayerViewProps): ReactElement {
return (
<Card className="w-max xs:w-fit">
<div className="flex flex-col gap-2 items-center">
<Card classNameCard="w-screen xs:w-fit">
<div className="flex gap-4 flex-col xs:flex-row relative">
<div className="absolute 8xl:top-0 xs:bottom-0">
<CodeDialog
title="Player Data"
description="The player's data from the API"
code={JSON.stringify(player, undefined, 2)}
>
<button className="bg-background rounded-lg">
<p className="p-1">JSON</p>
</button>
</CodeDialog>
</div>
<div className="flex justify-center xs:justify-start">
<Image
className="w-[96px] h-[96px]"
src={player.skin.parts.head}
width={96}
height={96}
quality={100}
alt="The player's skin"
/>
<div className="flex items-center flex-col">
<SkinPartImage playerName={player.username} part={SkinPart.HEAD} url={player.skin.parts.head} size={96} />
</div>
<div className="flex flex-col gap-2">
@ -50,31 +34,23 @@ export function PlayerView({ player }: PlayerViewProps): ReactElement {
<Separator />
<div className="flex flex-col gap-2">
<p className="text-lg">Skin Parts</p>
<div className="flex gap-2">
{Object.entries(player.skin.parts)
.filter(([part]) => part !== SkinPart.HEAD) // Don't show the head part again
.map(([part, url]) => {
return (
<Tooltip key={part}>
<TooltipTrigger>
<Link href={url} target="_blank">
<img className="h-[64px]" src={url} alt={`The player's ${part}`} loading="lazy" />
</Link>
</TooltipTrigger>
<TooltipContent>
<p>
Click to view {player.username}&apos;s {part}
</p>
</TooltipContent>
</Tooltip>
);
})}
</div>
</div>
<PlayerSkin player={player} />
</div>
</div>
</Card>
<div className="flex gap-2 flex-wrap justify-center">
<ReloadPageButton />
<CodeDialog
title="Player Data"
description="The player's data from the API"
code={JSON.stringify(player, undefined, 2)}
>
<Button>View as JSON</Button>
</CodeDialog>
<CacheInformation cache={player.cache}>
<Button>Cache Information</Button>
</CacheInformation>
</div>
</div>
);
}

View File

@ -0,0 +1,116 @@
/* eslint-disable @next/next/no-img-element */
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import { SkinPart } from "mcutils-library";
import Link from "next/link";
import { ReactElement } from "react";
import { Button } from "../ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
type SkinPartImageProps = {
/**
* The player's name.
*/
playerName: string;
/**
* The skin part.
*/
part: SkinPart;
/**
* The URL to the skin part.
*/
url: string;
/**
* The size to display the skin part.
*/
size?: number;
};
type SkinOverlay = {
/**
* The title to display.
*/
title: string;
/**
* Whether to show the part with overlays.
*/
overlays?: boolean;
};
const skinPartOverlay: SkinOverlay[] = [
{
title: "Without Overlays",
},
{
title: "With Overlays",
overlays: true,
},
];
export function SkinPartImage({ playerName, part, url, size = 64 }: SkinPartImageProps): ReactElement {
const partName = capitalizeFirstLetter(part);
return (
<Tooltip>
<TooltipTrigger>
<Dialog>
<DialogTrigger asChild>
<img
src={url}
alt={`The ${playerName}'s ${partName}`}
loading="lazy"
style={{
height: `${size}px`,
}}
/>
</DialogTrigger>
<DialogContent className="w-fit h-fit">
<DialogHeader>
<DialogTitle>
{playerName}&apos;s {partName}
</DialogTitle>
<DialogDescription>See the skin part below.</DialogDescription>
</DialogHeader>
<div className="flex items-center w-full flex-col divide-y md:flex-row md:divide-x md:divide-y-0 text-muted-foreground">
{skinPartOverlay.map((overlay, index) => {
return (
<div key={index} className="p-2 w-max text-center items-center font-semibold flex flex-col gap-2">
<p>{overlay.title}</p>
<img
className="h-[200px] md:h-[256px] w-max"
src={url + (overlay.overlays ? "?overlays=true" : "")}
alt={`The ${playerName}'s ${partName}`}
loading="lazy"
/>
</div>
);
})}
</div>
<DialogFooter>
<Link href={url} target="_blank">
<Button>Open in new tab</Button>
</Link>
</DialogFooter>
</DialogContent>
</Dialog>
</TooltipTrigger>
<TooltipContent>
<p>
Click to view {playerName}&apos;s {partName}
</p>
</TooltipContent>
</Tooltip>
);
}

View File

@ -0,0 +1,49 @@
import { ReactElement } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/app/components/ui/tooltip";
import Image from "next/image";
import config from "@root/config.json";
import Link from "next/link";
import { Card } from "@/app/components/card";
/**
* The players to try out.
*/
const tryMePlayers: string[] = ["Notch", "jeb_", "Dinnerbone", "Grumm", "deadmau5"];
export function TryAPlayer(): ReactElement {
return (
<Card>
<div className="flex flex-col items-center justify-center text-center">
<div>
<h2 className="text-lg font-semibold">Try a Player</h2>
<p className="text-muted-foreground">Try one of these players to see how the player view works.</p>
</div>
<div className="flex flex-wrap justify-center gap-2 max-w-2xl mt-4">
{tryMePlayers.map(playerName => (
<Tooltip key={playerName}>
<TooltipTrigger asChild>
<div className="flex items-center justify-center bg-background p-1.5 rounded-md gap-2">
<Image
src={`${config.apiEndpoint}/player/head/${playerName}`}
alt={"The player's head"}
width={32}
height={32}
/>
<Link href={`/player/${playerName}`} className="hover:opacity-85 transform-gpu transition-all">
{playerName}
</Link>
</div>
</TooltipTrigger>
<TooltipContent>
<p>
Click to try the player <b>{playerName}</b>.
</p>
</TooltipContent>
</Tooltip>
))}
</div>
</div>
</Card>
);
}

View File

@ -0,0 +1,28 @@
"use client";
import { ReactElement } from "react";
import { Button } from "@/app/components/ui/button";
import { ArrowPathIcon } from "@heroicons/react/16/solid";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/app/components/ui/tooltip";
export function ReloadPageButton(): ReactElement {
/**
* Reload the page.
*/
function reload(): void {
window.location.reload();
}
return (
<Tooltip>
<TooltipTrigger asChild>
<Button onClick={() => reload()}>
<ArrowPathIcon className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Reload the page</p>
</TooltipContent>
</Tooltip>
);
}

View File

@ -1,8 +1,8 @@
"use client";
import { capitalizeFirstLetter } from "@/common/string-utils";
import { useToast } from "@/common/use-toast";
import { ServerPlatform, getServer } from "mcutils-library";
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import { useToast } from "@/app/common/use-toast";
import { getServer, ServerPlatform } from "mcutils-library";
import { useRouter } from "next/navigation";
import { ReactElement, useState } from "react";
import ScaleLoader from "react-spinners/ScaleLoader";
@ -24,7 +24,6 @@ type LookupServerProps = {
};
export function LookupServer({ currentPlatform, currentServer }: LookupServerProps): ReactElement {
console.log(currentPlatform, currentServer);
const router = useRouter();
const { toast } = useToast();
const [loading, setLoading] = useState<boolean>(false);
@ -58,13 +57,14 @@ export function LookupServer({ currentPlatform, currentServer }: LookupServerPro
description: (err as Error).message,
duration: 5000,
});
return;
setLoading(false);
}
};
return (
<form
className="flex flex-col gap-2 justify-center items-center mt-4"
autoComplete="off"
action={(form: FormData) => {
lookupServer(form.get("platform") as ServerPlatform, form.get("query") as string);
}}

View File

@ -1,8 +1,10 @@
import { formatNumber } from "@/common/number-utils";
import { CachedBedrockMinecraftServer, CachedJavaMinecraftServer } from "mcutils-library";
import Image from "next/image";
import { ReactElement } from "react";
import { Card } from "../card";
import { CodeDialog } from "../code-dialog";
import { Button } from "../ui/button";
import { CacheInformation } from "@/app/components/cache-information";
import { ReloadPageButton } from "@/app/components/reload-page-button";
type ServerViewProps = {
/**
@ -18,38 +20,29 @@ type ServerViewProps = {
export function ServerView({ server, favicon }: ServerViewProps): ReactElement {
return (
<Card className="w-max xs:w-fit relative">
<div className="flex gap-2 flex-col">
<div className="flex gap-4 flex-col xs:flex-row">
{favicon && (
<div className="flex justify-center xs:justify-start">
<div className="flex relative flex-col gap-2 items-center w-screen">
<div>
<Image
className="w-[64px] h-[64px]"
src={favicon}
width={64}
height={64}
quality={100}
alt="The server's favicon"
src={`https://api.mcutils.xyz/server/java/preview/${server.hostname}`}
alt={"The server preview"}
width={650}
height={256}
unoptimized
/>
</div>
)}
<div className="flex flex-col">
<h2 className="text-xl text-primary font-semibold">{server.hostname}</h2>
<div>
<p>
Players online: {formatNumber(server.players.online)}/{formatNumber(server.players.max)}
</p>
<div className="flex gap-2 flex-wrap justify-center">
<ReloadPageButton />
<CodeDialog
title="Server Data"
description="The servers's data from the API"
code={JSON.stringify(server, undefined, 2)}
>
<Button>View as JSON</Button>
</CodeDialog>
<CacheInformation cache={server.cache}>
<Button>Cache Information</Button>
</CacheInformation>
</div>
</div>
</div>
<div className="bg-background rounded-lg p-2 text-sm xs:text-lg">
{server.motd.html.map((line, index) => {
return <p key={index} dangerouslySetInnerHTML={{ __html: line }}></p>;
})}
</div>
</div>
</Card>
);
}

View File

@ -0,0 +1,54 @@
import { ReactElement } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/app/components/ui/tooltip";
import Image from "next/image";
import Link from "next/link";
import { capitalizeFirstLetter } from "@/app/common/string-utils";
import { Card } from "@/app/components/card";
import { ServerPlatform } from "mcutils-library";
import { TryMeServer } from "@/app/types/server/try-me-server";
/**
* The servers to try out.
*/
const tryMeServers: TryMeServer[] = [
{ platform: ServerPlatform.Java, hostname: "mc.hypixel.net" },
{ platform: ServerPlatform.Java, hostname: "wildprison.net" },
{ platform: ServerPlatform.Java, hostname: "cubecraft.net" },
{ platform: ServerPlatform.Bedrock, hostname: "geo.hivebedrock.network" },
];
export function TryAServer(): ReactElement {
return (
<Card>
<div className="flex flex-col items-center justify-center text-center">
<div>
<h2 className="text-lg font-semibold">Try a Server</h2>
<p className="text-muted-foreground">Try one of these servers to see how the server view works.</p>
</div>
<div className="flex flex-wrap justify-center gap-2 max-w-2xl mt-4">
{tryMeServers.map(({ platform, hostname }) => (
<Tooltip key={hostname}>
<TooltipTrigger asChild>
<div className="flex items-center justify-center bg-background p-1.5 rounded-md gap-2">
<Image src={`/media/platform/${platform}.png`} alt={"The server's platform"} width={28} height={28} />
<Link
href={`/server/${platform}/${hostname}`}
className="hover:opacity-85 transform-gpu transition-all"
>
{hostname}
</Link>
</div>
</TooltipTrigger>
<TooltipContent>
<p>
Click to try the <b>{capitalizeFirstLetter(platform)}</b> server: <b>{hostname}</b>
</p>
</TooltipContent>
</Tooltip>
))}
</div>
</div>
</Card>
);
}

View File

@ -20,7 +20,7 @@ type StatProps = {
export function Stat({ title, value, icon }: StatProps): ReactElement {
return (
<div className="bg-secondary p-2 rounded-lg flex divide-x justify-center items-center text-center w-fit">
<div className="bg-card p-2 rounded-lg flex divide-x justify-center items-center text-center w-fit">
<div className="pr-2">{icon}</div>
<div className="pl-2">
<p>{title}</p>

View File

@ -36,23 +36,22 @@ const stats: Stat[] = [
icon: <ArrowTrendingUpIcon width={24} height={24} />,
},
{
id: "totalPlayerLookups",
id: "uniquePlayerLookups",
displayName: "Player Lookups",
tooltip: "The total amount of player lookups",
tooltip: "The unique amount of player lookups",
icon: <UserIcon width={24} height={24} />,
},
{
id: "totalServerLookups",
id: "uniqueServerLookups",
displayName: "Server Lookups",
tooltip: "The total amount of server lookups",
tooltip: "The unique amount of server lookups",
icon: <ServerIcon width={24} height={24} />,
},
];
export function Stats(): ReactElement {
const { lastMessage, readyState } = useWebSocket("wss://api.mcutils.xyz/websocket/metrics");
const metrics =
lastMessage !== null && readyState == ReadyState.OPEN ? JSON.parse(lastMessage.data).metrics : undefined;
const metrics = lastMessage !== null && readyState == ReadyState.OPEN ? JSON.parse(lastMessage.data) : undefined;
return (
<div className="flex gap-2 flex-wrap justify-center">

View File

@ -2,18 +2,27 @@
import { MoonIcon, SunIcon } from "@heroicons/react/16/solid";
import { useTheme } from "next-themes";
import { ReactElement } from "react";
import { ReactElement, useEffect, useState } from "react";
export function ToggleThemeButton(): ReactElement {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <></>;
}
return (
<button
className="p-2 rounded-lg"
className="rounded-lg"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
aria-label="Toggle Theme"
>
{theme === "dark" ? <SunIcon width={24} height={24} /> : <MoonIcon width={24} height={24} color="#000" />}
{theme == "dark" ? <SunIcon width={24} height={24} /> : <MoonIcon width={24} height={24} color="#000" />}
</button>
);
}

View File

@ -0,0 +1,15 @@
import { ReactElement } from "react";
type TitleProps = {
title: string | ReactElement;
subtitle: string | ReactElement;
};
export function Title({ title, subtitle }: TitleProps): ReactElement {
return (
<div className="flex flex-col gap-2 items-center">
<div className="text-3xl text-primary font-semibold">{typeof title === "string" ? <h1>{title}</h1> : title}</div>
{typeof subtitle === "string" ? <p>{subtitle}</p> : subtitle}
</div>
);
}

View File

@ -0,0 +1,90 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/app/common/utils";
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode;
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
Breadcrumb.displayName = "Breadcrumb";
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(
({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className,
)}
{...props}
/>
),
);
BreadcrumbList.displayName = "BreadcrumbList";
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
({ className, ...props }, ref) => (
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
),
);
BreadcrumbItem.displayName = "BreadcrumbItem";
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean;
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a";
return <Comp ref={ref} className={cn("transition-colors hover:text-foreground", className)} {...props} />;
});
BreadcrumbLink.displayName = "BreadcrumbLink";
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
),
);
BreadcrumbPage.displayName = "BreadcrumbPage";
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
<li role="presentation" aria-hidden="true" className={cn("[&>svg]:size-3.5", className)} {...props}>
{children ?? <ChevronRight />}
</li>
);
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
);
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};

View File

@ -2,7 +2,7 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/common/utils";
import { cn } from "@/app/common/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
@ -27,7 +27,7 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
}
},
);
export interface ButtonProps
@ -40,7 +40,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
}
},
);
Button.displayName = "Button";

View File

@ -0,0 +1,43 @@
import * as React from "react";
import { cn } from "@/app/common/utils";
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-4", className)} {...props} />
),
);
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
),
);
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
),
);
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn("p-4 pt-0", className)} {...props} />,
);
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-4 pt-0", className)} {...props} />
),
);
CardFooter.displayName = "CardFooter";
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };

View File

@ -0,0 +1,135 @@
"use client";
import * as React from "react";
import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import { cn } from "@/app/common/utils";
import { Dialog, DialogContent } from "@/app/components/ui/dialog";
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className,
)}
shouldFilter={false}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />);
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className,
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50",
className,
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />;
};
CommandShortcut.displayName = "CommandShortcut";
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View File

@ -1,27 +1,27 @@
"use client"
"use client";
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/common/utils"
import { cn } from "@/app/common/utils";
const ContextMenu = ContextMenuPrimitive.Root
const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group
const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub
const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
@ -29,15 +29,15 @@ const ContextMenuSubTrigger = React.forwardRef<
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
className,
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
@ -47,12 +47,12 @@ const ContextMenuSubContent = React.forwardRef<
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
className,
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
@ -63,18 +63,18 @@ const ContextMenuContent = React.forwardRef<
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
@ -82,12 +82,12 @@ const ContextMenuItem = React.forwardRef<
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
className,
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
@ -97,7 +97,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
className,
)}
checked={checked}
{...props}
@ -109,9 +109,8 @@ const ContextMenuCheckboxItem = React.forwardRef<
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
));
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
@ -121,7 +120,7 @@ const ContextMenuRadioItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
className,
)}
{...props}
>
@ -132,54 +131,35 @@ const ContextMenuRadioItem = React.forwardRef<
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
className={cn("px-2 py-1.5 text-sm font-semibold text-foreground", inset && "pl-8", className)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
<ContextMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />;
};
ContextMenuShortcut.displayName = "ContextMenuShortcut";
export {
ContextMenu,
@ -197,4 +177,4 @@ export {
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
};

View File

@ -4,7 +4,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import * as React from "react";
import { cn } from "@/common/utils";
import { cn } from "@/app/common/utils";
const Dialog = DialogPrimitive.Root;
@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-md md:max-w-2xl xl:max-w-6xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
"fixed left-[50%] top-[50%] z-50 grid max-w-md md:max-w-2xl xl:max-w-6xl translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
)}
{...props}

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { cn } from "@/common/utils";
import { cn } from "@/app/common/utils";
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type,
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
className,
)}
ref={ref}
{...props}

View File

@ -1,26 +1,19 @@
"use client"
"use client";
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/common/utils"
import { cn } from "@/app/common/utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label }
export { Label };

View File

@ -0,0 +1,31 @@
"use client";
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/app/common/utils";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };

View File

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

View File

@ -1,31 +1,22 @@
"use client"
"use client";
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/common/utils"
import { cn } from "@/app/common/utils";
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
));
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator }
export { Separator };

View File

@ -1,4 +1,4 @@
import { cn } from "@/common/utils";
import { cn } from "@/app/common/utils";
import * as React from "react";
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
@ -6,26 +6,26 @@ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableE
<div className="relative w-full overflow-auto">
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
)
),
);
Table.displayName = "Table";
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
({ className, ...props }, ref) => <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />,
);
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
)
),
);
TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<tfoot ref={ref} className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)} {...props} />
)
),
);
TableFooter.displayName = "TableFooter";
@ -36,7 +36,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
className={cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className)}
{...props}
/>
)
),
);
TableRow.displayName = "TableRow";
@ -46,25 +46,25 @@ const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
className,
)}
{...props}
/>
)
),
);
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => (
<td ref={ref} className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)} {...props} />
)
),
);
TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
({ className, ...props }, ref) => (
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
)
),
);
TableCaption.displayName = "TableCaption";

View File

@ -1,13 +1,13 @@
"use client"
"use client";
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import * as React from "react";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/common/utils"
import { cn } from "@/app/common/utils";
const ToastProvider = ToastPrimitives.Provider
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
@ -17,12 +17,12 @@ const ToastViewport = React.forwardRef<
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
className,
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
@ -30,30 +30,22 @@ const toastVariants = cva(
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
destructive: "destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
},
);
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
@ -63,12 +55,12 @@ const ToastAction = React.forwardRef<
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
className,
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
));
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
@ -78,43 +70,35 @@ const ToastClose = React.forwardRef<
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
<ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} />
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
<ToastPrimitives.Description ref={ref} className={cn("text-sm opacity-90", className)} {...props} />
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
@ -126,4 +110,4 @@ export {
ToastDescription,
ToastClose,
ToastAction,
}
};

View File

@ -8,7 +8,7 @@ import {
ToastTitle,
ToastViewport,
} from "@/app/components/ui/toast";
import { useToast } from "@/common/use-toast";
import { useToast } from "@/app/common/use-toast";
export function Toaster() {
const { toasts } = useToast();

View File

@ -1,15 +1,15 @@
"use client"
"use client";
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/common/utils"
import { cn } from "@/app/common/utils";
const TooltipProvider = TooltipPrimitive.Provider
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
@ -20,11 +20,11 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
className,
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

6
src/app/font/fonts.ts Normal file
View File

@ -0,0 +1,6 @@
import { Inter } from "next/font/google";
/**
* The default font to use for the site.
*/
export const inter = Inter({ subsets: ["latin"] });

22
src/app/global-error.tsx Normal file
View File

@ -0,0 +1,22 @@
"use client";
import * as Sentry from "@sentry/nextjs";
import Error from "next/error";
import { useEffect } from "react";
import { Button } from "@/app/components/ui/button";
export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<div className="flex text-center flex-col gap-4">
<div>
<h2 className="text-red-400 font-2xl font-semibold">Error</h2>
<p>An error occurred while rendering this page.</p>
</div>
<Button onClick={reset}>Reload</Button>
</div>
);
}

View File

@ -2,50 +2,51 @@
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--foreground: 0 0% 3.9%;
--card: 0 0% 95%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--popover-foreground: 0 0% 3.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 5 5% 95%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 142.1 76.2% 36.3%;
--radius: 0.5rem;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--radius: 0.3rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 8%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 144.9 80.4% 10%;
--secondary: 240 3.7% 15.9%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 12 6.5% 15.1%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 5.9% 30%;
--input: 240 3.7% 15.9%;
--ring: 142.4 71.8% 29.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
}
}

View File

@ -1,37 +1,36 @@
import { Fonts } from "@/common/fonts";
import { Metadata, Viewport } from "next";
import "./globals.css";
import config from "@root/config.json";
import Script from "next/script";
import { ReactElement } from "react";
import Container from "./components/container";
import ThemeProvider from "./components/theme-provider";
import { Toaster } from "./components/ui/toaster";
import { TooltipProvider } from "./components/ui/tooltip";
import { inter } from "@/app/font/fonts";
import config from "@root/config.json";
import "./globals.css";
export const viewport: Viewport = {
themeColor: "#3498DB",
};
export const metadata: Metadata = {
metadataBase: new URL(config.siteUrl),
metadataBase: new URL(config.publicUrl),
title: {
template: config.siteName + " - %s",
default: config.siteName,
template: "%s - " + config.name,
default: config.name,
},
description: config.siteDescription,
description: config.description,
keywords: "Minecraft, APIs, wrapper, utility, development",
openGraph: {
title: config.siteName,
description: config.siteDescription,
url: config.siteUrl,
title: config.name,
description: config.description,
url: config.publicUrl,
locale: "en_US",
type: "website",
images: [
{
url: "https://git.fascinated.cc/MinecraftUtilities/Assets/raw/branch/master/logo.png",
url: `${config.publicUrl}/media/logo.png`,
},
],
},
@ -48,10 +47,10 @@ export default function RootLayout({
return (
<>
<Script defer data-domain="mcutils.xyz" src="https://analytics.fascinated.cc/js/script.js" />
<html className={Fonts.inter.className} lang="en" suppressHydrationWarning>
<html className={inter.className} lang="en" suppressHydrationWarning>
<head />
<body>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
<TooltipProvider>
<Toaster />
<Container>{children}</Container>

17
src/app/not-found.tsx Normal file
View File

@ -0,0 +1,17 @@
import Link from "next/link";
import { Button } from "@/app/components/ui/button";
import { ReactElement } from "react";
export default function NotFound(): ReactElement {
return (
<div className="flex text-center flex-col gap-4">
<div>
<h2 className="text-red-400 font-2xl font-semibold">Not Found</h2>
<p>The page you are looking for was not found.</p>
</div>
<Link href="/">
<Button>Return Home</Button>
</Link>
</div>
);
}

View File

@ -0,0 +1,6 @@
export type CreateHasteButtonProps = {
/**
* The content to create the haste with.
*/
content: string;
};

View File

@ -0,0 +1,27 @@
export type LandingButton = {
/**
* The title of the button.
*/
title: string;
/**
* The tooltip to display for this statistic.
*/
tooltip: string;
/**
* The URL to go to.
*/
url: string;
/**
* Whether clicking the button will
* open the link in a new tab.
*/
openInNewTab?: boolean;
/**
* The class name to apply to the button.
*/
className?: string;
};

View File

@ -0,0 +1,5 @@
export type PlayerPageParams = {
params: {
id: string;
};
};

View File

@ -0,0 +1,8 @@
import { ServerPlatform } from "mcutils-library";
export type ServerPageParams = {
params: {
platform: ServerPlatform;
hostname: string;
};
};

Some files were not shown because too many files have changed in this diff Show More