@@ -9,10 +9,18 @@ | |||
"version": "0.0.0", | |||
"license": "ISC", | |||
"dependencies": { | |||
"@radix-ui/react-dialog": "^1.1.14", | |||
"@radix-ui/react-switch": "^1.2.5", | |||
"@radix-ui/react-toast": "^1.2.14", | |||
"axios": "^1.9.0", | |||
"class-variance-authority": "^0.7.1", | |||
"clsx": "^2.1.1", | |||
"humps": "^2.0.1", | |||
"lucide-react": "^0.511.0", | |||
"react": "^19.1.0", | |||
"react-dom": "^19.1.0", | |||
"react-router-dom": "^6.30.0" | |||
"react-router-dom": "^6.30.0", | |||
"tailwind-merge": "^3.3.0" | |||
}, | |||
"devDependencies": { | |||
"@eslint/js": "^9.25.0", | |||
@@ -1107,6 +1115,482 @@ | |||
"node": ">=14" | |||
} | |||
}, | |||
"node_modules/@radix-ui/primitive": { | |||
"version": "1.1.2", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", | |||
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", | |||
"license": "MIT" | |||
}, | |||
"node_modules/@radix-ui/react-collection": { | |||
"version": "1.1.7", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", | |||
"integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-context": "1.1.2", | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-slot": "1.2.3" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-compose-refs": { | |||
"version": "1.1.2", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", | |||
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-context": { | |||
"version": "1.1.2", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", | |||
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-dialog": { | |||
"version": "1.1.14", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", | |||
"integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/primitive": "1.1.2", | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-context": "1.1.2", | |||
"@radix-ui/react-dismissable-layer": "1.1.10", | |||
"@radix-ui/react-focus-guards": "1.1.2", | |||
"@radix-ui/react-focus-scope": "1.1.7", | |||
"@radix-ui/react-id": "1.1.1", | |||
"@radix-ui/react-portal": "1.1.9", | |||
"@radix-ui/react-presence": "1.1.4", | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-slot": "1.2.3", | |||
"@radix-ui/react-use-controllable-state": "1.2.2", | |||
"aria-hidden": "^1.2.4", | |||
"react-remove-scroll": "^2.6.3" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-dismissable-layer": { | |||
"version": "1.1.10", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", | |||
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/primitive": "1.1.2", | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-use-callback-ref": "1.1.1", | |||
"@radix-ui/react-use-escape-keydown": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-focus-guards": { | |||
"version": "1.1.2", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", | |||
"integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-focus-scope": { | |||
"version": "1.1.7", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", | |||
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-use-callback-ref": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-id": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", | |||
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-use-layout-effect": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-portal": { | |||
"version": "1.1.9", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", | |||
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-use-layout-effect": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-presence": { | |||
"version": "1.1.4", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", | |||
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-use-layout-effect": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-primitive": { | |||
"version": "2.1.3", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", | |||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-slot": "1.2.3" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-slot": { | |||
"version": "1.2.3", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", | |||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-compose-refs": "1.1.2" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-switch": { | |||
"version": "1.2.5", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz", | |||
"integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/primitive": "1.1.2", | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-context": "1.1.2", | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-use-controllable-state": "1.2.2", | |||
"@radix-ui/react-use-previous": "1.1.1", | |||
"@radix-ui/react-use-size": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-toast": { | |||
"version": "1.2.14", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.14.tgz", | |||
"integrity": "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/primitive": "1.1.2", | |||
"@radix-ui/react-collection": "1.1.7", | |||
"@radix-ui/react-compose-refs": "1.1.2", | |||
"@radix-ui/react-context": "1.1.2", | |||
"@radix-ui/react-dismissable-layer": "1.1.10", | |||
"@radix-ui/react-portal": "1.1.9", | |||
"@radix-ui/react-presence": "1.1.4", | |||
"@radix-ui/react-primitive": "2.1.3", | |||
"@radix-ui/react-use-callback-ref": "1.1.1", | |||
"@radix-ui/react-use-controllable-state": "1.2.2", | |||
"@radix-ui/react-use-layout-effect": "1.1.1", | |||
"@radix-ui/react-visually-hidden": "1.2.3" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-callback-ref": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", | |||
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-controllable-state": { | |||
"version": "1.2.2", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", | |||
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-use-effect-event": "0.0.2", | |||
"@radix-ui/react-use-layout-effect": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-effect-event": { | |||
"version": "0.0.2", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", | |||
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-use-layout-effect": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-escape-keydown": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", | |||
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-use-callback-ref": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-layout-effect": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", | |||
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-previous": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", | |||
"integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-use-size": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", | |||
"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-use-layout-effect": "1.1.1" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@radix-ui/react-visually-hidden": { | |||
"version": "1.2.3", | |||
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", | |||
"integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"@radix-ui/react-primitive": "2.1.3" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"@types/react-dom": "*", | |||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", | |||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
}, | |||
"@types/react-dom": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/@remix-run/router": { | |||
"version": "1.23.0", | |||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", | |||
@@ -1473,7 +1957,7 @@ | |||
"version": "19.1.4", | |||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", | |||
"integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", | |||
"dev": true, | |||
"devOptional": true, | |||
"license": "MIT", | |||
"dependencies": { | |||
"csstype": "^3.0.2" | |||
@@ -1483,7 +1967,7 @@ | |||
"version": "19.1.5", | |||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", | |||
"integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", | |||
"dev": true, | |||
"devOptional": true, | |||
"license": "MIT", | |||
"peerDependencies": { | |||
"@types/react": "^19.0.0" | |||
@@ -1865,6 +2349,18 @@ | |||
"dev": true, | |||
"license": "Python-2.0" | |||
}, | |||
"node_modules/aria-hidden": { | |||
"version": "1.2.6", | |||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", | |||
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"tslib": "^2.0.0" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
} | |||
}, | |||
"node_modules/asynckit": { | |||
"version": "0.4.0", | |||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", | |||
@@ -2106,6 +2602,27 @@ | |||
"node": ">= 6" | |||
} | |||
}, | |||
"node_modules/class-variance-authority": { | |||
"version": "0.7.1", | |||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", | |||
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", | |||
"license": "Apache-2.0", | |||
"dependencies": { | |||
"clsx": "^2.1.1" | |||
}, | |||
"funding": { | |||
"url": "https://polar.sh/cva" | |||
} | |||
}, | |||
"node_modules/clsx": { | |||
"version": "2.1.1", | |||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", | |||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", | |||
"license": "MIT", | |||
"engines": { | |||
"node": ">=6" | |||
} | |||
}, | |||
"node_modules/color-convert": { | |||
"version": "2.0.1", | |||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | |||
@@ -2194,7 +2711,7 @@ | |||
"version": "3.1.3", | |||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", | |||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", | |||
"dev": true, | |||
"devOptional": true, | |||
"license": "MIT" | |||
}, | |||
"node_modules/debug": { | |||
@@ -2231,6 +2748,12 @@ | |||
"node": ">=0.4.0" | |||
} | |||
}, | |||
"node_modules/detect-node-es": { | |||
"version": "1.1.0", | |||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", | |||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", | |||
"license": "MIT" | |||
}, | |||
"node_modules/didyoumean": { | |||
"version": "1.2.2", | |||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", | |||
@@ -2816,6 +3339,15 @@ | |||
"url": "https://github.com/sponsors/ljharb" | |||
} | |||
}, | |||
"node_modules/get-nonce": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", | |||
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", | |||
"license": "MIT", | |||
"engines": { | |||
"node": ">=6" | |||
} | |||
}, | |||
"node_modules/get-proto": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", | |||
@@ -2970,6 +3502,12 @@ | |||
"node": ">= 0.4" | |||
} | |||
}, | |||
"node_modules/humps": { | |||
"version": "2.0.1", | |||
"resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", | |||
"integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", | |||
"license": "MIT" | |||
}, | |||
"node_modules/ignore": { | |||
"version": "5.3.2", | |||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", | |||
@@ -3253,6 +3791,15 @@ | |||
"yallist": "^3.0.2" | |||
} | |||
}, | |||
"node_modules/lucide-react": { | |||
"version": "0.511.0", | |||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", | |||
"integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", | |||
"license": "ISC", | |||
"peerDependencies": { | |||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" | |||
} | |||
}, | |||
"node_modules/math-intrinsics": { | |||
"version": "1.1.0", | |||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", | |||
@@ -3824,6 +4371,53 @@ | |||
"node": ">=0.10.0" | |||
} | |||
}, | |||
"node_modules/react-remove-scroll": { | |||
"version": "2.7.0", | |||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.0.tgz", | |||
"integrity": "sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"react-remove-scroll-bar": "^2.3.7", | |||
"react-style-singleton": "^2.2.3", | |||
"tslib": "^2.1.0", | |||
"use-callback-ref": "^1.3.3", | |||
"use-sidecar": "^1.1.3" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/react-remove-scroll-bar": { | |||
"version": "2.3.8", | |||
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", | |||
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"react-style-singleton": "^2.2.2", | |||
"tslib": "^2.0.0" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/react-router": { | |||
"version": "6.30.0", | |||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", | |||
@@ -3856,6 +4450,28 @@ | |||
"react-dom": ">=16.8" | |||
} | |||
}, | |||
"node_modules/react-style-singleton": { | |||
"version": "2.2.3", | |||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", | |||
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"get-nonce": "^1.0.0", | |||
"tslib": "^2.0.0" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/read-cache": { | |||
"version": "1.0.0", | |||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", | |||
@@ -4213,6 +4829,16 @@ | |||
"url": "https://github.com/sponsors/ljharb" | |||
} | |||
}, | |||
"node_modules/tailwind-merge": { | |||
"version": "3.3.0", | |||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz", | |||
"integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==", | |||
"license": "MIT", | |||
"funding": { | |||
"type": "github", | |||
"url": "https://github.com/sponsors/dcastil" | |||
} | |||
}, | |||
"node_modules/tailwindcss": { | |||
"version": "3.4.13", | |||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", | |||
@@ -4352,6 +4978,12 @@ | |||
"dev": true, | |||
"license": "Apache-2.0" | |||
}, | |||
"node_modules/tslib": { | |||
"version": "2.8.1", | |||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", | |||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", | |||
"license": "0BSD" | |||
}, | |||
"node_modules/type-check": { | |||
"version": "0.4.0", | |||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", | |||
@@ -4443,6 +5075,49 @@ | |||
"punycode": "^2.1.0" | |||
} | |||
}, | |||
"node_modules/use-callback-ref": { | |||
"version": "1.3.3", | |||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", | |||
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"tslib": "^2.0.0" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/use-sidecar": { | |||
"version": "1.1.3", | |||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", | |||
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", | |||
"license": "MIT", | |||
"dependencies": { | |||
"detect-node-es": "^1.1.0", | |||
"tslib": "^2.0.0" | |||
}, | |||
"engines": { | |||
"node": ">=10" | |||
}, | |||
"peerDependencies": { | |||
"@types/react": "*", | |||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" | |||
}, | |||
"peerDependenciesMeta": { | |||
"@types/react": { | |||
"optional": true | |||
} | |||
} | |||
}, | |||
"node_modules/util-deprecate": { | |||
"version": "1.0.2", | |||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", | |||
@@ -10,10 +10,18 @@ | |||
"preview": "vite preview" | |||
}, | |||
"dependencies": { | |||
"@radix-ui/react-dialog": "^1.1.14", | |||
"@radix-ui/react-switch": "^1.2.5", | |||
"@radix-ui/react-toast": "^1.2.14", | |||
"axios": "^1.9.0", | |||
"class-variance-authority": "^0.7.1", | |||
"clsx": "^2.1.1", | |||
"humps": "^2.0.1", | |||
"lucide-react": "^0.511.0", | |||
"react": "^19.1.0", | |||
"react-dom": "^19.1.0", | |||
"react-router-dom": "^6.30.0" | |||
"react-router-dom": "^6.30.0", | |||
"tailwind-merge": "^3.3.0" | |||
}, | |||
"devDependencies": { | |||
"@eslint/js": "^9.25.0", | |||
@@ -8,6 +8,8 @@ import PostPage from './pages/PostPage' | |||
import PostDetailPage from './pages/PostDetailPage' | |||
import { API_BASE_URL } from './config' | |||
import axios from 'axios' | |||
import { Toaster } from '@/components/ui/toaster' | |||
import { camelizeKeys } from 'humps' | |||
type Tag = { id: number | |||
name: string | |||
@@ -33,10 +35,11 @@ const App = () => { | |||
const createUser = () => ( | |||
axios.post (`${ API_BASE_URL }/users`) | |||
.then (res => { | |||
if (res.data.code) | |||
{ | |||
localStorage.setItem ('user_code', res.data.code) | |||
setUser (res.data) | |||
setUser (camelizeKeys (res.data.user)) | |||
} | |||
})) | |||
@@ -46,7 +49,7 @@ const App = () => { | |||
void (axios.post (`${ API_BASE_URL }/users/verify`, { code }) | |||
.then (res => { | |||
if (res.data.valid) | |||
setUser (res.data.user) | |||
setUser (camelizeKeys (res.data.user)) | |||
else | |||
createUser () | |||
})) | |||
@@ -58,21 +61,24 @@ const App = () => { | |||
}, []) | |||
return ( | |||
<Router> | |||
<div className="flex flex-col h-screen w-screen"> | |||
<TopNav user={user} /> | |||
<div className="flex flex-1"> | |||
<TagSidebar posts={posts} setPosts={setPosts} /> | |||
<main className="flex-1 overflow-y-auto p-4"> | |||
<Routes> | |||
<Route path="/posts/:id" element={<PostDetailPage posts={posts} setPosts={setPosts} />} /> | |||
<Route path="/tags/:tag" element={<TagPage />} /> | |||
<Route path="*" element={<PostPage posts={posts} setPosts={setPosts} />} /> | |||
</Routes> | |||
</main> | |||
<> | |||
<Router> | |||
<div className="flex flex-col h-screen w-screen"> | |||
<TopNav user={user} setUser={setUser} /> | |||
<div className="flex flex-1"> | |||
<TagSidebar posts={posts} setPosts={setPosts} /> | |||
<main className="flex-1 overflow-y-auto p-4"> | |||
<Routes> | |||
<Route path="/posts/:id" element={<PostDetailPage posts={posts} setPosts={setPosts} />} /> | |||
<Route path="/tags/:tag" element={<TagPage />} /> | |||
<Route path="*" element={<PostPage posts={posts} setPosts={setPosts} />} /> | |||
</Routes> | |||
</main> | |||
</div> | |||
</div> | |||
</div> | |||
</Router>) | |||
</Router> | |||
<Toaster /> | |||
</>) | |||
} | |||
@@ -0,0 +1,86 @@ | |||
import React, { useEffect, useState } from 'react' | |||
import { Dialog, DialogTrigger, DialogContent, DialogTitle, DialogDescription } from './ui/dialog' | |||
import { Switch } from './ui/switch' | |||
import { Button } from './ui/button' | |||
import { Input } from '@/components/ui/input' | |||
import { toast } from '@/components/ui/use-toast' | |||
import axios from 'axios' | |||
import { Link, useNavigate, useLocation } from 'react-router-dom' | |||
import { API_BASE_URL } from '../config' | |||
import { camelizeKeys } from 'humps' | |||
type Tag = { id: number | |||
name: string | |||
category: string | |||
count?: number } | |||
type User = { id: number | |||
name: string | null | |||
inheritanceCode: string | |||
role: string } | |||
type Props = { visible: boolean | |||
onVisibleChange: (visible: boolean) => void | |||
user: User | |||
setUser: (user: User) => void } | |||
const SettingsDialogue: React.FC = ({ visible, onVisibleChange, user, setUser }: Props) => { | |||
const [inputCode, setInputCode] = useState ('') | |||
const handleShowCode = () => { | |||
if (user?.inheritanceCode) | |||
toast ({ title: 'あなたの引継ぎコード', description: user.inheritanceCode }) | |||
else | |||
toast ({ title: '引継ぎコードが見つかりません.' }) | |||
} | |||
const handleTransfer = async () => { | |||
try | |||
{ | |||
void (axios.post (`${ API_BASE_URL }/users/verify`, { code: inputCode }) | |||
.then (res => { | |||
if (res.data.valid) | |||
{ | |||
localStorage.setItem ('user_code', inputCode) | |||
setUser (camelizeKeys (res.data.user)) | |||
toast ({ title: '引継ぎ成功!' }) | |||
} | |||
else | |||
toast ({ title: '認証失敗', description: 'そのコードは使へません.' }) | |||
})) | |||
} | |||
catch (e) | |||
{ | |||
toast ({ title: '通信エラー', description: 'またあとで試してね.' }) | |||
} | |||
} | |||
return ( | |||
<Dialog open={visible} onOpenChange={onVisibleChange}> | |||
<DialogContent className="space-y-6"> | |||
<DialogTitle>ユーザ設定</DialogTitle> | |||
<DialogDescription> | |||
ユーザの設定や引継ぎコードの確認、変更ができます。 | |||
</DialogDescription> | |||
<div> | |||
<p className="mb-1 font-semibold">引継ぎコード</p> | |||
<Button variant="secondary" onClick={handleShowCode}> | |||
引継ぎコードを表示 | |||
</Button> | |||
</div> | |||
<div> | |||
<p className="mb-1 font-semibold">ほかのブラウザから引継ぐ</p> | |||
<div className="flex gap-2"> | |||
<Input placeholder="引継ぎコードを入力" | |||
value={inputCode} | |||
onChange={e => setInputCode (e.target.value)} /> | |||
<Button onClick={handleTransfer}>引継ぐ</Button> | |||
</div> | |||
</div> | |||
</DialogContent> | |||
</Dialog>) | |||
} | |||
export default SettingsDialogue |
@@ -1,27 +1,38 @@ | |||
import React from "react" | |||
import React, { useState } from "react" | |||
import { Link } from 'react-router-dom' | |||
import SettingsDialogue from './SettingsDialogue' | |||
import { Button } from './ui/button' | |||
type User = { id: number | |||
name: string | null | |||
inheritanceCode: string | |||
role: string } | |||
type Props = { user: User } | |||
type Props = { user: User | |||
setUser: (user: User) => void } | |||
const TopNav: React.FC = ({ user }: Props) => ( | |||
<nav className="bg-gray-800 text-white p-3 flex justify-between items-center w-full"> | |||
<div className="flex items-center gap-4"> | |||
<Link to="/" className="text-xl font-bold text-orange-500">ぼざクリ タグ広場</Link> | |||
<Link to="/posts" className="hover:text-orange-500">広場</Link> | |||
<Link to="/deerjikists" className="hover:text-orange-500">ニジラー</Link> | |||
<Link to="/tags" className="hover:text-orange-500">タグ</Link> | |||
<Link to="/wiki" className="hover:text-orange-500">Wiki</Link> | |||
</div> | |||
<div className="ml-auto pr-4"> | |||
<Link to="/setting" className="hover:text-orange-500">{user?.name || '名もなきニジラー'}</Link> | |||
</div> | |||
</nav>) | |||
const TopNav: React.FC = ({ user, setUser }: Props) => { | |||
const [settingsVisible, setSettingsVisible] = useState (false) | |||
return ( | |||
<nav className="bg-gray-800 text-white p-3 flex justify-between items-center w-full"> | |||
<div className="flex items-center gap-4"> | |||
<Link to="/" className="text-xl font-bold text-orange-500">ぼざクリ タグ広場</Link> | |||
<Link to="/posts" className="hover:text-orange-500">広場</Link> | |||
<Link to="/deerjikists" className="hover:text-orange-500">ニジラー</Link> | |||
<Link to="/tags" className="hover:text-orange-500">タグ</Link> | |||
<Link to="/wiki" className="hover:text-orange-500">Wiki</Link> | |||
</div> | |||
<div className="ml-auto pr-4"> | |||
<Button onClick={() => setSettingsVisible (true)}>{user?.name || '名もなきニジラー'}</Button> | |||
<SettingsDialogue visible={settingsVisible} | |||
onVisibleChange={setSettingsVisible} | |||
user={user} | |||
setUser={setUser} /> | |||
</div> | |||
</nav>) | |||
} | |||
export default TopNav |
@@ -0,0 +1,56 @@ | |||
import * as React from "react" | |||
import { Slot } from "@radix-ui/react-slot" | |||
import { cva, type VariantProps } from "class-variance-authority" | |||
import { cn } from "@/lib/utils" | |||
const buttonVariants = cva( | |||
"inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", | |||
{ | |||
variants: { | |||
variant: { | |||
default: "bg-primary text-primary-foreground hover:bg-primary/90", | |||
destructive: | |||
"bg-destructive text-destructive-foreground hover:bg-destructive/90", | |||
outline: | |||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground", | |||
secondary: | |||
"bg-secondary text-secondary-foreground hover:bg-secondary/80", | |||
ghost: "hover:bg-accent hover:text-accent-foreground", | |||
link: "text-primary underline-offset-4 hover:underline", | |||
}, | |||
size: { | |||
default: "h-10 px-4 py-2", | |||
sm: "h-9 rounded-md px-3", | |||
lg: "h-11 rounded-md px-8", | |||
icon: "h-10 w-10", | |||
}, | |||
}, | |||
defaultVariants: { | |||
variant: "default", | |||
size: "default", | |||
}, | |||
} | |||
) | |||
export interface ButtonProps | |||
extends React.ButtonHTMLAttributes<HTMLButtonElement>, | |||
VariantProps<typeof buttonVariants> { | |||
asChild?: boolean | |||
} | |||
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" | |||
export { Button, buttonVariants } |
@@ -0,0 +1,122 @@ | |||
"use client" | |||
import * as React from "react" | |||
import * as DialogPrimitive from "@radix-ui/react-dialog" | |||
import { X } from "lucide-react" | |||
import { cn } from "@/lib/utils" | |||
const Dialog = DialogPrimitive.Root | |||
const DialogTrigger = DialogPrimitive.Trigger | |||
const DialogPortal = DialogPrimitive.Portal | |||
const DialogClose = DialogPrimitive.Close | |||
const DialogOverlay = React.forwardRef< | |||
React.ElementRef<typeof DialogPrimitive.Overlay>, | |||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> | |||
>(({ className, ...props }, ref) => ( | |||
<DialogPrimitive.Overlay | |||
ref={ref} | |||
className={cn( | |||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", | |||
className | |||
)} | |||
{...props} | |||
/> | |||
)) | |||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName | |||
const DialogContent = React.forwardRef< | |||
React.ElementRef<typeof DialogPrimitive.Content>, | |||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> | |||
>(({ className, children, ...props }, ref) => ( | |||
<DialogPortal> | |||
<DialogOverlay /> | |||
<DialogPrimitive.Content | |||
ref={ref} | |||
className={cn( | |||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg 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} | |||
> | |||
{children} | |||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> | |||
<X className="h-4 w-4" /> | |||
<span className="sr-only">Close</span> | |||
</DialogPrimitive.Close> | |||
</DialogPrimitive.Content> | |||
</DialogPortal> | |||
)) | |||
DialogContent.displayName = DialogPrimitive.Content.displayName | |||
const DialogHeader = ({ | |||
className, | |||
...props | |||
}: React.HTMLAttributes<HTMLDivElement>) => ( | |||
<div | |||
className={cn( | |||
"flex flex-col space-y-1.5 text-center sm:text-left", | |||
className | |||
)} | |||
{...props} | |||
/> | |||
) | |||
DialogHeader.displayName = "DialogHeader" | |||
const DialogFooter = ({ | |||
className, | |||
...props | |||
}: React.HTMLAttributes<HTMLDivElement>) => ( | |||
<div | |||
className={cn( | |||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", | |||
className | |||
)} | |||
{...props} | |||
/> | |||
) | |||
DialogFooter.displayName = "DialogFooter" | |||
const DialogTitle = React.forwardRef< | |||
React.ElementRef<typeof DialogPrimitive.Title>, | |||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> | |||
>(({ className, ...props }, ref) => ( | |||
<DialogPrimitive.Title | |||
ref={ref} | |||
className={cn( | |||
"text-lg font-semibold leading-none tracking-tight", | |||
className | |||
)} | |||
{...props} | |||
/> | |||
)) | |||
DialogTitle.displayName = DialogPrimitive.Title.displayName | |||
const DialogDescription = React.forwardRef< | |||
React.ElementRef<typeof DialogPrimitive.Description>, | |||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> | |||
>(({ className, ...props }, ref) => ( | |||
<DialogPrimitive.Description | |||
ref={ref} | |||
className={cn("text-sm text-muted-foreground", className)} | |||
{...props} | |||
/> | |||
)) | |||
DialogDescription.displayName = DialogPrimitive.Description.displayName | |||
export { | |||
Dialog, | |||
DialogPortal, | |||
DialogOverlay, | |||
DialogClose, | |||
DialogTrigger, | |||
DialogContent, | |||
DialogHeader, | |||
DialogFooter, | |||
DialogTitle, | |||
DialogDescription, | |||
} |
@@ -0,0 +1,22 @@ | |||
import * as React from "react" | |||
import { cn } from "@/lib/utils" | |||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( | |||
({ className, type, ...props }, ref) => { | |||
return ( | |||
<input | |||
type={type} | |||
className={cn( | |||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", | |||
className | |||
)} | |||
ref={ref} | |||
{...props} | |||
/> | |||
) | |||
} | |||
) | |||
Input.displayName = "Input" | |||
export { Input } |
@@ -0,0 +1,29 @@ | |||
"use client" | |||
import * as React from "react" | |||
import * as SwitchPrimitives from "@radix-ui/react-switch" | |||
import { cn } from "@/lib/utils" | |||
const Switch = React.forwardRef< | |||
React.ElementRef<typeof SwitchPrimitives.Root>, | |||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> | |||
>(({ className, ...props }, ref) => ( | |||
<SwitchPrimitives.Root | |||
className={cn( | |||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", | |||
className | |||
)} | |||
{...props} | |||
ref={ref} | |||
> | |||
<SwitchPrimitives.Thumb | |||
className={cn( | |||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0" | |||
)} | |||
/> | |||
</SwitchPrimitives.Root> | |||
)) | |||
Switch.displayName = SwitchPrimitives.Root.displayName | |||
export { Switch } |
@@ -0,0 +1,129 @@ | |||
"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 { cn } from "@/lib/utils" | |||
const ToastProvider = ToastPrimitives.Provider | |||
const ToastViewport = React.forwardRef< | |||
React.ElementRef<typeof ToastPrimitives.Viewport>, | |||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> | |||
>(({ className, ...props }, ref) => ( | |||
<ToastPrimitives.Viewport | |||
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 | |||
)} | |||
{...props} | |||
/> | |||
)) | |||
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", | |||
{ | |||
variants: { | |||
variant: { | |||
default: "border bg-background text-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> | |||
>(({ className, variant, ...props }, ref) => { | |||
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>, | |||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> | |||
>(({ className, ...props }, ref) => ( | |||
<ToastPrimitives.Action | |||
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 | |||
)} | |||
{...props} | |||
/> | |||
)) | |||
ToastAction.displayName = ToastPrimitives.Action.displayName | |||
const ToastClose = React.forwardRef< | |||
React.ElementRef<typeof ToastPrimitives.Close>, | |||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> | |||
>(({ className, ...props }, ref) => ( | |||
<ToastPrimitives.Close | |||
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 | |||
)} | |||
toast-close="" | |||
{...props} | |||
> | |||
<X className="h-4 w-4" /> | |||
</ToastPrimitives.Close> | |||
)) | |||
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 | |||
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 | |||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> | |||
type ToastActionElement = React.ReactElement<typeof ToastAction> | |||
export { | |||
type ToastProps, | |||
type ToastActionElement, | |||
ToastProvider, | |||
ToastViewport, | |||
Toast, | |||
ToastTitle, | |||
ToastDescription, | |||
ToastClose, | |||
ToastAction, | |||
} |
@@ -0,0 +1,35 @@ | |||
"use client" | |||
import { useToast } from "@/components/ui/use-toast" | |||
import { | |||
Toast, | |||
ToastClose, | |||
ToastDescription, | |||
ToastProvider, | |||
ToastTitle, | |||
ToastViewport, | |||
} from "@/components/ui/toast" | |||
export function Toaster() { | |||
const { toasts } = useToast() | |||
return ( | |||
<ToastProvider> | |||
{toasts.map(function ({ id, title, description, action, ...props }) { | |||
return ( | |||
<Toast key={id} {...props}> | |||
<div className="grid gap-1"> | |||
{title && <ToastTitle>{title}</ToastTitle>} | |||
{description && ( | |||
<ToastDescription>{description}</ToastDescription> | |||
)} | |||
</div> | |||
{action} | |||
<ToastClose /> | |||
</Toast> | |||
) | |||
})} | |||
<ToastViewport /> | |||
</ToastProvider> | |||
) | |||
} |
@@ -0,0 +1,194 @@ | |||
"use client" | |||
// Inspired by react-hot-toast library | |||
import * as React from "react" | |||
import type { | |||
ToastActionElement, | |||
ToastProps, | |||
} from "@/registry/default/ui/toast" | |||
const TOAST_LIMIT = 1 | |||
const TOAST_REMOVE_DELAY = 1000000 | |||
type ToasterToast = ToastProps & { | |||
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 | |||
let count = 0 | |||
function genId() { | |||
count = (count + 1) % Number.MAX_SAFE_INTEGER | |||
return count.toString() | |||
} | |||
type ActionType = typeof actionTypes | |||
type Action = | |||
| { | |||
type: ActionType["ADD_TOAST"] | |||
toast: ToasterToast | |||
} | |||
| { | |||
type: ActionType["UPDATE_TOAST"] | |||
toast: Partial<ToasterToast> | |||
} | |||
| { | |||
type: ActionType["DISMISS_TOAST"] | |||
toastId?: ToasterToast["id"] | |||
} | |||
| { | |||
type: ActionType["REMOVE_TOAST"] | |||
toastId?: ToasterToast["id"] | |||
} | |||
interface State { | |||
toasts: ToasterToast[] | |||
} | |||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() | |||
const addToRemoveQueue = (toastId: string) => { | |||
if (toastTimeouts.has(toastId)) { | |||
return | |||
} | |||
const timeout = setTimeout(() => { | |||
toastTimeouts.delete(toastId) | |||
dispatch({ | |||
type: "REMOVE_TOAST", | |||
toastId: toastId, | |||
}) | |||
}, TOAST_REMOVE_DELAY) | |||
toastTimeouts.set(toastId, timeout) | |||
} | |||
export const reducer = (state: State, action: Action): State => { | |||
switch (action.type) { | |||
case "ADD_TOAST": | |||
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 | |||
), | |||
} | |||
case "DISMISS_TOAST": { | |||
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) | |||
} else { | |||
state.toasts.forEach((toast) => { | |||
addToRemoveQueue(toast.id) | |||
}) | |||
} | |||
return { | |||
...state, | |||
toasts: state.toasts.map((t) => | |||
t.id === toastId || toastId === undefined | |||
? { | |||
...t, | |||
open: false, | |||
} | |||
: t | |||
), | |||
} | |||
} | |||
case "REMOVE_TOAST": | |||
if (action.toastId === undefined) { | |||
return { | |||
...state, | |||
toasts: [], | |||
} | |||
} | |||
return { | |||
...state, | |||
toasts: state.toasts.filter((t) => t.id !== action.toastId), | |||
} | |||
} | |||
} | |||
const listeners: Array<(state: State) => void> = [] | |||
let memoryState: State = { toasts: [] } | |||
function dispatch(action: Action) { | |||
memoryState = reducer(memoryState, action) | |||
listeners.forEach((listener) => { | |||
listener(memoryState) | |||
}) | |||
} | |||
type Toast = Omit<ToasterToast, "id"> | |||
function toast({ ...props }: Toast) { | |||
const id = genId() | |||
const update = (props: ToasterToast) => | |||
dispatch({ | |||
type: "UPDATE_TOAST", | |||
toast: { ...props, id }, | |||
}) | |||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) | |||
dispatch({ | |||
type: "ADD_TOAST", | |||
toast: { | |||
...props, | |||
id, | |||
open: true, | |||
onOpenChange: (open) => { | |||
if (!open) dismiss() | |||
}, | |||
}, | |||
}) | |||
return { | |||
id: id, | |||
dismiss, | |||
update, | |||
} | |||
} | |||
function useToast() { | |||
const [state, setState] = React.useState<State>(memoryState) | |||
React.useEffect(() => { | |||
listeners.push(setState) | |||
return () => { | |||
const index = listeners.indexOf(setState) | |||
if (index > -1) { | |||
listeners.splice(index, 1) | |||
} | |||
} | |||
}, [state]) | |||
return { | |||
...state, | |||
toast, | |||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), | |||
} | |||
} | |||
export { useToast, toast } |
@@ -0,0 +1,6 @@ | |||
import { clsx, type ClassValue } from 'clsx' | |||
import { twMerge } from 'tailwind-merge' | |||
export function cn (...inputs: ClassValue[]) { | |||
return twMerge(clsx(...inputs)) | |||
} |
@@ -1,9 +1,11 @@ | |||
import { defineConfig } from 'vite' | |||
import react from '@vitejs/plugin-react' | |||
import path from 'path' | |||
// https://vite.dev/config/ | |||
export default defineConfig({ | |||
plugins: [react()], | |||
resolve: { alias: { '@': path.resolve (__dirname, './src') } }, | |||
server: { host: true, | |||
port: 5173, | |||
strictPort: true, | |||