mirror of
https://github.com/Snigdha-OS/package-browser.git
synced 2025-09-05 12:16:42 +02:00
Compare commits
11 Commits
fc5b5ba217
...
0e824e3a04
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0e824e3a04 | ||
![]() |
16611f41c2 | ||
![]() |
b6084343da | ||
![]() |
c09cd6bd43 | ||
![]() |
b2485151b6 | ||
![]() |
154cc8a6b0 | ||
![]() |
f1022a5a41 | ||
![]() |
7f264113c7 | ||
![]() |
c9b4e6615a | ||
![]() |
073d31a3b7 | ||
![]() |
b21d623aa0 |
32
README.md
32
README.md
@@ -1,10 +1,12 @@
|
||||
# 📦 Package Browser - Search Packages
|
||||
|
||||
[](LICENSE)
|
||||
[](https://github.com/Snigdha-OS/package-browser/releases)
|
||||
[](https://github.com/Snigdha-OS/package-browser/issues)
|
||||
[](https://github.com/Snigdha-OS/package-browser/actions)
|
||||
[](https://snigdha-os.github.io)
|
||||
<p align="center">
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge" alt="License: MIT"></a>
|
||||
<a href="https://github.com/Snigdha-OS/package-browser/releases"><img src="https://img.shields.io/github/package-json/v/Snigdha-OS/package-browser?style=for-the-badge" alt="Version"></a>
|
||||
<a href="https://github.com/Snigdha-OS/package-browser/issues"><img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=for-the-badge" alt="Contributions Welcome"></a>
|
||||
<!-- <a href="https://github.com/Snigdha-OS/package-browser/actions"><img src="https://img.shields.io/github/actions/workflow/status/Snigdha-OS/package-browser/ci.yml?branch=main&style=for-the-badge" alt="Build Status"></a> -->
|
||||
<a href="https://snigdha-os.github.io"><img src="https://img.shields.io/website?url=https%3A%2F%2Fsnigdha-os.github.io&style=for-the-badge" alt="Website Status"></a>
|
||||
</p>
|
||||
|
||||
🚀 **Package Browser** is a user-friendly web application designed to simplify browsing, searching, and exploring packages available for Snigdha OS. This tool helps users find the right software with ease, whether for development, daily use, or penetration testing.
|
||||
|
||||
@@ -20,22 +22,24 @@
|
||||
```plaintext
|
||||
package-browser/
|
||||
.
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── config.sh
|
||||
├── eslint.config.js
|
||||
├── index.html
|
||||
├── package.json
|
||||
├── package-lock.json
|
||||
├── pnpm-lock.yaml
|
||||
├── postcss.config.js
|
||||
├── public
|
||||
│ ├── favicon.ico
|
||||
│ └── snigdhaos-og-image.png
|
||||
├── push.sh
|
||||
├── README.md
|
||||
├── src
|
||||
│ ├── App.tsx
|
||||
│ ├── components
|
||||
│ │ ├── Header.tsx
|
||||
│ │ ├── InstallGuide.tsx
|
||||
│ │ ├── Logo.tsx
|
||||
│ │ ├── PackageCard
|
||||
│ │ │ ├── Badge.tsx
|
||||
│ │ │ ├── ExpandButton.tsx
|
||||
│ │ │ └── index.tsx
|
||||
│ │ ├── PackageCard.tsx
|
||||
│ │ ├── PackageList.tsx
|
||||
│ │ ├── SearchBar.tsx
|
||||
@@ -43,7 +47,13 @@ package-browser/
|
||||
│ ├── hooks
|
||||
│ │ ├── usePackages.ts
|
||||
│ │ └── useTheme.ts
|
||||
│ ├── i18n.tsx
|
||||
│ ├── index.css
|
||||
│ ├── locales
|
||||
│ │ ├── index.tsx
|
||||
│ │ ├── en.tsx
|
||||
│ │ ├── uk.tsx
|
||||
│ │ └── pl.tsx
|
||||
│ ├── main.tsx
|
||||
│ ├── services
|
||||
│ │ └── api.ts
|
||||
@@ -95,4 +105,4 @@ This project is licensed under the [MIT License](LICENSE).
|
||||
|
||||
## 💬 Feedback
|
||||
|
||||
We’d love to hear your feedback and suggestions! Feel free to open an issue or contact us directly via the [Snigdha OS](https://github.com/Snigdha-OS) organization.
|
||||
We’d love to hear your feedback and suggestions! Feel free to open an issue or contact us directly via the [Snigdha OS](https://github.com/Snigdha-OS) organization.
|
||||
|
4051
package-lock.json
generated
4051
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,11 @@
|
||||
"gh-pages": "^6.3.0",
|
||||
"lucide-react": "^0.471.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"i18next": "^24.2.1",
|
||||
"react-i18next": "^15.4.0",
|
||||
"i18next-http-backend": "^3.0.1",
|
||||
"i18next-browser-languagedetector": "^8.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
|
130
pnpm-lock.yaml
generated
130
pnpm-lock.yaml
generated
@@ -11,6 +11,15 @@ importers:
|
||||
gh-pages:
|
||||
specifier: ^6.3.0
|
||||
version: 6.3.0
|
||||
i18next:
|
||||
specifier: ^24.2.1
|
||||
version: 24.2.1(typescript@5.7.3)
|
||||
i18next-browser-languagedetector:
|
||||
specifier: ^8.0.2
|
||||
version: 8.0.2
|
||||
i18next-http-backend:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
lucide-react:
|
||||
specifier: ^0.471.0
|
||||
version: 0.471.0(react@19.0.0)
|
||||
@@ -20,6 +29,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
react-i18next:
|
||||
specifier: ^15.4.0
|
||||
version: 15.4.0(i18next@24.2.1(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.17.0
|
||||
@@ -141,6 +153,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.26.0':
|
||||
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -690,6 +706,9 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -930,6 +949,23 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
|
||||
i18next-browser-languagedetector@8.0.2:
|
||||
resolution: {integrity: sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==}
|
||||
|
||||
i18next-http-backend@3.0.1:
|
||||
resolution: {integrity: sha512-XT2lYSkbAtDE55c6m7CtKxxrsfuRQO3rUfHzj8ZyRtY9CkIX3aRGwXGTkUhpGWce+J8n7sfu3J0f2wTzo7Lw0A==}
|
||||
|
||||
i18next@24.2.1:
|
||||
resolution: {integrity: sha512-Q2wC1TjWcSikn1VAJg13UGIjc+okpFxQTxjVAymOnSA3RpttBQNMPf2ovcgoFVsV4QNxTfNZMAxorXZXsk4fBA==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -1078,6 +1114,15 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
@@ -1223,6 +1268,19 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
|
||||
react-i18next@15.4.0:
|
||||
resolution: {integrity: sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==}
|
||||
peerDependencies:
|
||||
i18next: '>= 23.2.3'
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
|
||||
react-refresh@0.14.2:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1238,6 +1296,9 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1344,6 +1405,9 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
trim-repeated@1.0.0:
|
||||
resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1429,6 +1493,16 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1554,6 +1628,10 @@ snapshots:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
|
||||
'@babel/runtime@7.26.0':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
@@ -2037,6 +2115,12 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -2310,6 +2394,26 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
|
||||
i18next-browser-languagedetector@8.0.2:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
|
||||
i18next-http-backend@3.0.1:
|
||||
dependencies:
|
||||
cross-fetch: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
i18next@24.2.1(typescript@5.7.3):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
optionalDependencies:
|
||||
typescript: 5.7.3
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
@@ -2435,6 +2539,10 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
@@ -2551,6 +2659,15 @@ snapshots:
|
||||
react: 19.0.0
|
||||
scheduler: 0.25.0
|
||||
|
||||
react-i18next@15.4.0(i18next@24.2.1(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 24.2.1(typescript@5.7.3)
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react@19.0.0: {}
|
||||
@@ -2563,6 +2680,8 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve@1.22.10:
|
||||
@@ -2701,6 +2820,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
trim-repeated@1.0.0:
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
@@ -2751,6 +2872,15 @@ snapshots:
|
||||
jiti: 1.21.7
|
||||
yaml: 2.7.0
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-url@5.0.0:
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
36
src/App.tsx
36
src/App.tsx
@@ -13,6 +13,10 @@ import {
|
||||
Repository
|
||||
} from './types';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from './i18n';
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
const { packages, loading, error } = usePackages();
|
||||
const [search, setSearch] = useState('');
|
||||
@@ -47,6 +51,8 @@ export default function App(): JSX.Element {
|
||||
setSelectedRepository(repo);
|
||||
};
|
||||
|
||||
const count = filteredPackages.length;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-nord-6 dark:bg-nord-0 transition-colors" role="main">
|
||||
<Header onRepositoryChange={handleRepositoryFilterChange} />
|
||||
@@ -59,26 +65,30 @@ export default function App(): JSX.Element {
|
||||
|
||||
{/* Package Counter */}
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<p className="text-sm text-nord-3 dark:text-nord-4" aria-live="polite">
|
||||
Showing {filteredPackages.length} package{filteredPackages.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
<p className="text-sm text-nord-3 dark:text-nord-4" aria-live="polite">{(count === 1) ? translate("App.package_count.single", {
|
||||
count
|
||||
}) : (count >= 2) && (count <= 4) ? translate("App.package_count.multiple", {
|
||||
count
|
||||
}) : translate("App.package_count.plural", {
|
||||
count
|
||||
})}</p>
|
||||
</div>
|
||||
|
||||
{/* Error State */}
|
||||
{error ? (
|
||||
<div className="rounded-lg bg-nord-11/10 dark:bg-nord-11/20 p-4 text-nord-11" role="alert">
|
||||
<p>An error occurred while fetching packages: {error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-2 inline-block text-sm text-nord-10 hover:underline"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<p>{translate("App.error.fetching-packages", {
|
||||
error
|
||||
})}</p>
|
||||
|
||||
<button onClick={
|
||||
() => window.location.reload()
|
||||
} className="mt-2 inline-block text-sm text-nord-10 hover:underline">{translate("App.error.retry-fetching-packages")}</button>
|
||||
</div>
|
||||
) : filteredPackages.length === 0 ? (
|
||||
) : (count === 0) ? (
|
||||
// Empty State
|
||||
<div className="text-center text-nord-3 dark:text-nord-4 mt-12">
|
||||
<p>No packages found matching your search.</p>
|
||||
<p>{translate("App.no-packages-found")}</p>
|
||||
</div>
|
||||
) : (
|
||||
// Package List
|
||||
@@ -89,7 +99,7 @@ export default function App(): JSX.Element {
|
||||
<footer className="bg-nord-5 dark:bg-nord-1 border-t border-nord-4 dark:border-nord-2 mt-12 transition-colors">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<p className="text-center text-sm text-nord-3 dark:text-nord-4">
|
||||
Data Source:{' '}
|
||||
{translate("App.footer")}{' '}
|
||||
<a
|
||||
href="https://github.com/Snigdha-OS/snigdhaos-core"
|
||||
className="text-nord-10 hover:text-nord-9 dark:text-nord-8 dark:hover:text-nord-7"
|
||||
|
@@ -1,18 +1,31 @@
|
||||
import {
|
||||
JSX
|
||||
JSX,
|
||||
useEffect
|
||||
} from 'react';
|
||||
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
import { Logo } from './Logo';
|
||||
import {
|
||||
ThemeToggle
|
||||
} from './ThemeToggle';
|
||||
|
||||
import {
|
||||
Repository
|
||||
Logo
|
||||
} from './Logo';
|
||||
|
||||
import {
|
||||
Repository,
|
||||
Languages
|
||||
} from '../types';
|
||||
|
||||
import {
|
||||
MIRRORS
|
||||
} from '../services/api';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
import i18next from 'i18next';
|
||||
|
||||
interface HeaderProps {
|
||||
onRepositoryChange: (repo: Repository) => void;
|
||||
}
|
||||
@@ -23,12 +36,24 @@ export function Header({
|
||||
const usedRepositories = new Set(Object.values(MIRRORS).map(mirror => mirror.repository));
|
||||
|
||||
const filteredRepository = Object.keys(Repository).reduce((acc, key) => {
|
||||
// This code is flawed because it explicitly checks for 'ALL', reducing flexibility.
|
||||
// A more generic approach should be used to filter unused repositories while preserving 'ALL'.
|
||||
if ((key === 'ALL') || usedRepositories.has(Repository[key as keyof typeof Repository])) acc[key] = Repository[key as keyof typeof Repository];
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const language = event.target.value;
|
||||
|
||||
i18next.changeLanguage(language).then(() => {
|
||||
localStorage.setItem('selectedLanguage', language);
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const savedLanguage = localStorage.getItem('selectedLanguage');
|
||||
if (savedLanguage && (savedLanguage !== i18next.language)) i18next.changeLanguage(savedLanguage);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header className="bg-gradient-to-r from-nord-9 to-nord-8 via-nord-10 text-nord-6 shadow-lg">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
@@ -46,7 +71,18 @@ export function Header({
|
||||
(e) => onRepositoryChange(e.target.value as Repository)
|
||||
} defaultValue="all" className="bg-nord-5 dark:bg-nord-1 text-black dark:text-white border-2 border-nord-4 dark:border-nord-2 rounded-lg py-2 px-4 focus:ring-2 focus:ring-nord-8">
|
||||
{Object.values(filteredRepository).map((repository) => (
|
||||
<option key={repository} value={repository}>{((repository === Repository.ALL) ? 'All Repositories' : repository.charAt(0).toUpperCase() + repository.slice(1))}</option>
|
||||
<option key={repository} value={repository}>{((repository === Repository.ALL) ? translate("Header.all-repositories") : repository.charAt(0).toUpperCase() + repository.slice(1))}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Language Dropdown */}
|
||||
<div>
|
||||
<select onChange={handleLanguageChange} defaultValue={i18next.language} className="bg-nord-5 dark:bg-nord-1 text-black dark:text-white border-2 border-nord-4 dark:border-nord-2 rounded-lg py-2 px-4 focus:ring-2 focus:ring-nord-8">
|
||||
{Object.entries(Languages).map(([key, label]) => (
|
||||
<option key={key} value={key.toLowerCase()}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
@@ -5,6 +5,10 @@ import {
|
||||
|
||||
import { Terminal, Copy, Check } from 'lucide-react';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
interface InstallGuideProps {
|
||||
packageName: string;
|
||||
}
|
||||
@@ -25,20 +29,13 @@ export function InstallGuide({ packageName }: InstallGuideProps): JSX.Element {
|
||||
{/* Title Section */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Terminal className="h-5 w-5 text-nord-9 dark:text-nord-7" />
|
||||
<span className="text-nord-2 dark:text-nord-5 text-sm font-semibold uppercase tracking-wide">
|
||||
Installation Command
|
||||
</span>
|
||||
<span className="text-nord-2 dark:text-nord-5 text-sm font-semibold uppercase tracking-wide">{translate("InstallGuide.installation-command")}</span>
|
||||
</div>
|
||||
|
||||
{/* Command Display Section */}
|
||||
<div className="flex items-center justify-between bg-nord-4 dark:bg-nord-2 rounded-lg px-5 py-3">
|
||||
<code className="text-nord-8 dark:text-nord-6 font-mono text-sm truncate">{command}</code>
|
||||
<button
|
||||
onClick={copyCommand}
|
||||
className="flex items-center justify-center w-8 h-8 rounded-full bg-nord-3 hover:bg-nord-7 dark:bg-nord-5 dark:hover:bg-nord-8 transition-colors"
|
||||
title={copied ? 'Copied!' : 'Copy to clipboard'}
|
||||
aria-label={copied ? 'Copied' : 'Copy command'}
|
||||
>
|
||||
<button onClick={copyCommand} className="flex items-center justify-center w-8 h-8 rounded-full bg-nord-3 hover:bg-nord-7 dark:bg-nord-5 dark:hover:bg-nord-8 transition-colors" title={copied ? translate("InstallGuide.copy.success") : translate("InstallGuide.copy.prompt")}>
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-nord-6 dark:text-nord-0" />
|
||||
) : (
|
||||
|
@@ -2,6 +2,10 @@ import {
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
export function Logo(): JSX.Element {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -22,10 +26,8 @@ export function Logo(): JSX.Element {
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Snigdha OS Package List</h1>
|
||||
<p className="text-nord-4 text-sm mt-1">
|
||||
Browse and search through the official Snigdha OS package repository
|
||||
</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight">{translate("Logo.title")}</h1>
|
||||
<p className="text-nord-4 text-sm mt-1">{translate("Logo.description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -7,6 +7,10 @@ import { Box, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { Package } from '../types';
|
||||
import { InstallGuide } from './InstallGuide';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
interface PackageCardProps {
|
||||
package: Package;
|
||||
}
|
||||
@@ -49,12 +53,12 @@ export function PackageCard({ package: pkg }: PackageCardProps): JSX.Element {
|
||||
{expanded ? (
|
||||
<>
|
||||
<ChevronUp className="h-5 w-5 animate-bounce" />
|
||||
Hide Installation
|
||||
{translate("PackageCard.hide-installation")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-5 w-5 animate-bounce" />
|
||||
Show Installation
|
||||
{translate("PackageCard.show-installation")}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
@@ -1,38 +0,0 @@
|
||||
import React, {
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
color?: 'default' | 'success' | 'warning' | 'error'; // Add more colors as needed
|
||||
size?: 'small' | 'medium' | 'large'; // Size options
|
||||
ariaLabel?: string; // For accessibility
|
||||
}
|
||||
|
||||
const colorClasses = {
|
||||
default: 'bg-gradient-to-r from-nord-7 to-nord-8/80 dark:from-nord-8/50 dark:to-nord-9/80 text-nord-0 dark:text-nord-6',
|
||||
success: 'bg-green-500 text-white',
|
||||
warning: 'bg-yellow-500 text-black',
|
||||
error: 'bg-red-500 text-white',
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
small: 'text-xs px-2 py-1',
|
||||
medium: 'text-sm px-3 py-1.5',
|
||||
large: 'text-base px-4 py-2',
|
||||
};
|
||||
|
||||
export function Badge({ children, color = 'default', size = 'medium', ariaLabel }: BadgeProps): JSX.Element {
|
||||
const badgeColorClass = colorClasses[color] || colorClasses.default;
|
||||
const badgeSizeClass = sizeClasses[size] || sizeClasses.medium;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full font-semibold shadow-md hover:shadow-lg transition-all duration-300 ${badgeColorClass} ${badgeSizeClass}`}
|
||||
aria-label={ariaLabel}
|
||||
title={ariaLabel} // Optional: Add title for tooltips
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
import {
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
interface ExpandButtonProps {
|
||||
expanded: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function ExpandButton({ expanded, onClick }: ExpandButtonProps): JSX.Element {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-2 px-3 py-1 text-sm font-medium text-nord-9 dark:text-nord-8 hover:text-nord-10 dark:hover:text-nord-7 focus:outline-none focus:ring-2 focus:ring-nord-8 dark:focus:ring-nord-9 rounded-lg transition-all duration-300"
|
||||
>
|
||||
<span
|
||||
className={`transform transition-transform duration-300 ${
|
||||
expanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
>
|
||||
{expanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
</span>
|
||||
{expanded ? 'Hide installation' : 'Show installation'}
|
||||
</button>
|
||||
);
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
import {
|
||||
useState,
|
||||
JSX
|
||||
} from 'react';
|
||||
|
||||
import { Boxes } from 'lucide-react';
|
||||
import { Package } from '../../types';
|
||||
import { InstallGuide } from '../InstallGuide';
|
||||
import { Badge } from './Badge';
|
||||
import { ExpandButton } from './ExpandButton';
|
||||
|
||||
interface PackageCardProps {
|
||||
package: Package;
|
||||
}
|
||||
|
||||
export function PackageCard({ package: pkg }: PackageCardProps): JSX.Element {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="group bg-nord-5 dark:bg-nord-1 rounded-xl shadow-lg border border-nord-4 dark:border-nord-2 hover:shadow-xl transition-all duration-300 overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="p-5">
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
<div className="p-3 bg-nord-7/10 dark:bg-nord-7/20 rounded-full shadow-md group-hover:scale-105 transition-transform">
|
||||
<Boxes className="h-6 w-6 text-nord-7" />
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-lg text-nord-0 dark:text-nord-6 group-hover:text-nord-8 transition-colors">
|
||||
{pkg.name}
|
||||
</h3>
|
||||
<span className="text-sm text-nord-3 dark:text-nord-4">{pkg.version}</span>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-nord-2 dark:text-nord-4 line-clamp-2">
|
||||
{pkg.description}
|
||||
</p>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
<Badge>{pkg.repository}</Badge>
|
||||
<ExpandButton
|
||||
expanded={expanded}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
aria-label={expanded ? 'Collapse package details' : 'Expand package details'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable Section */}
|
||||
{expanded && (
|
||||
<div className="border-t border-nord-4 dark:border-nord-2 bg-nord-6 dark:bg-nord-0 p-5 transition-opacity duration-300">
|
||||
<InstallGuide packageName={pkg.name} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -4,6 +4,10 @@ import {
|
||||
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from '../i18n';
|
||||
|
||||
interface SearchBarProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
@@ -26,7 +30,7 @@ export function SearchBar({
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder="Search packages..."
|
||||
placeholder={translate("SearchBar.placeholder")}
|
||||
className="block w-full pl-10 pr-4 py-3 border-2 border-nord-4 dark:border-nord-2 rounded-xl bg-nord-5 dark:bg-nord-1 focus:ring-2 focus:ring-nord-8 focus:border-transparent text-nord-0 dark:text-nord-6 placeholder-nord-3 dark:placeholder-nord-4 transition-all ease-in-out duration-200 shadow-md hover:shadow-lg"
|
||||
/>
|
||||
|
||||
|
42
src/i18n.tsx
Normal file
42
src/i18n.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import {
|
||||
initReactI18next
|
||||
} from "react-i18next";
|
||||
|
||||
import {
|
||||
LOCALES
|
||||
} from './locales';
|
||||
|
||||
const resources = Object.fromEntries(
|
||||
Object.entries(LOCALES).map(([key, value]) => [
|
||||
key.toLowerCase(),
|
||||
|
||||
{
|
||||
translation: value
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
i18n.use(initReactI18next).use(LanguageDetector).init({
|
||||
resources,
|
||||
fallbackLng: (localStorage.getItem('selectedLanguage') || "en"),
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
|
||||
detection: {
|
||||
order: ['localStorage', 'navigator'],
|
||||
caches: ['localStorage']
|
||||
}
|
||||
});
|
||||
|
||||
export const translate = (key: string, options?: {
|
||||
[key: string]: any
|
||||
}): string => {
|
||||
return i18n.t(key, options);
|
||||
};
|
||||
|
||||
export default i18n;
|
50
src/locales/en.tsx
Normal file
50
src/locales/en.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
export const EN = {
|
||||
// src/components/Header.tsx
|
||||
Header: {
|
||||
"all-repositories": "All Repositories"
|
||||
},
|
||||
|
||||
// src/components/InstallGuide.tsx
|
||||
InstallGuide: {
|
||||
"installation-command": "Installation Command",
|
||||
|
||||
"copy": {
|
||||
"prompt": "Copy to clipboard",
|
||||
"success": "Copied!"
|
||||
}
|
||||
},
|
||||
|
||||
// src/components/Logo.tsx
|
||||
Logo: {
|
||||
"title": "Snigdha OS Package List",
|
||||
"description": "Browse and search through the official Snigdha OS package repository",
|
||||
},
|
||||
|
||||
// src/components/PackageCard.tsx
|
||||
PackageCard: {
|
||||
"show-installation": "Show Installation",
|
||||
"hide-installation": "Hide Installation"
|
||||
},
|
||||
|
||||
// src/components/SearchBar.tsx
|
||||
SearchBar: {
|
||||
"placeholder": "Search packages..."
|
||||
},
|
||||
|
||||
// src/App.tsx
|
||||
App: {
|
||||
"package_count": {
|
||||
"single": "Showing {{count}} package",
|
||||
"multiple": "Showing {{count}} packages",
|
||||
"plural": "Showing {{count}} packages",
|
||||
},
|
||||
|
||||
error: {
|
||||
"fetching-packages": "An error occurred while fetching packages: {{error}}",
|
||||
"retry-fetching-packages": "Retry"
|
||||
},
|
||||
|
||||
"no-packages-found": "No packages found matching your search.",
|
||||
"footer": "Data Source:"
|
||||
}
|
||||
};
|
17
src/locales/index.tsx
Normal file
17
src/locales/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
EN
|
||||
} from './en'; // English
|
||||
|
||||
import {
|
||||
UK
|
||||
} from './uk'; // Ukrainian
|
||||
|
||||
import {
|
||||
PL
|
||||
} from './pl'; // Polish
|
||||
|
||||
export const LOCALES = {
|
||||
EN,
|
||||
UK,
|
||||
PL
|
||||
};
|
50
src/locales/pl.tsx
Normal file
50
src/locales/pl.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
export const PL = {
|
||||
// src/components/Header.tsx
|
||||
Header: {
|
||||
"all-repositories": "Wszystkie repozytoria"
|
||||
},
|
||||
|
||||
// src/components/InstallGuide.tsx
|
||||
InstallGuide: {
|
||||
"installation-command": "Polecenie instalacji",
|
||||
|
||||
"copy": {
|
||||
"prompt": "Skopiuj do schowka",
|
||||
"success": "Skopiowano!"
|
||||
}
|
||||
},
|
||||
|
||||
// src/components/Logo.tsx
|
||||
Logo: {
|
||||
"title": "Lista pakietów Snigdha OS",
|
||||
"description": "Przeglądaj i wyszukuj w oficjalnym repozytorium pakietów Snigdha OS",
|
||||
},
|
||||
|
||||
// src/components/PackageCard.tsx
|
||||
PackageCard: {
|
||||
"show-installation": "Pokaż instalację",
|
||||
"hide-installation": "Ukryj instalację"
|
||||
},
|
||||
|
||||
// src/components/SearchBar.tsx
|
||||
SearchBar: {
|
||||
"placeholder": "Szukaj pakietów..."
|
||||
},
|
||||
|
||||
// src/App.tsx
|
||||
App: {
|
||||
"package_count": {
|
||||
"single": "Wyświetlanie {{count}} pakietu",
|
||||
"multiple": "Wyświetlanie {{count}} pakietów",
|
||||
"plural": "Wyświetlanie {{count}} pakietów",
|
||||
},
|
||||
|
||||
error: {
|
||||
"fetching-packages": "Wystąpił błąd podczas pobierania pakietów: {{error}}",
|
||||
"retry-fetching-packages": "Spróbuj ponownie"
|
||||
},
|
||||
|
||||
"no-packages-found": "Nie znaleziono pakietów odpowiadających Twojemu wyszukiwaniu.",
|
||||
"footer": "Źródło danych:"
|
||||
}
|
||||
};
|
50
src/locales/uk.tsx
Normal file
50
src/locales/uk.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
export const UK = {
|
||||
// src/components/Header.tsx
|
||||
Header: {
|
||||
"all-repositories": "Усі репозиторії"
|
||||
},
|
||||
|
||||
// src/components/InstallGuide.tsx
|
||||
InstallGuide: {
|
||||
"installation-command": "Команда для встановлення",
|
||||
|
||||
"copy": {
|
||||
"prompt": "Копіювати в буфер обміну",
|
||||
"success": "Скопійовано!"
|
||||
}
|
||||
},
|
||||
|
||||
// src/components/Logo.tsx
|
||||
Logo: {
|
||||
"title": "Список пакетів Snigdha OS",
|
||||
"description": "Переглядайте та шукайте в офіційному репозиторії пакетів Snigdha OS",
|
||||
},
|
||||
|
||||
// src/components/PackageCard.tsx
|
||||
PackageCard: {
|
||||
"show-installation": "Показати інструкцію з встановлення",
|
||||
"hide-installation": "Сховати інструкцію з встановлення"
|
||||
},
|
||||
|
||||
// src/components/SearchBar.tsx
|
||||
SearchBar: {
|
||||
"placeholder": "Шукати пакети..."
|
||||
},
|
||||
|
||||
// src/App.tsx
|
||||
App: {
|
||||
"package_count": {
|
||||
"single": "Показано {{count}} пакет",
|
||||
"multiple": "Показано {{count}} пакети",
|
||||
"plural": "Показано {{count}} пакетів"
|
||||
},
|
||||
|
||||
error: {
|
||||
"fetching-packages": "Сталася помилка при отриманні пакетів: {{error}}",
|
||||
"retry-fetching-packages": "Спробувати ще раз"
|
||||
},
|
||||
|
||||
"no-packages-found": "Не знайдено жодного пакета, що відповідає вашому запиту.",
|
||||
"footer": "Джерело даних:"
|
||||
}
|
||||
};
|
17
src/main.tsx
17
src/main.tsx
@@ -1,10 +1,17 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import {
|
||||
StrictMode
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
createRoot
|
||||
} from 'react-dom/client';
|
||||
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
import './i18n';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
|
@@ -10,6 +10,13 @@ export enum Repository {
|
||||
// Type alias for UI themes
|
||||
export type Theme = ('light' | 'dark');
|
||||
|
||||
// Enumeration of supported languages
|
||||
export enum Languages {
|
||||
EN = "English",
|
||||
UK = "Українська",
|
||||
PL = "Polski"
|
||||
}
|
||||
|
||||
// Interface representing a single package
|
||||
export interface Package {
|
||||
/** The name of the package */
|
||||
|
Reference in New Issue
Block a user