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 */}
-
-
-
-
- |
- Rank
- |
-
- Runner
- |
-
- PB
- |
-
- Runs
- |
-
- Category
- |
-
-
-
- {(activeTab === "global"
- ? globalLeaderboard
- : weeklyLeaderboard
- ).map((entry, i) => (
+ {/* Table */}
+
+
+
+
+ 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" }}
>
-
+
-
);
-};
-
-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) => (
-
+ {categories.map((cat, i) => (
+
{cat}
-
+
))}
{/* Difficulty Filter */}
- {difficulties.map((diff) => (
- (
+
{diff}
-
+
))}
{/* View Toggle */}
-
- setViewMode("grid")}
- className={`p-2 rounded-md transition-all ${
- viewMode === "grid"
- ? "bg-primary text-primary-foreground"
- : "text-muted-foreground hover:text-foreground"
- }`}
- >
+ v && setViewMode(v as "grid" | "list")}
+ >
+
-
- setViewMode("list")}
- className={`p-2 rounded-md transition-all ${
- viewMode === "list"
- ? "bg-primary text-primary-foreground"
- : "text-muted-foreground hover:text-foreground"
- }`}
- >
+
+
-
-
+
+
{/* 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 */}
-
-
+
+
- {/* Stats Grid */}
-
-
+
-
-
- {
{/* Recent Runs */}
-
-
-
-
- Recent Runs
-
-
- View All
-
-
+
+
+ Recent Runs
+
+ View all
+
+
-
- {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 */}
-
-
-
-
-
-
-
-
{
- view.focus();
-
- view.dispatch({
- selection: { anchor: 1 },
- scrollIntoView: true,
- });
+
+
+ {/* File tree */}
+
+
-
-
+
+ {/* Code */}
+
+
+
+
+ {
+ 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
+
+
+
+
+
+
+ Watch
+
+
+
+ );
+}
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 */}
-
- Start Run
-
-
- {/* 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
+
+
+
+
+
+
+
+ GitHub
+
+
+
+ Google
+
+
+ >
+ );
+}
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.
+
+
+
+
+
+
+
+ Log In
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+ Create Account
+
+
+
+
+
+ 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 (
+
+ )
+}
+
+function PaginationContent({
+ className,
+ ...props
+}: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+ return
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick, "size"> &
+ React.ComponentProps<"a">
+
+function PaginationLink({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) {
+ return (
+
+ )
+}
+
+function PaginationPrevious({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ Previous
+
+ )
+}
+
+function PaginationNext({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ Next
+
+
+ )
+}
+
+function PaginationEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More pages
+
+ )
+}
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationLink,
+ PaginationItem,
+ PaginationPrevious,
+ PaginationNext,
+ PaginationEllipsis,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000..4c24b2a
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import { Separator as SeparatorPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000..3ac24f5
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,116 @@
+"use client"
+
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Table({ className, ...props }: React.ComponentProps<"table">) {
+ return (
+
+ )
+}
+
+function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
+ return (
+
+ )
+}
+
+function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
+ return (
+
+ )
+}
+
+function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
+ return (
+ tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ |
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ |
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..7f73dcd
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,91 @@
+"use client"
+
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Tabs as TabsPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ orientation = "horizontal",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+const tabsListVariants = cva(
+ "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
+ {
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function TabsList({
+ className,
+ variant = "default",
+ ...props
+}: React.ComponentProps &
+ VariantProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx
new file mode 100644
index 0000000..9894607
--- /dev/null
+++ b/src/components/ui/toggle-group.tsx
@@ -0,0 +1,83 @@
+"use client"
+
+import * as React from "react"
+import { type VariantProps } from "class-variance-authority"
+import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { toggleVariants } from "@/components/ui/toggle"
+
+const ToggleGroupContext = React.createContext<
+ VariantProps & {
+ spacing?: number
+ }
+>({
+ size: "default",
+ variant: "default",
+ spacing: 0,
+})
+
+function ToggleGroup({
+ className,
+ variant,
+ size,
+ spacing = 0,
+ children,
+ ...props
+}: React.ComponentProps &
+ VariantProps & {
+ spacing?: number
+ }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+function ToggleGroupItem({
+ className,
+ children,
+ variant,
+ size,
+ ...props
+}: React.ComponentProps &
+ VariantProps) {
+ const context = React.useContext(ToggleGroupContext)
+
+ return (
+
+ {children}
+
+ )
+}
+
+export { ToggleGroup, ToggleGroupItem }
diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx
new file mode 100644
index 0000000..901c7f4
--- /dev/null
+++ b/src/components/ui/toggle.tsx
@@ -0,0 +1,47 @@
+"use client"
+
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Toggle as TogglePrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
+ },
+ size: {
+ default: "h-9 px-2 min-w-9",
+ sm: "h-8 px-1.5 min-w-8",
+ lg: "h-10 px-2.5 min-w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Toggle({
+ className,
+ variant,
+ size,
+ ...props
+}: React.ComponentProps &
+ VariantProps) {
+ return (
+
+ )
+}
+
+export { Toggle, toggleVariants }
diff --git a/src/modules/run/run.actions.ts b/src/modules/run/run.actions.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/modules/run/run.service.ts b/src/modules/run/run.service.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/modules/run/run.types.ts b/src/modules/run/run.types.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/modules/user/user.actions.ts b/src/modules/user/user.actions.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/modules/user/user.types.ts b/src/modules/user/user.types.ts
new file mode 100644
index 0000000..e69de29