Skip to content

Commit 9b92b0a

Browse files
Merge pull request #5 from nayna-G/ui
improvised ui, added components
2 parents 2716c6d + 22f8cea commit 9b92b0a

4 files changed

Lines changed: 341 additions & 35 deletions

File tree

src/app/globals.css

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,52 @@
33
:root {
44
--background: #ffffff;
55
--foreground: #171717;
6+
--card-bg: #f8f9fa;
7+
--button-bg: #e9ecef;
8+
--button-text: #495057;
69
}
710

8-
@theme inline {
9-
--color-background: var(--background);
10-
--color-foreground: var(--foreground);
11-
--font-sans: var(--font-geist-sans);
12-
--font-mono: var(--font-geist-mono);
11+
[data-theme="dark"] {
12+
--background: #0f172a;
13+
--foreground: #f1f5f9;
14+
--card-bg: #1e293b;
15+
--button-bg: #334155;
16+
--button-text: #cbd5e1;
1317
}
1418

19+
/* Theme configuration for Tailwind CSS variables */
20+
1521
@media (prefers-color-scheme: dark) {
1622
:root {
17-
--background: #0a0a0a;
18-
--foreground: #ededed;
23+
--background: #0f172a;
24+
--foreground: #f1f5f9;
25+
--card-bg: #1e293b;
26+
--button-bg: #334155;
27+
--button-text: #cbd5e1;
1928
}
2029
}
2130

2231
body {
2332
background: var(--background);
2433
color: var(--foreground);
25-
font-family: Arial, Helvetica, sans-serif;
34+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
35+
transition: background-color 0.3s ease, color 0.3s ease;
36+
}
37+
38+
/* Custom scrollbar */
39+
::-webkit-scrollbar {
40+
width: 8px;
41+
}
42+
43+
::-webkit-scrollbar-track {
44+
background: var(--background);
45+
}
46+
47+
::-webkit-scrollbar-thumb {
48+
background: var(--button-bg);
49+
border-radius: 4px;
50+
}
51+
52+
::-webkit-scrollbar-thumb:hover {
53+
background: var(--button-text);
2654
}

src/app/page.tsx

Lines changed: 137 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use client';
22
import React, { useEffect, useState } from 'react';
3+
import Navbar from '../components/Navbar';
4+
import Footer from '../components/Footer';
35

46
type Feed = {
57
title: string;
@@ -15,24 +17,47 @@ type Category = {
1517

1618
export default function App() {
1719
const [data, setData] = useState<Category[]>([]);
20+
const [filteredData, setFilteredData] = useState<Category[]>([]);
1821
const [selected, setSelected] = useState<Record<string, boolean>>({});
1922
const [loading, setLoading] = useState(true);
2023
const [opml, setOpml] = useState('');
24+
const [searchQuery, setSearchQuery] = useState('');
2125

2226
useEffect(() => {
2327
fetch('/feeds.json')
2428
.then(res => {
2529
if (!res.ok) throw new Error('feeds.json not found');
2630
return res.json();
2731
})
28-
.then((json: Category[]) => setData(json))
32+
.then((json: Category[]) => {
33+
setData(json);
34+
setFilteredData(json);
35+
})
2936
.catch(err => {
3037
console.error(err);
3138
setData([]);
39+
setFilteredData([]);
3240
})
3341
.finally(() => setLoading(false));
3442
}, []);
3543

44+
// Filter data based on search query
45+
useEffect(() => {
46+
if (!searchQuery.trim()) {
47+
setFilteredData(data);
48+
} else {
49+
const filtered = data.map(category => ({
50+
...category,
51+
feeds: category.feeds.filter(feed =>
52+
feed.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
53+
feed.xmlUrl.toLowerCase().includes(searchQuery.toLowerCase()) ||
54+
(feed.htmlUrl && feed.htmlUrl.toLowerCase().includes(searchQuery.toLowerCase()))
55+
)
56+
})).filter(category => category.feeds.length > 0);
57+
setFilteredData(filtered);
58+
}
59+
}, [data, searchQuery]);
60+
3661
const feedKey = (c: string, f: Feed) => `${c}||${f.xmlUrl}`;
3762

3863
const toggleFeed = (c: string, f: Feed) => {
@@ -106,55 +131,140 @@ export default function App() {
106131
URL.revokeObjectURL(url);
107132
};
108133

109-
if (loading) return <div className="p-6">Loading feeds...</div>;
134+
const handleAddNew = () => {
135+
// Placeholder for Google Form integration
136+
window.open('https://forms.google.com', '_blank');
137+
};
138+
139+
const handleSearch = (query: string) => {
140+
setSearchQuery(query);
141+
};
142+
143+
if (loading) return (
144+
<div className="min-h-screen bg-[var(--background)] text-[var(--foreground)] flex flex-col">
145+
<Navbar title="RSS CSE Setup" onAddNew={handleAddNew} onSearch={handleSearch} />
146+
<div className="flex-1 flex items-center justify-center">
147+
<div className="text-center">
148+
<div className="text-lg mb-2">Loading feeds...</div>
149+
<div className="animate-spin text-2xl"></div>
150+
</div>
151+
</div>
152+
<Footer />
153+
</div>
154+
);
110155

111156
return (
112-
<div className="min-h-screen bg-slate-900 text-slate-100 p-6">
113-
<div className="max-w-5xl mx-auto">
114-
<header className="flex items-center justify-between mb-6">
115-
<h1 className="text-3xl font-bold">rss-cse-picker</h1>
116-
<a href="#" className="text-sm underline">Edit feeds.xlsx → convert → commit</a>
117-
</header>
157+
<div className="min-h-screen bg-[var(--background)] text-[var(--foreground)] flex flex-col transition-colors duration-300">
158+
<Navbar title="RSS CSE Setup" onAddNew={handleAddNew} onSearch={handleSearch} />
159+
160+
<main className="flex-1 w-full px-4 py-8">
161+
<div className="mb-6 text-center">
162+
<p className="text-lg text-gray-400 mb-4">
163+
Select RSS feeds from various Computer Science and Engineering categories to generate your OPML file.
164+
</p>
165+
{searchQuery && (
166+
<p className="text-sm text-gray-500 mt-2">
167+
Showing results for: "{searchQuery}"
168+
</p>
169+
)}
170+
</div>
118171

119172
<section>
120-
{data.map((cat) => (
121-
<div key={cat.category} className="mb-6 bg-slate-800 p-4 rounded-lg">
173+
{filteredData.length === 0 && searchQuery ? (
174+
<div className="text-center py-12">
175+
<p className="text-gray-400 text-lg">No feeds found matching "{searchQuery}"</p>
176+
<p className="text-gray-500 text-sm mt-2">Try adjusting your search terms</p>
177+
</div>
178+
) : (
179+
filteredData.map((cat) => (
180+
<div key={cat.category} className="mb-6 bg-[var(--card-bg)] p-4 rounded-lg shadow-lg border border-gray-700">
122181
<div className="flex items-center justify-between mb-3">
123-
<h2 className="text-xl font-semibold">{cat.category}</h2>
182+
<h2 className="text-xl font-semibold text-[var(--foreground)]">{cat.category}</h2>
124183
<div className="space-x-2">
125-
<button onClick={() => selectAllCategory(cat.category, true)} className="px-3 py-1 rounded bg-green-600 text-sm">Select all</button>
126-
<button onClick={() => selectAllCategory(cat.category, false)} className="px-3 py-1 rounded bg-red-600 text-sm">Clear</button>
184+
<button
185+
onClick={() => selectAllCategory(cat.category, true)}
186+
className="px-3 py-1 rounded bg-green-600 hover:bg-green-700 text-white text-sm font-medium transition-colors"
187+
>
188+
Select all
189+
</button>
190+
<button
191+
onClick={() => selectAllCategory(cat.category, false)}
192+
className="px-3 py-1 rounded bg-red-600 hover:bg-red-700 text-white text-sm font-medium transition-colors"
193+
>
194+
Clear
195+
</button>
127196
</div>
128197
</div>
129198

130-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
199+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-3">
131200
{cat.feeds.map(f => {
132201
const k = feedKey(cat.category, f);
133202
return (
134-
<label key={k} className={`flex items-start gap-3 p-2 rounded ${selected[k] ? 'bg-slate-700' : 'bg-slate-800'}`}>
135-
<input type="checkbox" checked={!!selected[k]} onChange={() => toggleFeed(cat.category, f)} />
136-
<div>
137-
<div className="font-medium">{f.title || f.xmlUrl}</div>
138-
<div className="text-xs text-slate-400">{f.htmlUrl || f.xmlUrl}</div>
203+
<label
204+
key={k}
205+
className={`flex items-start gap-3 p-3 rounded-lg cursor-pointer transition-all duration-200 ${
206+
selected[k]
207+
? 'bg-indigo-600/20 border border-indigo-500'
208+
: 'bg-[var(--button-bg)] hover:bg-gray-600 border border-transparent'
209+
}`}
210+
>
211+
<input
212+
type="checkbox"
213+
checked={!!selected[k]}
214+
onChange={() => toggleFeed(cat.category, f)}
215+
className="mt-1 w-4 h-4 accent-indigo-500"
216+
/>
217+
<div className="flex-1 min-w-0">
218+
<div className="font-medium text-[var(--foreground)] truncate">{f.title || f.xmlUrl}</div>
219+
<div className="text-xs text-gray-400 truncate">{f.htmlUrl || f.xmlUrl}</div>
139220
</div>
140221
</label>
141222
);
142223
})}
143224
</div>
144225
</div>
145-
))}
226+
))
227+
)}
146228
</section>
147229

148-
<div className="mt-6 flex gap-3">
149-
<button onClick={generateOpml} className="px-4 py-2 bg-indigo-600 rounded font-semibold">Generate OPML</button>
150-
<button onClick={copyToClipboard} className="px-4 py-2 bg-blue-600 rounded">Copy OPML</button>
151-
<button onClick={downloadOpml} className="px-4 py-2 bg-emerald-600 rounded">Download OPML</button>
230+
<div className="mt-8 flex flex-wrap gap-3">
231+
<button
232+
onClick={generateOpml}
233+
className="px-6 py-3 bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
234+
>
235+
Generate OPML
236+
</button>
237+
<button
238+
onClick={copyToClipboard}
239+
className="px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
240+
>
241+
Copy OPML
242+
</button>
243+
<button
244+
onClick={downloadOpml}
245+
className="px-6 py-3 bg-gradient-to-r from-emerald-500 to-emerald-600 hover:from-emerald-600 hover:to-emerald-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
246+
>
247+
Download OPML
248+
</button>
152249
</div>
153250

154-
<div className="mt-6">
155-
<textarea value={opml} readOnly rows={14} className="w-full bg-slate-900 border border-slate-700 p-3 rounded font-mono"></textarea>
251+
<div className="mt-8">
252+
<h3 className="text-lg font-semibold mb-3 text-[var(--foreground)]">Generated OPML:</h3>
253+
<textarea
254+
value={opml}
255+
readOnly
256+
rows={8}
257+
className="w-full bg-[var(--card-bg)] text-[var(--foreground)] border border-gray-600 rounded-lg p-4 font-mono resize-none transition-colors duration-300 focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
258+
placeholder="Generated OPML will appear here..."
259+
/>
156260
</div>
157-
</div>
261+
</main>
262+
263+
<Footer
264+
githubUrl="https://github.com/your-username/rss-cse-setup"
265+
linkedinUrl="https://linkedin.com/in/your-profile"
266+
codeCompassUrl="https://codecompass.com/your-profile"
267+
/>
158268
</div>
159269
);
160270
}

src/components/Footer.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use client';
2+
3+
import { FaGithub, FaLinkedin, FaCompass } from 'react-icons/fa';
4+
5+
interface FooterProps {
6+
githubUrl?: string;
7+
linkedinUrl?: string;
8+
codeCompassUrl?: string;
9+
}
10+
11+
export default function Footer({
12+
githubUrl = "https://github.com",
13+
linkedinUrl = "https://linkedin.com",
14+
codeCompassUrl = "https://codecompass.com"
15+
}: FooterProps) {
16+
return (
17+
<footer className="w-full bg-[var(--card-bg)] border-t border-gray-700 mt-auto">
18+
<div className="max-w-screen mx-auto px-4 py-8">
19+
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
20+
{/* Project Info */}
21+
<div className="text-center md:text-left">
22+
<h3 className="text-lg font-semibold text-[var(--foreground)] mb-2">
23+
RSS CSE Setup
24+
</h3>
25+
<p className="text-sm text-gray-400 max-w-md">
26+
A comprehensive tool for managing and organizing RSS feeds for Computer Science and Engineering resources.
27+
</p>
28+
</div>
29+
30+
{/* Social Links */}
31+
<div className="flex items-center gap-6">
32+
<a
33+
href={githubUrl}
34+
target="_blank"
35+
rel="noopener noreferrer"
36+
className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors duration-200"
37+
aria-label="GitHub"
38+
>
39+
<FaGithub className="text-xl" />
40+
<span className="hidden sm:inline">GitHub</span>
41+
</a>
42+
43+
<a
44+
href={linkedinUrl}
45+
target="_blank"
46+
rel="noopener noreferrer"
47+
className="flex items-center gap-2 text-gray-400 hover:text-blue-400 transition-colors duration-200"
48+
aria-label="LinkedIn"
49+
>
50+
<FaLinkedin className="text-xl" />
51+
<span className="hidden sm:inline">LinkedIn</span>
52+
</a>
53+
54+
<a
55+
href={codeCompassUrl}
56+
target="_blank"
57+
rel="noopener noreferrer"
58+
className="flex items-center gap-2 text-gray-400 hover:text-green-400 transition-colors duration-200"
59+
aria-label="CodeCompass"
60+
>
61+
<FaCompass className="text-xl" />
62+
<span className="hidden sm:inline">CodeCompass</span>
63+
</a>
64+
</div>
65+
</div>
66+
67+
{/* Bottom Section */}
68+
<div className="mt-6 pt-6 border-t border-gray-700 text-center">
69+
<p className="text-sm text-gray-500">
70+
© {new Date().getFullYear()} RSS CSE Setup. Built with ❤️ for the developer community.
71+
</p>
72+
</div>
73+
</div>
74+
</footer>
75+
);
76+
}

0 commit comments

Comments
 (0)