chore: update the project

This commit is contained in:
eshanized
2025-01-09 18:15:12 +05:30
commit d9e441e04e
19 changed files with 5726 additions and 0 deletions

3
.bolt/config.json Normal file
View File

@@ -0,0 +1,3 @@
{
"template": "bolt-vite-react-ts"
}

8
.bolt/prompt Normal file
View File

@@ -0,0 +1,8 @@
For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production.
By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them.
Use icons from lucide-react for logos.
Use stock photos from unsplash where appropriate, only valid URLs you know exist. Do not download the images, only link to them in image tags.

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

28
eslint.config.js Normal file
View File

@@ -0,0 +1,28 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
);

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

4680
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "vite-react-typescript-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"octokit": "^3.1.2"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.18",
"eslint": "^9.9.1",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^15.9.0",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

623
src/App.tsx Normal file
View File

@@ -0,0 +1,623 @@
import React, { useState } from 'react';
import { Terminal, Github, Copy, Check, RefreshCw, Code2, AlertCircle, Search, Briefcase, GraduationCap, Heart, Settings, Layout, ExternalLink, Twitter, Linkedin, Mail, Globe, MapPin, Building2, BookOpen, Coffee, Sparkles, Palette, Trophy, Activity, Clock, Calendar, Users, GitFork, Star, User } from 'lucide-react';
import { themes } from './themes';
import { generateReadme } from './utils/generateReadme';
import { Octokit } from 'octokit';
const octokit = new Octokit();
const tabs = [
{ id: 'basic', label: 'Basic Info', icon: Layout },
{ id: 'education', label: 'Education', icon: GraduationCap },
{ id: 'professional', label: 'Professional', icon: Briefcase },
{ id: 'interests', label: 'Interests', icon: Heart },
{ id: 'appearance', label: 'Appearance', icon: Settings },
];
function App() {
const [copied, setCopied] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState<any[]>([]);
const [searching, setSearching] = useState(false);
const [activeTab, setActiveTab] = useState('basic');
const [formData, setFormData] = useState({
username: '',
fullName: '',
bio: '',
location: '',
company: '',
website: '',
email: '',
twitter: '',
linkedin: '',
skills: '',
education: '',
interests: '',
theme: 'github-dark',
showStats: true,
showLanguages: true,
showStreak: false,
showActivityGraph: false,
showWakaTime: false,
showTrophies: false,
showContributionCalendar: false,
showSocial: true,
showSkills: true,
showEducation: true,
languages: [] as string[],
githubJoinDate: '',
totalContributions: '',
totalRepositories: '',
followers: '',
following: ''
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;
if (type === 'checkbox') {
const checkbox = e.target as HTMLInputElement;
setFormData(prev => ({ ...prev, [name]: checkbox.checked }));
} else {
setFormData(prev => ({ ...prev, [name]: value }));
}
};
const handleSearchChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const query = e.target.value;
setSearchQuery(query);
if (query.length < 3) {
setSearchResults([]);
return;
}
setSearching(true);
try {
const response = await octokit.rest.search.users({
q: query,
per_page: 5
});
setSearchResults(response.data.items);
} catch (err) {
setError('Failed to search users');
} finally {
setSearching(false);
}
};
const fetchGitHubInfo = async (username: string) => {
setLoading(true);
setError(null);
try {
const [userResponse, reposResponse] = await Promise.all([
octokit.rest.users.getByUsername({ username }),
octokit.rest.repos.listForUser({ username, per_page: 100 })
]);
const user = userResponse.data;
setFormData(prev => ({
...prev,
username: user.login,
fullName: user.name || '',
bio: user.bio || '',
location: user.location || '',
company: user.company || '',
website: user.blog || '',
email: user.email || '',
twitter: user.twitter_username || '',
githubJoinDate: new Date(user.created_at).toLocaleDateString(),
followers: user.followers.toString(),
following: user.following.toString(),
totalRepositories: user.public_repos.toString()
}));
setSearchResults([]);
setSearchQuery('');
} catch (err) {
setError('Failed to fetch user information');
} finally {
setLoading(false);
}
};
const handleCopy = () => {
navigator.clipboard.writeText(generateReadme(formData));
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-gray-100">
<nav className="border-b border-gray-800/50 backdrop-blur-sm bg-gray-900/50 sticky top-0 z-50 px-6 py-4">
<div className="container mx-auto flex justify-between items-center">
<div className="flex items-center space-x-3">
<Terminal className="w-6 h-6 text-green-400" />
<h1 className="text-xl font-bold bg-gradient-to-r from-green-400 to-blue-500 text-transparent bg-clip-text">GitHub README Generator</h1>
</div>
<div className="flex items-center space-x-6">
<a href="https://github.com/eshanized?tab=repositories" target="_blank" rel="noopener noreferrer" className="flex items-center space-x-2 text-gray-300 hover:text-green-400 transition-colors">
<Github className="w-4 h-4" />
<span>View Projects</span>
</a>
</div>
</div>
</nav>
<main className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div className="space-y-6">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 shadow-xl border border-gray-700/50">
<h2 className="text-lg font-semibold flex items-center gap-2 mb-4">
<Search className="w-5 h-5 text-green-400" />
Find GitHub User
</h2>
<div className="space-y-4">
<div className="relative">
<input
type="text"
value={searchQuery}
onChange={handleSearchChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 pl-10 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="Search GitHub users..."
/>
<Search className="absolute left-3 top-2.5 w-5 h-5 text-gray-400" />
{searching && (
<RefreshCw className="absolute right-3 top-2.5 w-5 h-5 animate-spin text-gray-400" />
)}
</div>
<div className="flex gap-2">
<input
type="text"
value={formData.username}
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value }))}
className="flex-1 bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="Enter GitHub username directly..."
/>
<button
onClick={() => formData.username && fetchGitHubInfo(formData.username)}
disabled={loading || !formData.username}
className="px-4 py-2 bg-gradient-to-r from-green-500 to-blue-500 hover:from-green-600 hover:to-blue-600 disabled:from-gray-600 disabled:to-gray-600 rounded-lg flex items-center gap-2 transition-all shadow-lg"
>
{loading ? <RefreshCw className="w-4 h-4 animate-spin" /> : <Github className="w-4 h-4" />}
Fetch
</button>
</div>
{error && (
<div className="flex items-center gap-2 text-red-400 text-sm bg-red-400/10 p-3 rounded-lg">
<AlertCircle className="w-4 h-4" />
{error}
</div>
)}
{searchResults.length > 0 && (
<div className="absolute z-10 w-full mt-1 bg-gray-700/90 backdrop-blur-sm rounded-lg shadow-xl border border-gray-600/50 max-h-60 overflow-auto">
{searchResults.map((user) => (
<button
key={user.id}
onClick={() => fetchGitHubInfo(user.login)}
className="w-full text-left px-4 py-3 hover:bg-gray-600/50 flex items-center space-x-3 transition-colors"
>
<img src={user.avatar_url} alt={user.login} className="w-8 h-8 rounded-full ring-2 ring-gray-700" />
<span className="font-medium">{user.login}</span>
</button>
))}
</div>
)}
</div>
</div>
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl overflow-hidden shadow-xl border border-gray-700/50">
<div className="flex border-b border-gray-700/50">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex-1 px-4 py-3 flex items-center justify-center gap-2 text-sm font-medium transition-all ${
activeTab === tab.id
? 'bg-gradient-to-r from-green-500/10 to-blue-500/10 text-green-400'
: 'hover:bg-gray-700/30'
}`}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</div>
<div className="p-6 space-y-6">
{activeTab === 'basic' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<User className="w-4 h-4 text-green-400" />
Full Name
</label>
<input
type="text"
name="fullName"
value={formData.fullName}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="John Doe"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<BookOpen className="w-4 h-4 text-green-400" />
Bio
</label>
<textarea
name="bio"
value={formData.bio}
onChange={handleChange}
rows={3}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="A passionate developer..."
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<MapPin className="w-4 h-4 text-green-400" />
Location
</label>
<input
type="text"
name="location"
value={formData.location}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="San Francisco, CA"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Globe className="w-4 h-4 text-green-400" />
Website
</label>
<input
type="url"
name="website"
value={formData.website}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="https://example.com"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Mail className="w-4 h-4 text-green-400" />
Email
</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="john@example.com"
/>
</div>
</div>
)}
{activeTab === 'education' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<GraduationCap className="w-4 h-4 text-green-400" />
Education History
</label>
<textarea
name="education"
value={formData.education}
onChange={handleChange}
rows={4}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="🎓 BSc in Computer Science - University Name (2018-2022)&#10;📚 Relevant Coursework: Data Structures, Algorithms, Web Development"
/>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showEducation"
checked={formData.showEducation}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm">Show education section in README</label>
</div>
</div>
)}
{activeTab === 'professional' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Building2 className="w-4 h-4 text-green-400" />
Company
</label>
<input
type="text"
name="company"
value={formData.company}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="Company Name"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Code2 className="w-4 h-4 text-green-400" />
Skills
</label>
<textarea
name="skills"
value={formData.skills}
onChange={handleChange}
rows={3}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="React, Node.js, TypeScript, Python..."
/>
<p className="text-sm text-gray-400 mt-1">Separate skills with commas</p>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Linkedin className="w-4 h-4 text-green-400" />
LinkedIn
</label>
<input
type="url"
name="linkedin"
value={formData.linkedin}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="https://linkedin.com/in/username"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Twitter className="w-4 h-4 text-green-400" />
Twitter
</label>
<input
type="text"
name="twitter"
value={formData.twitter}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="username (without @)"
/>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showSkills"
checked={formData.showSkills}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm">Show skills section in README</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showSocial"
checked={formData.showSocial}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm">Show social links in README</label>
</div>
</div>
)}
{activeTab === 'interests' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Sparkles className="w-4 h-4 text-green-400" />
Interests & Hobbies
</label>
<textarea
name="interests"
value={formData.interests}
onChange={handleChange}
rows={3}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
placeholder="Open Source, Machine Learning, Game Development..."
/>
<p className="text-sm text-gray-400 mt-1">Add your interests and hobbies, one per line</p>
</div>
</div>
)}
{activeTab === 'appearance' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1 flex items-center gap-2">
<Palette className="w-4 h-4 text-green-400" />
Theme
</label>
<select
name="theme"
value={formData.theme}
onChange={handleChange}
className="w-full bg-gray-700/50 rounded-lg px-3 py-2 focus:ring-2 focus:ring-green-400 focus:outline-none border border-gray-600/50"
>
{themes.map(theme => (
<option key={theme.id} value={theme.id}>
{theme.name}
</option>
))}
</select>
</div>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showStats"
checked={formData.showStats}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<Activity className="w-4 h-4" />
Show GitHub stats
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showLanguages"
checked={formData.showLanguages}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<Code2 className="w-4 h-4" />
Show most used languages
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showStreak"
checked={formData.showStreak}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<GitFork className="w-4 h-4" />
Show GitHub streak stats
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showActivityGraph"
checked={formData.showActivityGraph}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<Activity className="w-4 h-4" />
Show activity graph
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showWakaTime"
checked={formData.showWakaTime}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<Clock className="w-4 h-4" />
Show WakaTime stats
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showTrophies"
checked={formData.showTrophies}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<Trophy className="w-4 h-4" />
Show GitHub trophies
</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="showContributionCalendar"
checked={formData.showContributionCalendar}
onChange={handleChange}
className="rounded bg-gray-700 border-gray-600 text-green-400 focus:ring-green-400"
/>
<label className="text-sm flex items-center gap-2">
<Calendar className="w-4 h-4" />
Show contribution calendar
</label>
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className="space-y-6">
<div className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 shadow-xl border border-gray-700/50">
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Code2 className="w-5 h-5 text-green-400" />
Preview
</h2>
<button
onClick={handleCopy}
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-gradient-to-r from-green-500 to-blue-500 hover:from-green-600 hover:to-blue-600 transition-all shadow-lg"
>
{copied ? (
<>
<Check className="w-4 h-4" />
<span>Copied!</span>
</>
) : (
<>
<Copy className="w-4 h-4" />
<span>Copy</span>
</>
)}
</button>
</div>
<div className="bg-gray-900/50 rounded-lg p-4 font-mono text-sm overflow-auto max-h-[600px] border border-gray-700/50">
<pre className="whitespace-pre-wrap">{generateReadme(formData)}</pre>
</div>
</div>
</div>
</div>
</main>
<footer className="border-t border-gray-800/50 backdrop-blur-sm bg-gray-900/50 py-8">
<div className="container mx-auto px-6">
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
<div className="flex items-center gap-2">
<Terminal className="w-5 h-5 text-green-400" />
<span className="text-gray-400">GitHub README Generator</span>
<span className="text-gray-500">by</span>
<a href="https://github.com/eshanized" target="_blank" rel="noopener noreferrer" className="text-green-400 hover:text-green-300 transition-colors">eshanized</a>
</div>
<div className="flex items-center gap-6">
<a href="https://github.com/eshanized" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-green-400 transition-colors flex items-center gap-2">
<Github className="w-4 h-4" />
<span>GitHub</span>
</a>
<a href="https://twitter.com/eshanized" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-green-400 transition-colors flex items-center gap-2">
<Twitter className="w-4 h-4" />
<span>Twitter</span>
</a>
<a href="https://linkedin.com/in/eshanized" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-green-400 transition-colors flex items-center gap-2">
<Linkedin className="w-4 h-4" />
<span>LinkedIn</span>
</a>
<a href="https://eshanized.com" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-green-400 transition-colors flex items-center gap-2">
<Globe className="w-4 h-4" />
<span>Website</span>
</a>
</div>
</div>
</div>
</footer>
</div>
);
}
export default App;

9
src/index.css Normal file
View File

@@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
font-family: 'Fira Code', monospace;
}
}

10
src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);

12
src/themes.ts Normal file
View File

@@ -0,0 +1,12 @@
export const themes = [
{ id: 'github-dark', name: 'GitHub Dark', color: '#0d1117' },
{ id: 'dracula', name: 'Dracula', color: '#282a36' },
{ id: 'nord', name: 'Nord', color: '#2e3440' },
{ id: 'monokai', name: 'Monokai', color: '#272822' },
{ id: 'solarized-dark', name: 'Solarized Dark', color: '#002b36' },
{ id: 'material', name: 'Material', color: '#263238' },
{ id: 'one-dark', name: 'One Dark', color: '#282c34' },
{ id: 'synthwave', name: 'Synthwave', color: '#2b213a' },
{ id: 'cobalt', name: 'Cobalt', color: '#193549' },
{ id: 'tokyo-night', name: 'Tokyo Night', color: '#1a1b26' }
];

199
src/utils/generateReadme.ts Normal file
View File

@@ -0,0 +1,199 @@
import { type } from "os";
interface FormData {
username: string;
fullName: string;
bio: string;
location: string;
company: string;
website: string;
email: string;
twitter: string;
linkedin: string;
skills: string;
education: string;
interests: string;
theme: string;
showStats: boolean;
showLanguages: boolean;
showStreak: boolean;
showActivityGraph: boolean;
showWakaTime: boolean;
showTrophies: boolean;
showContributionCalendar: boolean;
showSocial: boolean;
showSkills: boolean;
showEducation: boolean;
languages: string[];
githubJoinDate: string;
totalContributions: string;
totalRepositories: string;
followers: string;
following: string;
}
function generateNeofetch(data: FormData): string {
const username = data.username || 'username';
const fullName = data.fullName || 'Full Name';
const location = data.location || 'Location';
const company = data.company || 'Company';
const pythonArt = `
.?77777777777777$.
777..777777777777$+
.77 7777777777$$$
.777 .7777777777$$$$
.7777777777777$$$$$$
..........:77$$$$$$$
.77777777777777777$$$$$$$$$.=======.
777777777777777777$$$$$$$$$$.========
7777777777777777$$$$$$$$$$$$$.=========
77777777777777$$$$$$$$$$$$$$$.=========
777777777777$$$$$$$$$$$$$$$$ :========+.
77777777777$$$$$$$$$$$$$$+..=========++~
777777777$$..~=====================+++++
77777777$~.~~~~=~=================+++++.
777777$$$.~~~===================+++++++.
77777$$$$.~~==================++++++++:
7$$$$$$$.==================++++++++++.
.,$$$$$$.================++++++++++~.
.=========~.........
.=============++++++
.===========+++..+++
.==========+++. .++
,=======++++++,,++,
..=====+++++++++=.
..~+=...
`;
const rightPart = [
`${fullName}`,
`----------------`,
`🐍 Language: Python`,
`⏱️ Runtime: ${new Date().getFullYear() - 2020}y`,
`🔧 Version: 3.${new Date().getFullYear() - 2015}.0`,
`----------------`,
`👤 User: ${username}`,
`📍 Location: ${location}`,
`🏢 Company: ${company}`,
`📅 GitHub: ${data.githubJoinDate || 'N/A'}`,
`📊 Contributions: ${data.totalContributions || 'N/A'}`,
`📚 Repositories: ${data.totalRepositories || 'N/A'}`,
`👥 Followers: ${data.followers || 'N/A'}`,
`👣 Following: ${data.following || 'N/A'}`,
data.bio && `📝 Bio: ${data.bio}`,
data.email && `📧 Email: ${data.email}`,
data.website && `🌐 Website: ${data.website}`,
data.twitter && `🐦 Twitter: @${data.twitter}`,
data.linkedin && `💼 LinkedIn: ${data.linkedin}`,
data.interests && `🌟 Interests: ${data.interests}`
].filter(Boolean);
const lines = pythonArt.split('\n');
const maxLeftWidth = Math.max(...lines.map(line => line.length));
return lines.map((line, i) => {
const padding = ' '.repeat(maxLeftWidth - line.length + 4);
return `${line}${padding}${rightPart[i] || ''}`;
}).join('\n');
}
export function generateReadme(data: FormData): string {
const sections: string[] = [];
// Top Wave Banner
sections.push(`<div align="center">
<img src="https://capsule-render.vercel.app/api?type=waving&color=gradient&height=200&section=header&text=${encodeURIComponent(`Hi 👋, I'm ${data.fullName || data.username}`)}&fontSize=50&animation=fadeIn&fontAlignY=30&desc=${encodeURIComponent('A passionate developer from ' + (data.location || 'Earth'))}&descAlignY=50" width="100%" />
<img src="https://raw.githubusercontent.com/platane/snk/output/github-contribution-grid-snake.svg" alt="Snake animation" />
</div>`);
// Terminal Style Info
sections.push('\n```bash\n' + generateNeofetch(data) + '\n```\n');
// About Me Section
const aboutPoints: string[] = [];
if (data.company) aboutPoints.push(`🏢 I'm currently working at **${data.company}**`);
if (data.location) aboutPoints.push(`📍 Based in **${data.location}**`);
if (data.website) aboutPoints.push(`🌐 Visit my portfolio at [${data.website}](${data.website})`);
if (data.email) aboutPoints.push(`📫 Contact me at **${data.email}**`);
if (aboutPoints.length > 0) {
sections.push('\n### About Me\n');
sections.push(aboutPoints.join('\n\n'));
}
// Skills Section
if (data.showSkills && data.skills) {
sections.push('\n### 💻 Tech Stack\n');
const skills = data.skills.split(',').map(skill => skill.trim());
sections.push(skills.map(skill => `![${skill}](https://img.shields.io/badge/${skill}-333333?style=flat&logo=${skill.toLowerCase()})`).join(' '));
}
// Education Section
if (data.showEducation && data.education) {
sections.push('\n### 🎓 Education\n');
sections.push(data.education);
}
// Interests Section
if (data.interests) {
sections.push('\n### 🌟 Interests\n');
sections.push(data.interests);
}
// Social Links
if (data.showSocial) {
const socials: string[] = [];
if (data.github) socials.push(`[<img src="https://img.shields.io/badge/GitHub-%2312100E.svg?&style=for-the-badge&logo=Github&logoColor=white" />](https://github.com/${data.username})`);
if (data.twitter) socials.push(`[<img src="https://img.shields.io/badge/twitter-%231DA1F2.svg?&style=for-the-badge&logo=twitter&logoColor=white" />](https://twitter.com/${data.twitter})`);
if (data.linkedin) socials.push(`[<img src="https://img.shields.io/badge/linkedin-%230077B5.svg?&style=for-the-badge&logo=linkedin&logoColor=white" />](${data.linkedin})`);
if (socials.length > 0) {
sections.push('\n### 🌐 Connect with me\n');
sections.push(socials.join(' '));
}
}
// GitHub Stats Section
if (data.showStats) {
sections.push('\n### 📊 GitHub Stats\n');
sections.push('<div align="center">\n');
sections.push(`<img src="https://github-readme-stats.vercel.app/api?username=${data.username}&show_icons=true&theme=${data.theme}" alt="GitHub Stats" />\n`);
if (data.showStreak) {
sections.push(`<img src="https://github-readme-streak-stats.herokuapp.com/?user=${data.username}&theme=${data.theme}" alt="GitHub Streak" />\n`);
}
if (data.showLanguages) {
sections.push(`<img src="https://github-readme-stats.vercel.app/api/top-langs/?username=${data.username}&layout=compact&theme=${data.theme}" alt="Top Languages" />\n`);
}
if (data.showActivityGraph) {
sections.push(`<img src="https://github-readme-activity-graph.vercel.app/graph?username=${data.username}&theme=${data.theme === 'github-dark' ? 'github-dark' : 'react-dark'}" alt="Activity Graph" />\n`);
}
if (data.showWakaTime) {
sections.push(`<img src="https://github-readme-stats.vercel.app/api/wakatime?username=${data.username}&theme=${data.theme}" alt="WakaTime Stats" />\n`);
}
if (data.showTrophies) {
sections.push(`<img src="https://github-profile-trophy.vercel.app/?username=${data.username}&theme=${data.theme === 'github-dark' ? 'darkhub' : 'onedark'}&column=4&margin-w=15&margin-h=15" alt="Trophies" />\n`);
}
sections.push('</div>\n');
if (data.showWakaTime) {
sections.push('> Note: To track coding time statistics, connect your GitHub account with [WakaTime](https://wakatime.com).');
}
}
// Profile Views Counter
sections.push(`\n<img src="https://komarev.com/ghpvc/?username=${data.username}&label=Profile%20Views&color=0e75b6&style=flat" alt="Profile Views" />`);
// Bottom Wave Banner
sections.push(`\n<img src="https://capsule-render.vercel.app/api?type=waving&color=gradient&height=150&section=footer" width="100%" />`);
return sections.join('\n');
}

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

12
tailwind.config.js Normal file
View File

@@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
sans: ['Fira Code', 'monospace'],
},
},
},
plugins: [],
};

24
tsconfig.app.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

7
tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

22
tsconfig.node.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

10
vite.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ['lucide-react'],
},
});