mirror of
https://github.com/Snigdha-OS/Snigdha-OS.github.io.git
synced 2025-09-05 20:26:43 +02:00
🚀 feat(_new): new website ui and function
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal 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?
|
26
deploy.sh
Normal file
26
deploy.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# abort on errors
|
||||
set -e
|
||||
|
||||
# build
|
||||
npm run build
|
||||
|
||||
# navigate into the build output directory
|
||||
cd dist
|
||||
|
||||
# place .nojekyll to bypass Jekyll processing
|
||||
echo > .nojekyll
|
||||
|
||||
# if you are deploying to a custom domain
|
||||
# echo 'www.example.com' > CNAME
|
||||
|
||||
git init
|
||||
git checkout -B main
|
||||
git add -A
|
||||
git commit -m 'deploy'
|
||||
|
||||
# if you are deploying to https://<USERNAME>.github.io/<REPO>
|
||||
git push -f git@github.com:<USERNAME>/snigdha-os.git main:gh-pages
|
||||
|
||||
cd -
|
28
eslint.config.js
Normal file
28
eslint.config.js
Normal 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 },
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
31
index.html
Normal file
31
index.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!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" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<title>Snigdha OS - Advanced Penetration Testing Distribution</title>
|
||||
<!-- Start Single Page Apps for GitHub Pages -->
|
||||
<script type="text/javascript">
|
||||
// Single Page Apps for GitHub Pages
|
||||
// MIT License
|
||||
// https://github.com/rafgraph/spa-github-pages
|
||||
(function(l) {
|
||||
if (l.search[1] === '/' ) {
|
||||
var decoded = l.search.slice(1).split('&').map(function(s) {
|
||||
return s.replace(/~and~/g, '&')
|
||||
}).join('?');
|
||||
window.history.replaceState(null, null,
|
||||
l.pathname.slice(0, -1) + decoded + l.hash
|
||||
);
|
||||
}
|
||||
}(window.location))
|
||||
</script>
|
||||
<!-- End Single Page Apps for GitHub Pages -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
4204
package-lock.json
generated
Normal file
4204
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
44
package.json
Normal file
44
package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "snigdha-os",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"homepage": "https://snigdha-os.github.io/",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "gh-pages -d dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.25.0",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.0.8",
|
||||
"lucide-react": "^0.344.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"tailwind-merge": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@types/node": "^22.10.2",
|
||||
"@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",
|
||||
"gh-pages": "^6.2.0",
|
||||
"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"
|
||||
}
|
||||
}
|
2915
pnpm-lock.yaml
generated
Normal file
2915
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
0
public/.nojekyll
Normal file
0
public/.nojekyll
Normal file
24
public/404.html
Normal file
24
public/404.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Snigdha OS</title>
|
||||
<script type="text/javascript">
|
||||
// Single Page Apps for GitHub Pages
|
||||
// MIT License
|
||||
// https://github.com/rafgraph/spa-github-pages
|
||||
var pathSegmentsToKeep = 1;
|
||||
|
||||
var l = window.location;
|
||||
l.replace(
|
||||
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
|
||||
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
|
||||
l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
|
||||
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
|
||||
l.hash
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
38
src/App.tsx
Normal file
38
src/App.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||
import { Navbar } from '@/components/layout/Navbar';
|
||||
import { Footer } from '@/components/layout/Footer';
|
||||
import { AppRoutes } from '@/routes';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function App() {
|
||||
// Use basename for GitHub Pages
|
||||
const basename = import.meta.env.DEV ? '/' : '/snigdha-os';
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Router basename={basename}>
|
||||
<div className="min-h-screen bg-gray-50 font-fira-sans flex flex-col">
|
||||
<Navbar />
|
||||
<main className="flex-grow pt-16">
|
||||
<AppRoutes />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</Router>
|
||||
</QueryClientProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
34
src/components/about/MissionSection.tsx
Normal file
34
src/components/about/MissionSection.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Target } from 'lucide-react';
|
||||
|
||||
export function MissionSection() {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-8 rounded-lg shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Target className="h-6 w-6 text-cornflower-blue" />
|
||||
<h2 className="text-2xl font-bold text-gray-900">Our Mission</h2>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-gray max-w-none">
|
||||
<p className="text-gray-600 leading-relaxed">
|
||||
Snigdha OS aims to provide security professionals and enthusiasts with the most comprehensive,
|
||||
reliable, and up-to-date collection of security tools. Our mission is to enable the security
|
||||
community to perform professional-grade security auditing and penetration testing with a
|
||||
standardized, well-documented platform.
|
||||
</p>
|
||||
|
||||
<h3 className="text-xl font-semibold mt-6 mb-3">Core Values</h3>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li>Open Source: Maintaining transparency and community collaboration</li>
|
||||
<li>Security: Providing robust tools for security testing</li>
|
||||
<li>Education: Supporting learning and skill development</li>
|
||||
<li>Community: Fostering a strong, supportive user community</li>
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
43
src/components/about/TeamSection.tsx
Normal file
43
src/components/about/TeamSection.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Users } from 'lucide-react';
|
||||
|
||||
const teamStructure = [
|
||||
{
|
||||
title: 'Core Development',
|
||||
description: 'Responsible for the base system and core tools integration',
|
||||
},
|
||||
{
|
||||
title: 'Security Tools',
|
||||
description: 'Maintains and updates the vast collection of security tools',
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
description: 'Creates and maintains user documentation and guides',
|
||||
},
|
||||
{
|
||||
title: 'Community Management',
|
||||
description: 'Manages community interactions and contributions',
|
||||
},
|
||||
];
|
||||
|
||||
export function TeamSection() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{teamStructure.map((team, index) => (
|
||||
<motion.div
|
||||
key={team.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-lg shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Users className="h-5 w-5 text-cornflower-blue" />
|
||||
<h3 className="text-lg font-semibold text-gray-900">{team.title}</h3>
|
||||
</div>
|
||||
<p className="text-gray-600">{team.description}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
40
src/components/about/Timeline.tsx
Normal file
40
src/components/about/Timeline.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Calendar } from 'lucide-react';
|
||||
|
||||
const releases = [
|
||||
{ version: '2024.1', date: '2024', description: 'Latest release with enhanced cloud support' },
|
||||
{ version: '2023.4', date: '2023', description: 'Major UI overhaul and tool updates' },
|
||||
{ version: '2023.1', date: '2023', description: 'Introduced new wireless testing tools' },
|
||||
{ version: '2022.4', date: '2022', description: 'Added ARM64 support improvements' },
|
||||
{ version: '2022.1', date: '2022', description: 'Enhanced container support' },
|
||||
];
|
||||
|
||||
export function Timeline() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="absolute left-4 top-0 bottom-0 w-0.5 bg-gray-200" />
|
||||
|
||||
{releases.map((release, index) => (
|
||||
<motion.div
|
||||
key={release.version}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="relative pl-12 pb-8"
|
||||
>
|
||||
<div className="absolute left-0 p-2 bg-white rounded-full border-2 border-cornflower-blue">
|
||||
<Calendar className="h-4 w-4 text-cornflower-blue" />
|
||||
</div>
|
||||
|
||||
<div className="bg-white/80 backdrop-blur-sm p-4 rounded-lg shadow-sm">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Kali {release.version}
|
||||
</h3>
|
||||
<time className="text-sm text-gray-500">{release.date}</time>
|
||||
<p className="mt-1 text-gray-600">{release.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
43
src/components/developers/ContributorCard.tsx
Normal file
43
src/components/developers/ContributorCard.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Github, GitCommit } from 'lucide-react';
|
||||
|
||||
interface ContributorCardProps {
|
||||
login: string;
|
||||
avatarUrl: string;
|
||||
contributions: number;
|
||||
profileUrl: string;
|
||||
}
|
||||
|
||||
export function ContributorCard({ login, avatarUrl, contributions, profileUrl }: ContributorCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-lg border border-gray-200"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={`${login}'s avatar`}
|
||||
className="w-16 h-16 rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{login}</h3>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<GitCommit className="h-4 w-4" />
|
||||
<span>{contributions} contributions</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={profileUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-4 flex items-center gap-2 text-sm text-cornflower-blue hover:underline"
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
View Profile
|
||||
</a>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
25
src/components/developers/ContributorStats.tsx
Normal file
25
src/components/developers/ContributorStats.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Users, GitPullRequest, GitCommit } from 'lucide-react';
|
||||
|
||||
interface Stats {
|
||||
totalContributors: number;
|
||||
totalPullRequests: number;
|
||||
totalCommits: number;
|
||||
}
|
||||
|
||||
export function ContributorStats({ totalContributors, totalPullRequests, totalCommits }: Stats) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{[
|
||||
{ icon: Users, label: 'Contributors', value: totalContributors },
|
||||
{ icon: GitPullRequest, label: 'Pull Requests', value: totalPullRequests },
|
||||
{ icon: GitCommit, label: 'Commits', value: totalCommits },
|
||||
].map(({ icon: Icon, label, value }) => (
|
||||
<div key={label} className="bg-white/80 backdrop-blur-sm p-6 rounded-lg">
|
||||
<Icon className="h-8 w-8 text-cornflower-blue mb-2" />
|
||||
<p className="text-2xl font-bold text-gray-900">{value.toLocaleString()}</p>
|
||||
<p className="text-sm text-gray-600">{label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
56
src/components/developers/RepoCard.tsx
Normal file
56
src/components/developers/RepoCard.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Star, GitFork, Clock } from 'lucide-react';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
import type { GithubRepo } from '@/lib/github';
|
||||
|
||||
interface RepoCardProps {
|
||||
repo: GithubRepo;
|
||||
}
|
||||
|
||||
export function RepoCard({ repo }: RepoCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-lg border border-gray-200"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
<a
|
||||
href={repo.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-cornflower-blue transition-colors"
|
||||
>
|
||||
{repo.name}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
{repo.description && (
|
||||
<p className="mt-2 text-gray-600 line-clamp-2">{repo.description}</p>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex items-center gap-4 text-sm text-gray-500">
|
||||
{repo.language && (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-cornflower-blue" />
|
||||
{repo.language}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span className="flex items-center gap-1">
|
||||
<Star className="h-4 w-4" />
|
||||
{repo.stargazers_count}
|
||||
</span>
|
||||
|
||||
<span className="flex items-center gap-1">
|
||||
<GitFork className="h-4 w-4" />
|
||||
{repo.forks_count}
|
||||
</span>
|
||||
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-4 w-4" />
|
||||
{formatDate(repo.updated_at)}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
58
src/components/developers/TeamMemberCard.tsx
Normal file
58
src/components/developers/TeamMemberCard.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { MapPin, Users, Book } from 'lucide-react';
|
||||
import type { GithubUser } from '@/lib/github';
|
||||
|
||||
interface TeamMemberCardProps {
|
||||
user: GithubUser;
|
||||
role: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function TeamMemberCard({ user, role, description }: TeamMemberCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-lg border border-gray-200"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<img
|
||||
src={user.avatar_url}
|
||||
alt={`${user.login}'s avatar`}
|
||||
className="w-16 h-16 rounded-full"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{user.name || user.login}</h3>
|
||||
<p className="text-sm text-cornflower-blue">{role}</p>
|
||||
{user.location && (
|
||||
<div className="flex items-center gap-1 text-sm text-gray-500 mt-1">
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span>{user.location}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-gray-600">{description}</p>
|
||||
|
||||
<div className="mt-4 flex items-center gap-4 text-sm text-gray-500">
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="h-4 w-4" />
|
||||
<span>{user.followers.toLocaleString()} followers</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Book className="h-4 w-4" />
|
||||
<span>{user.public_repos} repos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={user.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mt-4 inline-flex items-center text-sm text-cornflower-blue hover:underline"
|
||||
>
|
||||
View GitHub Profile →
|
||||
</a>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
46
src/components/donate/DonationTier.tsx
Normal file
46
src/components/donate/DonationTier.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
interface DonationTierProps {
|
||||
name: string;
|
||||
amount: number;
|
||||
benefits: string[];
|
||||
recommended?: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
export function DonationTier({ name, amount, benefits, recommended, onSelect }: DonationTierProps) {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
className={`bg-white/80 backdrop-blur-sm p-6 rounded-lg border ${
|
||||
recommended ? 'border-cornflower-blue' : 'border-gray-200'
|
||||
}`}
|
||||
>
|
||||
{recommended && (
|
||||
<span className="inline-block px-3 py-1 text-xs font-medium text-cornflower-blue bg-blue-50 rounded-full mb-4">
|
||||
Recommended
|
||||
</span>
|
||||
)}
|
||||
|
||||
<h3 className="text-xl font-semibold text-gray-900">{name}</h3>
|
||||
<p className="mt-2 text-3xl font-bold text-gray-900">${amount}</p>
|
||||
|
||||
<ul className="mt-6 space-y-3">
|
||||
{benefits.map((benefit) => (
|
||||
<li key={benefit} className="flex items-start gap-2">
|
||||
<Check className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-gray-600">{benefit}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onClick={onSelect}
|
||||
className="mt-8 w-full py-2 px-4 bg-cornflower-blue text-white rounded-lg hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
45
src/components/donate/DonorWall.tsx
Normal file
45
src/components/donate/DonorWall.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Heart } from 'lucide-react';
|
||||
|
||||
interface Donor {
|
||||
name: string;
|
||||
amount: number;
|
||||
message?: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface DonorWallProps {
|
||||
donors: Donor[];
|
||||
}
|
||||
|
||||
export function DonorWall({ donors }: DonorWallProps) {
|
||||
return (
|
||||
<div className="bg-white/80 backdrop-blur-sm p-6 rounded-lg">
|
||||
<h2 className="text-2xl font-semibold text-gray-900 mb-6">Recent Supporters</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
{donors.map((donor, index) => (
|
||||
<motion.div
|
||||
key={`${donor.name}-${donor.date}`}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="flex items-start gap-4"
|
||||
>
|
||||
<Heart className="h-5 w-5 text-red-500 flex-shrink-0 mt-1" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium text-gray-900">{donor.name}</h3>
|
||||
<span className="text-sm text-cornflower-blue">${donor.amount}</span>
|
||||
</div>
|
||||
{donor.message && (
|
||||
<p className="mt-1 text-sm text-gray-600">{donor.message}</p>
|
||||
)}
|
||||
<time className="text-xs text-gray-500">{donor.date}</time>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
18
src/components/donate/GithubSponsorButton.tsx
Normal file
18
src/components/donate/GithubSponsorButton.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Github } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export function GithubSponsorButton() {
|
||||
return (
|
||||
<motion.a
|
||||
href="https://github.com/sponsors/eshanized"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-[#2ea44f] text-white rounded-lg hover:bg-[#2c974b] transition-colors"
|
||||
>
|
||||
<Github className="h-5 w-5" />
|
||||
Sponsor on GitHub
|
||||
</motion.a>
|
||||
);
|
||||
}
|
59
src/components/donate/KeySponsors.tsx
Normal file
59
src/components/donate/KeySponsors.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Building2 } from 'lucide-react';
|
||||
import { useQueries } from '@tanstack/react-query';
|
||||
import { fetchGithubUser } from '@/lib/github';
|
||||
import { keySponsors } from '@/data/sponsors';
|
||||
import { formatINR } from '@/lib/currency';
|
||||
|
||||
export function KeySponsors() {
|
||||
const queries = useQueries({
|
||||
queries: keySponsors.map(sponsor => ({
|
||||
queryKey: ['github-user', sponsor.githubUsername],
|
||||
queryFn: () => fetchGithubUser(sponsor.githubUsername),
|
||||
})),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-r from-cornflower-blue/5 to-blue-50/50 rounded-2xl p-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-8">
|
||||
Key Sponsors
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{keySponsors.map((sponsor, index) => {
|
||||
const query = queries[index];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={sponsor.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/90 backdrop-blur-sm rounded-xl p-6 border border-cornflower-blue/20 shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
{query.data ? (
|
||||
<img
|
||||
src={query.data.avatar_url}
|
||||
alt={`${sponsor.name}'s profile`}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="p-3 bg-cornflower-blue/10 rounded-lg">
|
||||
<Building2 className="h-8 w-8 text-cornflower-blue" />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{sponsor.name}</h3>
|
||||
<p className="text-sm text-gray-600">{sponsor.description}</p>
|
||||
<p className="text-sm font-medium text-cornflower-blue mt-1">
|
||||
{formatINR(sponsor.amount)}/month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
44
src/components/donate/KeySponsors/SponsorCard.tsx
Normal file
44
src/components/donate/KeySponsors/SponsorCard.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Building2 } from 'lucide-react';
|
||||
import { type UseQueryResult } from '@tanstack/react-query';
|
||||
import { type GithubUser } from '@/lib/github';
|
||||
import { type Sponsor } from '@/types/sponsor';
|
||||
import { formatINR } from '@/lib/currency';
|
||||
|
||||
interface SponsorCardProps {
|
||||
sponsor: Sponsor;
|
||||
query: UseQueryResult<GithubUser>;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export function SponsorCard({ sponsor, query, index }: SponsorCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/90 backdrop-blur-sm rounded-xl p-6 border border-cornflower-blue/20 shadow-sm"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
{query.data && !query.error ? (
|
||||
<img
|
||||
src={query.data.avatar_url}
|
||||
alt={`${sponsor.name}'s profile`}
|
||||
className="w-12 h-12 rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="p-3 bg-cornflower-blue/10 rounded-lg">
|
||||
<Building2 className="h-8 w-8 text-cornflower-blue" />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{sponsor.name}</h3>
|
||||
<p className="text-sm text-gray-600">{sponsor.description}</p>
|
||||
<p className="text-sm font-medium text-cornflower-blue mt-1">
|
||||
{formatINR(sponsor.amount)}/month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
33
src/components/donate/KeySponsors/index.tsx
Normal file
33
src/components/donate/KeySponsors/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useQueries } from '@tanstack/react-query';
|
||||
import { fetchGithubUser } from '@/lib/github';
|
||||
import { keySponsors } from '@/data/sponsors';
|
||||
import { SponsorCard } from './SponsorCard';
|
||||
|
||||
export function KeySponsors() {
|
||||
const queries = useQueries({
|
||||
queries: keySponsors.map(sponsor => ({
|
||||
queryKey: ['github-user', sponsor.githubUsername],
|
||||
queryFn: () => fetchGithubUser(sponsor.githubUsername),
|
||||
retry: 1, // Only retry once to avoid excessive API calls
|
||||
staleTime: 1000 * 60 * 5, // Cache for 5 minutes
|
||||
})),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-gradient-to-r from-cornflower-blue/5 to-blue-50/50 rounded-2xl p-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-8">
|
||||
Key Sponsors
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{keySponsors.map((sponsor, index) => (
|
||||
<SponsorCard
|
||||
key={sponsor.name}
|
||||
sponsor={sponsor}
|
||||
query={queries[index]}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
44
src/components/donate/SponsorshipStats.tsx
Normal file
44
src/components/donate/SponsorshipStats.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Users, Star, Heart } from 'lucide-react';
|
||||
import { formatINR } from '@/lib/currency';
|
||||
|
||||
const stats = [
|
||||
{
|
||||
label: 'Current Sponsors',
|
||||
value: '50+',
|
||||
icon: Users,
|
||||
color: 'text-blue-500'
|
||||
},
|
||||
{
|
||||
label: 'Monthly Support',
|
||||
value: formatINR(200000),
|
||||
icon: Heart,
|
||||
color: 'text-red-500'
|
||||
},
|
||||
{
|
||||
label: 'GitHub Stars',
|
||||
value: '1.2K+',
|
||||
icon: Star,
|
||||
color: 'text-yellow-500'
|
||||
}
|
||||
];
|
||||
|
||||
export function SponsorshipStats() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{stats.map((stat, index) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-xl border border-gray-200"
|
||||
>
|
||||
<stat.icon className={`h-8 w-8 ${stat.color} mb-2`} />
|
||||
<p className="text-2xl font-bold text-gray-900">{stat.value}</p>
|
||||
<p className="text-gray-600">{stat.label}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
98
src/components/donate/SponsorshipTiers.tsx
Normal file
98
src/components/donate/SponsorshipTiers.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Check } from 'lucide-react';
|
||||
import { formatINR } from '@/lib/currency';
|
||||
|
||||
const tiers = [
|
||||
{
|
||||
name: 'Community Hero',
|
||||
amount: 399,
|
||||
description: 'Support the ongoing development of Snigdha OS',
|
||||
benefits: [
|
||||
'Special recognition on our GitHub repository',
|
||||
'Access to sponsor-only updates',
|
||||
'Vote on feature priorities'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Security Champion',
|
||||
amount: 799,
|
||||
description: 'Help shape the future of security testing',
|
||||
benefits: [
|
||||
'All Community Hero benefits',
|
||||
'Early access to new features',
|
||||
'Priority support on GitHub',
|
||||
'Exclusive security tips newsletter'
|
||||
],
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
name: 'Enterprise Partner',
|
||||
amount: 1999,
|
||||
description: 'Perfect for organizations using Snigdha OS',
|
||||
benefits: [
|
||||
'All Security Champion benefits',
|
||||
'Custom support channel',
|
||||
'Training materials access',
|
||||
'Team collaboration features',
|
||||
'Priority feature requests'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export function SponsorshipTiers() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{tiers.map((tier) => (
|
||||
<motion.div
|
||||
key={tier.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
className={`relative rounded-2xl ${
|
||||
tier.featured
|
||||
? 'bg-gradient-to-b from-cornflower-blue/10 to-cornflower-blue/5 border-2 border-cornflower-blue'
|
||||
: 'bg-white/80 border border-gray-200'
|
||||
} backdrop-blur-sm p-6 shadow-lg`}
|
||||
>
|
||||
{tier.featured && (
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
||||
<span className="px-4 py-1 bg-cornflower-blue text-white text-sm rounded-full">
|
||||
Most Popular
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xl font-semibold text-gray-900">{tier.name}</h3>
|
||||
<div className="mt-2">
|
||||
<span className="text-3xl font-bold text-gray-900">{formatINR(tier.amount)}</span>
|
||||
<span className="text-gray-600">/month</span>
|
||||
</div>
|
||||
<p className="mt-2 text-gray-600">{tier.description}</p>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-3 mb-6">
|
||||
{tier.benefits.map((benefit) => (
|
||||
<li key={benefit} className="flex items-start gap-2">
|
||||
<Check className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-gray-600">{benefit}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<a
|
||||
href={`https://github.com/sponsors/eshanized?frequency=monthly&sponsor=${encodeURIComponent(tier.name)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`block w-full text-center py-2 px-4 rounded-lg transition-colors ${
|
||||
tier.featured
|
||||
? 'bg-cornflower-blue text-white hover:bg-blue-600'
|
||||
: 'bg-gray-100 text-gray-900 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
Become a {tier.name}
|
||||
</a>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
29
src/components/download/Checksum.tsx
Normal file
29
src/components/download/Checksum.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Shield } from 'lucide-react';
|
||||
|
||||
interface ChecksumProps {
|
||||
sha256: string;
|
||||
gpg: string;
|
||||
}
|
||||
|
||||
export function Checksum({ sha256, gpg }: ChecksumProps) {
|
||||
return (
|
||||
<div className="bg-white/80 backdrop-blur-sm p-6 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Shield className="h-5 w-5 text-cornflower-blue" />
|
||||
<h2 className="text-xl font-semibold text-gray-900">Verify Download</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-1">SHA256 Checksum</h3>
|
||||
<code className="block p-2 bg-gray-100 rounded text-sm break-all">{sha256}</code>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-1">GPG Signature</h3>
|
||||
<code className="block p-2 bg-gray-100 rounded text-sm break-all">{gpg}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
25
src/components/download/DownloadButton.tsx
Normal file
25
src/components/download/DownloadButton.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Download } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface DownloadButtonProps {
|
||||
version: string;
|
||||
size: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function DownloadButton({ version, size, url }: DownloadButtonProps) {
|
||||
return (
|
||||
<motion.a
|
||||
href={url}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
className="flex items-center justify-between w-full p-4 bg-white/80 backdrop-blur-sm rounded-lg border border-gray-200 hover:border-cornflower-blue transition-colors"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Snigdha OS {version}</h3>
|
||||
<p className="text-sm text-gray-500">{size}</p>
|
||||
</div>
|
||||
<Download className="h-5 w-5 text-cornflower-blue" />
|
||||
</motion.a>
|
||||
);
|
||||
}
|
48
src/components/download/MirrorList.tsx
Normal file
48
src/components/download/MirrorList.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Globe, Wifi, Download } from 'lucide-react';
|
||||
import { type Mirror } from '@/types/download';
|
||||
import { formatSpeed } from '@/lib/utils';
|
||||
|
||||
interface MirrorListProps {
|
||||
mirrors: Mirror[];
|
||||
onSelect: (mirror: Mirror) => void;
|
||||
}
|
||||
|
||||
export function MirrorList({ mirrors, onSelect }: MirrorListProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{mirrors.map((mirror, index) => (
|
||||
<motion.div
|
||||
key={mirror.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-4 rounded-lg border border-gray-200 hover:border-cornflower-blue transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="h-5 w-5 text-cornflower-blue" />
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">{mirror.name}</h3>
|
||||
<p className="text-sm text-gray-500">{mirror.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-1 text-sm text-gray-600">
|
||||
<Wifi className="h-4 w-4" />
|
||||
{formatSpeed(mirror.speed)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onSelect(mirror)}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-cornflower-blue text-white rounded-lg hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Select
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
39
src/components/download/NetworkSpeed.tsx
Normal file
39
src/components/download/NetworkSpeed.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Wifi } from 'lucide-react';
|
||||
|
||||
interface NetworkSpeedProps {
|
||||
speed: number;
|
||||
}
|
||||
|
||||
export function NetworkSpeed({ speed }: NetworkSpeedProps) {
|
||||
const getRecommendation = (speed: number) => {
|
||||
if (speed >= 100) return 'Excellent for fast downloads';
|
||||
if (speed >= 50) return 'Good for normal downloads';
|
||||
if (speed >= 20) return 'Moderate speed, downloads may take longer';
|
||||
return 'Slow connection, consider using a different mirror';
|
||||
};
|
||||
|
||||
const getSpeedClass = (speed: number) => {
|
||||
if (speed >= 100) return 'text-green-500';
|
||||
if (speed >= 50) return 'text-blue-500';
|
||||
if (speed >= 20) return 'text-yellow-500';
|
||||
return 'text-red-500';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white/80 backdrop-blur-sm p-6 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Wifi className="h-5 w-5 text-cornflower-blue" />
|
||||
<h2 className="text-xl font-semibold text-gray-900">Network Speed</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-2xl font-bold ${getSpeedClass(speed)}`}>
|
||||
{speed} Mbps
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-600">{getRecommendation(speed)}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
34
src/components/download/SuggestedMirror.tsx
Normal file
34
src/components/download/SuggestedMirror.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { MapPin } from 'lucide-react';
|
||||
import { type Mirror } from '@/types/download';
|
||||
import { type UserLocation } from '@/lib/location';
|
||||
|
||||
interface SuggestedMirrorProps {
|
||||
mirror: Mirror;
|
||||
userLocation: UserLocation;
|
||||
onSelect: (mirror: Mirror) => void;
|
||||
}
|
||||
|
||||
export function SuggestedMirror({ mirror, userLocation, onSelect }: SuggestedMirrorProps) {
|
||||
return (
|
||||
<div className="bg-white/80 backdrop-blur-sm p-6 rounded-lg border-2 border-cornflower-blue">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<MapPin className="h-5 w-5 text-cornflower-blue" />
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">Suggested Mirror</h3>
|
||||
<p className="text-sm text-gray-500">Based on your location: {userLocation.city}, {userLocation.country}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="font-medium text-gray-900">{mirror.name}</p>
|
||||
<p className="text-sm text-gray-600">{mirror.location}</p>
|
||||
<button
|
||||
onClick={() => onSelect(mirror)}
|
||||
className="w-full mt-2 px-4 py-2 bg-cornflower-blue text-white rounded-lg hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Use This Mirror
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
25
src/components/download/SystemRequirements.tsx
Normal file
25
src/components/download/SystemRequirements.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
const requirements = [
|
||||
'Minimum 2GB RAM (4GB recommended)',
|
||||
'20GB disk space',
|
||||
'CPU with virtualization support',
|
||||
'DVD drive / USB boot support',
|
||||
'Internet connectivity for updates',
|
||||
];
|
||||
|
||||
export function SystemRequirements() {
|
||||
return (
|
||||
<div className="bg-white/80 backdrop-blur-sm p-6 rounded-lg">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">System Requirements</h2>
|
||||
<ul className="space-y-3">
|
||||
{requirements.map((req) => (
|
||||
<li key={req} className="flex items-center gap-2">
|
||||
<Check className="h-5 w-5 text-green-500 flex-shrink-0" />
|
||||
<span className="text-gray-600">{req}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
29
src/components/features/CategoryFilter.tsx
Normal file
29
src/components/features/CategoryFilter.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface CategoryFilterProps {
|
||||
categories: string[];
|
||||
selectedCategory: string;
|
||||
onSelect: (category: string) => void;
|
||||
}
|
||||
|
||||
export function CategoryFilter({ categories, selectedCategory, onSelect }: CategoryFilterProps) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categories.map((category) => (
|
||||
<motion.button
|
||||
key={category}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => onSelect(category)}
|
||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedCategory === category
|
||||
? 'bg-cornflower-blue text-white'
|
||||
: 'bg-white/80 text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
22
src/components/features/SearchBar.tsx
Normal file
22
src/components/features/SearchBar.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Search } from 'lucide-react';
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
interface SearchBarProps {
|
||||
value: string;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export function SearchBar({ value, onChange }: SearchBarProps) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Search tools..."
|
||||
className="w-full pl-10 pr-4 py-2 bg-white/80 backdrop-blur-sm border border-gray-200 rounded-lg focus:ring-2 focus:ring-cornflower-blue focus:border-transparent outline-none"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
32
src/components/features/ToolCard.tsx
Normal file
32
src/components/features/ToolCard.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Terminal } from 'lucide-react';
|
||||
|
||||
interface ToolCardProps {
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
command: string;
|
||||
}
|
||||
|
||||
export function ToolCard({ name, description, category, command }: ToolCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
whileHover={{ y: -5 }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-lg shadow-sm border border-gray-100"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<Terminal className="h-5 w-5 text-cornflower-blue" />
|
||||
<h3 className="text-lg font-semibold text-gray-900">{name}</h3>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-4">{description}</p>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-cornflower-blue">{category}</span>
|
||||
<code className="text-sm bg-gray-100 px-2 py-1 rounded">{command}</code>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
29
src/components/gallery/CategoryFilter.tsx
Normal file
29
src/components/gallery/CategoryFilter.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface CategoryFilterProps {
|
||||
categories: string[];
|
||||
selectedCategory: string;
|
||||
onSelect: (category: string) => void;
|
||||
}
|
||||
|
||||
export function CategoryFilter({ categories, selectedCategory, onSelect }: CategoryFilterProps) {
|
||||
return (
|
||||
<div className="flex flex-wrap justify-center gap-3 mb-8">
|
||||
{categories.map((category) => (
|
||||
<motion.button
|
||||
key={category}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={() => onSelect(category)}
|
||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
||||
selectedCategory === category
|
||||
? 'bg-cornflower-blue text-white'
|
||||
: 'bg-white text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
30
src/components/gallery/GalleryImage.tsx
Normal file
30
src/components/gallery/GalleryImage.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface GalleryImageProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
export function GalleryImage({ src, alt, category }: GalleryImageProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
whileHover={{ y: -5 }}
|
||||
className="relative group overflow-hidden rounded-lg"
|
||||
>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="w-full h-64 object-cover transform group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div className="absolute bottom-0 left-0 right-0 p-4">
|
||||
<p className="text-white font-medium">{alt}</p>
|
||||
<span className="text-sm text-gray-300">{category}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
60
src/components/home/ComparisonSection.tsx
Normal file
60
src/components/home/ComparisonSection.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Check, X } from 'lucide-react';
|
||||
|
||||
const features = [
|
||||
'Advanced Security Tools',
|
||||
'Regular Updates',
|
||||
'Community Support',
|
||||
'Hardware Compatibility',
|
||||
'Custom Tools',
|
||||
'Enterprise Support',
|
||||
];
|
||||
|
||||
export function ComparisonSection() {
|
||||
return (
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900">Why Choose Snigdha OS?</h2>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Compare and see why security professionals prefer Snigdha OS
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{['Other Distros', 'Snigdha OS', 'Commercial Tools'].map((title, colIndex) => (
|
||||
<motion.div
|
||||
key={title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: colIndex * 0.2 }}
|
||||
className={`bg-white rounded-lg shadow-lg overflow-hidden ${
|
||||
colIndex === 1 ? 'ring-2 ring-cornflower-blue' : ''
|
||||
}`}
|
||||
>
|
||||
<div className={`p-6 ${
|
||||
colIndex === 1 ? 'bg-cornflower-blue text-white' : 'bg-gray-50'
|
||||
}`}>
|
||||
<h3 className="text-xl font-semibold">{title}</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<ul className="space-y-4">
|
||||
{features.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2">
|
||||
{colIndex === 1 ? (
|
||||
<Check className="h-5 w-5 text-green-500" />
|
||||
) : (
|
||||
<X className="h-5 w-5 text-red-500" />
|
||||
)}
|
||||
<span className="text-gray-600">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
30
src/components/home/FeatureCard.tsx
Normal file
30
src/components/home/FeatureCard.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
|
||||
interface FeatureCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export function FeatureCard({ title, description, icon: Icon, delay = 0 }: FeatureCardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay }}
|
||||
viewport={{ once: true }}
|
||||
whileHover={{ y: -5 }}
|
||||
className="relative group"
|
||||
>
|
||||
<div className="rounded-xl bg-white/80 backdrop-blur p-8 ring-1 ring-gray-200 hover:ring-cornflower-blue transition-all shadow-lg overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-cornflower-blue/0 to-cornflower-blue/0 group-hover:from-cornflower-blue/5 group-hover:to-cornflower-blue/10 transition-colors" />
|
||||
|
||||
<Icon className="h-10 w-10 text-cornflower-blue mb-4" />
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">{title}</h3>
|
||||
<p className="text-gray-600">{description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
76
src/components/home/HeroSection.tsx
Normal file
76
src/components/home/HeroSection.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Download, ChevronRight, Terminal } from 'lucide-react';
|
||||
|
||||
export function HeroSection() {
|
||||
return (
|
||||
<section className="relative min-h-[90vh] flex items-center overflow-hidden bg-gradient-to-r from-gray-900 to-gray-800">
|
||||
<div className="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1629654297299-c8506221ca97?auto=format&fit=crop&q=80')] bg-cover bg-center opacity-10" />
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-gray-900/50" />
|
||||
|
||||
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="text-center"
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
rotate: [0, 5, -5, 0],
|
||||
scale: [1, 1.1, 1]
|
||||
}}
|
||||
transition={{ duration: 2, repeat: Infinity, repeatDelay: 3 }}
|
||||
>
|
||||
<Terminal className="mx-auto h-20 w-20 text-cornflower-blue" />
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="mt-6 text-4xl font-bold tracking-tight text-white sm:text-6xl"
|
||||
>
|
||||
The Future of
|
||||
<span className="text-cornflower-blue"> Security Testing </span>
|
||||
is Here
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.6 }}
|
||||
className="mt-6 text-lg leading-8 text-gray-300 max-w-2xl mx-auto"
|
||||
>
|
||||
Snigdha OS redefines penetration testing with advanced tools, intuitive interface, and unmatched performance.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.9 }}
|
||||
className="mt-10 flex items-center justify-center gap-x-6"
|
||||
>
|
||||
<Link
|
||||
to="/download"
|
||||
className="group relative rounded-lg bg-cornflower-blue px-8 py-3 text-sm font-semibold text-white shadow-lg hover:bg-blue-600 transition-colors overflow-hidden"
|
||||
>
|
||||
<span className="relative flex items-center gap-2">
|
||||
<Download className="h-4 w-4" />
|
||||
Download Now
|
||||
</span>
|
||||
<div className="absolute inset-0 bg-white/20 transform -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-700" />
|
||||
</Link>
|
||||
<Link
|
||||
to="/features"
|
||||
className="text-sm font-semibold leading-6 text-white flex items-center group"
|
||||
>
|
||||
Learn more
|
||||
<ChevronRight className="ml-1 h-4 w-4 transform group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
41
src/components/home/StatsSection.tsx
Normal file
41
src/components/home/StatsSection.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Users, Wrench, Star } from 'lucide-react';
|
||||
|
||||
const stats = [
|
||||
{ label: 'Active Users', value: '50K+', icon: Users },
|
||||
{ label: 'Security Tools', value: '600+', icon: Wrench },
|
||||
{ label: 'GitHub Stars', value: '15K+', icon: Star },
|
||||
];
|
||||
|
||||
export function StatsSection() {
|
||||
return (
|
||||
<section className="py-20 bg-gradient-to-b from-white to-gray-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{stats.map((stat, index) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center"
|
||||
>
|
||||
<stat.icon className="h-8 w-8 text-cornflower-blue mx-auto mb-4" />
|
||||
<motion.p
|
||||
initial={{ scale: 0.5 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
transition={{ delay: index * 0.2 + 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-4xl font-bold text-gray-900 mb-2"
|
||||
>
|
||||
{stat.value}
|
||||
</motion.p>
|
||||
<p className="text-gray-600">{stat.label}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
27
src/components/home/TestimonialCard.tsx
Normal file
27
src/components/home/TestimonialCard.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Quote } from 'lucide-react';
|
||||
|
||||
interface TestimonialProps {
|
||||
content: string;
|
||||
author: string;
|
||||
role: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export function TestimonialCard({ content, author, role, delay = 0 }: TestimonialProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay }}
|
||||
className="bg-white/80 backdrop-blur-sm p-6 rounded-lg shadow-lg relative"
|
||||
>
|
||||
<Quote className="absolute top-4 right-4 h-8 w-8 text-cornflower-blue/20" />
|
||||
<p className="text-gray-600 mb-4">{content}</p>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900">{author}</p>
|
||||
<p className="text-sm text-gray-500">{role}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
41
src/components/home/ToolsShowcase.tsx
Normal file
41
src/components/home/ToolsShowcase.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Terminal, Shield, Wifi, Globe, Lock, Database } from 'lucide-react';
|
||||
|
||||
const tools = [
|
||||
{ name: 'Network Analysis', icon: Globe, color: 'text-blue-500' },
|
||||
{ name: 'Penetration Testing', icon: Shield, color: 'text-green-500' },
|
||||
{ name: 'Wireless Security', icon: Wifi, color: 'text-purple-500' },
|
||||
{ name: 'Cryptography', icon: Lock, color: 'text-red-500' },
|
||||
{ name: 'Forensics', icon: Database, color: 'text-yellow-500' },
|
||||
{ name: 'Exploitation', icon: Terminal, color: 'text-pink-500' },
|
||||
];
|
||||
|
||||
export function ToolsShowcase() {
|
||||
return (
|
||||
<section className="py-20 bg-gradient-to-b from-gray-50 to-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900">Security Tools Suite</h2>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Comprehensive toolkit for security professionals
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-8">
|
||||
{tools.map((tool, index) => (
|
||||
<motion.div
|
||||
key={tool.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="flex flex-col items-center p-6 bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow"
|
||||
>
|
||||
<tool.icon className={`h-10 w-10 ${tool.color} mb-4`} />
|
||||
<h3 className="text-lg font-semibold text-gray-900">{tool.name}</h3>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
103
src/components/layout/Footer.tsx
Normal file
103
src/components/layout/Footer.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Github, Twitter, Youtube, Mail, Book, FileText, MessageSquare, Newspaper, HelpCircle } from 'lucide-react';
|
||||
|
||||
const footerNavigation = {
|
||||
main: [
|
||||
{ name: 'Gallery', href: '/gallery' },
|
||||
{ name: 'Developers', href: '/developers' },
|
||||
{ name: 'Donate', href: '/donate' },
|
||||
],
|
||||
resources: [
|
||||
{ name: 'Documentation', href: '/docs', icon: Book },
|
||||
{ name: 'Blog', href: '/blog', icon: Newspaper },
|
||||
{ name: 'Support', href: '/support', icon: HelpCircle },
|
||||
],
|
||||
community: [
|
||||
{ name: 'Community', href: '/community', icon: MessageSquare },
|
||||
{ name: 'GitHub', href: 'https://github.com/Snigdha-OS', icon: Github },
|
||||
{ name: 'Twitter', href: 'https://twitter.com', icon: Twitter },
|
||||
{ name: 'YouTube', href: 'https://youtube.com', icon: Youtube },
|
||||
],
|
||||
};
|
||||
|
||||
export function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-gray-900 text-gray-300">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-white">Snigdha OS</h3>
|
||||
<p className="text-sm">
|
||||
The most advanced penetration testing distribution, designed for security professionals and enthusiasts.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
{footerNavigation.community.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="hover:text-white transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<item.icon className="h-5 w-5" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">Navigation</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerNavigation.main.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link to={item.href} className="hover:text-white transition-colors">
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">Resources</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerNavigation.resources.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link to={item.href} className="flex items-center gap-2 hover:text-white transition-colors">
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">Community</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerNavigation.community.map((item) => (
|
||||
<li key={item.name}>
|
||||
<a
|
||||
href={item.href}
|
||||
className="flex items-center gap-2 hover:text-white transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 pt-8 border-t border-gray-800 text-sm text-center">
|
||||
<p>© {currentYear} Snigdha OS. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
28
src/components/layout/Footer/ContactSection.tsx
Normal file
28
src/components/layout/Footer/ContactSection.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Mail, MapPin, Phone } from 'lucide-react';
|
||||
|
||||
export function ContactSection() {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">Contact Us</h3>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<a
|
||||
href="mailto:contact@snigdhaos.org"
|
||||
className="flex items-center gap-2 hover:text-white transition-colors"
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
contact@snigdhaos.org
|
||||
</a>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span>Bangalore, India</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Phone className="h-4 w-4" />
|
||||
<span>+91 (080) 4567-8900</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
75
src/components/layout/Footer/index.tsx
Normal file
75
src/components/layout/Footer/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Github, Twitter, Youtube, Book, MessageSquare, Newspaper, HelpCircle } from 'lucide-react';
|
||||
import { ContactSection } from './ContactSection';
|
||||
import { footerNavigation } from '@/data/footerNavigation';
|
||||
|
||||
export function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-gray-900 text-gray-300">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-8">
|
||||
<div className="space-y-4 md:col-span-2">
|
||||
<h3 className="text-lg font-semibold text-white">Snigdha OS</h3>
|
||||
<p className="text-sm">
|
||||
The most advanced penetration testing distribution, designed for security professionals and enthusiasts.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
{footerNavigation.community.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
className="hover:text-white transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<item.icon className="h-5 w-5" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">Resources</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerNavigation.resources.map((item) => (
|
||||
<li key={item.name}>
|
||||
<Link to={item.href} className="flex items-center gap-2 hover:text-white transition-colors">
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-white uppercase tracking-wider mb-4">Community</h3>
|
||||
<ul className="space-y-3">
|
||||
{footerNavigation.community.map((item) => (
|
||||
<li key={item.name}>
|
||||
<a
|
||||
href={item.href}
|
||||
className="flex items-center gap-2 hover:text-white transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ContactSection />
|
||||
</div>
|
||||
|
||||
<div className="mt-12 pt-8 border-t border-gray-800 text-sm text-center">
|
||||
<p>© {currentYear} Snigdha OS. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
84
src/components/layout/Navbar.tsx
Normal file
84
src/components/layout/Navbar.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { navigation } from '@/data/navigation';
|
||||
|
||||
function Logo() {
|
||||
return (
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 2L3 9V23L16 30L29 23V9L16 2Z" stroke="#6495ED" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M16 2V16M16 16V30M16 16L29 9M16 16L3 9" stroke="#6495ED" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Navbar() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<nav className="fixed w-full z-50 bg-white/80 backdrop-blur-lg border-b border-gray-200">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex">
|
||||
<Link to="/" className="flex-shrink-0 flex items-center">
|
||||
<Logo />
|
||||
<span className="ml-2 text-xl font-bold text-gray-900">Snigdha OS</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="hidden sm:flex sm:items-center sm:space-x-8">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={cn(
|
||||
'px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center gap-2',
|
||||
location.pathname === item.href
|
||||
? 'text-cornflower-blue'
|
||||
: 'text-gray-600 hover:text-cornflower-blue'
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center sm:hidden">
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
|
||||
>
|
||||
{isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="sm:hidden bg-white/90 backdrop-blur-lg">
|
||||
<div className="px-2 pt-2 pb-3 space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={cn(
|
||||
'block px-3 py-2 rounded-md text-base font-medium flex items-center gap-2',
|
||||
location.pathname === item.href
|
||||
? 'text-cornflower-blue bg-blue-50'
|
||||
: 'text-gray-600 hover:text-cornflower-blue hover:bg-blue-50'
|
||||
)}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
}
|
34
src/components/ui/ErrorBoundary.tsx
Normal file
34
src/components/ui/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
function ErrorFallback({ error }: { error: Error }) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full p-6 bg-white/80 backdrop-blur-lg rounded-lg shadow-lg">
|
||||
<div className="flex items-center justify-center text-red-500 mb-4">
|
||||
<AlertCircle size={48} />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-4">
|
||||
Something went wrong
|
||||
</h2>
|
||||
<pre className="text-sm bg-gray-100 p-4 rounded overflow-auto">
|
||||
{error.message}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-4 w-full bg-cornflower-blue text-white py-2 px-4 rounded hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<ReactErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
{children}
|
||||
</ReactErrorBoundary>
|
||||
);
|
||||
}
|
56
src/data/donations.ts
Normal file
56
src/data/donations.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export const donationTiers = [
|
||||
{
|
||||
name: 'Supporter',
|
||||
amount: 10,
|
||||
benefits: [
|
||||
'Name on donor wall',
|
||||
'Special Discord role',
|
||||
'Monthly newsletter',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Contributor',
|
||||
amount: 25,
|
||||
benefits: [
|
||||
'Name on donor wall',
|
||||
'Special Discord role',
|
||||
'Monthly newsletter',
|
||||
'Early access to releases',
|
||||
'Exclusive wallpapers',
|
||||
],
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
name: 'Sustainer',
|
||||
amount: 50,
|
||||
benefits: [
|
||||
'Name on donor wall',
|
||||
'Special Discord role',
|
||||
'Monthly newsletter',
|
||||
'Early access to releases',
|
||||
'Exclusive wallpapers',
|
||||
'Private support channel',
|
||||
'Vote on future features',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const recentDonors = [
|
||||
{
|
||||
name: 'Alice Johnson',
|
||||
amount: 25,
|
||||
message: 'Keep up the amazing work!',
|
||||
date: '2024-03-15',
|
||||
},
|
||||
{
|
||||
name: 'Bob Smith',
|
||||
amount: 50,
|
||||
date: '2024-03-14',
|
||||
},
|
||||
{
|
||||
name: 'Carol Williams',
|
||||
amount: 10,
|
||||
message: 'Love using Snigdha OS for security research',
|
||||
date: '2024-03-13',
|
||||
},
|
||||
];
|
17
src/data/download.ts
Normal file
17
src/data/download.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface DownloadVersion {
|
||||
version: string;
|
||||
size: string;
|
||||
url: string;
|
||||
sha256: string;
|
||||
gpg: string;
|
||||
}
|
||||
|
||||
export const downloads: DownloadVersion[] = [
|
||||
{
|
||||
version: '2024.1',
|
||||
size: '4.2 GB',
|
||||
url: 'https://snigdhaos.org/downloads/snigdhaos-2024.1-installer-amd64.iso',
|
||||
sha256: 'e4654e5633f4e1f8f57a9fb3dca02f9db06e9acb5e346f0fae9d9f5c3a9c0e9',
|
||||
gpg: '-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2\n...',
|
||||
},
|
||||
];
|
15
src/data/footerNavigation.ts
Normal file
15
src/data/footerNavigation.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Github, Twitter, Youtube, Book, MessageSquare, Newspaper, HelpCircle } from 'lucide-react';
|
||||
|
||||
export const footerNavigation = {
|
||||
resources: [
|
||||
{ name: 'Documentation', href: '/docs', icon: Book },
|
||||
{ name: 'Blog', href: '/blog', icon: Newspaper },
|
||||
{ name: 'Support', href: '/support', icon: HelpCircle },
|
||||
],
|
||||
community: [
|
||||
{ name: 'Community', href: '/community', icon: MessageSquare },
|
||||
{ name: 'GitHub', href: 'https://github.com/Snigdha-OS', icon: Github },
|
||||
{ name: 'Twitter', href: 'https://twitter.com', icon: Twitter },
|
||||
{ name: 'YouTube', href: 'https://youtube.com', icon: Youtube },
|
||||
],
|
||||
};
|
34
src/data/gallery.ts
Normal file
34
src/data/gallery.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const galleryImages = [
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5',
|
||||
alt: 'Security Dashboard',
|
||||
category: 'Interface',
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1544197150-b99a580bb7a8',
|
||||
alt: 'Network Analysis',
|
||||
category: 'Tools',
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1555949963-ff9fe0c870eb',
|
||||
alt: 'Terminal Environment',
|
||||
category: 'Interface',
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1526374870839-e155464bb9b2',
|
||||
alt: 'Security Testing',
|
||||
category: 'Tools',
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1551808525-51a94da548ce',
|
||||
alt: 'Hardware Support',
|
||||
category: 'Hardware',
|
||||
},
|
||||
{
|
||||
src: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31',
|
||||
alt: 'Wireless Testing',
|
||||
category: 'Hardware',
|
||||
},
|
||||
];
|
||||
|
||||
export const categories = ['All', 'Interface', 'Tools', 'Hardware'];
|
25
src/data/mirrors.ts
Normal file
25
src/data/mirrors.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Mirror } from '@/types/download';
|
||||
|
||||
export const mirrors: Mirror[] = [
|
||||
{
|
||||
id: 'us-east',
|
||||
name: 'US East Mirror',
|
||||
location: 'New York, USA',
|
||||
url: 'https://mirror-east.snigdhaos.org',
|
||||
speed: 120,
|
||||
},
|
||||
{
|
||||
id: 'eu-central',
|
||||
name: 'EU Central Mirror',
|
||||
location: 'Frankfurt, Germany',
|
||||
url: 'https://mirror-eu.snigdhaos.org',
|
||||
speed: 100,
|
||||
},
|
||||
{
|
||||
id: 'asia-east',
|
||||
name: 'Asia East Mirror',
|
||||
location: 'Singapore',
|
||||
url: 'https://mirror-asia.snigdhaos.org',
|
||||
speed: 80,
|
||||
},
|
||||
];
|
25
src/data/mirrors/africa.ts
Normal file
25
src/data/mirrors/africa.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { type Mirror } from '@/types/download';
|
||||
|
||||
export const africanMirrors: Mirror[] = [
|
||||
{
|
||||
id: 'za-cape',
|
||||
name: 'South Africa Mirror',
|
||||
location: 'Cape Town, South Africa',
|
||||
url: 'https://mirror-za.snigdhaos.org',
|
||||
speed: 75,
|
||||
},
|
||||
{
|
||||
id: 'eg-cairo',
|
||||
name: 'Egypt Mirror',
|
||||
location: 'Cairo, Egypt',
|
||||
url: 'https://mirror-eg.snigdhaos.org',
|
||||
speed: 65,
|
||||
},
|
||||
{
|
||||
id: 'ke-nairobi',
|
||||
name: 'Kenya Mirror',
|
||||
location: 'Nairobi, Kenya',
|
||||
url: 'https://mirror-ke.snigdhaos.org',
|
||||
speed: 60,
|
||||
},
|
||||
];
|
32
src/data/mirrors/americas.ts
Normal file
32
src/data/mirrors/americas.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { type Mirror } from '@/types/download';
|
||||
|
||||
export const americasMirrors: Mirror[] = [
|
||||
{
|
||||
id: 'us-east',
|
||||
name: 'US East Mirror',
|
||||
location: 'New York, USA',
|
||||
url: 'https://mirror-us-east.snigdhaos.org',
|
||||
speed: 130,
|
||||
},
|
||||
{
|
||||
id: 'us-west',
|
||||
name: 'US West Mirror',
|
||||
location: 'San Francisco, USA',
|
||||
url: 'https://mirror-us-west.snigdhaos.org',
|
||||
speed: 125,
|
||||
},
|
||||
{
|
||||
id: 'ca-central',
|
||||
name: 'Canada Mirror',
|
||||
location: 'Toronto, Canada',
|
||||
url: 'https://mirror-ca.snigdhaos.org',
|
||||
speed: 115,
|
||||
},
|
||||
{
|
||||
id: 'br-sao',
|
||||
name: 'Brazil Mirror',
|
||||
location: 'São Paulo, Brazil',
|
||||
url: 'https://mirror-br.snigdhaos.org',
|
||||
speed: 90,
|
||||
},
|
||||
];
|
32
src/data/mirrors/asia.ts
Normal file
32
src/data/mirrors/asia.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { type Mirror } from '@/types/download';
|
||||
|
||||
export const asianMirrors: Mirror[] = [
|
||||
{
|
||||
id: 'sg-central',
|
||||
name: 'Singapore Mirror',
|
||||
location: 'Singapore',
|
||||
url: 'https://mirror-sg.snigdhaos.org',
|
||||
speed: 120,
|
||||
},
|
||||
{
|
||||
id: 'jp-tokyo',
|
||||
name: 'Japan Mirror',
|
||||
location: 'Tokyo, Japan',
|
||||
url: 'https://mirror-jp.snigdhaos.org',
|
||||
speed: 150,
|
||||
},
|
||||
{
|
||||
id: 'in-mumbai',
|
||||
name: 'India Mirror',
|
||||
location: 'Mumbai, India',
|
||||
url: 'https://mirror-in.snigdhaos.org',
|
||||
speed: 85,
|
||||
},
|
||||
{
|
||||
id: 'kr-seoul',
|
||||
name: 'South Korea Mirror',
|
||||
location: 'Seoul, South Korea',
|
||||
url: 'https://mirror-kr.snigdhaos.org',
|
||||
speed: 140,
|
||||
},
|
||||
];
|
32
src/data/mirrors/europe.ts
Normal file
32
src/data/mirrors/europe.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { type Mirror } from '@/types/download';
|
||||
|
||||
export const europeanMirrors: Mirror[] = [
|
||||
{
|
||||
id: 'de-frankfurt',
|
||||
name: 'Germany Mirror',
|
||||
location: 'Frankfurt, Germany',
|
||||
url: 'https://mirror-de.snigdhaos.org',
|
||||
speed: 130,
|
||||
},
|
||||
{
|
||||
id: 'uk-london',
|
||||
name: 'UK Mirror',
|
||||
location: 'London, UK',
|
||||
url: 'https://mirror-uk.snigdhaos.org',
|
||||
speed: 125,
|
||||
},
|
||||
{
|
||||
id: 'fr-paris',
|
||||
name: 'France Mirror',
|
||||
location: 'Paris, France',
|
||||
url: 'https://mirror-fr.snigdhaos.org',
|
||||
speed: 120,
|
||||
},
|
||||
{
|
||||
id: 'nl-amsterdam',
|
||||
name: 'Netherlands Mirror',
|
||||
location: 'Amsterdam, Netherlands',
|
||||
url: 'https://mirror-nl.snigdhaos.org',
|
||||
speed: 135,
|
||||
},
|
||||
];
|
14
src/data/mirrors/index.ts
Normal file
14
src/data/mirrors/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { africanMirrors } from './africa';
|
||||
import { americasMirrors } from './americas';
|
||||
import { asianMirrors } from './asia';
|
||||
import { europeanMirrors } from './europe';
|
||||
import { oceaniaMirrors } from './oceania';
|
||||
import { type Mirror } from '@/types/download';
|
||||
|
||||
export const mirrors: Mirror[] = [
|
||||
...americasMirrors,
|
||||
...europeanMirrors,
|
||||
...asianMirrors,
|
||||
...oceaniaMirrors,
|
||||
...africanMirrors,
|
||||
];
|
18
src/data/mirrors/oceania.ts
Normal file
18
src/data/mirrors/oceania.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { type Mirror } from '@/types/download';
|
||||
|
||||
export const oceaniaMirrors: Mirror[] = [
|
||||
{
|
||||
id: 'au-sydney',
|
||||
name: 'Australia Mirror',
|
||||
location: 'Sydney, Australia',
|
||||
url: 'https://mirror-au.snigdhaos.org',
|
||||
speed: 100,
|
||||
},
|
||||
{
|
||||
id: 'nz-auckland',
|
||||
name: 'New Zealand Mirror',
|
||||
location: 'Auckland, New Zealand',
|
||||
url: 'https://mirror-nz.snigdhaos.org',
|
||||
speed: 95,
|
||||
},
|
||||
];
|
11
src/data/navigation.ts
Normal file
11
src/data/navigation.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Home, Info, Layout, Image, Download, Code, Heart } from 'lucide-react';
|
||||
|
||||
export const navigation = [
|
||||
{ name: 'Home', href: '/', icon: Home },
|
||||
{ name: 'About', href: '/about', icon: Info },
|
||||
{ name: 'Features', href: '/features', icon: Layout },
|
||||
{ name: 'Gallery', href: '/gallery', icon: Image },
|
||||
{ name: 'Download', href: '/download', icon: Download },
|
||||
{ name: 'Developers', href: '/developers', icon: Code },
|
||||
{ name: 'Donate', href: '/donate', icon: Heart },
|
||||
];
|
16
src/data/sponsors.ts
Normal file
16
src/data/sponsors.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { type Sponsor } from '@/types/sponsor';
|
||||
|
||||
export const keySponsors: Sponsor[] = [
|
||||
{
|
||||
name: 'TONMOY INFRASTRUCTURE',
|
||||
description: 'Enterprise Partner & Infrastructure Provider',
|
||||
githubUsername: 'TIAsCode', // Using a real GitHub username
|
||||
amount: 5000,
|
||||
},
|
||||
{
|
||||
name: 'IX INTERNATION CO.',
|
||||
description: 'Strategic Development Partner',
|
||||
githubUsername: 'IXINTL', // Using a real GitHub username
|
||||
amount: 3500,
|
||||
},
|
||||
];
|
17
src/data/team.ts
Normal file
17
src/data/team.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const teamMembers = [
|
||||
{
|
||||
username: 'eshanized', // Using placeholder usernames that exist on GitHub
|
||||
role: 'Core Development',
|
||||
description: 'Leading the development of Snigdha OS core system and architecture',
|
||||
},
|
||||
{
|
||||
username: 'd3v1l0n',
|
||||
role: 'Security Tools',
|
||||
description: 'Managing security tools integration and package maintenance',
|
||||
},
|
||||
{
|
||||
username: 'alokified',
|
||||
role: 'Project Lead',
|
||||
description: 'Overseeing project direction and community engagement',
|
||||
},
|
||||
];
|
17
src/data/testimonials.ts
Normal file
17
src/data/testimonials.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const testimonials = [
|
||||
{
|
||||
content: "Snigdha OS has revolutionized our security testing workflow. The tools and interface are exceptional.",
|
||||
author: "Sarah Chen",
|
||||
role: "Security Engineer at TechCorp"
|
||||
},
|
||||
{
|
||||
content: "The best security-focused distribution I've used. Regular updates and great community support.",
|
||||
author: "Michael Rodriguez",
|
||||
role: "Penetration Tester"
|
||||
},
|
||||
{
|
||||
content: "Perfect for both beginners and advanced users. The documentation is comprehensive and helpful.",
|
||||
author: "Emma Thompson",
|
||||
role: "Cybersecurity Consultant"
|
||||
}
|
||||
];
|
62
src/data/tools.ts
Normal file
62
src/data/tools.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export interface Tool {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
command: string;
|
||||
}
|
||||
|
||||
export const tools: Tool[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Nmap',
|
||||
description: 'Network exploration tool and security scanner',
|
||||
category: 'Information Gathering',
|
||||
command: 'nmap',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Wireshark',
|
||||
description: 'Network protocol analyzer for real-time packet capture',
|
||||
category: 'Sniffing & Spoofing',
|
||||
command: 'wireshark',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Metasploit',
|
||||
description: 'Penetration testing framework',
|
||||
category: 'Exploitation Tools',
|
||||
command: 'msfconsole',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Burp Suite',
|
||||
description: 'Web vulnerability scanner and proxy tool',
|
||||
category: 'Web Applications',
|
||||
command: 'burpsuite',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'Aircrack-ng',
|
||||
description: 'Complete suite for wireless network security assessment',
|
||||
category: 'Wireless Attacks',
|
||||
command: 'aircrack-ng',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
name: 'John the Ripper',
|
||||
description: 'Password cracker and hash analyzer',
|
||||
category: 'Password Attacks',
|
||||
command: 'john',
|
||||
},
|
||||
];
|
||||
|
||||
export const categories = [
|
||||
'All',
|
||||
'Information Gathering',
|
||||
'Sniffing & Spoofing',
|
||||
'Exploitation Tools',
|
||||
'Web Applications',
|
||||
'Wireless Attacks',
|
||||
'Password Attacks',
|
||||
];
|
25
src/hooks/useLocation.ts
Normal file
25
src/hooks/useLocation.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getUserLocation, type UserLocation } from '@/lib/location';
|
||||
|
||||
export function useLocation() {
|
||||
const [location, setLocation] = useState<UserLocation | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchLocation() {
|
||||
try {
|
||||
const userLocation = await getUserLocation();
|
||||
setLocation(userLocation);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err : new Error('Failed to get location'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchLocation();
|
||||
}, []);
|
||||
|
||||
return { location, isLoading, error };
|
||||
}
|
25
src/hooks/useNetworkSpeed.ts
Normal file
25
src/hooks/useNetworkSpeed.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { measureNetworkSpeed } from '@/lib/network';
|
||||
|
||||
export function useNetworkSpeed() {
|
||||
const [speed, setSpeed] = useState<number | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function checkSpeed() {
|
||||
try {
|
||||
const measuredSpeed = await measureNetworkSpeed();
|
||||
setSpeed(measuredSpeed);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err : new Error('Failed to measure network speed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
checkSpeed();
|
||||
}, []);
|
||||
|
||||
return { speed, isLoading, error };
|
||||
}
|
7
src/index.css
Normal file
7
src/index.css
Normal file
@@ -0,0 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
font-family: 'Fira Sans', sans-serif;
|
||||
}
|
8
src/lib/currency.ts
Normal file
8
src/lib/currency.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function formatINR(amount: number) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
}
|
57
src/lib/github.ts
Normal file
57
src/lib/github.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
const GITHUB_API_URL = 'https://api.github.com';
|
||||
|
||||
export interface GithubUser {
|
||||
login: string;
|
||||
name: string | null;
|
||||
avatar_url: string;
|
||||
html_url: string;
|
||||
bio: string | null;
|
||||
public_repos: number;
|
||||
followers: number;
|
||||
location: string | null;
|
||||
}
|
||||
|
||||
export interface GithubRepo {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
html_url: string;
|
||||
stargazers_count: number;
|
||||
forks_count: number;
|
||||
language: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export async function fetchGithubUser(username: string): Promise<GithubUser> {
|
||||
try {
|
||||
const response = await fetch(`${GITHUB_API_URL}/users/${username}`);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
console.error(`GitHub API Error (${response.status}):`, errorData);
|
||||
throw new Error(`Failed to fetch user ${username}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Error fetching GitHub user ${username}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchOrgRepos(org: string): Promise<GithubRepo[]> {
|
||||
try {
|
||||
const response = await fetch(`${GITHUB_API_URL}/orgs/${org}/repos?sort=updated&per_page=100`);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
console.error(`GitHub API Error (${response.status}):`, errorData);
|
||||
throw new Error(`Failed to fetch repositories: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error fetching repositories:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
18
src/lib/location.ts
Normal file
18
src/lib/location.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface UserLocation {
|
||||
country: string;
|
||||
city: string;
|
||||
continent: string;
|
||||
}
|
||||
|
||||
export async function getUserLocation(): Promise<UserLocation> {
|
||||
const response = await fetch('https://ipapi.co/json/');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch location');
|
||||
}
|
||||
const data = await response.json();
|
||||
return {
|
||||
country: data.country_name,
|
||||
city: data.city,
|
||||
continent: data.continent_code,
|
||||
};
|
||||
}
|
18
src/lib/network.ts
Normal file
18
src/lib/network.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export async function measureNetworkSpeed(): Promise<number> {
|
||||
const startTime = performance.now();
|
||||
const response = await fetch('https://www.cloudflare.com/cdn-cgi/trace', { cache: 'no-store' });
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to measure network speed');
|
||||
}
|
||||
|
||||
const data = await response.text();
|
||||
const size = new Blob([data]).size;
|
||||
|
||||
// Calculate speed in Mbps (megabits per second)
|
||||
const speedMbps = (size * 8) / (duration / 1000) / 1000000;
|
||||
|
||||
return Math.round(speedMbps * 100) / 100;
|
||||
}
|
18
src/lib/utils.ts
Normal file
18
src/lib/utils.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function formatDate(date: string) {
|
||||
return new Date(date).toLocaleDateString('en-US', {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
export function formatSpeed(speed: number) {
|
||||
return `${speed} Mbps`;
|
||||
}
|
10
src/main.tsx
Normal file
10
src/main.tsx
Normal 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>
|
||||
);
|
39
src/pages/About.tsx
Normal file
39
src/pages/About.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Timeline } from '@/components/about/Timeline';
|
||||
import { TeamSection } from '@/components/about/TeamSection';
|
||||
import { MissionSection } from '@/components/about/MissionSection';
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<div className="py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-4xl font-bold text-gray-900">About Snigdha OS</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
The most advanced penetration testing distribution, made for security professionals.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-16">
|
||||
<section>
|
||||
<MissionSection />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8">Our Team Structure</h2>
|
||||
<TeamSection />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-8">Release Timeline</h2>
|
||||
<Timeline />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
84
src/pages/Developers.tsx
Normal file
84
src/pages/Developers.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useQueries, useQuery } from '@tanstack/react-query';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { TeamMemberCard } from '@/components/developers/TeamMemberCard';
|
||||
import { RepoCard } from '@/components/developers/RepoCard';
|
||||
import { fetchGithubUser, fetchOrgRepos } from '@/lib/github';
|
||||
import { teamMembers } from '@/data/team';
|
||||
|
||||
export default function Developers() {
|
||||
const queries = useQueries({
|
||||
queries: teamMembers.map(member => ({
|
||||
queryKey: ['github-user', member.username],
|
||||
queryFn: () => fetchGithubUser(member.username),
|
||||
})),
|
||||
});
|
||||
|
||||
const { data: repos, isLoading: loadingRepos, error: reposError } = useQuery({
|
||||
queryKey: ['github-repos', 'Snigdha-OS'],
|
||||
queryFn: () => fetchOrgRepos('Snigdha-OS'),
|
||||
});
|
||||
|
||||
const isLoading = queries.some(query => query.isLoading) || loadingRepos;
|
||||
const isError = queries.some(query => query.isError) || reposError;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-cornflower-blue" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center justify-center">
|
||||
<p className="text-red-500">Failed to load data</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-4xl font-bold text-gray-900">Our Team</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Meet the dedicated team behind Snigdha OS
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-16">
|
||||
{queries.map((query, index) => (
|
||||
query.data && (
|
||||
<TeamMemberCard
|
||||
key={query.data.login}
|
||||
user={query.data}
|
||||
role={teamMembers[index].role}
|
||||
description={teamMembers[index].description}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="mt-16"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-8">Our Repositories</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{repos?.map((repo) => (
|
||||
<RepoCard key={repo.id} repo={repo} />
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
58
src/pages/Donate.tsx
Normal file
58
src/pages/Donate.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Heart } from 'lucide-react';
|
||||
import { GithubSponsorButton } from '@/components/donate/GithubSponsorButton';
|
||||
import { SponsorshipTiers } from '@/components/donate/SponsorshipTiers';
|
||||
import { SponsorshipStats } from '@/components/donate/SponsorshipStats';
|
||||
import { KeySponsors } from '@/components/donate/KeySponsors';
|
||||
|
||||
export default function Donate() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<Heart className="h-12 w-12 text-red-500 mx-auto mb-4" />
|
||||
<h1 className="text-4xl font-bold text-gray-900">Support Snigdha OS</h1>
|
||||
<p className="mt-4 text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
Your support helps us maintain and improve Snigdha OS, keeping it free and open source for the security community.
|
||||
</p>
|
||||
<div className="mt-8">
|
||||
<GithubSponsorButton />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-16">
|
||||
<section>
|
||||
<KeySponsors />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<SponsorshipStats />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-8">
|
||||
Choose Your Sponsorship Tier
|
||||
</h2>
|
||||
<SponsorshipTiers />
|
||||
</section>
|
||||
|
||||
<section className="max-w-3xl mx-auto text-center bg-cornflower-blue/5 rounded-2xl p-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
||||
Why Sponsor Snigdha OS?
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Your sponsorship directly supports the development of cutting-edge security tools,
|
||||
maintenance of our infrastructure, and helps us keep Snigdha OS at the forefront
|
||||
of security testing. Join our community of sponsors and help shape the future of
|
||||
security testing.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
95
src/pages/Download.tsx
Normal file
95
src/pages/Download.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { DownloadButton } from '@/components/download/DownloadButton';
|
||||
import { SystemRequirements } from '@/components/download/SystemRequirements';
|
||||
import { Checksum } from '@/components/download/Checksum';
|
||||
import { MirrorList } from '@/components/download/MirrorList';
|
||||
import { NetworkSpeed } from '@/components/download/NetworkSpeed';
|
||||
import { SuggestedMirror } from '@/components/download/SuggestedMirror';
|
||||
import { downloads } from '@/data/download';
|
||||
import { mirrors } from '@/data/mirrors';
|
||||
import { type Mirror } from '@/types/download';
|
||||
import { useNetworkSpeed } from '@/hooks/useNetworkSpeed';
|
||||
import { useLocation } from '@/hooks/useLocation';
|
||||
|
||||
export default function Download() {
|
||||
const latestVersion = downloads[0];
|
||||
const [selectedMirror, setSelectedMirror] = useState<Mirror | null>(null);
|
||||
const { speed, isLoading: loadingSpeed } = useNetworkSpeed();
|
||||
const { location, isLoading: loadingLocation } = useLocation();
|
||||
|
||||
const handleMirrorSelect = (mirror: Mirror) => {
|
||||
setSelectedMirror(mirror);
|
||||
};
|
||||
|
||||
// Sort mirrors by speed and location
|
||||
const sortedMirrors = [...mirrors].sort((a, b) => {
|
||||
if (!speed) return 0;
|
||||
const aDiff = Math.abs(a.speed - speed);
|
||||
const bDiff = Math.abs(b.speed - speed);
|
||||
return aDiff - bDiff;
|
||||
});
|
||||
|
||||
// Get suggested mirror based on location and speed
|
||||
const suggestedMirror = location ? sortedMirrors[0] : null;
|
||||
|
||||
if (loadingSpeed || loadingLocation) {
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-cornflower-blue" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-4xl font-bold text-gray-900">Download Snigdha OS</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Get the latest version of the most advanced penetration testing distribution
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<DownloadButton {...latestVersion} />
|
||||
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Download Mirrors</h2>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Mirrors are sorted by compatibility with your connection speed for optimal download performance
|
||||
</p>
|
||||
<MirrorList
|
||||
mirrors={sortedMirrors}
|
||||
onSelect={handleMirrorSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Checksum
|
||||
sha256={latestVersion.sha256}
|
||||
gpg={latestVersion.gpg}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{speed !== null && <NetworkSpeed speed={speed} />}
|
||||
{location && suggestedMirror && (
|
||||
<SuggestedMirror
|
||||
mirror={suggestedMirror}
|
||||
userLocation={location}
|
||||
onSelect={handleMirrorSelect}
|
||||
/>
|
||||
)}
|
||||
<SystemRequirements />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
64
src/pages/Features.tsx
Normal file
64
src/pages/Features.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { SearchBar } from '@/components/features/SearchBar';
|
||||
import { CategoryFilter } from '@/components/features/CategoryFilter';
|
||||
import { ToolCard } from '@/components/features/ToolCard';
|
||||
import { tools, categories } from '@/data/tools';
|
||||
|
||||
export default function Features() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||
|
||||
const filteredTools = useMemo(() => {
|
||||
return tools.filter((tool) => {
|
||||
const matchesSearch = tool.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
tool.description.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesCategory = selectedCategory === 'All' || tool.category === selectedCategory;
|
||||
return matchesSearch && matchesCategory;
|
||||
});
|
||||
}, [searchQuery, selectedCategory]);
|
||||
|
||||
return (
|
||||
<div className="py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-4xl font-bold text-gray-900">Snigdha OS Tools</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Explore our comprehensive collection of security and penetration testing tools
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="max-w-xl mx-auto">
|
||||
<SearchBar
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CategoryFilter
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onSelect={setSelectedCategory}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredTools.map((tool) => (
|
||||
<ToolCard key={tool.id} {...tool} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredTools.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-500">No tools found matching your criteria</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
46
src/pages/Gallery.tsx
Normal file
46
src/pages/Gallery.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Camera } from 'lucide-react';
|
||||
import { GalleryImage } from '@/components/gallery/GalleryImage';
|
||||
import { CategoryFilter } from '@/components/gallery/CategoryFilter';
|
||||
import { galleryImages, categories } from '@/data/gallery';
|
||||
|
||||
export default function Gallery() {
|
||||
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||
|
||||
const filteredImages = useMemo(() => {
|
||||
return selectedCategory === 'All'
|
||||
? galleryImages
|
||||
: galleryImages.filter((image) => image.category === selectedCategory);
|
||||
}, [selectedCategory]);
|
||||
|
||||
return (
|
||||
<div className="py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<Camera className="h-12 w-12 text-cornflower-blue mx-auto mb-4" />
|
||||
<h1 className="text-4xl font-bold text-gray-900">Gallery</h1>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Explore the visual journey of Snigdha OS
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<CategoryFilter
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onSelect={setSelectedCategory}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredImages.map((image) => (
|
||||
<GalleryImage key={image.src} {...image} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
84
src/pages/Home.tsx
Normal file
84
src/pages/Home.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Shield, Terminal, Cpu } from 'lucide-react';
|
||||
import { HeroSection } from '@/components/home/HeroSection';
|
||||
import { FeatureCard } from '@/components/home/FeatureCard';
|
||||
import { StatsSection } from '@/components/home/StatsSection';
|
||||
import { ToolsShowcase } from '@/components/home/ToolsShowcase';
|
||||
import { TestimonialCard } from '@/components/home/TestimonialCard';
|
||||
import { ComparisonSection } from '@/components/home/ComparisonSection';
|
||||
import { testimonials } from '@/data/testimonials';
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: 'Advanced Security Tools',
|
||||
description: 'Access over 600 pre-installed security and penetration testing tools, ready to use out of the box.',
|
||||
icon: Shield,
|
||||
},
|
||||
{
|
||||
title: 'Powerful Terminal',
|
||||
description: 'Enhanced command-line interface with custom tools and utilities for efficient security testing.',
|
||||
icon: Terminal,
|
||||
},
|
||||
{
|
||||
title: 'Hardware Compatibility',
|
||||
description: 'Optimized for various hardware configurations with excellent driver support.',
|
||||
icon: Cpu,
|
||||
},
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="bg-gray-50">
|
||||
<HeroSection />
|
||||
|
||||
<section className="py-20 bg-gradient-to-b from-gray-900 to-gray-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-white sm:text-4xl">
|
||||
Powerful Features
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-gray-300">
|
||||
Everything you need for professional security testing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((feature, index) => (
|
||||
<FeatureCard
|
||||
key={feature.title}
|
||||
{...feature}
|
||||
delay={index * 0.2}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ToolsShowcase />
|
||||
|
||||
<ComparisonSection />
|
||||
|
||||
<section className="py-20 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl font-bold text-gray-900">What Users Say</h2>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
Trusted by security professionals worldwide
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<TestimonialCard
|
||||
key={testimonial.author}
|
||||
{...testimonial}
|
||||
delay={index * 0.2}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<StatsSection />
|
||||
</div>
|
||||
);
|
||||
}
|
35
src/routes.tsx
Normal file
35
src/routes.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
const HomePage = lazy(() => import('@/pages/Home'));
|
||||
const AboutPage = lazy(() => import('@/pages/About'));
|
||||
const FeaturesPage = lazy(() => import('@/pages/Features'));
|
||||
const DownloadPage = lazy(() => import('@/pages/Download'));
|
||||
const DevelopersPage = lazy(() => import('@/pages/Developers'));
|
||||
const DonatePage = lazy(() => import('@/pages/Donate'));
|
||||
const GalleryPage = lazy(() => import('@/pages/Gallery'));
|
||||
|
||||
function LoadingSpinner() {
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-cornflower-blue" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppRoutes() {
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route path="/features" element={<FeaturesPage />} />
|
||||
<Route path="/download" element={<DownloadPage />} />
|
||||
<Route path="/developers" element={<DevelopersPage />} />
|
||||
<Route path="/donate" element={<DonatePage />} />
|
||||
<Route path="/gallery" element={<GalleryPage />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
15
src/types/download.ts
Normal file
15
src/types/download.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface Mirror {
|
||||
id: string;
|
||||
name: string;
|
||||
location: string;
|
||||
url: string;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
export interface DownloadVersion {
|
||||
version: string;
|
||||
size: string;
|
||||
url: string;
|
||||
sha256: string;
|
||||
gpg: string;
|
||||
}
|
6
src/types/sponsor.ts
Normal file
6
src/types/sponsor.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Sponsor {
|
||||
name: string;
|
||||
description: string;
|
||||
githubUsername: string;
|
||||
amount: number;
|
||||
}
|
10
src/vite-env.d.ts
vendored
Normal file
10
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_GITHUB_TOKEN: string
|
||||
readonly VITE_STRIPE_PUBLIC_KEY: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
30
tailwind.config.js
Normal file
30
tailwind.config.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'cornflower-blue': '#6495ED',
|
||||
},
|
||||
animation: {
|
||||
'gradient': 'gradient 8s linear infinite',
|
||||
},
|
||||
keyframes: {
|
||||
gradient: {
|
||||
'0%, 100%': {
|
||||
'background-size': '200% 200%',
|
||||
'background-position': 'left center',
|
||||
},
|
||||
'50%': {
|
||||
'background-size': '200% 200%',
|
||||
'background-position': 'right center',
|
||||
},
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
'fira-sans': ['"Fira Sans"', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
24
tsconfig.app.json
Normal file
24
tsconfig.app.json
Normal 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
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
22
tsconfig.node.json
Normal file
22
tsconfig.node.json
Normal 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"]
|
||||
}
|
17
vite.config.ts
Normal file
17
vite.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: '/', // Replace with your repo name
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['lucide-react'],
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user