Skip to content

Commit df4fee9

Browse files
feat: premium Compare page, Dashboard redesign, fixed MobileNav bleed-through
1 parent 194c971 commit df4fee9

2 files changed

Lines changed: 329 additions & 196 deletions

File tree

src/app/compare/page.tsx

Lines changed: 150 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import { useSearchParams } from "next/navigation";
44
import { useEffect, useState, Suspense } from "react";
55
import { UniTable } from "@/components/UniTable";
6-
import { SlidersHorizontal, MapPin, DollarSign, GraduationCap } from "lucide-react";
6+
import { SlidersHorizontal, MapPin, DollarSign, GraduationCap, Search, X, Sparkles } from "lucide-react";
77

88
function CompareContent() {
99
const searchParams = useSearchParams();
10-
// Strip wildcard values so API treats them as "show all"
1110
const rawCourse = searchParams.get("course") || "";
1211
const rawBudget = searchParams.get("budget") || "";
1312
const initialCourse = ["any", "Any"].includes(rawCourse) ? "" : rawCourse;
@@ -16,8 +15,6 @@ function CompareContent() {
1615

1716
const [data, setData] = useState<any[]>([]);
1817
const [loading, setLoading] = useState(true);
19-
20-
// Filter states
2118
const [course, setCourse] = useState(initialCourse);
2219
const [budget, setBudget] = useState(initialBudget);
2320
const [location, setLocation] = useState(initialLocation);
@@ -34,94 +31,169 @@ function CompareContent() {
3431
setLoading(false);
3532
};
3633

37-
useEffect(() => {
38-
fetchResults();
39-
}, [course, budget, location]); // Re-fetch when filters change
34+
useEffect(() => { fetchResults(); }, [course, budget, location]);
35+
36+
const clearFilters = () => { setCourse(""); setBudget(""); setLocation("Anywhere"); };
37+
const hasFilters = course || budget || location !== "Anywhere";
4038

4139
return (
42-
<div className="flex flex-col md:flex-row gap-6 mt-10">
43-
<aside className="w-full md:w-64 bg-white dark:bg-slate-900 border border-gray-100 dark:border-gray-800 rounded-2xl p-6 shadow-sm h-fit sticky top-24 z-10">
44-
<div className="flex items-center mb-6 text-gray-800 dark:text-gray-100 font-bold text-lg">
45-
<SlidersHorizontal className="mr-2" size={20} /> Filters
40+
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 px-4 sm:px-6 lg:px-8 py-8">
41+
{/* ── Page Header ──────────────────────────────────────────────── */}
42+
<div className="max-w-7xl mx-auto mb-8">
43+
<div className="flex items-start justify-between gap-4 flex-wrap">
44+
<div>
45+
<div className="flex items-center gap-2 mb-2">
46+
<span className="badge badge-primary">QS 2026 Live</span>
47+
<span className="w-2 h-2 rounded-full bg-accent-500 animate-pulse" />
48+
<span className="text-xs font-semibold text-accent-600 dark:text-accent-400">Updated Daily</span>
49+
</div>
50+
<h1 className="font-display font-extrabold text-3xl md:text-4xl text-slate-900 dark:text-white">
51+
Compare <span className="gradient-brand-text">Universities</span>
52+
</h1>
53+
<p className="text-slate-500 dark:text-slate-400 mt-1 text-sm">
54+
{loading ? "Loading..." : <><span className="font-bold text-slate-700 dark:text-slate-300">{data.length}</span> institutions match your criteria</>}
55+
</p>
56+
</div>
57+
{hasFilters && (
58+
<button onClick={clearFilters} className="flex items-center gap-1.5 text-sm text-slate-500 hover:text-red-500 transition-colors font-semibold border border-slate-200 dark:border-slate-700 px-4 py-2 rounded-xl hover:border-red-200 dark:hover:border-red-900">
59+
<X size={14} /> Clear Filters
60+
</button>
61+
)}
4662
</div>
63+
</div>
4764

48-
<div className="space-y-6">
49-
<div className="space-y-2">
50-
<label className="text-sm font-semibold text-gray-500 uppercase flex items-center"><GraduationCap size={14} className="mr-1" /> Course</label>
51-
<input
52-
type="text"
53-
className="w-full bg-gray-50 dark:bg-slate-800 border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-primary outline-none"
54-
value={course}
55-
onChange={(e) => setCourse(e.target.value)}
56-
placeholder="e.g. Computer Science"
57-
/>
58-
</div>
65+
<div className="max-w-7xl mx-auto flex flex-col lg:flex-row gap-6">
66+
{/* ── Sidebar Filters ───────────────────────────────────────── */}
67+
<aside className="w-full lg:w-72 shrink-0">
68+
<div className="bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 rounded-3xl p-6 shadow-card sticky top-24">
69+
<div className="flex items-center gap-2 mb-6">
70+
<div className="w-8 h-8 rounded-xl gradient-brand flex items-center justify-center">
71+
<SlidersHorizontal size={15} className="text-white" />
72+
</div>
73+
<span className="font-display font-bold text-slate-900 dark:text-white">Filters</span>
74+
</div>
5975

60-
<div className="space-y-2">
61-
<label className="text-sm font-semibold text-gray-500 uppercase flex items-center"><DollarSign size={14} className="mr-1" /> Max Tuition</label>
62-
<select
63-
className="w-full bg-gray-50 dark:bg-slate-800 border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-primary outline-none cursor-pointer"
64-
value={budget}
65-
onChange={(e) => setBudget(e.target.value)}
66-
>
67-
<option value="">Any Budget</option>
68-
<option value="10L">&lt; 10 Lakhs INR/yr</option>
69-
<option value="15L">&lt; 15 Lakhs INR/yr</option>
70-
<option value="20L">&lt; 20 Lakhs INR/yr</option>
71-
<option value="30L">&lt; 30 Lakhs INR/yr</option>
72-
<option value="50L">&lt; 50 Lakhs INR/yr</option>
73-
<option value="75L">&lt; 75 Lakhs INR/yr</option>
74-
<option value="1Cr+">1 Cr+ (Premium)</option>
75-
</select>
76-
</div>
76+
<div className="space-y-5">
77+
{/* Course */}
78+
<div>
79+
<label className="flex items-center gap-1.5 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">
80+
<GraduationCap size={12} /> Course / Discipline
81+
</label>
82+
<input
83+
type="text"
84+
className="w-full bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl px-4 py-2.5 text-sm text-slate-800 dark:text-white focus:border-primary-400 focus:ring-2 focus:ring-primary-100 dark:focus:ring-primary-900 outline-none transition-all font-medium placeholder:text-slate-400"
85+
value={course}
86+
onChange={(e) => setCourse(e.target.value)}
87+
placeholder="e.g. Computer Science"
88+
/>
89+
</div>
7790

78-
<div className="space-y-2">
79-
<label className="text-sm font-semibold text-gray-500 uppercase flex items-center"><MapPin size={14} className="mr-1" /> Location</label>
80-
<select
81-
className="w-full bg-gray-50 dark:bg-slate-800 border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-primary outline-none cursor-pointer"
82-
value={location}
83-
onChange={(e) => setLocation(e.target.value)}
84-
>
85-
<option value="Anywhere">Global (All)</option>
86-
<option value="India">India</option>
87-
<option value="USA">United States</option>
88-
<option value="UK">United Kingdom</option>
89-
<option value="Canada">Canada</option>
90-
<option value="Australia">Australia</option>
91-
<option value="Germany">Germany</option>
92-
<option value="Singapore">Singapore</option>
93-
</select>
94-
</div>
95-
</div>
96-
</aside>
91+
{/* Budget */}
92+
<div>
93+
<label className="flex items-center gap-1.5 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">
94+
<DollarSign size={12} /> Max Tuition / Year
95+
</label>
96+
<select
97+
className="w-full bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl px-4 py-2.5 text-sm text-slate-800 dark:text-white focus:border-primary-400 outline-none cursor-pointer font-medium"
98+
value={budget}
99+
onChange={(e) => setBudget(e.target.value)}
100+
>
101+
<option value="">Any Budget</option>
102+
<option value="10L">&lt; 10 Lakhs INR/yr</option>
103+
<option value="15L">&lt; 15 Lakhs INR/yr</option>
104+
<option value="20L">&lt; 20 Lakhs INR/yr</option>
105+
<option value="30L">&lt; 30 Lakhs INR/yr</option>
106+
<option value="50L">&lt; 50 Lakhs INR/yr</option>
107+
<option value="75L">&lt; 75 Lakhs INR/yr</option>
108+
<option value="1Cr+">1 Cr+ (Premium)</option>
109+
</select>
110+
</div>
97111

98-
<section className="flex-1">
99-
<div className="mb-6 flex justify-between items-end">
100-
<div>
101-
<h2 className="text-3xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-gray-400">
102-
University Rankings 2026
103-
</h2>
104-
<p className="text-gray-500 mt-1 font-medium">{data.length} institutions match your criteria</p>
105-
</div>
106-
</div>
107-
108-
{loading ? (
109-
<div className="flex space-x-2 justify-center items-center h-64">
110-
<div className="w-3 h-3 bg-primary rounded-full animate-bounce"></div>
111-
<div className="w-3 h-3 bg-primary rounded-full animate-bounce delay-75"></div>
112-
<div className="w-3 h-3 bg-primary rounded-full animate-bounce delay-150"></div>
112+
{/* Location */}
113+
<div>
114+
<label className="flex items-center gap-1.5 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">
115+
<MapPin size={12} /> Country / Region
116+
</label>
117+
<select
118+
className="w-full bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl px-4 py-2.5 text-sm text-slate-800 dark:text-white focus:border-primary-400 outline-none cursor-pointer font-medium"
119+
value={location}
120+
onChange={(e) => setLocation(e.target.value)}
121+
>
122+
<option value="Anywhere">Global (All)</option>
123+
<option value="India">India</option>
124+
<option value="USA">United States</option>
125+
<option value="UK">United Kingdom</option>
126+
<option value="Canada">Canada</option>
127+
<option value="Australia">Australia</option>
128+
<option value="Germany">Germany</option>
129+
<option value="Singapore">Singapore</option>
130+
</select>
131+
</div>
132+
133+
{/* Quick filter chips */}
134+
<div>
135+
<label className="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2 block">Popular Searches</label>
136+
<div className="flex flex-wrap gap-2">
137+
{["MBA", "CS", "Data Science", "AI", "Finance"].map((s) => (
138+
<button key={s} onClick={() => setCourse(s)}
139+
className={`text-xs font-bold px-3 py-1.5 rounded-full transition-all ${course === s ? "gradient-brand text-white" : "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-primary-50 dark:hover:bg-primary-950 hover:text-primary"}`}>
140+
{s}
141+
</button>
142+
))}
143+
</div>
144+
</div>
145+
146+
{/* AI suggestion hint */}
147+
<div className="bg-primary-50 dark:bg-primary-950/50 rounded-2xl p-4 border border-primary-100 dark:border-primary-900">
148+
<div className="flex items-center gap-2 mb-2">
149+
<Sparkles size={14} className="text-primary" />
150+
<span className="text-xs font-bold text-primary">AI Counselor Tip</span>
151+
</div>
152+
<p className="text-xs text-slate-600 dark:text-slate-400">
153+
Not sure which country? Use the AI chatbot (bottom right) for personalized recommendations.
154+
</p>
155+
</div>
156+
</div>
113157
</div>
114-
) : (
115-
<UniTable data={data} />
116-
)}
117-
</section>
158+
</aside>
159+
160+
{/* ── Results Table ─────────────────────────────────────────── */}
161+
<section className="flex-1 min-w-0">
162+
{loading ? (
163+
<div className="bg-white dark:bg-slate-900 rounded-3xl border border-slate-100 dark:border-slate-800 shadow-card p-10">
164+
<div className="space-y-4">
165+
{[...Array(6)].map((_, i) => (
166+
<div key={i} className="animate-pulse flex items-center gap-4 p-4 rounded-2xl bg-slate-50 dark:bg-slate-800">
167+
<div className="w-10 h-10 rounded-xl bg-slate-200 dark:bg-slate-700 shrink-0" />
168+
<div className="flex-1 space-y-2">
169+
<div className="h-4 bg-slate-200 dark:bg-slate-700 rounded w-3/4" />
170+
<div className="h-3 bg-slate-100 dark:bg-slate-700 rounded w-1/2" />
171+
</div>
172+
<div className="h-6 w-16 bg-slate-200 dark:bg-slate-700 rounded-full" />
173+
</div>
174+
))}
175+
</div>
176+
</div>
177+
) : (
178+
<div className="bg-white dark:bg-slate-900 rounded-3xl border border-slate-100 dark:border-slate-800 shadow-card overflow-hidden">
179+
<UniTable data={data} />
180+
</div>
181+
)}
182+
</section>
183+
</div>
118184
</div>
119185
);
120186
}
121187

122188
export default function ComparePage() {
123189
return (
124-
<Suspense fallback={<div className="text-center py-20 text-gray-500 animate-pulse">Loading Comparison Interface...</div>}>
190+
<Suspense fallback={
191+
<div className="flex items-center justify-center h-64 gap-3">
192+
<div className="w-3 h-3 bg-primary rounded-full animate-bounce" />
193+
<div className="w-3 h-3 bg-primary rounded-full animate-bounce delay-100" />
194+
<div className="w-3 h-3 bg-primary rounded-full animate-bounce delay-200" />
195+
</div>
196+
}>
125197
<CompareContent />
126198
</Suspense>
127199
);

0 commit comments

Comments
 (0)