diff --git a/bun.lock b/bun.lock
index 06203fb..806a6c4 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,6 +5,8 @@
"": {
"name": "spaceward-frontend",
"dependencies": {
+ "i": "^0.3.7",
+ "lucide-react": "^0.562.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
},
@@ -310,6 +312,8 @@
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
+ "i": ["i@0.3.7", "", {}, "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q=="],
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
@@ -346,6 +350,8 @@
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+ "lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],
+
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
diff --git a/package.json b/package.json
index 5f0947b..cbc3c1c 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
+ "i": "^0.3.7",
+ "lucide-react": "^0.562.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
diff --git a/src/App.css b/src/App.css
index b9d355d..8a877a1 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,24 +1,73 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
+.app-container {
+ min-height: 100vh;
+ background-color: #1e1e1e;
padding: 2rem;
+}
+
+.app-header {
+ max-width: 1400px;
+ margin: 0 auto 2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.app-title {
+ font-size: 2.5rem;
+ font-weight: 700;
+ background: linear-gradient(to right, #a78bfa, #ec4899);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.app-subtitle {
+ color: #9ca3af;
+ font-size: 0.875rem;
+ margin-top: 0.5rem;
+}
+
+.username-highlight {
+ color: #a78bfa;
+}
+
+.refresh-button {
+ background-color: #7c3aed;
+ color: white;
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 0.375rem;
+ font-family: "JetBrains Mono", monospace;
+ font-size: 0.875rem;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.refresh-button:hover {
+ background-color: #6d28d9;
+}
+
+.loading-container,
+.error-container {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #1e1e1e;
+}
+
+.loading-content,
+.error-content {
text-align: center;
}
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
+.loading-spinner {
+ animation: spin 1s linear infinite;
+ color: #a78bfa;
+ margin: 0 auto 1rem;
}
-@keyframes logo-spin {
+@keyframes spin {
from {
transform: rotate(0deg);
}
@@ -27,16 +76,33 @@
}
}
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
+.loading-text,
+.error-text {
+ color: #9ca3af;
+ font-family: "JetBrains Mono", monospace;
}
-.card {
- padding: 2em;
+.error-icon {
+ color: #ef4444;
+ margin: 0 auto 1rem;
}
-.read-the-docs {
- color: #888;
+.error-message {
+ color: #f87171;
+ margin-bottom: 1rem;
+}
+
+.retry-button {
+ background-color: #7c3aed;
+ color: white;
+ padding: 0.5rem 1.5rem;
+ border: none;
+ border-radius: 0.375rem;
+ font-family: "JetBrains Mono", monospace;
+ cursor: pointer;
+ transition: background-color 0.2s;
+}
+
+.retry-button:hover {
+ background-color: #6d28d9;
}
diff --git a/src/App.jsx b/src/App.jsx
index f67355a..d77753d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,35 +1,71 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import { useState, useEffect } from 'react';
+import { AlertCircle } from 'lucide-react';
+import { GradesTable } from './components/GradesTable';
+import { LoadingSpinner } from './components/LoadingSpinner';
+import { api } from './services/api';
+import './App.css';
function App() {
- const [count, setCount] = useState(0)
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [username] = useState(
+ import.meta.env.VITE_DEFAULT_USERNAME || 'keshav.anand.1'
+ );
+
+ useEffect(() => {
+ fetchGrades();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [username]);
+
+ const fetchGrades = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const result = await api.getGrades(username);
+ setData(result.user);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (loading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
+
+
Error: {error}
+
+
+
+ );
+ }
return (
- <>
-
- Vite + React
-
-