diff --git a/.env.example b/.env.example index 12655715..26b25fb2 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,6 @@ AUTH_REDIRECT_PROXY_URL="http://localhost:3000/api/auth" GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" GITHUB_AUTH_TOKEN="" + +# Local default textures repository path +DEV_LOCAL_REPOSITORY_PATH="" diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..124d9396 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,59 @@ +'use strict'; + +module.exports = { + extends: 'next/core-web-vitals', + plugins: [ + 'unused-imports', + 'custom-rules', + ], + rules: { + 'comma-dangle': ['error', 'always-multiline'], + 'object-curly-spacing': ['warn', 'always'], + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import/order': [ + 'error', + { + 'newlines-between': 'always', + groups: [ + 'builtin', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + 'type', + ], + pathGroups: [ + { + pattern: 'react', + group: 'builtin', + position: 'after', + }, + { + pattern: 'react-icons/**', + group: 'builtin', + position: 'after', + }, + { + pattern: 'next/**', + group: 'builtin', + position: 'before', + }, + ], + pathGroupsExcludedImportTypes: ['react', 'react-icons/**', 'next/**'], + alphabetize: { + order: 'asc', + caseInsensitive: false, + }, + }, + ], + indent: ['error', 'tab', { SwitchCase: 1 }], + 'no-multi-spaces': ['error', { ignoreEOLComments: true }], + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 1 }], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'unused-imports/no-unused-imports': 'error', + + 'custom-rules/page-export-naming': 'error', + 'custom-rules/layout-export-naming': 'error', + }, +}; + diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 254a7285..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "plugins": ["unused-imports"], - "rules": { - "comma-dangle": ["error", "always-multiline"], - "object-curly-spacing": ["warn", "always"], - "import/consistent-type-specifier-style": ["error", "prefer-top-level"], - "import/order": [ - "error", - { - "newlines-between": "always", - "groups": ["builtin", "external", "internal", ["parent","sibling", "index"], "type"], - "pathGroups": [ - { - "pattern": "react", - "group": "builtin", - "position": "after" - }, - { - "pattern": "react-icons/**", - "group": "builtin", - "position": "after" - }, - { - "pattern": "next/**", - "group": "builtin", - "position": "before" - } - ], - "pathGroupsExcludedImportTypes": [ - "react", "react-icons/**", "next/**" - ], - "alphabetize": { - "order": "asc", - "caseInsensitive": false - } - } - ], - "indent": ["error", "tab", { "SwitchCase": 1 }], - "no-multi-spaces": ["error", { "ignoreEOLComments": true }], - "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }], - "quotes": ["error", "single"], - "semi": ["error", "always"], - "unused-imports/no-unused-imports": "error" - } -} diff --git a/eslint-plugins/eslint-plugin-custom-rules.cjs b/eslint-plugins/eslint-plugin-custom-rules.cjs new file mode 100644 index 00000000..617a2a02 --- /dev/null +++ b/eslint-plugins/eslint-plugin-custom-rules.cjs @@ -0,0 +1,8 @@ +const plugin = { + rules: { + 'page-export-naming': require('./rules/page-export-naming.cjs'), + 'layout-export-naming': require('./rules/layout-export-naming.cjs'), + }, +}; + +module.exports = plugin; diff --git a/eslint-plugins/package.json b/eslint-plugins/package.json new file mode 100644 index 00000000..37408c2d --- /dev/null +++ b/eslint-plugins/package.json @@ -0,0 +1,12 @@ +{ + "name": "eslint-plugin-custom-rules", + "type":"module", + "version": "1.0.0", + "main": "eslint-plugin-custom-rules.cjs", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "" +} diff --git a/eslint-plugins/rules/layout-export-naming.cjs b/eslint-plugins/rules/layout-export-naming.cjs new file mode 100644 index 00000000..99a8102c --- /dev/null +++ b/eslint-plugins/rules/layout-export-naming.cjs @@ -0,0 +1,57 @@ +const { sep } = require('path'); + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Enforce the default export names to ends with "Layout" in layout.tsx files', + category: 'Best Practices', + recommended: true, + }, + messages: { + invalidExportName: 'Default export name must ends with "Layout"', + invalidExportType: 'Default export must be a function', + }, + fixable: 'code', + schema: [], + }, + /** + * @param {import('eslint').Rule.RuleContext} context + */ + create(context) { + const filename = context.filename.split(sep).pop(); + if (filename !== 'layout.tsx') return {}; + + return { + ExportDefaultDeclaration(node) { + if (node.declaration.type === 'FunctionDeclaration' || (node.declaration.type === 'ArrowFunctionExpression' && node.declaration.id)) { + /** + * @type {string | undefined} + */ + const functionName = node.declaration.id && node.declaration.id.name; + + if (functionName && !functionName.endsWith('Layout')) { + context.report({ + node, + messageId: 'invalidExportName', + fix(fixed) { + const newName = `${functionName}Layout`; + const start = node.declaration.id.range[0]; + const end = node.declaration.id.range[1]; + return fixed.replaceTextRange([start, end], newName); + }, + }); + } + } + + if (node.declaration.type === 'Identifier') { + context.report({ + node, + messageId: 'invalidExportType', + }); + } + }, + }; + + }, +}; diff --git a/eslint-plugins/rules/page-export-naming.cjs b/eslint-plugins/rules/page-export-naming.cjs new file mode 100644 index 00000000..bf8469b9 --- /dev/null +++ b/eslint-plugins/rules/page-export-naming.cjs @@ -0,0 +1,57 @@ +const { sep } = require('path'); + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Enforce the default export names to ends with "Page" in .tsx files', + category: 'Best Practices', + recommended: true, + }, + messages: { + invalidExportName: 'Default export name must ends with "Page"', + invalidExportType: 'Default export must be a function', + }, + fixable: 'code', + schema: [], + }, + /** + * @param {import('eslint').Rule.RuleContext} context + */ + create(context) { + const filename = context.filename.split(sep).pop(); + if (filename !== 'page.tsx') return {}; + + return { + ExportDefaultDeclaration(node) { + if (node.declaration.type === 'FunctionDeclaration' || (node.declaration.type === 'ArrowFunctionExpression' && node.declaration.id)) { + /** + * @type {string | undefined} + */ + const functionName = node.declaration.id && node.declaration.id.name; + + if (functionName && !functionName.endsWith('Page')) { + context.report({ + node, + messageId: 'invalidExportName', + fix(fixed) { + const newName = `${functionName}Page`; + const start = node.declaration.id.range[0]; + const end = node.declaration.id.range[1]; + return fixed.replaceTextRange([start, end], newName); + }, + }); + } + } + + if (node.declaration.type === 'Identifier') { + context.report({ + node, + messageId: 'invalidExportType', + }); + } + }, + }; + + }, +}; diff --git a/package-lock.json b/package-lock.json index 9459728b..4ba8d3c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "faithful-mods", - "version": "1.0.9", + "version": "1.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "faithful-mods", - "version": "1.0.9", + "version": "1.0.10", "hasInstallScript": true, "dependencies": { "@auth/prisma-adapter": "^2.4.2", @@ -25,7 +25,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "jszip": "^3.10.1", - "next": "^14.2.5", + "next": "^14.2.6", "next-auth": "5.0.0-beta.18", "next-themes": "^0.3.0", "react": "^18", @@ -33,9 +33,11 @@ "react-dom": "^18", "react-hook-form": "^7.52.2", "react-icons": "^5.3.0", + "react-minecraft": "^1.1.0", "react-spinners": "^0.14.1", "sass": "^1.77.8", "server-only": "^0.0.1", + "simple-git": "^3.25.0", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "tailwind-merge": "^2.5.2", @@ -53,7 +55,8 @@ "autoprefixer": "^10.4.20", "cross-env": "^7.0.3", "eslint": "^8.57.0", - "eslint-config-next": "^14.2.5", + "eslint-config-next": "^14.2.6", + "eslint-plugin-custom-rules": "file:eslint-plugins", "eslint-plugin-unused-imports": "^4.1.3", "postcss": "^8.4.39", "postcss-preset-mantine": "^1.17.0", @@ -68,6 +71,12 @@ "typescript": "^5.5.4" } }, + "eslint-plugins": { + "name": "eslint-plugin-custom-rules", + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -995,6 +1004,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, "node_modules/@ltd/j-toml": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/@ltd/j-toml/-/j-toml-1.38.0.tgz", @@ -1124,15 +1148,15 @@ } }, "node_modules/@next/env": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", - "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.6.tgz", + "integrity": "sha512-bs5DFKV+08EjWrl8EB+KKqev1ZTNONH1vFCaHh911aaB362NnP32UDTbE9VQhyiAgbFqJsfDkSxFERNDDb3j0g==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", - "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.6.tgz", + "integrity": "sha512-d3+p4AjIYmhqzYHhhmkRYYN6ZU35TwZAKX08xKRfnHkz72KhWL2kxMFsDptpZs5e8bBGdepn7vn1+9DaF8iX+A==", "dev": true, "license": "MIT", "dependencies": { @@ -1140,9 +1164,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", - "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.6.tgz", + "integrity": "sha512-BtJZb+hYXGaVJJivpnDoi3JFVn80SHKCiiRUW3kk1SY6UCUy5dWFFSbh+tGi5lHAughzeduMyxbLt3pspvXNSg==", "cpu": [ "arm64" ], @@ -1156,9 +1180,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.6.tgz", + "integrity": "sha512-ZHRbGpH6KHarzm6qEeXKSElSXh8dS2DtDPjQt3IMwY8QVk7GbdDYjvV4NgSnDA9huGpGgnyy3tH8i5yHCqVkiQ==", "cpu": [ "x64" ], @@ -1172,9 +1196,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.6.tgz", + "integrity": "sha512-O4HqUEe3ZvKshXHcDUXn1OybN4cSZg7ZdwHJMGCXSUEVUqGTJVsOh17smqilIjooP/sIJksgl+1kcf2IWMZWHg==", "cpu": [ "arm64" ], @@ -1188,9 +1212,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.6.tgz", + "integrity": "sha512-xUcdhr2hfalG8RDDGSFxQ75yOG894UlmFS4K2M0jLrUhauRBGOtUOxoDVwiIIuZQwZ3Y5hDsazNjdYGB0cQ9yQ==", "cpu": [ "arm64" ], @@ -1204,9 +1228,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.6.tgz", + "integrity": "sha512-InosKxw8UMcA/wEib5n2QttwHSKHZHNSbGcMepBM0CTcNwpxWzX32KETmwbhKod3zrS8n1vJ+DuJKbL9ZAB0Ag==", "cpu": [ "x64" ], @@ -1220,9 +1244,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.6.tgz", + "integrity": "sha512-d4QXfJmt5pGJ7cG8qwxKSBnO5AXuKAFYxV7qyDRHnUNvY/dgDh+oX292gATpB2AAHgjdHd5ks1wXxIEj6muLUQ==", "cpu": [ "x64" ], @@ -1236,9 +1260,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.6.tgz", + "integrity": "sha512-AlgIhk4/G+PzOG1qdF1b05uKTMsuRatFlFzAi5G8RZ9h67CVSSuZSbqGHbJDlcV1tZPxq/d4G0q6qcHDKWf4aQ==", "cpu": [ "arm64" ], @@ -1252,9 +1276,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.6.tgz", + "integrity": "sha512-hNukAxq7hu4o5/UjPp5jqoBEtrpCbOmnUqZSKNJG8GrUVzfq0ucdhQFVrHcLRMvQcwqqDh1a5AJN9ORnNDpgBQ==", "cpu": [ "ia32" ], @@ -1268,9 +1292,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.6.tgz", + "integrity": "sha512-NANtw+ead1rSDK1jxmzq3TYkl03UNK2KHqUYf1nIhNci6NkeqBD4s1njSzYGIlSHxCK+wSaL8RXZm4v+NF/pMw==", "cpu": [ "x64" ], @@ -3120,13 +3144,13 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz", - "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.6.tgz", + "integrity": "sha512-z0URA5LO6y8lS/YLN0EDW/C4LEkDODjJzA37dvLVdzCPzuewjzTe1os5g3XclZAZrQ8X8hPaSMQ2JuVWwMmrTA==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "14.2.5", + "@next/eslint-plugin-next": "14.2.6", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", @@ -3385,6 +3409,10 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-custom-rules": { + "resolved": "eslint-plugins", + "link": true + }, "node_modules/eslint-plugin-unused-imports": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.3.tgz", @@ -4865,9 +4893,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -4975,12 +5003,12 @@ } }, "node_modules/next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", - "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.6.tgz", + "integrity": "sha512-57Su7RqXs5CBKKKOagt8gPhMM3CpjgbeQhrtei2KLAA1vTNm7jfKS+uDARkSW8ZETUflDCBIsUKGSyQdRs4U4g==", "license": "MIT", "dependencies": { - "@next/env": "14.2.5", + "@next/env": "14.2.6", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -4995,15 +5023,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.5", - "@next/swc-darwin-x64": "14.2.5", - "@next/swc-linux-arm64-gnu": "14.2.5", - "@next/swc-linux-arm64-musl": "14.2.5", - "@next/swc-linux-x64-gnu": "14.2.5", - "@next/swc-linux-x64-musl": "14.2.5", - "@next/swc-win32-arm64-msvc": "14.2.5", - "@next/swc-win32-ia32-msvc": "14.2.5", - "@next/swc-win32-x64-msvc": "14.2.5" + "@next/swc-darwin-arm64": "14.2.6", + "@next/swc-darwin-x64": "14.2.6", + "@next/swc-linux-arm64-gnu": "14.2.6", + "@next/swc-linux-arm64-musl": "14.2.6", + "@next/swc-linux-x64-gnu": "14.2.6", + "@next/swc-linux-x64-musl": "14.2.6", + "@next/swc-win32-arm64-msvc": "14.2.6", + "@next/swc-win32-ia32-msvc": "14.2.6", + "@next/swc-win32-x64-msvc": "14.2.6" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -5904,6 +5932,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-minecraft": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-minecraft/-/react-minecraft-1.1.0.tgz", + "integrity": "sha512-wJkKuxumahvEP8IOoQoZArIPMZmqbPPqW90yd47EdzjheGOersmCh5MoyqFb1ZFLjuG5tmZT599Amc2WVcx0DA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-number-format": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.4.tgz", @@ -6401,6 +6441,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-git": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.25.0.tgz", + "integrity": "sha512-KIY5sBnzc4yEcJXW7Tdv4viEz8KyG+nU0hay+DWZasvdFOYKeUZ6Xc25LUHHjw0tinPT7O1eY6pzX7pRT1K8rw==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index b55d519d..3957d3a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "faithful-mods", - "version": "1.0.9", + "version": "1.0.10", "private": true, "scripts": { "dev": "npx cross-env NODE_ENV=development tsx watch src/server.ts", @@ -28,7 +28,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "jszip": "^3.10.1", - "next": "^14.2.5", + "next": "^14.2.6", "next-auth": "5.0.0-beta.18", "next-themes": "^0.3.0", "react": "^18", @@ -36,9 +36,11 @@ "react-dom": "^18", "react-hook-form": "^7.52.2", "react-icons": "^5.3.0", + "react-minecraft": "^1.1.0", "react-spinners": "^0.14.1", "sass": "^1.77.8", "server-only": "^0.0.1", + "simple-git": "^3.25.0", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "tailwind-merge": "^2.5.2", @@ -56,7 +58,8 @@ "autoprefixer": "^10.4.20", "cross-env": "^7.0.3", "eslint": "^8.57.0", - "eslint-config-next": "^14.2.5", + "eslint-config-next": "^14.2.6", + "eslint-plugin-custom-rules": "file:eslint-plugins", "eslint-plugin-unused-imports": "^4.1.3", "postcss": "^8.4.39", "postcss-preset-mantine": "^1.17.0", diff --git a/public/gh_desktop.png b/public/gh_desktop.png new file mode 100644 index 00000000..6d4ef564 Binary files /dev/null and b/public/gh_desktop.png differ diff --git a/src/app/(pages)/(protected)/contribute/page.tsx b/src/app/(pages)/(protected)/contribute/page.tsx index 814a142f..20613856 100644 --- a/src/app/(pages)/(protected)/contribute/page.tsx +++ b/src/app/(pages)/(protected)/contribute/page.tsx @@ -9,22 +9,21 @@ import { IoReload } from 'react-icons/io5'; import { LuArrowUpDown } from 'react-icons/lu'; import { ActionIcon, Badge, Button, FloatingIndicator, Group, Indicator, Kbd, List, Select, Stack, Text } from '@mantine/core'; -import { useHotkeys, useOs, usePrevious } from '@mantine/hooks'; +import { useHotkeys, useOs, usePrevious, useViewportSize } from '@mantine/hooks'; import { Resolution, Status } from '@prisma/client'; +import { SmallTile } from '~/components/base/small-tile'; +import { Tile } from '~/components/base/tile'; import ForkInfo from '~/components/fork'; -import { SmallTile } from '~/components/small-tile'; -import { TextureImage } from '~/components/texture-img'; -import { Tile } from '~/components/tile'; +import { TextureImage } from '~/components/textures/texture-img'; import { useCurrentUser } from '~/hooks/use-current-user'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE, COLORS, gitBlobUrl, gitCommitUrl, GRADIENT, GRADIENT_DANGER } from '~/lib/constants'; -import { getContributionsOfFork } from '~/server/actions/git'; +import { getContributionsOfFork } from '~/server/actions/octokit'; import { archiveContributions, createContributionsFromGitFiles, deleteContributions, deleteContributionsOrArchive, getContributionsOfUser, submitContributions } from '~/server/data/contributions'; import { getTextures } from '~/server/data/texture'; -import type { GitFile } from '~/server/actions/git'; +import type { GitFile } from '~/server/actions/octokit'; import type { GetContributionsOfUser } from '~/server/data/contributions'; import type { GetTextures } from '~/server/data/texture'; @@ -49,7 +48,7 @@ export default function ContributeSubmissionsPage() { const [groupRef, setGroupRef] = useState(null); const [controlsRefs, setControlsRefs] = useState>({}); const [activeTab, setActiveTab] = useState(0); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); useHotkeys([ [ @@ -146,7 +145,7 @@ export default function ContributeSubmissionsPage() { rightSection={contributions.filter((c) => c.status === Object.keys(Status)[index]).length ?? 0} justify="space-between" className="slider-button" - style={windowWidth > BREAKPOINT_MOBILE_LARGE + style={width > BREAKPOINT_MOBILE_LARGE ? { borderRight: !isLast && activeTab !== index + 1 && activeTab !== index ? 'calc(0.0625rem * var(--mantine-scale)) solid var(--mantine-color-default-border)' @@ -203,7 +202,7 @@ export default function ContributeSubmissionsPage() { style={{ position: 'relative', }} - orientation={windowWidth <= BREAKPOINT_MOBILE_LARGE ? 'vertical' : 'horizontal'} + orientation={width <= BREAKPOINT_MOBILE_LARGE ? 'vertical' : 'horizontal'} > {controls} { +export default function CouncilLayout({ children }: ProtectedLayoutProps) { const tabs = [ { value: 'submissions', label: 'Submissions' }, { value: 'modpacks', label: 'Modpacks' }, @@ -28,5 +28,3 @@ const CouncilPage = ({ children }: ProtectedLayoutProps) => { ); }; - -export default CouncilPage; diff --git a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-general.tsx b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-general.tsx index 0efc49cc..c664a8e6 100644 --- a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-general.tsx +++ b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-general.tsx @@ -1,8 +1,8 @@ import { Group, FileInput, TextInput, Badge, Stack, Text } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { TextureImage } from '~/components/texture-img'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { TextureImage } from '~/components/textures/texture-img'; import { BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; import type { ModpackModalFormValues } from './modpack-modal'; @@ -11,8 +11,8 @@ import type { UseFormReturnType } from '@mantine/form'; import type { Modpack } from '@prisma/client'; export function ModpackModalGeneral({ previewImg, modpack, form }: { form: UseFormReturnType, previewImg: string, modpack: Modpack | undefined }) { - const [windowWidth] = useDeviceSize(); - const imageWidth = windowWidth <= BREAKPOINT_MOBILE_LARGE ? windowWidth * 0.7 : 220; + const { width } = useViewportSize(); + const imageWidth = width <= BREAKPOINT_MOBILE_LARGE ? width * 0.7 : 220; const filename: FileInputProps['valueComponent'] = ({ value }) => { if (value === null) return None; @@ -39,7 +39,7 @@ export function ModpackModalGeneral({ previewImg, modpack, form }: { form: UseFo } - BREAKPOINT_MOBILE_LARGE ? `calc(100% - ${imageWidth}px - var(--mantine-spacing-md))` : '100%'} gap="sm"> + BREAKPOINT_MOBILE_LARGE ? `calc(100% - ${imageWidth}px - var(--mantine-spacing-md))` : '100%'} gap="sm"> diff --git a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-modal.tsx b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-modal.tsx index f6f14e2d..759a32b0 100644 --- a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-modal.tsx +++ b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-modal.tsx @@ -3,8 +3,8 @@ import { useState, useTransition } from 'react'; import { Group, Button, Tabs } from '@mantine/core'; import { useForm } from '@mantine/form'; +import { useViewportSize } from '@mantine/hooks'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE, GRADIENT, GRADIENT_DANGER, MAX_NAME_LENGTH, MIN_NAME_LENGTH } from '~/lib/constants'; import { notify } from '~/lib/utils'; @@ -26,7 +26,7 @@ export interface ModpackModalFormValues { export function ModpackModal({ modpack, onClose }: { modpack?: Modpack | undefined, onClose: (editedModpack: Modpack | string) => void }) { const [isPending, startTransition] = useTransition(); const [previewImg, setPreviewImg] = useState(modpack?.image ?? ''); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const form = useForm({ initialValues: { @@ -121,7 +121,7 @@ export function ModpackModal({ modpack, onClose }: { modpack?: Modpack | undefin onClick={() => onDelete(modpack.id)} disabled={isPending} loading={isPending} - fullWidth={windowWidth <= BREAKPOINT_MOBILE_LARGE} + fullWidth={width <= BREAKPOINT_MOBILE_LARGE} > Delete Modpack @@ -132,7 +132,7 @@ export function ModpackModal({ modpack, onClose }: { modpack?: Modpack | undefin onClick={() => onSubmit(form.values)} disabled={isPending || !form.isValid()} loading={isPending} - fullWidth={windowWidth <= BREAKPOINT_MOBILE_LARGE} + fullWidth={width <= BREAKPOINT_MOBILE_LARGE} > {modpack ? 'Update' : 'Create'} Modpack diff --git a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version-modal.tsx b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version-modal.tsx index d7c23d09..958e95f2 100644 --- a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version-modal.tsx +++ b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version-modal.tsx @@ -5,10 +5,10 @@ import { useEffect, useState, useTransition } from 'react'; import { Button, Code, Group, Progress, Stack, Text, TextInput } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { useForm } from '@mantine/form'; +import { useViewportSize } from '@mantine/hooks'; -import { TextureImage } from '~/components/texture-img'; +import { TextureImage } from '~/components/textures/texture-img'; import { useCurrentUser } from '~/hooks/use-current-user'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { useWebsocket } from '~/hooks/use-websocket'; import { BREAKPOINT_MOBILE_LARGE, GRADIENT, GRADIENT_DANGER } from '~/lib/constants'; @@ -29,7 +29,7 @@ export function ModpackVersionModal({ modpack, modpackVersion, onClose }: { modp const [search, setSearch] = useState(''); const [displayedMods, setDisplayedMods] = useState([]); - const [windowWidth, windowHeight] = useDeviceSize(); + const { width, height } = useViewportSize(); const [status, setStatus] = useState(null); const socketId = modpack.id + '-' + useCurrentUser()!.id!; @@ -129,7 +129,7 @@ export function ModpackVersionModal({ modpack, modpackVersion, onClose }: { modp }; return ( - + Add mods @@ -190,13 +190,13 @@ export function ModpackVersionModal({ modpack, modpackVersion, onClose }: { modp Current mods setSearch(e.currentTarget.value)} /> - 5 ? 'auto' : 'hidden' }}> + 5 ? 'auto' : 'hidden' }}> {displayedMods.map((mod, index) => - {mod.name} + {mod.name} {modVersions.find((v) => v.modId === mod.id)?.version} diff --git a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version.tsx b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version.tsx index 12083f34..5308d6ec 100644 --- a/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version.tsx +++ b/src/app/(pages)/(protected)/council/modpacks/modal/modpack-versions/modpack-version.tsx @@ -3,10 +3,9 @@ import { useState } from 'react'; import { Button, Group, Table, Text } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; +import { useDisclosure, useViewportSize } from '@mantine/hooks'; -import { Modal } from '~/components/modal'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { Modal } from '~/components/base/modal'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; import { notify } from '~/lib/utils'; @@ -22,7 +21,7 @@ export function ModpackVersions({ modpack }: { modpack: Modpack }) { const [modalModpackVersion, setModalModpackVersion] = useState(); const [modpackVersions, setModpackVersions] = useState([]); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); useEffectOnce(() => { getModpackVersions(modpack.id) @@ -61,7 +60,7 @@ export function ModpackVersions({ modpack }: { modpack: Modpack }) { Version - {windowWidth > BREAKPOINT_MOBILE_LARGE && Number of mods} + {width > BREAKPOINT_MOBILE_LARGE && Number of mods} Created Updated @@ -70,7 +69,7 @@ export function ModpackVersions({ modpack }: { modpack: Modpack }) { {modpackVersions.map((version) => ( openModpackVersionModal(version)} className="cursor-pointer"> {version.version} - {windowWidth > BREAKPOINT_MOBILE_LARGE && {version.mods.length}} + {width > BREAKPOINT_MOBILE_LARGE && {version.mods.length}} {version.createdAt.toLocaleString()} {version.updatedAt.toLocaleString()} diff --git a/src/app/(pages)/(protected)/council/modpacks/page.tsx b/src/app/(pages)/(protected)/council/modpacks/page.tsx index 67538649..1ec05fd6 100644 --- a/src/app/(pages)/(protected)/council/modpacks/page.tsx +++ b/src/app/(pages)/(protected)/council/modpacks/page.tsx @@ -3,14 +3,13 @@ import { useEffect, useMemo, useState, useTransition } from 'react'; import { Badge, Group, Text, TextInput, Button, Select, Pagination, Stack } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; +import { useDisclosure, usePrevious } from '@mantine/hooks'; import { UserRole } from '@prisma/client'; +import { Modal } from '~/components/base/modal'; import { DashboardItem } from '~/components/dashboard-item/dashboard-item'; -import { Modal } from '~/components/modal'; import { useCurrentUser } from '~/hooks/use-current-user'; import { useEffectOnce } from '~/hooks/use-effect-once'; -import { usePrevious } from '~/hooks/use-previous'; import { GRADIENT, GRADIENT_DANGER, ITEMS_PER_PAGE, ITEMS_PER_PAGE_DEFAULT } from '~/lib/constants'; import { notify, searchFilter, sortByName } from '~/lib/utils'; import { getModpacks, voidModpacks } from '~/server/data/modpacks'; @@ -19,7 +18,7 @@ import { ModpackModal } from './modal/modpack-modal'; import type { Modpack } from '@prisma/client'; -const ModpacksPanel = () => { +export default function ModpacksPage() { const user = useCurrentUser()!; const itemsPerPage = useMemo(() => ITEMS_PER_PAGE, []); @@ -213,5 +212,3 @@ const ModpacksPanel = () => { ); }; - -export default ModpacksPanel; diff --git a/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version-modal.tsx b/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version-modal.tsx index 4016dc2f..db2baf30 100644 --- a/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version-modal.tsx +++ b/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version-modal.tsx @@ -5,7 +5,7 @@ import { useState, useTransition } from 'react'; import { Button, Group, Stack, Text, TextInput } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { TextureImage } from '~/components/texture-img'; +import { TextureImage } from '~/components/textures/texture-img'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { GRADIENT, GRADIENT_DANGER } from '~/lib/constants'; import { extractSemver } from '~/lib/utils'; diff --git a/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version.tsx b/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version.tsx index f3f0e78c..a6f9e09b 100644 --- a/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version.tsx +++ b/src/app/(pages)/(protected)/council/mods/modal/mod-versions/mod-version.tsx @@ -3,12 +3,11 @@ import { useState } from 'react'; import { Group, Table, Text } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; +import { useDisclosure, useViewportSize } from '@mantine/hooks'; -import { Modal } from '~/components/modal'; +import { Modal } from '~/components/base/modal'; import { ModUpload } from '~/components/mods-upload'; import { WarningIcon } from '~/components/warning-icon'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE, BREAKPOINT_TABLET } from '~/lib/constants'; import { extractSemver, notify } from '~/lib/utils'; @@ -24,7 +23,7 @@ export function ModVersions({ mod }: { mod: Mod }) { const [modVersions, setModVersions] = useState([]); const [modalModVersion, setModalModVersion] = useState(); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); useEffectOnce(() => { getModVersionsWithModpacks(mod.id) @@ -78,8 +77,8 @@ export function ModVersions({ mod }: { mod: Mod }) { Version Minecraft - {windowWidth > BREAKPOINT_MOBILE_LARGE && Modpacks} - {windowWidth > BREAKPOINT_MOBILE_LARGE && Textures} + {width > BREAKPOINT_MOBILE_LARGE && Modpacks} + {width > BREAKPOINT_MOBILE_LARGE && Textures} Created Updated @@ -100,10 +99,10 @@ export function ModVersions({ mod }: { mod: Mod }) { /> )} - {windowWidth > BREAKPOINT_MOBILE_LARGE && {version.modpacks.length}} - {windowWidth > BREAKPOINT_MOBILE_LARGE && linked: {version.linked}, unique: {version.textures}} - {windowWidth > BREAKPOINT_TABLET ? version.createdAt.toLocaleString() : version.createdAt.toLocaleDateString()} - {windowWidth > BREAKPOINT_TABLET ? version.updatedAt.toLocaleString() : version.updatedAt.toLocaleDateString()} + {width > BREAKPOINT_MOBILE_LARGE && {version.modpacks.length}} + {width > BREAKPOINT_MOBILE_LARGE && linked: {version.linked}, unique: {version.textures}} + {width > BREAKPOINT_TABLET ? version.createdAt.toLocaleString() : version.createdAt.toLocaleDateString()} + {width > BREAKPOINT_TABLET ? version.updatedAt.toLocaleString() : version.updatedAt.toLocaleDateString()} ))} diff --git a/src/app/(pages)/(protected)/council/mods/modal/mods-general.tsx b/src/app/(pages)/(protected)/council/mods/modal/mods-general.tsx index b2576f1e..b1506b1f 100644 --- a/src/app/(pages)/(protected)/council/mods/modal/mods-general.tsx +++ b/src/app/(pages)/(protected)/council/mods/modal/mods-general.tsx @@ -1,8 +1,8 @@ import { Badge, FileInput, Group, MultiSelect, Stack, TextInput, Text } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { TextureImage } from '~/components/texture-img'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { TextureImage } from '~/components/textures/texture-img'; import { BREAKPOINT_MOBILE_LARGE, MODS_LOADERS } from '~/lib/constants'; import type { ModModalFormValues } from './mods-modal'; @@ -17,8 +17,8 @@ export interface ModModalGeneralProps { } export function ModModalGeneral({ previewImg, mod, form }: ModModalGeneralProps) { - const [windowWidth] = useDeviceSize(); - const imageWidth = windowWidth <= BREAKPOINT_MOBILE_LARGE ? windowWidth * 0.7 : 220; + const { width } = useViewportSize(); + const imageWidth = width <= BREAKPOINT_MOBILE_LARGE ? width * 0.7 : 220; const filename: FileInputProps['valueComponent'] = ({ value }) => { if (value === null) return None; @@ -45,7 +45,7 @@ export function ModModalGeneral({ previewImg, mod, form }: ModModalGeneralProps) } - BREAKPOINT_MOBILE_LARGE ? `calc(100% - ${imageWidth}px - var(--mantine-spacing-md))` : '100%'} gap="sm"> + BREAKPOINT_MOBILE_LARGE ? `calc(100% - ${imageWidth}px - var(--mantine-spacing-md))` : '100%'} gap="sm"> diff --git a/src/app/(pages)/(protected)/council/mods/modal/mods-modal.tsx b/src/app/(pages)/(protected)/council/mods/modal/mods-modal.tsx index 6d81b370..0e2b0433 100644 --- a/src/app/(pages)/(protected)/council/mods/modal/mods-modal.tsx +++ b/src/app/(pages)/(protected)/council/mods/modal/mods-modal.tsx @@ -2,8 +2,8 @@ import { useState, useTransition } from 'react'; import { Button, Group, Tabs } from '@mantine/core'; import { useForm } from '@mantine/form'; +import { useViewportSize } from '@mantine/hooks'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_DESKTOP_LARGE, GRADIENT, GRADIENT_DANGER } from '~/lib/constants'; import { notify } from '~/lib/utils'; @@ -28,7 +28,7 @@ export interface ModModalFormValues { export function ModModal({ mod, onClose }: {mod: Mod, onClose: (editedMod: Mod | string) => void }) { const [isPending, startTransition] = useTransition(); const [previewImg, setPreviewImg] = useState(mod?.image || ''); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const form = useForm({ initialValues: { @@ -127,7 +127,7 @@ export function ModModal({ mod, onClose }: {mod: Mod, onClose: (editedMod: Mod | onClick={() => onDelete(mod.id)} disabled={isPending} loading={isPending} - fullWidth={windowWidth <= BREAKPOINT_DESKTOP_LARGE} + fullWidth={width <= BREAKPOINT_DESKTOP_LARGE} > Delete Mod @@ -137,7 +137,7 @@ export function ModModal({ mod, onClose }: {mod: Mod, onClose: (editedMod: Mod | onClick={() => onSubmit(form.values)} disabled={isPending || !form.isValid()} loading={isPending} - fullWidth={windowWidth <= BREAKPOINT_DESKTOP_LARGE} + fullWidth={width <= BREAKPOINT_DESKTOP_LARGE} > Update Mod diff --git a/src/app/(pages)/(protected)/council/mods/page.tsx b/src/app/(pages)/(protected)/council/mods/page.tsx index 74d5a8eb..89888fdc 100644 --- a/src/app/(pages)/(protected)/council/mods/page.tsx +++ b/src/app/(pages)/(protected)/council/mods/page.tsx @@ -3,16 +3,14 @@ import { useEffect, useMemo, useState, useTransition } from 'react'; import { Badge, Button, Group, Pagination, Select, Stack, Switch, Text, TextInput } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; +import { useDisclosure, usePrevious, useViewportSize } from '@mantine/hooks'; import { UserRole } from '@prisma/client'; +import { Modal } from '~/components/base/modal'; import { DashboardItem } from '~/components/dashboard-item/dashboard-item'; -import { Modal } from '~/components/modal'; import { ModUpload } from '~/components/mods-upload'; import { useCurrentUser } from '~/hooks/use-current-user'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; -import { usePrevious } from '~/hooks/use-previous'; import { BREAKPOINT_MOBILE_LARGE, GRADIENT_DANGER, ITEMS_PER_PAGE, ITEMS_PER_PAGE_DEFAULT } from '~/lib/constants'; import { searchFilter, sortByName } from '~/lib/utils'; import { getMods, modHasUnknownVersion, voidMods } from '~/server/data/mods'; @@ -23,9 +21,9 @@ import type { Mod } from '@prisma/client'; type ModWVer = Mod & { unknownVersion: boolean }; -const ModsPanel = () => { +export default function ModsPage() { const user = useCurrentUser()!; - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const [isPending, startTransition] = useTransition(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); @@ -146,7 +144,7 @@ const ModsPanel = () => { Select an existing mod or create a new one} label="Mod" diff --git a/src/app/(pages)/(protected)/council/textures/modal/texture-uses.tsx b/src/app/(pages)/(protected)/council/textures/modal/texture-uses.tsx index 3356881f..6224c7bf 100644 --- a/src/app/(pages)/(protected)/council/textures/modal/texture-uses.tsx +++ b/src/app/(pages)/(protected)/council/textures/modal/texture-uses.tsx @@ -9,8 +9,8 @@ import { TbHttpDelete } from 'react-icons/tb'; import { Stack, Group, Divider, Code, Image, Text, Button } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; -import { Modal } from '~/components/modal'; -import { Tile } from '~/components/tile'; +import { Modal } from '~/components/base/modal'; +import { Tile } from '~/components/base/tile'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { GRADIENT, GRADIENT_DANGER } from '~/lib/constants'; import { extractSemver } from '~/lib/utils'; diff --git a/src/app/(pages)/(protected)/council/textures/page.tsx b/src/app/(pages)/(protected)/council/textures/page.tsx index 0e0a78b8..5e136f66 100644 --- a/src/app/(pages)/(protected)/council/textures/page.tsx +++ b/src/app/(pages)/(protected)/council/textures/page.tsx @@ -3,13 +3,11 @@ import { useEffect, useMemo, useRef, useState, useTransition } from 'react'; import { Badge, Group, Loader, Pagination, Select, Stack, Text, TextInput } from '@mantine/core'; -import { useDisclosure } from '@mantine/hooks'; +import { useDisclosure, useViewportSize, usePrevious } from '@mantine/hooks'; -import { Modal } from '~/components/modal'; -import { GalleryTexture } from '~/components/texture'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { Modal } from '~/components/base/modal'; +import { GalleryTexture } from '~/components/textures/texture-gallery'; import { useEffectOnce } from '~/hooks/use-effect-once'; -import { usePrevious } from '~/hooks/use-previous'; import { BREAKPOINT_MOBILE_LARGE, ITEMS_PER_PAGE, ITEMS_PER_ROW } from '~/lib/constants'; import { notify, searchFilter, sortByName } from '~/lib/utils'; import { getTexture, getTextures } from '~/server/data/texture'; @@ -18,9 +16,9 @@ import { TextureModal } from './modal/texture-modal'; import type { Texture } from '@prisma/client'; -const CouncilTexturesPage = () => { +export default function CouncilTexturesPage() { const [isLoading, startTransition] = useTransition(); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const itemsPerPage = useMemo(() => ITEMS_PER_PAGE, []); @@ -147,7 +145,7 @@ const CouncilTexturesPage = () => { value={texturesShownPerPage} onChange={(e) => e ? setTexturesShownPerPage(e) : null} checkIconPosition="right" - w={windowWidth <= BREAKPOINT_MOBILE_LARGE ? 'calc(50% - var(--mantine-spacing-sm) / 2)' : 120} + w={width <= BREAKPOINT_MOBILE_LARGE ? 'calc(50% - var(--mantine-spacing-sm) / 2)' : 120} /> e ? setTexturesShownPerRow(parseInt(e)) : null} checkIconPosition="right" - w={windowWidth <= BREAKPOINT_MOBILE_LARGE ? 'calc(50% - var(--mantine-spacing-sm) / 2)' : 120} + w={width <= BREAKPOINT_MOBILE_LARGE ? 'calc(50% - var(--mantine-spacing-sm) / 2)' : 120} /> diff --git a/src/app/(pages)/mods/[modId]/layout.tsx b/src/app/(pages)/mods/[modId]/layout.tsx index 0197c14d..0271daaf 100644 --- a/src/app/(pages)/mods/[modId]/layout.tsx +++ b/src/app/(pages)/mods/[modId]/layout.tsx @@ -10,10 +10,10 @@ import { IoChevronBackOutline } from 'react-icons/io5'; import { TfiWorld } from 'react-icons/tfi'; import { Button, Group, Loader, Stack, Text, Title } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { TabsLayout } from '~/components/tabs'; -import { TextureImage } from '~/components/texture-img'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { TabsLayout } from '~/components/base/tabs-layout'; +import { TextureImage } from '~/components/textures/texture-img'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; import { getModDownloads, getModFromForgeId } from '~/server/data/mods'; @@ -27,8 +27,8 @@ export default function ModLayout({ children }: { children: React.ReactNode }) { const modId = useParams().modId as string; const [mod, setMod] = useState(null); - const [windowWidth] = useDeviceSize(); - const slice = windowWidth <= BREAKPOINT_MOBILE_LARGE ? 2 : 5; + const { width } = useViewportSize(); + const slice = width <= BREAKPOINT_MOBILE_LARGE ? 2 : 5; const [downloads, setDownloads] = useState(null); @@ -84,7 +84,7 @@ export default function ModLayout({ children }: { children: React.ReactNode }) { @@ -108,7 +108,7 @@ export default function ModLayout({ children }: { children: React.ReactNode }) { )} - {mod.description && ({mod.description})} + {mod.description && ({mod.description})} {!mod.description && (No description)} diff --git a/src/app/(pages)/mods/[modId]/page.tsx b/src/app/(pages)/mods/[modId]/page.tsx index 6d1469a6..878632bf 100644 --- a/src/app/(pages)/mods/[modId]/page.tsx +++ b/src/app/(pages)/mods/[modId]/page.tsx @@ -8,10 +8,10 @@ import { HiDownload } from 'react-icons/hi'; import { Button, Group, Pagination, Progress, Select, Stack, Text, TextInput, Tooltip } from '@mantine/core'; import { usePrevious } from '@mantine/hooks'; +import { useViewportSize } from '@mantine/hooks'; import { Resolution } from '@prisma/client'; -import { Tile } from '~/components/tile'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { Tile } from '~/components/base/tile'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE, ITEMS_PER_PAGE, RESOLUTIONS_COLORS, EMPTY_PROGRESSION, ITEMS_PER_PAGE_DEFAULT } from '~/lib/constants'; import { getModFromForgeId } from '~/server/data/mods'; @@ -24,7 +24,7 @@ export default function ModPage() { const modId = useParams().modId as string; const [mod, setMod] = useState(null); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const itemsPerPage = useMemo(() => ITEMS_PER_PAGE, []); const [activePage, setActivePage] = useState(1); @@ -168,9 +168,9 @@ export default function ModPage() { {resolutions.map((res) => ( @@ -188,7 +188,7 @@ export default function ModPage() { leftSection={} variant="light" color={RESOLUTIONS_COLORS[res]} - w={windowWidth <= BREAKPOINT_MOBILE_LARGE ? '100%' : 'auto'} + w={width <= BREAKPOINT_MOBILE_LARGE ? '100%' : 'auto'} className={progressions?.[ver.id]?.textures.done[res] === 0 ? 'button-disabled-with-bg' : ''} disabled={progressions?.[ver.id]?.textures.done[res] === 0} onClick={() => handlePackDownload(ver.id, res)} diff --git a/src/app/(pages)/mods/page.tsx b/src/app/(pages)/mods/page.tsx index 0be00c6d..d46747c1 100644 --- a/src/app/(pages)/mods/page.tsx +++ b/src/app/(pages)/mods/page.tsx @@ -11,12 +11,11 @@ import { SiMojangstudios } from 'react-icons/si'; import { TfiWorld } from 'react-icons/tfi'; import { ActionIcon, Button, Checkbox, Group, InputLabel, MultiSelect, Pagination, Radio, Select, Stack, Text, TextInput } from '@mantine/core'; +import { useViewportSize, usePrevious } from '@mantine/hooks'; -import { TextureImage } from '~/components/texture-img'; -import { Tile } from '~/components/tile'; -import { useDeviceSize } from '~/hooks/use-device-size'; +import { Tile } from '~/components/base/tile'; +import { TextureImage } from '~/components/textures/texture-img'; import { useEffectOnce } from '~/hooks/use-effect-once'; -import { usePrevious } from '~/hooks/use-previous'; import { BREAKPOINT_MOBILE_LARGE, BREAKPOINT_TABLET, ITEMS_PER_PAGE, ITEMS_PER_PAGE_DEFAULT, MODS_LOADERS } from '~/lib/constants'; import { searchFilter, sortByName, sortBySemver } from '~/lib/utils'; import { getModsOfModsPage } from '~/server/data/mods'; @@ -29,12 +28,12 @@ import type { Writable } from '~/types'; import './mods.scss'; import '~/lib/polyfills'; -export default function Mods() { - const [windowWidth] = useDeviceSize(); +export default function ModsPage() { + const { width } = useViewportSize(); const [activePage, setActivePage] = useState(1); const itemsPerPage = useMemo(() => ITEMS_PER_PAGE, []); - const slice = windowWidth <= BREAKPOINT_MOBILE_LARGE ? 2 : 5; + const slice = width <= BREAKPOINT_MOBILE_LARGE ? 2 : 5; const [mods, setMods] = useState([]); const [modsShown, setModsShown] = useState([[]]); @@ -53,7 +52,7 @@ export default function Mods() { const [showModsNoTextures, setShowModsNoTextures] = useState(false); const router = useRouter(); - const maxOptionsShown = windowWidth > BREAKPOINT_TABLET ? 2 : 1; + const maxOptionsShown = width > BREAKPOINT_TABLET ? 2 : 1; useEffectOnce(() => { getModsOfModsPage().then(setMods); @@ -112,8 +111,8 @@ export default function Mods() { const filter = () => { return ( @@ -185,9 +184,9 @@ export default function Mods() { const details = (m: ModOfModsPage) => { return ( {m.url && ( )} @@ -232,11 +231,11 @@ export default function Mods() { wrap="nowrap" > - {windowWidth > BREAKPOINT_TABLET && filter()} + {width > BREAKPOINT_TABLET && filter()} - {windowWidth <= BREAKPOINT_TABLET && ( + {width <= BREAKPOINT_TABLET && ( - {windowWidth <= BREAKPOINT_TABLET && showFilters && filter()} + {width <= BREAKPOINT_TABLET && showFilters && filter()} {filteredMods.length === 0 && ( BREAKPOINT_TABLET || (windowWidth <= BREAKPOINT_TABLET && !showFilters)) && + (width > BREAKPOINT_TABLET || (width <= BREAKPOINT_TABLET && !showFilters)) && modsShown[activePage - 1] && modsShown[activePage - 1]?.map((m) => ( @@ -310,11 +309,11 @@ export default function Mods() { {!m.description && (No description)} - {windowWidth > BREAKPOINT_MOBILE_LARGE && details(m)} + {width > BREAKPOINT_MOBILE_LARGE && details(m)} - {windowWidth <= BREAKPOINT_MOBILE_LARGE && details(m)} + {width <= BREAKPOINT_MOBILE_LARGE && details(m)} ))} diff --git a/src/app/(pages)/page.tsx b/src/app/(pages)/page.tsx index 444380bd..c798ea36 100644 --- a/src/app/(pages)/page.tsx +++ b/src/app/(pages)/page.tsx @@ -1,6 +1,6 @@ import { Image, Text, Stack } from '@mantine/core'; -export default async function Home() { +export default async function HomePage() { return ( diff --git a/src/components/base/fake-input-label.tsx b/src/components/base/fake-input-label.tsx new file mode 100644 index 00000000..3dda7683 --- /dev/null +++ b/src/components/base/fake-input-label.tsx @@ -0,0 +1,49 @@ +import type { FC } from 'react'; + +import { Stack, Text } from '@mantine/core'; + +interface Props { + children: React.ReactNode; + label: string; + description?: string; + gap?: number | string; + style?: React.CSSProperties; +} + +export const FakeInputLabel: FC = ({ gap, label, description, children, style }) => { + return ( + + + + {label} + + + + +
+ {children} +
+
+ ); +}; + +export const FakeInputDescription: FC> = ({ description, style }) => { + return ( + + {description} + + ); +}; diff --git a/src/components/modal.tsx b/src/components/base/modal.tsx similarity index 63% rename from src/components/modal.tsx rename to src/components/base/modal.tsx index 46fd2bdc..d21b3cce 100644 --- a/src/components/modal.tsx +++ b/src/components/base/modal.tsx @@ -1,9 +1,11 @@ +import type { FC } from 'react'; + import { Modal as MantineModal, Title } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; -interface ModalProps { +interface Props { opened: boolean; onClose: () => void; title?: React.ReactNode; @@ -13,8 +15,8 @@ interface ModalProps { children?: React.ReactNode; } -export function Modal({ opened, onClose, title, children, popup, forceFullScreen }: ModalProps) { - const [windowWidth] = useDeviceSize(); +export const Modal: FC = ({ opened, onClose, title, children, popup, forceFullScreen }) => { + const { width } = useViewportSize(); return ( {children} ); -} +}; diff --git a/src/components/role-gate.tsx b/src/components/base/role-gate.tsx similarity index 84% rename from src/components/role-gate.tsx rename to src/components/base/role-gate.tsx index 3dc5e582..c5623cb6 100644 --- a/src/components/role-gate.tsx +++ b/src/components/base/role-gate.tsx @@ -1,19 +1,21 @@ 'use client'; +import type { FC } from 'react'; + import { Stack, Title, Text } from '@mantine/core'; import { UserRole } from '@prisma/client'; import { useCurrentUser } from '~/hooks/use-current-user'; -interface RoleGateProps { +interface Props { children: React.ReactNode; allowedRoles: UserRole[]; }; -export const RoleGate = ({ - children, - allowedRoles, -}: RoleGateProps) => { +/** + * A component that gates access to children based on the logged user's role. + */ +export const RoleGate: FC = ({ children, allowedRoles }) => { const user = useCurrentUser(); const role = user?.role; diff --git a/src/components/small-tile.tsx b/src/components/base/small-tile.tsx similarity index 65% rename from src/components/small-tile.tsx rename to src/components/base/small-tile.tsx index d4311d20..833e1052 100644 --- a/src/components/small-tile.tsx +++ b/src/components/base/small-tile.tsx @@ -1,8 +1,10 @@ +import type { FC } from 'react'; + import { Tile } from './tile'; import type { CardProps, PolymorphicComponentProps } from '@mantine/core'; -export function SmallTile({ children, style, ...props }: PolymorphicComponentProps<'div', CardProps>) { +export const SmallTile: FC> = ({ children, style, ...props }) => { return ( ); -} +}; diff --git a/src/components/tabs.tsx b/src/components/base/tabs-layout.tsx similarity index 71% rename from src/components/tabs.tsx rename to src/components/base/tabs-layout.tsx index e3d52f3e..b3aca0de 100644 --- a/src/components/tabs.tsx +++ b/src/components/base/tabs-layout.tsx @@ -3,11 +3,11 @@ import { usePathname, useRouter } from 'next/navigation'; import { useMemo } from 'react'; import { Tabs } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; -interface TabsLayoutProps { +interface Props { noMargin?: boolean; children: React.ReactNode; defaultValue?: T[number]; @@ -20,8 +20,8 @@ interface TabsLayoutProps { }[]; }; -export const TabsLayout = ({ children, tabs, defaultValue, isLayout, variant, noMargin }: TabsLayoutProps) => { - const [windowWidth] = useDeviceSize(); +export const TabsLayout = ({ children, tabs, defaultValue, isLayout, variant, noMargin }: Props) => { + const { width } = useViewportSize(); const router = useRouter(); const pathname = usePathname(); @@ -47,20 +47,20 @@ export const TabsLayout = ({ children, tabs, defaultValue, i return router.push(`${(pathname.endsWith('/') ? pathname : `${pathname}/`).replace(currentTab, '')}${value}`); }} - orientation={windowWidth > BREAKPOINT_MOBILE_LARGE ? 'vertical' : 'horizontal'} - ml={windowWidth > BREAKPOINT_MOBILE_LARGE && tabsStyle === 'default' ? -125 : 0} + orientation={width > BREAKPOINT_MOBILE_LARGE ? 'vertical' : 'horizontal'} + ml={width > BREAKPOINT_MOBILE_LARGE && tabsStyle === 'default' ? -125 : 0} variant={tabsStyle === 'filled' - ? windowWidth > BREAKPOINT_MOBILE_LARGE ? 'pills' : 'default' + ? width > BREAKPOINT_MOBILE_LARGE ? 'pills' : 'default' : 'default' } defaultValue={defaultValue} > BREAKPOINT_MOBILE_LARGE && !noMargin ? 26 : 0} + mt={tabsStyle === 'filled' && width > BREAKPOINT_MOBILE_LARGE && !noMargin ? 26 : 0} mah={tabsStyle === 'default' ? tabs.length * 34 : undefined} - w={windowWidth > BREAKPOINT_MOBILE_LARGE ? (tabsStyle === 'default' ? 200 : 120) : undefined} + w={width > BREAKPOINT_MOBILE_LARGE ? (tabsStyle === 'default' ? 200 : 120) : undefined} > {tabs.map((tab) => ( ({ children, tabs, defaultValue, i BREAKPOINT_MOBILE_LARGE ? 'sm' : '0'} - pt={windowWidth > BREAKPOINT_MOBILE_LARGE ? '0' : 'sm'} + pl={width > BREAKPOINT_MOBILE_LARGE ? 'sm' : '0'} + pt={width > BREAKPOINT_MOBILE_LARGE ? '0' : 'sm'} > {children} diff --git a/src/components/theme-switch.tsx b/src/components/base/theme-switch.tsx similarity index 94% rename from src/components/theme-switch.tsx rename to src/components/base/theme-switch.tsx index 3414abf4..efe4b878 100644 --- a/src/components/theme-switch.tsx +++ b/src/components/base/theme-switch.tsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useState } from 'react'; +import type { FC } from 'react'; import { TbSun, TbMoon, TbSunMoon, TbCloud } from 'react-icons/tb'; @@ -8,7 +9,7 @@ import { ActionIcon, useMantineColorScheme } from '@mantine/core'; import type { MantineColorScheme } from '@mantine/core'; -export const ThemeSwitch = () => { +export const ThemeSwitch: FC = () => { const colorSchemes: MantineColorScheme[] = ['light', 'dark', 'auto']; const [icon, setIcon] = useState(); diff --git a/src/components/base/tile.tsx b/src/components/base/tile.tsx new file mode 100644 index 00000000..27a2fece --- /dev/null +++ b/src/components/base/tile.tsx @@ -0,0 +1,35 @@ +import type { FC } from 'react'; + +import { Card } from '@mantine/core'; + +import type { CardProps, MantineColor, PolymorphicComponentProps } from '@mantine/core'; + +interface Props { + shadowless?: boolean; + transparent?: boolean; + color?: MantineColor; +} + +export const Tile: FC & Props> = ({ + children, + shadowless, + transparent, + color, + ...props +}) => { + return ( + + {children} + + ); +}; diff --git a/src/components/dashboard-item/dashboard-item.tsx b/src/components/dashboard-item/dashboard-item.tsx index 96d4df6a..9c979fa9 100644 --- a/src/components/dashboard-item/dashboard-item.tsx +++ b/src/components/dashboard-item/dashboard-item.tsx @@ -1,15 +1,17 @@ +import type { FC } from 'react'; + import { Group, Image, Stack, Text } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { BREAKPOINT_DESKTOP_LARGE, BREAKPOINT_DESKTOP_MEDIUM, BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; -import { Tile } from '../tile'; +import { Tile } from '../base/tile'; import { WarningIcon } from '../warning-icon'; import './dashboard.scss'; -export interface ItemDisplayProps { +interface Props { image?: string | null, title: string, description?: string | null, @@ -17,19 +19,19 @@ export interface ItemDisplayProps { onClick: () => void } -export function DashboardItem({ image, title, description, onClick, warning }: ItemDisplayProps) { - const [windowWidth] = useDeviceSize(); +export const DashboardItem: FC = ({ image, title, description, onClick, warning }) => { + const { width } = useViewportSize(); return ( ); -} +}; diff --git a/src/components/fakeInputLabel.tsx b/src/components/fakeInputLabel.tsx deleted file mode 100644 index 5021e9d1..00000000 --- a/src/components/fakeInputLabel.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Stack, Text } from '@mantine/core'; - -interface Props { - children: React.ReactNode; - label: string; - description?: string; - gap?: number | string; -} - -export function FakeInputLabel({ gap, label, description, children }: Props) { - return ( - - - - {label} - - - {description} - - - -
- {children} -
-
- ); -} diff --git a/src/components/fork.tsx b/src/components/fork.tsx index 3bb06793..cd2675af 100644 --- a/src/components/fork.tsx +++ b/src/components/fork.tsx @@ -4,14 +4,14 @@ import { useTransition } from 'react'; import { GoCheckCircle, GoStop } from 'react-icons/go'; -import { Button, Group, Text } from '@mantine/core'; +import { Button, Group, Image, Text } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { useEffectOnce } from '~/hooks/use-effect-once'; import { BREAKPOINT_MOBILE_LARGE } from '~/lib/constants'; -import { forkRepository, getFork } from '~/server/actions/git'; +import { forkRepository, getFork } from '~/server/actions/octokit'; -import { Tile } from './tile'; +import { Tile } from './base/tile'; interface Props { onUrlUpdate: (url: string | null) => void; @@ -20,7 +20,7 @@ interface Props { } export default function ForkInfo({ onUrlUpdate, forkUrl, hideIfForked }: Props) { - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const [loading, startTransition] = useTransition(); const handleSetupForkedRepository = async () => { @@ -41,16 +41,31 @@ export default function ForkInfo({ onUrlUpdate, forkUrl, hideIfForked }: Props) if (forkUrl) { return ( - - - - - Default textures repository forked: - - - {windowWidth <= BREAKPOINT_MOBILE_LARGE ? 'link' : forkUrl} - - + + + + + + Default textures repository forked: + + + {width <= BREAKPOINT_MOBILE_LARGE ? 'link' : forkUrl} + + + + + + + @@ -58,7 +73,7 @@ export default function ForkInfo({ onUrlUpdate, forkUrl, hideIfForked }: Props) } return ( - + @@ -73,7 +88,7 @@ export default function ForkInfo({ onUrlUpdate, forkUrl, hideIfForked }: Props) onClick={handleSetupForkedRepository} disabled={!!forkUrl} loading={loading} - fullWidth={windowWidth <= BREAKPOINT_MOBILE_LARGE} + fullWidth={width <= BREAKPOINT_MOBILE_LARGE} > Create Fork diff --git a/src/components/mods-upload.tsx b/src/components/mods-upload.tsx index c2212005..cd3c0d05 100644 --- a/src/components/mods-upload.tsx +++ b/src/components/mods-upload.tsx @@ -11,12 +11,12 @@ import { addModVersionsFromJAR } from '~/server/data/mods-version'; import type { SocketModUpload } from '~/types'; -interface ModUploadProps { +interface Props { onUpload: () => void; socketIdSuffix?: string; } -export const ModUpload = ({ socketIdSuffix, onUpload }: ModUploadProps) => { +export const ModUpload = ({ socketIdSuffix, onUpload }: Props) => { const [status, setStatus] = useState(null); const userId = (socketIdSuffix ?? '') + useCurrentUser()!.id!; diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 413b18e8..c6306cea 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -14,21 +14,21 @@ import { MdDashboard } from 'react-icons/md'; import { TbPackage, TbPackages } from 'react-icons/tb'; import { ActionIcon, Avatar, Badge, Button, Divider, Group, Image, Menu } from '@mantine/core'; +import { useViewportSize } from '@mantine/hooks'; import { UserRole } from '@prisma/client'; import { signOut } from 'next-auth/react'; import { GitHubLogin } from '~/components/github-login'; import { useCurrentUser } from '~/hooks/use-current-user'; -import { useDeviceSize } from '~/hooks/use-device-size'; import { BREAKPOINT_TABLET, GRADIENT } from '~/lib/constants'; -import { ThemeSwitch } from './theme-switch'; -import { Tile } from './tile'; +import { ThemeSwitch } from './base/theme-switch'; +import { Tile } from './base/tile'; export const Navbar = () => { const pathname = usePathname(); const user = useCurrentUser(); - const [windowWidth] = useDeviceSize(); + const { width } = useViewportSize(); const [userPicture, setUserPicture] = useState(user?.image ?? undefined); const links = [ @@ -53,7 +53,7 @@ export const Navbar = () => { icon: , }); - if (windowWidth < BREAKPOINT_TABLET) links.unshift({ + if (width < BREAKPOINT_TABLET) links.unshift({ href: '/', label: 'Home', disabled: false, @@ -62,7 +62,7 @@ export const Navbar = () => { return ( - {windowWidth >= BREAKPOINT_TABLET && ( + {width >= BREAKPOINT_TABLET && ( <> @@ -73,13 +73,13 @@ export const Navbar = () => { )} - = BREAKPOINT_TABLET ? 'wrap' : 'nowrap'}> - = BREAKPOINT_TABLET ? 'wrap' : 'nowrap'}> - {windowWidth < BREAKPOINT_TABLET && + = BREAKPOINT_TABLET ? 'wrap' : 'nowrap'}> + = BREAKPOINT_TABLET ? 'wrap' : 'nowrap'}> + {width < BREAKPOINT_TABLET && } - {user && user.role === UserRole.BANNED && windowWidth < BREAKPOINT_TABLET && + {user && user.role === UserRole.BANNED && width < BREAKPOINT_TABLET && banned } - {windowWidth >= BREAKPOINT_TABLET && links.map((link, index) => ( + {width >= BREAKPOINT_TABLET && links.map((link, index) => (