Skip to content

Commit 9f0824a

Browse files
committed
refctor the entire code
1 parent c460cca commit 9f0824a

22 files changed

Lines changed: 421 additions & 304 deletions

public/.~lock.tools.xlsx#

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/app/page.tsx

Lines changed: 3 additions & 303 deletions
Original file line numberDiff line numberDiff line change
@@ -1,305 +1,5 @@
1-
'use client';
1+
import ScriptGenerator from "../components/ScriptGenerator/ScriptGenerator";
22

3-
import { useEffect, useState } from "react";
4-
5-
// Define types
6-
type OS = "windows" | "macos" | "linux";
7-
type WindowsPkg = "choco" | "winget" | "scoop";
8-
type MacPkg = "homebrew";
9-
type LinuxPkg = "apt" | "dnf" | "pacman";
10-
type PkgManager = WindowsPkg | MacPkg | LinuxPkg;
11-
12-
interface Tool {
13-
name: string;
14-
iconsrc: string;
15-
install: Partial<Record<PkgManager, string>>;
16-
}
17-
18-
interface ToolCategory {
19-
category: string;
20-
tools: Tool[];
21-
}
22-
23-
const osOptions: OS[] = ["windows", "macos", "linux"];
24-
const pkgManagers: Record<OS, PkgManager[]> = {
25-
windows: ["choco", "winget", "scoop"],
26-
macos: ["homebrew"],
27-
linux: ["apt", "dnf", "pacman"],
28-
};
29-
30-
export default function ScriptGenerator() {
31-
const [toolsData, setToolsData] = useState<ToolCategory[]>([]);
32-
const [selectedOS, setSelectedOS] = useState<OS>("windows");
33-
const [selectedPkg, setSelectedPkg] = useState<PkgManager>("choco");
34-
const [selectedTools, setSelectedTools] = useState<string[]>([]);
35-
const [loading, setLoading] = useState(true);
36-
const [searchQuery, setSearchQuery] = useState<string>("");
37-
38-
// Theme
39-
const [theme, setTheme] = useState<"light" | "dark">("dark");
40-
useEffect(() => {
41-
document.documentElement.setAttribute("data-theme", theme);
42-
}, [theme]);
43-
44-
// Fetch tools
45-
useEffect(() => {
46-
const fetchTools = async () => {
47-
try {
48-
const res = await fetch("./tools.json");
49-
if (!res.ok) throw new Error("Failed to fetch JSON");
50-
const data: ToolCategory[] = await res.json();
51-
setToolsData(data);
52-
} catch {
53-
alert("Failed to load tool data.");
54-
} finally {
55-
setLoading(false);
56-
}
57-
};
58-
fetchTools();
59-
}, []);
60-
61-
const handleToolSelect = (toolName: string) => {
62-
setSelectedTools((prev) =>
63-
prev.includes(toolName) ? prev.filter((t) => t !== toolName) : [...prev, toolName]
64-
);
65-
};
66-
67-
const generateScript = () =>
68-
toolsData
69-
.flatMap((category) =>
70-
category.tools
71-
.filter((tool) => selectedTools.includes(tool.name) && tool.install[selectedPkg])
72-
.map((tool) => tool.install[selectedPkg]!)
73-
)
74-
.join("\n");
75-
76-
const handleCopy = async () => {
77-
try {
78-
await navigator.clipboard.writeText(generateScript());
79-
alert("Script copied!");
80-
} catch {
81-
alert("Failed to copy");
82-
}
83-
};
84-
85-
const handleDownload = () => {
86-
const blob = new Blob([generateScript()], { type: "text/plain" });
87-
const link = document.createElement("a");
88-
link.href = URL.createObjectURL(blob);
89-
link.download = "install_script.sh";
90-
link.click();
91-
};
92-
93-
if (loading)
94-
return (
95-
<div className="text-[var(--foreground)] font-sans text-center mt-20">
96-
<div>Loading tools data...</div>
97-
<div className="spinner"></div>
98-
</div>
99-
);
100-
101-
return (
102-
<div
103-
className="min-h-screen font-sans bg-[var(--background)] text-[var(--foreground)] transition-colors duration-300"
104-
data-theme={theme}
105-
>
106-
<div className="max-w-screen mx-auto px-4 py-10">
107-
{/* Header with Title, Search, and Action buttons */}
108-
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 mb-12">
109-
{/* Left side - Title */}
110-
<h1 className="text-4xl sm:text-6xl font-extrabold tracking-tight text-left">
111-
<span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 via-purple-600 to-pink-500">
112-
DevSetup
113-
</span>
114-
</h1>
115-
116-
{/* Center/Bottom - Search Box */}
117-
<div className="relative w-full max-w-2xl lg:max-w-3xl hidden sm:block">
118-
<input
119-
type="text"
120-
placeholder="Search for development tools..."
121-
value={searchQuery}
122-
onChange={(e) => setSearchQuery(e.target.value)}
123-
className="w-full px-8 py-4 pl-16 bg-gradient-to-r from-[var(--card-bg)] to-gray-800/50 text-[var(--foreground)] border-2 border-gray-600/50 rounded-2xl focus:outline-none focus:ring-4 focus:ring-indigo-500/30 focus:border-indigo-400 transition-all duration-500 text-lg placeholder-gray-400 shadow-2xl hover:shadow-indigo-500/20 backdrop-blur-sm"
124-
/>
125-
<div className="absolute left-5 top-1/2 transform -translate-y-1/2">
126-
<svg
127-
className="w-6 h-6 text-indigo-400"
128-
fill="none"
129-
stroke="currentColor"
130-
viewBox="0 0 24 24"
131-
>
132-
<path
133-
strokeLinecap="round"
134-
strokeLinejoin="round"
135-
strokeWidth={2.5}
136-
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
137-
/>
138-
</svg>
139-
</div>
140-
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-indigo-500/10 to-purple-500/10 pointer-events-none"></div>
141-
</div>
142-
143-
{/* Right side - Action buttons */}
144-
<div className="flex gap-4 lg:flex-shrink-0">
145-
{/* Add New + button */}
146-
<a
147-
href="https://forms.gle/cWfDnzvYo5dBuy7C7"
148-
target="_blank"
149-
rel="noopener noreferrer"
150-
className="px-8 py-4 text-lg bg-gradient-to-r from-indigo-500 via-purple-600 to-pink-500 hover:from-indigo-600 hover:via-purple-700 hover:to-pink-600 text-white font-bold rounded-2xl shadow-2xl hover:shadow-indigo-500/30 transition-all duration-300 transform hover:scale-105 hover:-translate-y-1"
151-
>
152-
Add New +
153-
</a>
154-
155-
{/* Theme Toggle button */}
156-
<button
157-
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
158-
className="px-8 py-4 text-lg bg-gradient-to-r from-gray-600 to-gray-800 hover:from-gray-700 hover:to-gray-900 text-white font-bold rounded-2xl shadow-2xl hover:shadow-gray-500/30 transition-all duration-300 transform hover:scale-105 hover:-translate-y-1"
159-
>
160-
{theme === "light" ? "🌞 Light" : "🌙 Dark"}
161-
</button>
162-
</div>
163-
</div>
164-
165-
{/* Mobile Search Box */}
166-
<div className="mb-10 sm:hidden">
167-
<div className="relative">
168-
<input
169-
type="text"
170-
placeholder="🔍 Search for development tools..."
171-
value={searchQuery}
172-
onChange={(e) => setSearchQuery(e.target.value)}
173-
className="w-full px-8 py-5 pl-16 bg-gradient-to-r from-[var(--card-bg)] to-gray-800/50 text-[var(--foreground)] border-2 border-gray-600/50 rounded-2xl focus:outline-none focus:ring-4 focus:ring-indigo-500/30 focus:border-indigo-400 transition-all duration-500 text-lg placeholder-gray-400 shadow-2xl hover:shadow-indigo-500/20 backdrop-blur-sm"
174-
/>
175-
<div className="absolute left-5 top-1/2 transform -translate-y-1/2">
176-
<svg
177-
className="w-6 h-6 text-indigo-400"
178-
fill="none"
179-
stroke="currentColor"
180-
viewBox="0 0 24 24"
181-
>
182-
<path
183-
strokeLinecap="round"
184-
strokeLinejoin="round"
185-
strokeWidth={2.5}
186-
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
187-
/>
188-
</svg>
189-
</div>
190-
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-indigo-500/10 to-purple-500/10 pointer-events-none"></div>
191-
</div>
192-
</div>
193-
194-
{/* OS Selector */}
195-
<div className="flex flex-wrap gap-3 justify-center sm:justify-start mb-6">
196-
{osOptions.map((os) => (
197-
<button
198-
key={os}
199-
onClick={() => {
200-
setSelectedOS(os);
201-
setSelectedPkg(pkgManagers[os][0]);
202-
setSelectedTools([]);
203-
}}
204-
className={`px-6 py-3 rounded-full text-sm font-semibold transition duration-200 ${
205-
selectedOS === os
206-
? "bg-gradient-to-r from-indigo-500 to-purple-600 text-white shadow-lg"
207-
: "bg-[var(--button-bg)] text-[var(--button-text)] hover:bg-gray-700"
208-
}`}
209-
>
210-
{os.toUpperCase()}
211-
</button>
212-
))}
213-
</div>
214-
215-
{/* Package Manager Selector */}
216-
<div className="flex flex-wrap gap-3 justify-center sm:justify-start mb-10">
217-
{pkgManagers[selectedOS].map((pkg) => (
218-
<button
219-
key={pkg}
220-
onClick={() => {
221-
setSelectedPkg(pkg);
222-
setSelectedTools([]);
223-
}}
224-
className={`px-6 py-3 rounded-full text-sm font-semibold transition duration-200 ${
225-
selectedPkg === pkg
226-
? "bg-gradient-to-r from-green-500 to-green-600 text-white shadow-lg"
227-
: "bg-[var(--button-bg)] text-[var(--button-text)] hover:bg-gray-700"
228-
}`}
229-
>
230-
{pkg}
231-
</button>
232-
))}
233-
</div>
234-
235-
{/* Tool Categories */}
236-
{toolsData.map((group, i) => {
237-
// Filter tools based on search query
238-
const filteredTools = group.tools.filter(tool =>
239-
tool.name.toLowerCase().includes(searchQuery.toLowerCase())
240-
);
241-
242-
// Only render category if it has matching tools
243-
if (filteredTools.length === 0) return null;
244-
245-
return (
246-
<div key={i} className="mb-10">
247-
<h2 className="text-2xl font-semibold mb-4 border-b border-gray-700 pb-1">
248-
{group.category}
249-
</h2>
250-
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5 gap-6">
251-
{filteredTools.map((tool, index) => {
252-
const isAvailable = !!tool.install[selectedPkg];
253-
return (
254-
<label
255-
key={index}
256-
className={`relative flex flex-col items-center justify-center rounded-2xl p-4 transition cursor-pointer border border-transparent bg-[var(--card-bg)] ${
257-
isAvailable? "hover:shadow-xl hover:border-indigo-500" : "opacity-50 cursor-not-allowed"
258-
}`}
259-
>
260-
<input
261-
type="checkbox"
262-
disabled={!isAvailable}
263-
checked={selectedTools.includes(tool.name)}
264-
onChange={() => handleToolSelect(tool.name)}
265-
className="absolute top-2 right-2 w-5 h-5 accent-indigo-500"
266-
/>
267-
<span className="text-sm text-center">{tool.name}</span>
268-
</label>
269-
);
270-
})}
271-
</div>
272-
</div>
273-
);
274-
})}
275-
276-
{/* Script Output */}
277-
<div className="mt-12">
278-
<h3 className="text-lg font-semibold mb-2">Generated Script:</h3>
279-
<textarea
280-
readOnly
281-
value={generateScript()}
282-
rows={6}
283-
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"
284-
/>
285-
</div>
286-
287-
{/* Actions */}
288-
<div className="mt-6 flex flex-wrap gap-4">
289-
<button
290-
onClick={handleCopy}
291-
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
292-
>
293-
Copy to Clipboard
294-
</button>
295-
<button
296-
onClick={handleDownload}
297-
className="px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
298-
>
299-
Download Script
300-
</button>
301-
</div>
302-
</div>
303-
</div>
304-
);
3+
export default function Home() {
4+
return <ScriptGenerator />;
3055
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
interface Props {
2+
onCopy: () => void;
3+
onDownload: () => void;
4+
}
5+
6+
export default function ActionButtons({ onCopy, onDownload }: Props) {
7+
return (
8+
<div className="mt-6 flex flex-wrap gap-4">
9+
<button
10+
onClick={onCopy}
11+
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
12+
>
13+
Copy to Clipboard
14+
</button>
15+
<button
16+
onClick={onDownload}
17+
className="px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-full shadow-lg transition duration-300"
18+
>
19+
Download Script
20+
</button>
21+
</div>
22+
);
23+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
interface Props {
2+
theme: "light" | "dark";
3+
toggleTheme: () => void;
4+
}
5+
6+
export default function Header({ theme, toggleTheme }: Props) {
7+
return (
8+
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 mb-12">
9+
<h1 className="text-4xl sm:text-6xl font-extrabold tracking-tight text-left">
10+
<span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 via-purple-600 to-pink-500">
11+
DevSetup
12+
</span>
13+
</h1>
14+
15+
<div className="flex gap-4 lg:flex-shrink-0">
16+
<a
17+
href="https://forms.gle/cWfDnzvYo5dBuy7C7"
18+
target="_blank"
19+
rel="noopener noreferrer"
20+
className="px-8 py-4 text-lg bg-gradient-to-r from-indigo-500 via-purple-600 to-pink-500 hover:from-indigo-600 hover:via-purple-700 hover:to-pink-600 text-white font-bold rounded-2xl shadow-2xl hover:shadow-indigo-500/30 transition-all duration-300 transform hover:scale-105 hover:-translate-y-1"
21+
>
22+
Add New +
23+
</a>
24+
25+
<button
26+
onClick={toggleTheme}
27+
className="px-8 py-4 text-lg bg-gradient-to-r from-gray-600 to-gray-800 hover:from-gray-700 hover:to-gray-900 text-white font-bold rounded-2xl shadow-2xl hover:shadow-gray-500/30 transition-all duration-300 transform hover:scale-105 hover:-translate-y-1"
28+
>
29+
{theme === "light" ? "🌞 Light" : "🌙 Dark"}
30+
</button>
31+
</div>
32+
</div>
33+
);
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { OS } from "../../lib/types";
2+
3+
interface Props {
4+
options: OS[];
5+
selectedOS: OS;
6+
onSelect: (os: OS) => void;
7+
}
8+
9+
export default function OSSelector({ options, selectedOS, onSelect }: Props) {
10+
return (
11+
<div className="flex flex-wrap gap-3 justify-center sm:justify-start mb-6">
12+
{options.map((os) => (
13+
<button
14+
key={os}
15+
onClick={() => onSelect(os)}
16+
className={`px-6 py-3 rounded-full text-sm font-semibold transition duration-200 ${
17+
selectedOS === os
18+
? "bg-gradient-to-r from-indigo-500 to-purple-600 text-white shadow-lg"
19+
: "bg-[var(--button-bg)] text-[var(--button-text)] hover:bg-gray-700"
20+
}`}
21+
>
22+
{os.toUpperCase()}
23+
</button>
24+
))}
25+
</div>
26+
);
27+
}

0 commit comments

Comments
 (0)