diff --git a/package-lock.json b/package-lock.json index 2416fec..0240847 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,6 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", - "@sinm/react-file-tree": "^1.1.1", "@uiw/codemirror-theme-vscode": "^4.25.4", "@uiw/react-codemirror": "^4.25.4", "class-variance-authority": "^0.7.1", @@ -45,6 +44,7 @@ "embla-carousel-react": "^8.6.0", "lucide-react": "^0.563.0", "next": "16.1.6", + "radix-ui": "^1.4.3", "react": "19.2.3", "react-day-picker": "^9.13.0", "react-dom": "19.2.3", @@ -923,6 +923,29 @@ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", + "license": "MIT", + "dependencies": { + "@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-accordion": { "version": "1.2.12", "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", @@ -1461,6 +1484,57 @@ } } }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@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/@radix-ui/react-form/node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "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/@radix-ui/react-hover-card": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", @@ -1682,6 +1756,70 @@ } } }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@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-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@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-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" + }, + "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-popover": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", @@ -2319,6 +2457,58 @@ } } }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" + }, + "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-toolbar/node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "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/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", @@ -2554,16 +2744,6 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, - "node_modules/@sinm/react-file-tree": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@sinm/react-file-tree/-/react-file-tree-1.1.1.tgz", - "integrity": "sha512-XlHLLNV18zWx7mYEH5gtlBb7jYFX7GaBA5jmhsG9YXhgwyyK33JgymrbU+IHDDBakAoZ+xISLKzu2ldzKbA/wg==", - "license": "MIT", - "dependencies": { - "@types/react-virtualized": "^9.21.15", - "react-virtualized": "^9.22.3" - } - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2917,16 +3097,11 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.2.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2942,16 +3117,6 @@ "@types/react": "^19.2.0" } }, - "node_modules/@types/react-virtualized": { - "version": "9.22.3", - "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.22.3.tgz", - "integrity": "sha512-UKRWeBIrECaKhE4O//TSFhlgwntMwyiEIOA7WZoVkr52Jahv0dH6YIOorqc358N2V3oKFclsq5XxPmx2PiYB5A==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "@types/react": "*" - } - }, "node_modules/@uiw/codemirror-extensions-basic-setup": { "version": "4.25.4", "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.4.tgz", @@ -3872,6 +4037,221 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@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-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "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/node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "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/radix-ui/node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@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/node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "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/radix-ui/node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@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/radix-ui/node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "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/radix-ui/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/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -3920,12 +4300,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "license": "MIT" - }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -4026,33 +4400,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/react-virtualized": { - "version": "9.22.6", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz", - "integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.2", - "clsx": "^1.0.4", - "dom-helpers": "^5.1.3", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-virtualized/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/recharts": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", diff --git a/package.json b/package.json index 924bf88..db0abb3 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", - "@sinm/react-file-tree": "^1.1.1", "@uiw/codemirror-theme-vscode": "^4.25.4", "@uiw/react-codemirror": "^4.25.4", "class-variance-authority": "^0.7.1", @@ -45,6 +44,7 @@ "embla-carousel-react": "^8.6.0", "lucide-react": "^0.563.0", "next": "16.1.6", + "radix-ui": "^1.4.3", "react": "19.2.3", "react-day-picker": "^9.13.0", "react-dom": "19.2.3", diff --git a/src/app/globals.css b/src/app/globals.css index 94ebdb5..9b4f79b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -215,7 +215,6 @@ } .card-hover:hover { - @apply -translate-y-1; box-shadow: 0 10px 40px color-mix(in oklch, var(--primary) 15%, transparent); } diff --git a/src/components/app/LeaderboardRow.tsx b/src/app/leaderboards/components/LeaderboardRow.tsx similarity index 100% rename from src/components/app/LeaderboardRow.tsx rename to src/app/leaderboards/components/LeaderboardRow.tsx diff --git a/src/app/leaderboards/page.tsx b/src/app/leaderboards/page.tsx index 93974f9..fd28720 100644 --- a/src/app/leaderboards/page.tsx +++ b/src/app/leaderboards/page.tsx @@ -1,305 +1,292 @@ "use client"; import { Trophy, Calendar, Globe, TrendingUp } from "lucide-react"; -import LeaderboardRow from "@/components/app/LeaderboardRow"; import { useState } from "react"; -const Leaderboards = () => { +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { + Table, + TableBody, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + +import LeaderboardRow from "@/app/leaderboards/components/LeaderboardRow"; +import StatCard from "@/components/app/StatCard"; + +const globalLeaderboard = [ + { + rank: 1, + username: "speedmaster_x", + pb: "2:34.21", + runsCount: 2341, + category: "Any%", + }, + { + rank: 2, + username: "algo_ninja", + pb: "2:38.54", + runsCount: 1876, + category: "Any%", + }, + { + rank: 3, + username: "clockwork", + pb: "2:41.02", + runsCount: 3102, + category: "Any%", + }, + { + rank: 4, + username: "fastfingers99", + pb: "2:45.33", + runsCount: 987, + category: "Any%", + }, + { + rank: 5, + username: "recursion_queen", + pb: "2:47.89", + runsCount: 1543, + category: "Any%", + }, + { + rank: 6, + username: "bitshifter", + pb: "2:52.11", + runsCount: 2109, + category: "Any%", + }, + { + rank: 7, + username: "nullpointer", + pb: "2:54.67", + runsCount: 876, + category: "Any%", + }, + { + rank: 8, + username: "stackattack", + pb: "2:58.23", + runsCount: 1234, + category: "Any%", + }, + { + rank: 9, + username: "looplegend", + pb: "3:01.45", + runsCount: 654, + category: "Any%", + }, + { + rank: 10, + username: "coderunner", + pb: "3:05.78", + runsCount: 1098, + category: "Any%", + }, +]; +const weeklyLeaderboard = [ + { + rank: 1, + username: "weekly_warrior", + pb: "2:51.33", + runsCount: 156, + category: "Any%", + }, + { + rank: 2, + username: "speedmaster_x", + pb: "2:53.12", + runsCount: 89, + category: "Any%", + }, + { + rank: 3, + username: "newbie_grinder", + pb: "2:58.67", + runsCount: 234, + category: "Any%", + }, + { + rank: 4, + username: "algo_ninja", + pb: "3:02.45", + runsCount: 67, + category: "Any%", + }, + { + rank: 5, + username: "fresh_runner", + pb: "3:05.89", + runsCount: 45, + category: "Any%", + }, + { + rank: 6, + username: "practice_makes", + pb: "3:08.12", + runsCount: 112, + category: "Any%", + }, + { + rank: 7, + username: "splitmaster", + pb: "3:12.34", + runsCount: 78, + category: "Any%", + }, + { + rank: 8, + username: "grindtime", + pb: "3:15.56", + runsCount: 92, + category: "Any%", + }, + { + rank: 9, + username: "pb_hunter", + pb: "3:18.90", + runsCount: 56, + category: "Any%", + }, + { + rank: 10, + username: "reset_king", + pb: "3:22.11", + runsCount: 187, + category: "Any%", + }, +]; + +export default function Leaderboards() { const [activeTab, setActiveTab] = useState<"global" | "weekly">("global"); - const globalLeaderboard = [ - { - rank: 1, - username: "speedmaster_x", - pb: "2:34.21", - runsCount: 2341, - category: "Any%", - }, - { - rank: 2, - username: "algo_ninja", - pb: "2:38.54", - runsCount: 1876, - category: "Any%", - }, - { - rank: 3, - username: "clockwork", - pb: "2:41.02", - runsCount: 3102, - category: "Any%", - }, - { - rank: 4, - username: "fastfingers99", - pb: "2:45.33", - runsCount: 987, - category: "Any%", - }, - { - rank: 5, - username: "recursion_queen", - pb: "2:47.89", - runsCount: 1543, - category: "Any%", - }, - { - rank: 6, - username: "bitshifter", - pb: "2:52.11", - runsCount: 2109, - category: "Any%", - }, - { - rank: 7, - username: "nullpointer", - pb: "2:54.67", - runsCount: 876, - category: "Any%", - }, - { - rank: 8, - username: "stackattack", - pb: "2:58.23", - runsCount: 1234, - category: "Any%", - }, - { - rank: 9, - username: "looplegend", - pb: "3:01.45", - runsCount: 654, - category: "Any%", - }, - { - rank: 10, - username: "coderunner", - pb: "3:05.78", - runsCount: 1098, - category: "Any%", - }, - ]; - - const weeklyLeaderboard = [ - { - rank: 1, - username: "weekly_warrior", - pb: "2:51.33", - runsCount: 156, - category: "Any%", - }, - { - rank: 2, - username: "speedmaster_x", - pb: "2:53.12", - runsCount: 89, - category: "Any%", - }, - { - rank: 3, - username: "newbie_grinder", - pb: "2:58.67", - runsCount: 234, - category: "Any%", - }, - { - rank: 4, - username: "algo_ninja", - pb: "3:02.45", - runsCount: 67, - category: "Any%", - }, - { - rank: 5, - username: "fresh_runner", - pb: "3:05.89", - runsCount: 45, - category: "Any%", - }, - { - rank: 6, - username: "practice_makes", - pb: "3:08.12", - runsCount: 112, - category: "Any%", - }, - { - rank: 7, - username: "splitmaster", - pb: "3:12.34", - runsCount: 78, - category: "Any%", - }, - { - rank: 8, - username: "grindtime", - pb: "3:15.56", - runsCount: 92, - category: "Any%", - }, - { - rank: 9, - username: "pb_hunter", - pb: "3:18.90", - runsCount: 56, - category: "Any%", - }, - { - rank: 10, - username: "reset_king", - pb: "3:22.11", - runsCount: 187, - category: "Any%", - }, - ]; - const categories = ["Any%", "100%", "Easy Only", "No DP", "Blind"]; + const data = activeTab === "global" ? globalLeaderboard : weeklyLeaderboard; + return (
-
+
{/* Header */} -
-

- Leaderboards -

+
+

Leaderboards

The fastest runners in the game. Will you claim a spot?

- {/* Stats Row */} -
-
- -

- 12,453 -

-

Total Runners

-
-
- -

- 2:34.21 -

-

World Record

-
-
- -

847K

-

Runs This Week

-
-
- -

- 5d 12h -

-

Until Reset

-
+ {/* Stats */} +
+ + + +
- {/* Tabs and Filters */} -
- {/* Global/Weekly Toggle */} -
- - -
+ {/* Tabs + Filters */} +
+ setActiveTab(v as "global" | "weekly")} + > + + + + Global + + + + Weekly + + + - {/* Category Filter */} -
+
{categories.map((cat, i) => ( - + ))}
- {/* Leaderboard Table */} -
- - - - - - - - - - - - {(activeTab === "global" - ? globalLeaderboard - : weeklyLeaderboard - ).map((entry, i) => ( + {/* Table */} + +
- Rank - - Runner - - PB - - Runs - - Category -
+ + + Rank + Runner + PB + Runs + Category + + + + {data.map((entry, i) => ( ))} - -
-
+ + + {/* Pagination */} -
- - {[1, 2, 3, 50].map((page, i) => ( - - ))} - -
+ + + + + + + {[1, 2, 3, 50].map((p) => ( + + {p} + + ))} + + + + + +
); -}; - -export default Leaderboards; +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..b489077 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,5 @@ +import { LoginPage } from "@/components/auth/LoginPage"; + +export default function Page() { + return ; +} diff --git a/src/app/page.tsx b/src/app/page.tsx index dfb6439..5c412ef 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,7 +4,7 @@ import { Timer, Zap, Trophy, Users, ArrowRight, Play } from "lucide-react"; import { Button } from "@/components/ui/button"; import Link from "next/link"; -const Landing = () => { +export default function Landing() { const features = [ { icon: Timer, @@ -77,7 +77,7 @@ const Landing = () => { className="flex flex-col sm:flex-row items-center justify-center gap-4 animate-fade-in stagger-3 opacity-0" style={{ animationFillMode: "forwards" }} > - + - @@ -183,6 +183,4 @@ const Landing = () => {
); -}; - -export default Landing; +} diff --git a/src/components/app/ProblemCard.tsx b/src/app/problems/components/ProblemCard.tsx similarity index 75% rename from src/components/app/ProblemCard.tsx rename to src/app/problems/components/ProblemCard.tsx index 60481d2..bd881e4 100644 --- a/src/components/app/ProblemCard.tsx +++ b/src/app/problems/components/ProblemCard.tsx @@ -1,3 +1,4 @@ +import { Button } from "@/components/ui/button"; import { Clock, Zap, Users } from "lucide-react"; interface ProblemCardProps { @@ -9,7 +10,14 @@ interface ProblemCardProps { index?: number; } -const ProblemCard = ({ name, difficulty, tags, bestTime, attempts, index = 0 }: ProblemCardProps) => { +const ProblemCard = ({ + name, + difficulty, + tags, + bestTime, + attempts, + index = 0, +}: ProblemCardProps) => { const difficultyStyles = { easy: "diff-easy", medium: "diff-medium", @@ -23,20 +31,25 @@ const ProblemCard = ({ name, difficulty, tags, bestTime, attempts, index = 0 }: }; return ( -
{/* Glow effect on hover */}
- +
{/* Header */}

{name}

- + {difficultyLabels[difficulty]}
@@ -44,8 +57,8 @@ const ProblemCard = ({ name, difficulty, tags, bestTime, attempts, index = 0 }: {/* Tags */}
{tags.map((tag) => ( - {tag} @@ -68,10 +81,10 @@ const ProblemCard = ({ name, difficulty, tags, bestTime, attempts, index = 0 }:
{/* Quick run button - appears on hover */} -
- +
diff --git a/src/app/problems/page.tsx b/src/app/problems/page.tsx index b0112c6..35b15cd 100644 --- a/src/app/problems/page.tsx +++ b/src/app/problems/page.tsx @@ -1,180 +1,78 @@ "use client"; import { Search, Grid, List } from "lucide-react"; -import ProblemCard from "@/components/app/ProblemCard"; import { useState } from "react"; -const Problems = () => { - const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); +import ProblemCard from "@/app/problems/components/ProblemCard"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; - const problems = [ - { - name: "Two Sum Dash", - difficulty: "easy" as const, - tags: ["arrays", "hash-map"], - bestTime: "0:42", - attempts: 12453, - }, - { - name: "Binary Sprint", - difficulty: "easy" as const, - tags: ["binary-search"], - bestTime: "0:38", - attempts: 8921, - }, - { - name: "Linked List Rush", - difficulty: "medium" as const, - tags: ["linked-list", "pointers"], - bestTime: "1:24", - attempts: 6234, - }, - { - name: "Tree Traverse Blitz", - difficulty: "medium" as const, - tags: ["trees", "recursion"], - bestTime: "2:01", - attempts: 5102, - }, - { - name: "DP Gauntlet", - difficulty: "hard" as const, - tags: ["dynamic-programming"], - bestTime: "4:32", - attempts: 2341, - }, - { - name: "Graph Navigator", - difficulty: "hard" as const, - tags: ["graphs", "bfs", "dfs"], - bestTime: "3:45", - attempts: 3456, - }, - { - name: "String Scramble", - difficulty: "easy" as const, - tags: ["strings", "manipulation"], - bestTime: "0:55", - attempts: 9876, - }, - { - name: "Stack Overflow", - difficulty: "medium" as const, - tags: ["stacks", "queues"], - bestTime: "1:42", - attempts: 4567, - }, - { - name: "Heap Hero", - difficulty: "hard" as const, - tags: ["heaps", "priority-queue"], - bestTime: "3:21", - attempts: 1890, - }, - { - name: "Sliding Window Slam", - difficulty: "medium" as const, - tags: ["sliding-window", "arrays"], - bestTime: "1:56", - attempts: 5678, - }, - { - name: "Trie Triumph", - difficulty: "hard" as const, - tags: ["trie", "strings"], - bestTime: "4:12", - attempts: 1234, - }, - { - name: "Backtrack Blaster", - difficulty: "hard" as const, - tags: ["backtracking", "recursion"], - bestTime: "5:01", - attempts: 987, - }, - ]; +export default function Problems() { + const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const categories = ["All", "Arrays", "Trees", "Graphs", "DP", "Strings"]; const difficulties = ["All", "Easy", "Medium", "Hard"]; return (
-
+
{/* Header */} -
-

Problems

+
+

Problems

Choose your battleground. Each run brings you closer to your PB.

{/* Filters */} -
+
{/* Search */}
- - + +
{/* Category Filter */} -
- {categories.map((cat) => ( - + ))}
{/* Difficulty Filter */}
- {difficulties.map((diff) => ( - + ))}
{/* View Toggle */} -
- - -
+ +
{/* Problem Grid */} @@ -192,6 +90,93 @@ const Problems = () => {
); -}; +} -export default Problems; +/* ---------------- data ---------------- */ + +const problems = [ + { + name: "Two Sum Dash", + difficulty: "easy" as const, + tags: ["arrays", "hash-map"], + bestTime: "0:42", + attempts: 12453, + }, + { + name: "Binary Sprint", + difficulty: "easy" as const, + tags: ["binary-search"], + bestTime: "0:38", + attempts: 8921, + }, + { + name: "Linked List Rush", + difficulty: "medium" as const, + tags: ["linked-list", "pointers"], + bestTime: "1:24", + attempts: 6234, + }, + { + name: "Tree Traverse Blitz", + difficulty: "medium" as const, + tags: ["trees", "recursion"], + bestTime: "2:01", + attempts: 5102, + }, + { + name: "DP Gauntlet", + difficulty: "hard" as const, + tags: ["dynamic-programming"], + bestTime: "4:32", + attempts: 2341, + }, + { + name: "Graph Navigator", + difficulty: "hard" as const, + tags: ["graphs", "bfs", "dfs"], + bestTime: "3:45", + attempts: 3456, + }, + { + name: "String Scramble", + difficulty: "easy" as const, + tags: ["strings", "manipulation"], + bestTime: "0:55", + attempts: 9876, + }, + { + name: "Stack Overflow", + difficulty: "medium" as const, + tags: ["stacks", "queues"], + bestTime: "1:42", + attempts: 4567, + }, + { + name: "Heap Hero", + difficulty: "hard" as const, + tags: ["heaps", "priority-queue"], + bestTime: "3:21", + attempts: 1890, + }, + { + name: "Sliding Window Slam", + difficulty: "medium" as const, + tags: ["sliding-window", "arrays"], + bestTime: "1:56", + attempts: 5678, + }, + { + name: "Trie Triumph", + difficulty: "hard" as const, + tags: ["trie", "strings"], + bestTime: "4:12", + attempts: 1234, + }, + { + name: "Backtrack Blaster", + difficulty: "hard" as const, + tags: ["backtracking", "recursion"], + bestTime: "5:01", + attempts: 987, + }, +]; diff --git a/src/app/profile/components/ProfileStatCard.tsx b/src/app/profile/components/ProfileStatCard.tsx new file mode 100644 index 0000000..23c438e --- /dev/null +++ b/src/app/profile/components/ProfileStatCard.tsx @@ -0,0 +1,46 @@ +import { LucideIcon } from "lucide-react"; + +interface StatCardProps { + label: string; + value: string; + icon: LucideIcon; + trend?: string; + index?: number; +} + +export default function ProfileStatCard({ + label, + value, + icon: Icon, + trend, + index = 0, +}: StatCardProps) { + return ( +
+
+
+ +
+ {trend && ( + + {trend} + + )} +
+

{value}

+

{label}

+
+ ); +} diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 30bda86..1b4411f 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -1,15 +1,15 @@ -import { - Trophy, - Timer, - Zap, - Target, - Calendar, - TrendingUp, - Award, -} from "lucide-react"; -import StatCard from "@/components/app/StatCard"; +"use client"; -const Profile = () => { +import { Trophy, Timer, Zap, Target, Calendar, Award } from "lucide-react"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; + +import ProfileStatCard from "@/app/profile/components/ProfileStatCard"; + +export default function Profile() { const user = { username: "speedmaster_x", joinDate: "Jan 2024", @@ -21,159 +21,69 @@ const Profile = () => { favoriteCategory: "Any%", }; - const recentRuns = [ - { - id: 1, - category: "Any%", - time: "2:38.54", - date: "2 hours ago", - status: "clean", - }, - { - id: 2, - category: "Any%", - time: "2:45.12", - date: "3 hours ago", - status: "clean", - }, - { - id: 3, - category: "Easy Only", - time: "1:23.45", - date: "5 hours ago", - status: "clean", - }, - { - id: 4, - category: "Any%", - time: "--:--", - date: "5 hours ago", - status: "reset", - }, - { - id: 5, - category: "Any%", - time: "2:51.33", - date: "Yesterday", - status: "clean", - }, - { - id: 6, - category: "No DP", - time: "3:45.67", - date: "Yesterday", - status: "clean", - }, - ]; - - const achievements = [ - { - name: "First Blood", - description: "Complete your first run", - icon: Zap, - unlocked: true, - }, - { - name: "Sub-3 Club", - description: "Finish a run under 3 minutes", - icon: Timer, - unlocked: true, - }, - { - name: "World Record", - description: "Hold a world record", - icon: Trophy, - unlocked: true, - }, - { - name: "Century", - description: "Complete 100 runs", - icon: Target, - unlocked: true, - }, - { - name: "Perfectionist", - description: "100 clean runs", - icon: Award, - unlocked: false, - }, - { - name: "Marathon", - description: "10 hours of running", - icon: Calendar, - unlocked: true, - }, - ]; - return (
-
+
{/* Profile Header */} -
- {/* Background gradient */} -
+ +
-
+ {/* Avatar */}
-
- {user.username.charAt(0).toUpperCase()} +
+ {user.username[0].toUpperCase()}
- #1 + #{user.rank}
{/* User Info */}
-

- {user.username} -

+

{user.username}

Runner since {user.joinDate}

-
- - World Record Holder - - - {user.favoriteCategory} Main - + +
+ World Record Holder + {user.favoriteCategory} Main
- {/* Quick Stats */} + {/* PB */}

{user.pb}

Personal Best

-
-
+
+ - {/* Stats Grid */} -
- + - - - {
{/* Recent Runs */} -
-
-
-

- Recent Runs -

- -
+ + + Recent Runs + + -
- {recentRuns.map((run, i) => ( -
-
-
-
-

- {run.category} -

-

- {run.date} -

-
-
-
-

- {run.time} -

-

- {run.status} + + {recentRuns.map((run, i) => ( +

+
+
+
+

{run.category}

+

+ {run.date}

- ))} -
-
-
- {/* Achievements */} -
-
-
-

- Achievements -

- 5/6 -
+
+

+ {run.time} +

+

+ {run.status} +

+
+
+ ))} + + -
- {achievements.map((achievement, i) => ( + {/* Achievements + PB */} +
+ + + Achievements + 5 / 6 + + + + {achievements.map((a, i) => (
{ >
- +
+
-

- {achievement.name} -

+

{a.name}

- {achievement.description} + {a.description}

- {achievement.unlocked && ( + + {a.unlocked && (
@@ -307,44 +202,124 @@ const Profile = () => { )}
))} -
-
+ + {/* PB History */} -
-

- PB History -

-
-
- 2:34.21 - Today -
-
- 2:38.54 - - 3 days ago - -
-
- 2:45.12 - - 1 week ago - -
-
- 2:52.33 - - 2 weeks ago - -
-
-
+ + + PB History + + + {pbHistory.map((pb, i) => ( +
+
+ {pb.time} + {pb.date} +
+ {i !== pbHistory.length - 1 && ( + + )} +
+ ))} +
+
); -}; +} -export default Profile; +/* ---------------- data ---------------- */ + +const recentRuns = [ + { + id: 1, + category: "Any%", + time: "2:38.54", + date: "2 hours ago", + status: "clean", + }, + { + id: 2, + category: "Any%", + time: "2:45.12", + date: "3 hours ago", + status: "clean", + }, + { + id: 3, + category: "Easy Only", + time: "1:23.45", + date: "5 hours ago", + status: "clean", + }, + { + id: 4, + category: "Any%", + time: "--:--", + date: "5 hours ago", + status: "reset", + }, + { + id: 5, + category: "Any%", + time: "2:51.33", + date: "Yesterday", + status: "clean", + }, + { + id: 6, + category: "No DP", + time: "3:45.67", + date: "Yesterday", + status: "clean", + }, +]; + +const achievements = [ + { + name: "First Blood", + description: "Complete your first run", + icon: Zap, + unlocked: true, + }, + { + name: "Sub-3 Club", + description: "Finish a run under 3 minutes", + icon: Timer, + unlocked: true, + }, + { + name: "World Record", + description: "Hold a world record", + icon: Trophy, + unlocked: true, + }, + { + name: "Century", + description: "Complete 100 runs", + icon: Target, + unlocked: true, + }, + { + name: "Perfectionist", + description: "100 clean runs", + icon: Award, + unlocked: false, + }, + { + name: "Marathon", + description: "10 hours of running", + icon: Calendar, + unlocked: true, + }, +]; + +const pbHistory = [ + { time: "2:34.21", date: "Today" }, + { time: "2:38.54", date: "3 days ago" }, + { time: "2:45.12", date: "1 week ago" }, + { time: "2:52.33", date: "2 weeks ago" }, +]; diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx new file mode 100644 index 0000000..1c5913b --- /dev/null +++ b/src/app/register/page.tsx @@ -0,0 +1,5 @@ +import { RegisterPage } from "@/components/auth/RegisterPage"; + +export default function Page() { + return ; +} diff --git a/src/app/run/components/FileTree.tsx b/src/app/run/components/FileTree.tsx new file mode 100644 index 0000000..4bd23e7 --- /dev/null +++ b/src/app/run/components/FileTree.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { useState } from "react"; +import { Folder, FolderMinus, FolderPlus, File } from "lucide-react"; + +type TreeNode = { + type: "directory" | "file"; + uri: string; + children?: TreeNode[]; +}; + +interface FileTreeProps { + tree: TreeNode; + level?: number; // for indentation +} + +export const FileTree: React.FC = ({ tree, level = 0 }) => { + const [expanded, setExpanded] = useState( + tree.type !== "directory" || level === 0 ? true : false, + ); + + const toggleExpand = () => { + if (tree.type === "directory") { + setExpanded(!expanded); + } + }; + + return ( +
+ {/* Directory/File Row */} +
+ {tree.type === "directory" ? ( + expanded ? ( + + ) : ( + + ) + ) : ( + + )} + {tree.uri} +
+ + {/* Children */} + {expanded && + tree.children?.map((child) => ( + + ))} +
+ ); +}; diff --git a/src/app/run/components/RunChat.tsx b/src/app/run/components/RunChat.tsx new file mode 100644 index 0000000..9516d3b --- /dev/null +++ b/src/app/run/components/RunChat.tsx @@ -0,0 +1,48 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; + +export type ChatMessage = { + user: string; + message: string; + time: string; +}; + +export default function RunChat({ + chatMessages, +}: { + chatMessages: ChatMessage[]; +}) { + return ( + + +
+ Chat + + {chatMessages.length} messages + +
+
+ + + + + {/* Messages */} +
+ {chatMessages.map((msg, i) => ( +
+ {msg.user}:{" "} + {msg.message} + + {msg.time} + +
+ ))} +
+ + {/* Input */} + +
+
+ ); +} diff --git a/src/app/run/components/RunTimer.tsx b/src/app/run/components/RunTimer.tsx new file mode 100644 index 0000000..8a5c5d1 --- /dev/null +++ b/src/app/run/components/RunTimer.tsx @@ -0,0 +1,24 @@ +import { Card, CardContent } from "@/components/ui/card"; + +export type RunInfo = { + category: "any%" | "100%"; + problems: number; + currentAttempt: number; + pbTime: string; + worldRecord: string; +}; + +export default function RunTimer({ runInfo }: { runInfo: RunInfo }) { + return ( + +
+ +

1:52.34

+

+ PB: {runInfo.pbTime} | Diff:{" "} + +0:12 +

+
+ + ); +} diff --git a/src/app/run/page.tsx b/src/app/run/page.tsx index 7ed19bf..2e86e67 100644 --- a/src/app/run/page.tsx +++ b/src/app/run/page.tsx @@ -1,64 +1,16 @@ "use client"; -import { FileTree } from "@sinm/react-file-tree"; -import CodeMirror, { ReactCodeMirrorRef } from "@uiw/react-codemirror"; +import CodeMirror, { EditorState } from "@uiw/react-codemirror"; import { vscodeDark } from "@uiw/codemirror-theme-vscode"; -import { useEffect, useRef } from "react"; -type RunInfo = { - category: "any%" | "100%"; - problems: number; - currentAttempt: number; - pbTime: string; - worldRecord: string; -}; - -type ChatMessage = { - user: string; - message: string; - time: string; -}; +import { Card, CardContent } from "@/components/ui/card"; +import RunTimer, { RunInfo } from "./components/RunTimer"; +import RunChat, { ChatMessage } from "./components/RunChat"; +import { useState } from "react"; +import { FileTree } from "./components/FileTree"; +import { File } from "lucide-react"; export default function Run() { - const splits = [ - { - problemName: "Two Sum Dash", - splitTime: "0:38", - delta: "-0:04", - isCompleted: true, - pbTime: "0:42", - }, - { - problemName: "Binary Sprint", - splitTime: "1:12", - delta: "+0:02", - isCompleted: true, - pbTime: "0:32", - }, - { - problemName: "String Scramble", - splitTime: "--:--", - delta: undefined, - isCompleted: false, - isCurrent: true, - pbTime: "0:55", - }, - { - problemName: "Linked List Rush", - splitTime: "--:--", - delta: undefined, - isCompleted: false, - pbTime: "1:24", - }, - { - problemName: "Stack Overflow", - splitTime: "--:--", - delta: undefined, - isCompleted: false, - pbTime: "1:42", - }, - ]; - const runInfo: RunInfo = { category: "any%", problems: 5, @@ -67,7 +19,7 @@ export default function Run() { currentAttempt: 47, }; - const chatMessages = [ + const chatMessages: ChatMessage[] = [ { user: "Alice", message: "Nice split!", time: "1:53" }, { user: "Bob", message: "Watch out for the string problem!", time: "1:54" }, { user: "Charlie", message: "PB incoming? 👀", time: "1:55" }, @@ -76,99 +28,63 @@ export default function Run() { return (
{/* Editor */} -
-
- -
-
-
myfile.js
-
- { - view.focus(); - - view.dispatch({ - selection: { anchor: 1 }, - scrollIntoView: true, - }); + + + {/* File tree */} +
+
-
-
+ + {/* Code */} +
+
+ myfile.js +
+ +
+ { + if (tr.selection && tr.isUserEvent("select")) { + return []; + } + return tr; + }), + ]} + theme={vscodeDark} + onCreateEditor={(view) => { + view.focus(); + view.dispatch({ + selection: { anchor: 1 }, + scrollIntoView: true, + }); + }} + /> +
+
+ + {/* Timer & Chat */} -
- - -
-
- ); -} - -function Timer({ runInfo }: { runInfo: RunInfo }) { - return ( -
-
-
-

1:52.34

-

- PB: {runInfo.pbTime} | Diff:{" "} - +0:12 -

-
-
- ); -} - -function Chat({ chatMessages }: { chatMessages: ChatMessage[] }) { - return ( -
- {/* Header */} -
-

Chat

- - {chatMessages.length} messages - -
- - {/* Messages area */} -
- {chatMessages.map((msg, idx) => ( -
- {msg.user}:{" "} - {msg.message} - - {msg.time} - -
- ))} -
- - {/* Input */} -
- +
+ +
); diff --git a/src/app/runs/components/LiveRunNow.tsx b/src/app/runs/components/LiveRunNow.tsx new file mode 100644 index 0000000..13e9919 --- /dev/null +++ b/src/app/runs/components/LiveRunNow.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { Eye } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { TableRow, TableCell } from "@/components/ui/table"; +import { cn } from "@/lib/utils"; + +export default function LiveRunRow({ + index, + username, + problem, + category, + time, + pace, + status, +}: any) { + const pacePositive = pace.startsWith("+"); + const isTop = status === "pb"; + + return ( + + + {isTop ? `🔥 ${username}` : username} + + + +
+ {problem} + + {category} + +
+
+ + + {time} + + + + + {pace} + + + + +
+ + Live +
+
+ + + + +
+ ); +} diff --git a/src/app/runs/page.tsx b/src/app/runs/page.tsx new file mode 100644 index 0000000..7ba7a3c --- /dev/null +++ b/src/app/runs/page.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { Zap, Users, Timer, TrendingUp, Eye } from "lucide-react"; + +import { Card, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableHead, + TableHeader, + TableRow, + TableCell, +} from "@/components/ui/table"; +import { cn } from "@/lib/utils"; +import LiveRunRow from "./components/LiveRunNow"; +import StatCard from "@/components/app/StatCard"; + +/* ---------------- mock data ---------------- */ + +const liveRuns = [ + { + id: 1, + username: "speedmaster_x", + problem: "Two Sum Dash", + category: "Any%", + time: "1:52.34", + pace: "-0:03", + status: "pb", + }, + { + id: 2, + username: "algo_ninja", + problem: "Binary Sprint", + category: "Any%", + time: "2:04.11", + pace: "+0:05", + status: "normal", + }, + { + id: 3, + username: "stackattack", + problem: "String Scramble", + category: "Easy", + time: "0:48.92", + pace: "-0:01", + status: "pb", + }, + { + id: 4, + username: "reset_king", + problem: "Linked List Rush", + category: "Any%", + time: "1:31.77", + pace: "+0:12", + status: "danger", + }, +]; + +/* ---------------- page ---------------- */ + +export default function LiveRuns() { + // Sort top runners (status === 'pb') to the top + const sortedRuns = [...liveRuns].sort((a, b) => { + if (a.status === "pb" && b.status !== "pb") return -1; + if (a.status !== "pb" && b.status === "pb") return 1; + return 0; + }); + + return ( +
+
+ {/* Header */} +
+

Live Runs

+

+ Watch runners compete in real time. Every second counts. +

+
+ + {/* Stats */} +
+ + + + +
+ + {/* Filters */} +
+ {["Any%", "100%", "Easy", "Hard"].map((cat) => ( + + {cat} + + ))} + + 🔥 PB Pace +
+ + {/* Table */} + + + + + Runner + Problem + Time + Pace + Status + + + + + + {sortedRuns.map((run, index) => ( + + ))} + +
+
+
+
+ ); +} diff --git a/src/components/app/Navbar.tsx b/src/components/app/Navbar.tsx index 5906f8c..92b2d45 100644 --- a/src/components/app/Navbar.tsx +++ b/src/components/app/Navbar.tsx @@ -1,9 +1,10 @@ "use client"; import Link from "next/link"; -import { Timer, Trophy, User, Zap } from "lucide-react"; +import { Radio, Trophy, User, Zap } from "lucide-react"; import { usePathname } from "next/navigation"; import { cn } from "@/lib/utils"; +import { Avatar, AvatarFallback } from "../ui/avatar"; const navs = [ { @@ -18,13 +19,8 @@ const navs = [ }, { link: "/runs", - icon: Timer, - name: "Runs", - }, - { - link: "/profile", - icon: User, - name: "Profile", + icon: Radio, + name: "Live", }, ]; @@ -51,7 +47,7 @@ const Navbar = () => { {/* Navigation Links */} -
+
{navs.map(({ link, icon: Icon, name }) => ( { {/* CTA Button */}
- - - {/* Mobile menu button */} - + {}}> + + + +
diff --git a/src/components/app/SplitRow.tsx b/src/components/app/SplitRow.tsx deleted file mode 100644 index 9762457..0000000 --- a/src/components/app/SplitRow.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Check, Minus } from "lucide-react"; - -interface SplitRowProps { - problemName: string; - splitTime: string; - delta?: string; - isCompleted: boolean; - isCurrent?: boolean; - pbTime?: string; -} - -const SplitRow = ({ problemName, splitTime, delta, isCompleted, isCurrent, pbTime }: SplitRowProps) => { - const getDeltaColor = (delta?: string) => { - if (!delta) return "text-muted-foreground"; - if (delta.startsWith("-")) return "text-emerald-400"; - if (delta.startsWith("+")) return "text-red-400"; - return "text-muted-foreground"; - }; - - return ( -
-
- {/* Status indicator */} -
- {isCompleted ? : } -
- - - {problemName} - -
- -
- {/* Delta */} - {isCompleted && delta && ( - - {delta} - - )} - - {/* Split time */} - - {isCompleted ? splitTime : pbTime || '--:--'} - -
-
- ); -}; - -export default SplitRow; diff --git a/src/components/app/StatCard.tsx b/src/components/app/StatCard.tsx index ada35c5..9fb77d2 100644 --- a/src/components/app/StatCard.tsx +++ b/src/components/app/StatCard.tsx @@ -1,35 +1,29 @@ -import { LucideIcon } from "lucide-react"; +import { Card, CardHeader, CardTitle } from "@/components/ui/card"; -interface StatCardProps { +export default function StatCard({ + icon: Icon, + label, + value, + index = 0, +}: { + icon: any; label: string; value: string; - icon: LucideIcon; - trend?: string; index?: number; -} - -const StatCard = ({ label, value, icon: Icon, trend, index = 0 }: StatCardProps) => { +}) { return ( -
-
-
- -
- {trend && ( - - {trend} - - )} -
-

{value}

-

{label}

-
+ + + {value} +

{label}

+
+ ); -}; - -export default StatCard; +} diff --git a/src/components/auth/ContinueWith.tsx b/src/components/auth/ContinueWith.tsx new file mode 100644 index 0000000..9e94783 --- /dev/null +++ b/src/components/auth/ContinueWith.tsx @@ -0,0 +1,30 @@ +import { Github, Mail } from "lucide-react"; +import { Button } from "../ui/button"; + +export default function ContinueWith() { + return ( + <> +
+
+ +
+
+ + Or continue with + +
+
+ +
+ + +
+ + ); +} diff --git a/src/components/auth/LoginPage.tsx b/src/components/auth/LoginPage.tsx new file mode 100644 index 0000000..2ba1a4f --- /dev/null +++ b/src/components/auth/LoginPage.tsx @@ -0,0 +1,81 @@ +"use client"; + +import Link from "next/link"; +import { + ArrowRight, + Github, + KeyRound, + LogIn, + Mail, + UserRound, + Zap, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import ContinueWith from "./ContinueWith"; + +export function LoginPage() { + return ( +
+ {/* Background glow */} +
+ + + +
+ +
+ + Welcome back + + + Log in to continue chasing your personal best. + +
+ + +
+
+ + +
+
+ + +
+
+ + + + + +

+ Don't have an account?{" "} + + Create one + +

+
+
+
+ ); +} diff --git a/src/components/auth/RegisterPage.tsx b/src/components/auth/RegisterPage.tsx new file mode 100644 index 0000000..2b1158c --- /dev/null +++ b/src/components/auth/RegisterPage.tsx @@ -0,0 +1,77 @@ +"use client"; + +import Link from "next/link"; +import { ArrowRight, Github, Mail, UserPlus, Zap } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import ContinueWith from "./ContinueWith"; + +export function RegisterPage() { + return ( +
+ {/* Background glow */} +
+ + + +
+ +
+ + Create your account + + + Join the grind. Track runs, set records, compete globally. + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +

+ Already have an account?{" "} + + Log in + +

+
+
+
+ ); +} diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx new file mode 100644 index 0000000..1ac1570 --- /dev/null +++ b/src/components/ui/avatar.tsx @@ -0,0 +1,109 @@ +"use client" + +import * as React from "react" +import { Avatar as AvatarPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", + className + )} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarBadge, + AvatarGroup, + AvatarGroupCount, +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..beb56ed --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + link: "text-primary underline-offset-4 [a&]:hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index be9ad05..78749fb 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,37 +1,36 @@ import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; +import { Slot } from "radix-ui"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-semibold transition-all duration-300 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", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { - default: - "bg-primary text-primary-foreground hover:bg-primary/90 glow-purple hover:glow-purple-intense", + default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - "border-2 border-primary/50 bg-transparent text-primary hover:bg-primary/10 hover:border-primary", + "border bg-background shadow-xs hover:bg-accent hover:text-primary dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", + "bg-secondary text-secondary-foreground hover:bg-secondary/80 hover:text-primary", ghost: - "text-muted-foreground hover:text-foreground hover:bg-secondary", - link: - "text-primary underline-offset-4 hover:underline", - hero: - "bg-primary text-primary-foreground px-8 py-6 text-lg font-bold glow-purple hover:glow-purple-intense hover:scale-105", - "hero-outline": - "border-2 border-primary/50 bg-transparent text-primary px-8 py-6 text-lg font-bold hover:bg-primary/10 hover:border-primary hover:scale-105", + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + hero: "transition-colors duration-300 hover:text-primary text-glow", }, size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-12 rounded-lg px-8", - xl: "h-14 rounded-lg px-10 text-base", - icon: "h-10 w-10", + default: "h-9 px-4 py-2 has-[>svg]:px-3", + p2: "rounded-md p-2", + xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", }, }, defaultVariants: { @@ -41,18 +40,27 @@ const buttonVariants = cva( }, ); -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot.Root : "button"; + + return ( + + ); } -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ; - }, -); -Button.displayName = "Button"; - export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..681ad98 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..8916905 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..1ac80f7 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client" + +import * as React from "react" +import { Label as LabelPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label } diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 0000000..1dcfb0c --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import { + ChevronLeftIcon, + ChevronRightIcon, + MoreHorizontalIcon, +} from "lucide-react" + +import { cn } from "@/lib/utils" +import { buttonVariants, type Button } from "@/components/ui/button" + +function Pagination({ className, ...props }: React.ComponentProps<"nav">) { + return ( +