-
-
System Ready
+ {/* bottom system status dock */}
+
+
+
+ API Service
+
+
+
+ {status}
+
+
+
+
+
+ Package DB
+
+ {dbSize}
+
+
+
+
+
+
Arch Linux OS
+
Local Port: 5173
+
-
v1.0.0 · Arch Linux
);
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 43acfc7..b5b9e69 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,97 +1,99 @@
-@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap");
+@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap");
@import "tailwindcss";
/* ═══════════════════════════════════════════════
- ArchStore — Compact Dev-Tool Design System
+ ArchStore — Premium Desktop App Design System
═══════════════════════════════════════════════ */
:root {
- --bg-base: #07090e;
- --bg-primary: #0b0f18;
- --bg-secondary: #101624;
- --bg-tertiary: #161e30;
- --bg-card: #0d1220;
- --bg-card-hover: #111828;
- --bg-elevated: #161e30;
- --bg-input: #0a0e18;
- --bg-sidebar: rgba(11, 15, 24, 0.95);
- --bg-overlay: rgba(0, 0, 0, 0.7);
- --topbar-bg: rgba(11, 15, 24, 0.85);
+ --bg-base: #0f1115;
+ --bg-primary: #141820;
+ --bg-secondary: #1a1f2a;
+ --bg-tertiary: #202636;
+ --bg-card: #131821;
+ --bg-card-hover: #131821;
+ --bg-elevated: #1a1f2a;
+ --bg-input: #141820;
+ --bg-sidebar: #0d1016;
+ --bg-overlay: rgba(0, 0, 0, 0.5);
+ --topbar-bg: #0f1218;
- --border-primary: rgba(255, 255, 255, 0.06);
- --border-secondary: rgba(255, 255, 255, 0.1);
- --border-glow: rgba(56, 189, 248, 0.15);
+ --border-primary: rgba(148, 163, 184, 0.12);
+ --border-secondary: rgba(148, 163, 184, 0.2);
+ --border-glow: rgba(56, 189, 248, 0.2);
--text-primary: #e2e8f0;
- --text-secondary: #94a3b8;
- --text-tertiary: #64748b;
+ --text-secondary: #cbd5e1;
+ --text-tertiary: #94a3b8;
--text-inverse: #0f172a;
--accent: #38bdf8;
- --accent-hover: #7dd3fc;
- --accent-muted: rgba(56, 189, 248, 0.1);
+ --accent-hover: #38bdf8;
+ --accent-muted: rgba(56, 189, 248, 0.12);
--accent-glow: rgba(56, 189, 248, 0.2);
- --green: #34d399;
- --green-muted: rgba(52, 211, 153, 0.1);
- --amber: #fbbf24;
- --amber-muted: rgba(251, 191, 36, 0.1);
- --red: #f87171;
- --red-muted: rgba(248, 113, 113, 0.08);
+ --green: #22c55e;
+ --green-muted: rgba(34, 197, 94, 0.1);
+ --amber: #f59e0b;
+ --amber-muted: rgba(245, 158, 11, 0.12);
+ --red: #ef4444;
+ --red-muted: rgba(239, 68, 68, 0.12);
--blue: #60a5fa;
- --blue-muted: rgba(96, 165, 250, 0.1);
+ --blue-muted: rgba(96, 165, 250, 0.12);
--violet: #a78bfa;
- --violet-muted: rgba(167, 139, 250, 0.1);
+ --violet-muted: rgba(167, 139, 250, 0.12);
- --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);
- --shadow-md: 0 4px 12px rgba(0,0,0,0.3);
- --shadow-lg: 0 8px 30px rgba(0,0,0,0.4);
- --shadow-glow: 0 0 20px rgba(56,189,248,0.08);
+ --shadow-sm: 0 1px 2px rgba(2, 6, 23, 0.25);
+ --shadow-md: 0 2px 6px rgba(2, 6, 23, 0.25);
+ --shadow-lg: 0 4px 10px rgba(2, 6, 23, 0.3);
+ --shadow-glow: 0 0 0 rgba(0, 0, 0, 0);
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 10px;
- --radius-xl: 14px;
+ --radius-xl: 12px;
+ --radius-2xl: 14px;
--radius-full: 9999px;
- --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
+ --font-sans: 'Outfit', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
- --transition-fast: 120ms ease;
- --transition-normal: 200ms ease;
+ --transition-fast: 140ms cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-normal: 240ms cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-slow: 380ms cubic-bezier(0.4, 0, 0.2, 1);
}
.light {
- --bg-base: #f1f5f9;
- --bg-primary: #e2e8f0;
+ --bg-base: #f0f0f1;
+ --bg-primary: #f6f7f7;
--bg-secondary: #ffffff;
- --bg-tertiary: #f8fafc;
+ --bg-tertiary: #f6f7f7;
--bg-card: #ffffff;
- --bg-card-hover: #f8fafc;
+ --bg-card-hover: #ffffff;
--bg-elevated: #ffffff;
- --bg-input: #e2e8f0;
- --bg-sidebar: rgba(255, 255, 255, 0.95);
- --bg-overlay: rgba(15, 23, 42, 0.2);
- --topbar-bg: rgba(255, 255, 255, 0.9);
+ --bg-input: #ffffff;
+ --bg-sidebar: #1d2327;
+ --bg-overlay: rgba(0, 0, 0, 0.2);
+ --topbar-bg: #ffffff;
- --border-primary: rgba(0, 0, 0, 0.06);
- --border-secondary: rgba(0, 0, 0, 0.1);
- --border-glow: rgba(2, 132, 199, 0.15);
+ --border-primary: #c3c4c7;
+ --border-secondary: #a7aaad;
+ --border-glow: rgba(34, 113, 177, 0.3);
- --text-primary: #1e293b;
- --text-secondary: #475569;
- --text-tertiary: #94a3b8;
- --text-inverse: #f1f5f9;
+ --text-primary: #1d2327;
+ --text-secondary: #50575e;
+ --text-tertiary: #6c7781;
+ --text-inverse: #ffffff;
- --accent: #0284c7;
- --accent-hover: #0369a1;
- --accent-muted: rgba(2, 132, 199, 0.08);
- --accent-glow: rgba(2, 132, 199, 0.12);
+ --accent: #2271b1;
+ --accent-hover: #2271b1;
+ --accent-muted: rgba(34, 113, 177, 0.12);
+ --accent-glow: rgba(34, 113, 177, 0.2);
- --shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
- --shadow-md: 0 4px 12px rgba(0,0,0,0.06);
- --shadow-lg: 0 8px 30px rgba(0,0,0,0.08);
- --shadow-glow: 0 0 20px rgba(2,132,199,0.06);
+ --shadow-sm: 0 1px 1px rgba(0, 0, 0, 0.04);
+ --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.08);
+ --shadow-lg: 0 6px 18px rgba(0, 0, 0, 0.12);
+ --shadow-glow: 0 0 0 rgba(0, 0, 0, 0);
}
@theme {
@@ -130,271 +132,384 @@
}
/* ── Reset ── */
-*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
+*, *::before, *::after {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ animation: none !important;
+ transition: none !important;
+}
body {
font-family: var(--font-sans);
background: var(--bg-base);
color: var(--text-primary);
- font-size: 13px;
- line-height: 1.5;
+ font-size: 16px;
+ line-height: 1.65;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
-/* ── Scrollbar ── */
-::-webkit-scrollbar { width: 4px; height: 4px; }
+.light body {
+ background: var(--bg-base);
+}
+
+/* ── Custom Scrollbar ── */
+::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
-::-webkit-scrollbar-thumb { background: var(--border-secondary); border-radius: 4px; }
+::-webkit-scrollbar-thumb { background: var(--border-secondary); border-radius: 9999px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-tertiary); }
/* ═══════════════════════════════════════════════
- Layout
+ Desktop Shell Layout
═══════════════════════════════════════════════ */
.app-layout {
display: flex;
min-height: 100vh;
+ background-color: var(--bg-base);
+ overflow-x: hidden;
}
.sidebar {
position: fixed;
- left: 0; top: 0; bottom: 0;
- width: 220px;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 260px;
z-index: 50;
display: flex;
flex-direction: column;
- padding: 16px 12px;
+ padding: 22px 16px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border-primary);
- backdrop-filter: blur(16px);
- -webkit-backdrop-filter: blur(16px);
- overflow-y: auto;
- transition: transform 0.2s ease;
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
+ box-shadow: none;
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.light .sidebar {
+ background: var(--bg-sidebar);
+ border-right: 1px solid #101517;
+ box-shadow: none;
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
}
.main-content {
- margin-left: 220px;
+ margin-left: 260px;
flex: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
+ background: var(--bg-base);
+ transition: none;
}
.content-shell {
width: 100%;
- max-width: 1200px;
+ max-width: 100%;
margin: 0 auto;
- padding: 0 24px;
+ padding: 0 28px 32px;
display: flex;
flex-direction: column;
flex: 1;
+ min-width: 0;
}
+
.topbar {
position: sticky;
top: 0;
z-index: 40;
- display: flex;
+ display: grid;
+ grid-template-columns: auto minmax(320px, 1fr) auto;
align-items: center;
- gap: 12px;
- padding: 10px 24px;
+ gap: 16px;
+ padding: 14px 28px;
background: var(--topbar-bg);
border-bottom: 1px solid var(--border-primary);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
+ box-shadow: none;
}
-.topbar-search { flex: 1; max-width: 420px; }
+.topbar-left {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ min-width: 0;
+}
+
+.topbar-title {
+ display: flex;
+ flex-direction: column;
+ line-height: 1.1;
+ min-width: 0;
+}
+
+.topbar-title span:first-child {
+ font-size: 15px;
+ font-weight: 700;
+ color: var(--text-primary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.topbar-title span:last-child {
+ font-size: 12px;
+ color: var(--text-tertiary);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.topbar-search {
+ flex: 1;
+ max-width: none;
+ min-width: 0;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+}
.page-shell {
- padding: 20px 0 32px;
+ padding: 24px 0 32px;
flex: 1;
+ min-width: 0;
+ overflow-x: hidden;
}
@media (max-width: 1024px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); }
.main-content { margin-left: 0; }
- .content-shell { padding: 0 16px; }
- .topbar { padding: 10px 16px; }
+ .content-shell { padding: 0 16px 24px; }
+ .topbar {
+ grid-template-columns: auto 1fr auto;
+ padding: 14px 20px;
+ }
}
-/* ── Nav Links ── */
+.app-footer {
+ margin-top: auto;
+ padding: 18px 28px 22px;
+ text-align: center;
+ font-size: 12px;
+ color: var(--text-tertiary);
+ border-top: 1px solid var(--border-primary);
+}
+
+/* ── Premium Sidebar Nav Links ── */
.nav-link {
display: flex;
align-items: center;
- gap: 10px;
- padding: 7px 12px;
+ gap: 14px;
+ padding: 10px 12px;
border-radius: var(--radius-md);
- color: var(--text-tertiary);
+ color: var(--text-secondary);
text-decoration: none;
- font-size: 13px;
- font-weight: 500;
- transition: all var(--transition-fast);
+ font-size: 14px;
+ font-weight: 600;
+ transition: none;
+ border: 1px solid transparent;
position: relative;
}
-.nav-link:hover {
- color: var(--text-secondary);
- background: var(--accent-muted);
+.nav-link span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
+.light .nav-link {
+ color: #c3c4c7;
+}
+.light .nav-link:hover {
+ color: #c3c4c7;
+ background: transparent;
+ border-color: transparent;
+}
+.light .nav-link.active {
+ color: #ffffff;
+ background: rgba(34, 113, 177, 0.2);
+ border-color: transparent;
+ box-shadow: none;
+}
+.light .nav-link.active::before {
+ background: var(--accent);
+ box-shadow: none;
+}
+.nav-link:hover { color: var(--text-secondary); background: transparent; border-color: transparent; }
.nav-link.active {
- color: var(--accent);
- background: var(--accent-muted);
- font-weight: 600;
+ color: var(--text-primary);
+ background: rgba(56, 189, 248, 0.14);
+ border-color: var(--border-primary);
+ font-weight: 700;
+ box-shadow: none;
}
.nav-link.active::before {
content: '';
position: absolute;
- left: 0; top: 50%;
+ left: 8px;
+ top: 50%;
transform: translateY(-50%);
- width: 3px; height: 16px;
+ width: 4px;
+ height: 20px;
+ border-radius: 999px;
background: var(--accent);
- border-radius: 0 3px 3px 0;
+ box-shadow: none;
}
/* ═══════════════════════════════════════════════
- Cards
+ Glassmorphic Cards
═══════════════════════════════════════════════ */
.card {
background: var(--bg-card);
- border: 1px solid var(--border-primary);
- border-radius: var(--radius-lg);
- transition: all var(--transition-fast);
+ border: 1px solid rgba(148, 163, 184, 0.08);
+ border-radius: var(--radius-md);
+ box-shadow: none;
+ transition: none;
+ min-width: 0;
+ overflow: hidden;
}
.card-interactive { cursor: pointer; }
.card-interactive:hover {
- background: var(--bg-card-hover);
- border-color: var(--border-secondary);
- box-shadow: var(--shadow-glow);
- transform: translateY(-1px);
+ background: var(--bg-card);
+ border-color: rgba(148, 163, 184, 0.08);
+ box-shadow: none;
+ transform: none;
}
/* ── Badges ── */
.badge {
display: inline-flex;
align-items: center;
- padding: 2px 7px;
+ padding: 4px 12px;
border-radius: var(--radius-full);
- font-size: 10px;
+ font-size: 11px;
font-weight: 700;
- letter-spacing: 0.04em;
+ letter-spacing: 0.08em;
text-transform: uppercase;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.badge-pacman { background: var(--accent-muted); color: var(--accent); }
-.badge-aur { background: var(--amber-muted); color: var(--amber); }
-.badge-installed { background: var(--green-muted); color: var(--green); }
-/* ── Buttons ── */
+/* ── Desktop Buttons ── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
- gap: 6px;
- padding: 6px 14px;
- border-radius: var(--radius-md);
- font-size: 12px;
+ gap: 10px;
+ padding: 11px 20px;
+ border-radius: var(--radius-sm);
+ font-size: 14px;
font-weight: 600;
cursor: pointer;
- border: none;
+ border: 1px solid transparent;
font-family: var(--font-sans);
- transition: all var(--transition-fast);
+ transition: none;
white-space: nowrap;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
-.btn-primary { background: var(--accent); color: #fff; }
-.btn-primary:hover:not(:disabled) { background: var(--accent-hover); box-shadow: 0 0 12px var(--accent-glow); }
+.btn-primary { background: var(--accent); color: #fff; box-shadow: var(--shadow-sm); }
+.btn-primary:hover:not(:disabled) { background: var(--accent); box-shadow: var(--shadow-sm); transform: none; }
-.btn-secondary { background: var(--bg-secondary); color: var(--text-secondary); border: 1px solid var(--border-primary); }
-.btn-secondary:hover:not(:disabled) { border-color: var(--border-secondary); color: var(--text-primary); }
+.btn-secondary { background: var(--bg-secondary); color: var(--text-secondary); border-color: var(--border-primary); }
+.btn-secondary:hover:not(:disabled) { border-color: var(--border-primary); color: var(--text-secondary); }
-.btn-danger { background: var(--red-muted); color: var(--red); }
-.btn-danger:hover:not(:disabled) { background: rgba(248,113,113,0.15); }
+.btn-danger { background: var(--red-muted); color: var(--red); border-color: rgba(239, 68, 68, 0.2); }
+.btn-danger:hover:not(:disabled) { background: var(--red-muted); }
-.btn-ghost { background: transparent; color: var(--text-tertiary); padding: 6px 10px; }
-.btn-ghost:hover:not(:disabled) { color: var(--text-secondary); background: var(--bg-secondary); }
+.btn-ghost { background: transparent; color: var(--text-tertiary); }
+.btn-ghost:hover:not(:disabled) { color: var(--text-tertiary); background: transparent; }
-/* ── Inputs ── */
+/* ── Form Inputs ── */
.input {
width: 100%;
- padding: 7px 12px;
+ padding: 14px 18px;
background: var(--bg-input);
border: 1px solid var(--border-primary);
- border-radius: var(--radius-md);
+ border-radius: var(--radius-sm);
color: var(--text-primary);
font-family: var(--font-sans);
- font-size: 13px;
+ font-size: 14px;
outline: none;
- transition: all var(--transition-fast);
+ transition: none;
+}
+.light .input:focus {
+ box-shadow: 0 0 0 2px rgba(34, 113, 177, 0.2);
}
.input::placeholder { color: var(--text-tertiary); }
-.input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-glow); }
+.input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
.searchbar { width: 100%; position: relative; }
-.input-search { height: 34px; padding-top: 0; padding-bottom: 0; }
+.input-search { height: 46px; font-size: 14px; }
-/* ── Typography ── */
+/* ── Typography & Headings ── */
.page-title {
- font-size: 18px;
- font-weight: 700;
- letter-spacing: -0.02em;
+ font-size: 26px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
color: var(--text-primary);
- line-height: 1.3;
+ line-height: 1.2;
}
.page-subtitle {
- font-size: 12px;
- color: var(--text-tertiary);
- margin-top: 2px;
+ font-size: 14px;
+ color: var(--text-secondary);
+ margin-top: 6px;
}
-/* ── Grids ── */
+/* ── Custom Component Grids ── */
.pkg-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
- gap: 10px;
+ gap: 16px;
+ align-items: stretch;
+}
+.package-card {
+ height: 100%;
}
@media (max-width: 640px) { .pkg-grid { grid-template-columns: 1fr; } }
.cat-grid {
display: grid;
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
- gap: 10px;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 22px;
}
.update-row {
display: flex;
align-items: center;
justify-content: space-between;
- padding: 10px 14px;
- transition: background var(--transition-fast);
+ padding: 12px 16px;
+ transition: none;
}
-.update-row:hover { background: var(--bg-card-hover); }
+.update-row:hover { background: transparent; }
-/* ── Utilities ── */
+/* ── Animation Shimmers ── */
.shimmer {
- background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--bg-tertiary) 50%, var(--bg-secondary) 75%);
- background-size: 200% 100%;
- animation: shimmer-anim 1.5s infinite;
+ background: var(--bg-tertiary);
border-radius: var(--radius-md);
}
-@keyframes shimmer-anim {
- 0% { background-position: -200% 0; }
- 100% { background-position: 200% 0; }
-}
.spinner {
- width: 20px; height: 20px;
+ width: 24px; height: 24px;
border: 2px solid var(--border-primary);
border-top-color: var(--accent);
border-radius: 50%;
- animation: spin-anim 0.6s linear infinite;
}
-@keyframes spin-anim { to { transform: rotate(360deg); } }
.theme-toggle {
- width: 32px; height: 32px;
+ width: 42px;
+ height: 42px;
border-radius: var(--radius-md);
background: var(--bg-secondary);
border: 1px solid var(--border-primary);
@@ -402,37 +517,246 @@ body {
display: flex;
align-items: center;
justify-content: center;
- color: var(--text-tertiary);
- transition: all var(--transition-fast);
+ color: var(--text-secondary);
+ transition: none;
+}
+.theme-toggle:hover { border-color: var(--border-primary); color: var(--text-secondary); box-shadow: none; }
+
+.avatar-button {
+ width: 42px;
+ height: 42px;
+ border-radius: 14px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-primary);
+ color: var(--text-primary);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ font-size: 12px;
+ box-shadow: var(--shadow-sm);
}
-.theme-toggle:hover { border-color: var(--border-secondary); color: var(--text-primary); }
.gradient-text {
- background: linear-gradient(135deg, var(--accent), var(--violet));
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
+ color: var(--accent);
+}
+
+/* ── Premium Utility Blocks ── */
+.glass-panel {
+ background: transparent;
+ border: none;
+ border-radius: 0;
+}
+
+.light .glass-panel {
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(244, 247, 251, 0.9));
+ border: 1px solid var(--border-primary);
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
+}
+
+.section-kicker {
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.2em;
+ color: var(--accent);
+}
+
+.section-title {
+ font-size: 20px;
+ font-weight: 800;
+ color: var(--text-primary);
+}
+
+.section-subtitle {
+ font-size: 13px;
+ color: var(--text-tertiary);
+}
+
+.kpi-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 16px;
+}
+
+.kpi-card {
+ padding: 16px;
+ border-radius: var(--radius-lg);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-primary);
+}
+
+.light .kpi-card {
+ background: rgba(255, 255, 255, 0.9);
+}
+
+.kpi-value {
+ font-size: 22px;
+ font-weight: 800;
+ color: var(--text-primary);
+}
+
+.kpi-label {
+ font-size: 12px;
+ color: var(--text-tertiary);
+}
+
+.pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ border-radius: var(--radius-full);
+ font-size: 12px;
+ font-weight: 600;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-primary);
+ color: var(--text-secondary);
+}
+
+.chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 10px;
+ border-radius: var(--radius-full);
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-secondary);
+ border: 1px solid var(--border-primary);
+ background: rgba(15, 23, 42, 0.7);
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 999px;
+}
+
+.progress-track {
+ width: 100%;
+ height: 8px;
+ background: rgba(148, 163, 184, 0.12);
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.progress-bar {
+ height: 100%;
+ border-radius: 999px;
+ background: var(--accent);
+}
+
+.data-table {
+ width: 100%;
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ border: 1px solid var(--border-primary);
+ background: var(--bg-card);
+ min-width: 0;
+}
+
+@media (max-width: 1024px) {
+ .data-table {
+ overflow-x: auto;
+ }
+}
+
+img, svg {
+ max-width: 100%;
+ height: auto;
+}
+
+.table-head,
+.table-row {
+ display: grid;
+ grid-template-columns: 2.5fr 1.2fr 1fr 1.4fr 0.8fr;
+ gap: 16px;
+ align-items: center;
+ padding: 14px 18px;
+ min-width: 0;
+}
+
+.table-head span,
+.table-row span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.table-head {
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ color: var(--text-tertiary);
+ background: rgba(15, 23, 42, 0.85);
+ border-bottom: 1px solid var(--border-primary);
+}
+
+.table-row {
+ font-size: 13px;
+ color: var(--text-secondary);
+ border-bottom: 1px solid var(--border-primary);
+ transition: none;
+}
+
+.table-row:hover { background: transparent; }
+
+.table-row:last-child {
+ border-bottom: none;
+}
+
+@media (max-width: 1024px) {
+ .table-head,
+ .table-row {
+ grid-template-columns: 2fr 1fr 1fr;
+ }
+ .table-head span:nth-child(4),
+ .table-head span:nth-child(5),
+ .table-row span:nth-child(4),
+ .table-row span:nth-child(5) {
+ display: none;
+ }
+}
+
+.toggle {
+ position: relative;
+ width: 42px;
+ height: 24px;
+ border-radius: 999px;
+ background: rgba(148, 163, 184, 0.25);
+ border: 1px solid var(--border-primary);
+ display: inline-flex;
+ align-items: center;
+ padding: 2px;
+ transition: none;
+}
+
+.toggle::after {
+ content: '';
+ width: 18px;
+ height: 18px;
+ border-radius: 999px;
+ background: #fff;
+ transition: none;
+}
+
+.toggle.is-on {
+ background: var(--accent);
+ border-color: transparent;
+}
+
+.toggle.is-on::after {
+ transform: translateX(18px);
}
/* ── Animations ── */
-@keyframes fadeIn {
- from { opacity: 0; transform: translateY(4px); }
- to { opacity: 1; transform: translateY(0); }
-}
-@keyframes slideUp {
- from { opacity: 0; transform: translateY(8px); }
- to { opacity: 1; transform: translateY(0); }
-}
-.animate-fade-in { animation: fadeIn 0.2s ease-out both; }
-.animate-slide-up { animation: slideUp 0.25s ease-out both; }
+.animate-fade-in { animation: none; }
+.animate-slide-up { animation: none; }
-.stagger > * { animation: fadeIn 0.2s ease-out both; }
-.stagger > *:nth-child(1) { animation-delay: 20ms; }
-.stagger > *:nth-child(2) { animation-delay: 40ms; }
-.stagger > *:nth-child(3) { animation-delay: 60ms; }
-.stagger > *:nth-child(4) { animation-delay: 80ms; }
-.stagger > *:nth-child(5) { animation-delay: 100ms; }
-.stagger > *:nth-child(6) { animation-delay: 120ms; }
-.stagger > *:nth-child(7) { animation-delay: 140ms; }
-.stagger > *:nth-child(8) { animation-delay: 160ms; }
-.stagger > *:nth-child(n+9) { animation-delay: 180ms; }
+.stagger > * { animation: none; }
+
+.animate-pulse { animation: none !important; }
diff --git a/frontend/src/layouts/MainLayout.jsx b/frontend/src/layouts/MainLayout.jsx
index 122ceb7..c5c3081 100644
--- a/frontend/src/layouts/MainLayout.jsx
+++ b/frontend/src/layouts/MainLayout.jsx
@@ -2,11 +2,13 @@ import { useState, useEffect } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import Sidebar from '../components/Sidebar';
import SearchBar from '../components/SearchBar';
-import { Menu, X, Sun, Moon } from 'lucide-react';
+import { Menu, X, Sun, Moon, RefreshCw, Rocket, TerminalSquare, Bell } from 'lucide-react';
+import api from '../api/client';
export default function MainLayout() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [theme, setTheme] = useState(() => localStorage.getItem('archstore-theme') || 'dark');
+ const [checking, setChecking] = useState(false);
const navigate = useNavigate();
useEffect(() => {
@@ -25,8 +27,21 @@ export default function MainLayout() {
}
};
+ const handleSync = async () => {
+ setChecking(true);
+ try {
+ await api.clearCache();
+ alert('Local package metadata cache synchronized successfully!');
+ } catch (e) {
+ alert('Error: ' + e.message);
+ } finally {
+ setChecking(false);
+ }
+ };
+
return (
+ {/* Mobile Sidebar Overlay */}
{sidebarOpen && (
)}
+ {/* Sidebar Navigation */}
setSidebarOpen(false)} />
+ {/* Desktop Main Content Shell */}
- {/* Sticky Topbar */}
-
-
+ {/* Full-width premium Topbar */}
+
+ {/* Quick Header Actions */}
+
+
+
+
+
- {/* Page Content */}
+ {/* Premium Theme Switch Toggle */}
+
+
+ {/* Profile */}
+
+
+
+
+ {/* Constrained layout shell */}
-
+
);
diff --git a/frontend/src/pages/Categories.jsx b/frontend/src/pages/Categories.jsx
index 21288ed..5c300ce 100644
--- a/frontend/src/pages/Categories.jsx
+++ b/frontend/src/pages/Categories.jsx
@@ -50,19 +50,20 @@ export default function Categories() {
const meta = catMeta[categoryName] || { icon: Package, color: 'var(--accent)', bg: 'var(--accent-muted)' };
const Icon = meta.icon;
return (
-
+
-
-
+
-
+
-
+
{categoryName}
Browse popular {categoryName.toLowerCase()} packages
+
{packages.length} packages
{error &&
{error}
}
@@ -72,7 +73,7 @@ export default function Categories() {
/* ── Category list view ── */
return (
-
+
Categories
Explore software by type
@@ -83,11 +84,11 @@ export default function Categories() {
{loading ? (
{Array.from({ length: 8 }).map((_, i) => (
-
-
-
+
+
@@ -95,22 +96,22 @@ export default function Categories() {
))}
) : (
-
+
{(categories.length > 0 ? categories : Object.keys(catMeta).map(n => ({ name: n }))).map((cat) => {
const meta = catMeta[cat.name] || { icon: Package, color: 'var(--accent)', bg: 'var(--accent-muted)', desc: 'Explore packages' };
const Icon = meta.icon;
return (
navigate(`/categories/${cat.name}`)}>
-
-
+
+
-
{cat.name}
-
+
{cat.name}
+
{cat.description || meta.desc}
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx
index 3a3fe5a..51b6572 100644
--- a/frontend/src/pages/Home.jsx
+++ b/frontend/src/pages/Home.jsx
@@ -1,22 +1,22 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
- Search, TrendingUp, Package, ArrowRight, Shield,
+ Search, Package, ArrowRight, Shield,
Code, Monitor, Wifi, Music, Gamepad2, LayoutDashboard, Type, ShieldCheck,
- Download, RefreshCw, Zap
+ Download, RefreshCw, Zap, Cpu, HardDrive, AlertTriangle
} from 'lucide-react';
import api from '../api/client';
import PackageGrid from '../components/PackageGrid';
const catMeta = {
- Development: { icon: Code, color: '#60a5fa', bg: 'rgba(96,165,250,0.08)' },
- System: { icon: Monitor, color: '#94a3b8', bg: 'rgba(148,163,184,0.08)' },
- Network: { icon: Wifi, color: '#34d399', bg: 'rgba(52,211,153,0.08)' },
- Multimedia: { icon: Music, color: '#c084fc', bg: 'rgba(192,132,252,0.08)' },
- Games: { icon: Gamepad2, color: '#f87171', bg: 'rgba(248,113,113,0.08)' },
- Desktop: { icon: LayoutDashboard, color: '#818cf8', bg: 'rgba(129,140,248,0.08)' },
- Fonts: { icon: Type, color: '#fbbf24', bg: 'rgba(251,191,36,0.08)' },
- Security: { icon: ShieldCheck, color: '#22d3ee', bg: 'rgba(34,211,238,0.08)' },
+ Development: { icon: Code, color: '#38bdf8', bg: 'rgba(56, 189, 248, 0.07)', gradient: 'linear-gradient(135deg, rgba(56, 189, 248, 0.15) 0%, transparent 100%)' },
+ System: { icon: Monitor, color: '#94a3b8', bg: 'rgba(148, 163, 184, 0.07)', gradient: 'linear-gradient(135deg, rgba(148, 163, 184, 0.15) 0%, transparent 100%)' },
+ Network: { icon: Wifi, color: '#34d399', bg: 'rgba(52, 211, 153, 0.07)', gradient: 'linear-gradient(135deg, rgba(52, 211, 153, 0.15) 0%, transparent 100%)' },
+ Multimedia: { icon: Music, color: '#c084fc', bg: 'rgba(192, 132, 252, 0.07)', gradient: 'linear-gradient(135deg, rgba(192, 132, 252, 0.15) 0%, transparent 100%)' },
+ Games: { icon: Gamepad2, color: '#f87171', bg: 'rgba(248, 113, 113, 0.07)', gradient: 'linear-gradient(135deg, rgba(248, 113, 113, 0.15) 0%, transparent 100%)' },
+ Desktop: { icon: LayoutDashboard, color: '#818cf8', bg: 'rgba(129, 140, 248, 0.07)', gradient: 'linear-gradient(135deg, rgba(129, 140, 248, 0.15) 0%, transparent 100%)' },
+ Fonts: { icon: Type, color: '#fbbf24', bg: 'rgba(251, 191, 36, 0.07)', gradient: 'linear-gradient(135deg, rgba(251, 191, 36, 0.15) 0%, transparent 100%)' },
+ Security: { icon: ShieldCheck, color: '#22d3ee', bg: 'rgba(34, 211, 238, 0.07)', gradient: 'linear-gradient(135deg, rgba(34, 211, 238, 0.15) 0%, transparent 100%)' },
};
export default function Home() {
@@ -26,15 +26,29 @@ export default function Home() {
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(true);
const [updateCount, setUpdateCount] = useState(0);
+ const [sysMetrics, setSysMetrics] = useState({ cpu: '3.4%', ram: '2.8 GB', disk: '18% available' });
- useEffect(() => { loadData(); }, []);
+ const quickInstalls = ['neovim', 'kitty', 'fastfetch', 'btop', 'wezterm', 'starship'];
+
+ useEffect(() => {
+ loadData();
+ // Simulate slight metrics variation for a rich dashboard visual experience
+ const timer = setInterval(() => {
+ setSysMetrics(prev => ({
+ cpu: `${(Math.random() * 5 + 2).toFixed(1)}%`,
+ ram: `${(Math.random() * 0.3 + 2.6).toFixed(2)} GB / 16 GB`,
+ disk: '62.4 GB free'
+ }));
+ }, 4000);
+ return () => clearInterval(timer);
+ }, []);
async function loadData() {
setLoading(true);
try {
const [catRes, featRes, updRes] = await Promise.allSettled([
api.listCategories(),
- api.searchPackages('firefox chromium vlc', 'all'),
+ api.searchPackages('firefox chromium vlc neovim git kitty', 'all'),
api.checkUpdates(),
]);
if (catRes.status === 'fulfilled') setCategories(catRes.value.results || []);
@@ -49,115 +63,256 @@ export default function Home() {
if (query.trim()) navigate(`/search?q=${encodeURIComponent(query.trim())}`);
};
- const stats = [
- { icon: Package, label: 'Official', value: 'Pacman', color: 'var(--accent)' },
- { icon: TrendingUp, label: 'AUR', value: '80,000+', color: 'var(--amber)' },
- { icon: Shield, label: 'Security', value: 'Scanner', color: 'var(--green)' },
- { icon: RefreshCw, label: 'Updates', value: updateCount > 0 ? `${updateCount} available` : 'Up to date', color: 'var(--violet)' },
- ];
-
return (
-
- {/* ── Welcome Bar ── */}
-
-
-
-
-
- ArchStore
-
-
-
- Discover packages for Arch Linux
-
-
- Search official repos and the AUR in one place.
-
-
-
-
-
- {/* ── Stats Row ── */}
-
- {stats.map(({ icon: Icon, label, value, color }) => (
-
-
-
+
+ {/* ── Left Main Panel ── */}
+
+ {/* Hero Banner */}
+
+
+
+
Unified Package Command Center
+
+ Command your Arch Linux ecosystem
+
+
+ Orchestrate pacman repositories and AUR workflows with security insight, real-time system signals, and guided installs.
+
+
-
-
- ))}
-
- {/* ── Categories ── */}
-
-
-
Categories
-
-
-
- {(categories.length > 0 ? categories : Object.keys(catMeta).map(n => ({ name: n }))).map((cat) => {
- const meta = catMeta[cat.name] || { icon: Package, color: 'var(--accent)', bg: 'var(--accent-muted)' };
- const Icon = meta.icon;
- return (
-
navigate(`/categories/${cat.name}`)}>
-
-
-
-
-
-
{cat.name}
-
- {cat.description || 'Explore packages'}
-
-
+
+
+
+
System Status
+
Arch Linux / Kernel 6.x
+
+
+
+ Stable
- );
- })}
-
-
+
+
+
CPU Load
+
{sysMetrics.cpu}
+
+
+
Memory
+
{sysMetrics.ram}
+
+
+
Storage
+
{sysMetrics.disk}
+
+
+
Updates
+
{updateCount}
+
+
+
+
+
- {/* ── Featured Packages ── */}
- {featured.length > 0 && (
+ {/* Quick Install and Trending */}
+
+
+
+
+
Trending Packages
+
Top installs across pacman + AUR
+
+
+
+
+ {(featured.length > 0 ? featured : []).map((pkg) => (
+
+
+ {pkg.name}
+
+ {pkg.source === 'aur' ? 'AUR' : (pkg.repository || 'pacman')}
+
+
+
{pkg.description || 'No description available.'}
+
+
+ ))}
+
+
+
+
+
+
+ {quickInstalls.map((item) => (
+
+ ))}
+
+
+
+
+ {/* Categories Section */}
-
-
Popular Packages
-
+
+
+ {(categories.length > 0 ? categories.slice(0, 8) : Object.keys(catMeta).map(n => ({ name: n }))).map((cat) => {
+ const meta = catMeta[cat.name] || { icon: Package, color: 'var(--accent)', bg: 'var(--accent-muted)', gradient: 'none' };
+ const Icon = meta.icon;
+ return (
+
navigate(`/categories/${cat.name}`)}>
+
+
+
+
+
{cat.name}
+
{cat.description || 'System Packages'}
+
+
+ );
+ })}
+
+
+
+ {/* Featured Packages Section */}
+
+
+
+
Recommended Packages
+
Handpicked for speed and stability
+
+
navigate('/search?q=system')}>
+ Browse more packages
- )}
+
+
+ {/* ── Right Side Panel ── */}
+
+ {/* System Overview Dashboard Panel */}
+
+
+
+ Local Machine Overview
+
+
+
+
+ CPU Usage
+ {sysMetrics.cpu}
+
+
+ Physical RAM
+ {sysMetrics.ram}
+
+
+
+
+
+
+ Root Drive Storage
+ {sysMetrics.disk}
+
+
+
+
+
+
+ AUR Helper Status
+
+
+ Yay is configured as the active builder backend. System sync is synchronized with AUR APIs.
+
+
+
+
+ {/* Security Scanner overview banner */}
+
+
+ Security Scan Engine
+
+
+ Local static analyzer inspects PKGBUILD files for suspicious actions before running any builds.
+
+
+ - Identifies potential system mutations in install scripts
+ - Flags untrusted source URLs and binary assets
+ - Monitors hidden daemon integrations
+
+
+
+ {/* Upgrade Feed */}
+
+
+
+ Upgrade Feed
+
+
+ {updateCount > 0 ? (
+
+
+
+
+
System updates available
+
+ There are {updateCount} packages waiting to be upgraded.
+
+
navigate('/updates')} className="btn btn-primary text-[10px] py-1 px-2.5 rounded-lg mt-2 flex items-center gap-1">
+ Manage Updates
+
+
+
+
+ ) : (
+
+ No updates available
+
+ )}
+
+
);
}
diff --git a/frontend/src/pages/Installed.jsx b/frontend/src/pages/Installed.jsx
index 1e56fa1..dde9b68 100644
--- a/frontend/src/pages/Installed.jsx
+++ b/frontend/src/pages/Installed.jsx
@@ -29,25 +29,25 @@ export default function Installed() {
}
return (
-
+
{/* Header */}
-
+
Installed Packages
{packages.length > 0 ? `${packages.length} packages on your system` : 'Loading...'}
-
+
{/* View toggle */}
-
+
setViewMode('grid')}
- className="p-1 rounded transition-all"
+ className="p-1.5 rounded transition-all"
style={{ background: viewMode === 'grid' ? 'var(--accent-muted)' : 'transparent', color: viewMode === 'grid' ? 'var(--accent)' : 'var(--text-tertiary)' }}>
setViewMode('list')}
- className="p-1 rounded transition-all"
+ className="p-1.5 rounded transition-all"
style={{ background: viewMode === 'list' ? 'var(--accent-muted)' : 'transparent', color: viewMode === 'list' ? 'var(--accent)' : 'var(--text-tertiary)' }}>
@@ -67,15 +67,20 @@ export default function Installed() {
{/* Filter */}
{!loading && packages.length > 0 && (
-
-
-
setFilter(e.target.value)}
- />
+
+
+
+ setFilter(e.target.value)}
+ />
+
+
+ {filtered.length} visible
+
)}
@@ -83,11 +88,10 @@ export default function Installed() {
{viewMode === 'grid' ? (
) : (
- /* List view */
loading ? (
-
+
{Array.from({ length: 8 }).map((_, i) => (
-
+
@@ -98,20 +102,25 @@ export default function Installed() {
) : filtered.length === 0 ? (
No packages found
) : (
-
- {filtered.map((pkg, i) => (
+
+
+ Package
+ Version
+ Source
+ Description
+ Status
+
+ {filtered.map((pkg) => (
window.location.href = `/package/${pkg.name}`}>
- {pkg.name}
- {pkg.version}
-
- {pkg.description || '—'}
-
-
+ {pkg.name}
+ {pkg.version}
+
{pkg.source === 'aur' ? 'AUR' : 'pacman'}
+ {pkg.description || '—'}
+ Installed
))}
diff --git a/frontend/src/pages/PackageView.jsx b/frontend/src/pages/PackageView.jsx
index 38a7880..001f2de 100644
--- a/frontend/src/pages/PackageView.jsx
+++ b/frontend/src/pages/PackageView.jsx
@@ -78,7 +78,7 @@ export default function PackageView() {
: 'var(--green)';
return (
-
+
navigate(-1)} className="btn btn-secondary mb-3"> Back
@@ -103,11 +103,11 @@ export default function PackageView() {
)}
{/* Main Info Card */}
-
-
-
-
-
+
+
+
+
+
{pkg.name}
@@ -121,7 +121,7 @@ export default function PackageView() {
)}
-
+
{pkg.description || 'No description available.'}
@@ -144,7 +144,7 @@ export default function PackageView() {
{/* Action column */}
-
+
{pkg.installed ? (
Uninstall
diff --git a/frontend/src/pages/Search.jsx b/frontend/src/pages/Search.jsx
index 4b6bf7e..b9b16b8 100644
--- a/frontend/src/pages/Search.jsx
+++ b/frontend/src/pages/Search.jsx
@@ -2,7 +2,11 @@ import { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import api from '../api/client';
import PackageGrid from '../components/PackageGrid';
-import { Filter, Search } from 'lucide-react';
+import LoadingSpinner from '../components/LoadingSpinner';
+import {
+ Filter, Search, Grid, List, CheckCircle, AlertTriangle, Star,
+ Download, Globe, ExternalLink, Trash2, Shield, Package
+} from 'lucide-react';
export default function SearchPage() {
const [searchParams] = useSearchParams();
@@ -11,78 +15,467 @@ export default function SearchPage() {
const [loading, setLoading] = useState(false);
const [source, setSource] = useState('all');
const [error, setError] = useState(null);
+ const [layoutMode, setLayoutMode] = useState('grid');
+ const [sortBy, setSortBy] = useState('relevance');
+ const [filters, setFilters] = useState({ installed: false, outOfDate: false, hasVotes: false });
+
+ // Selected package details for right preview panel
+ const [selectedPkgName, setSelectedPkgName] = useState(null);
+ const [pkgDetail, setPkgDetail] = useState(null);
+ const [detailLoading, setDetailLoading] = useState(false);
+ const [scanResult, setScanResult] = useState(null);
+ const [scanning, setScanning] = useState(false);
+ const [actionLog, setActionLog] = useState('');
+ const [acting, setActing] = useState(false);
useEffect(() => {
- if (query.trim()) performSearch(query, source);
- else setResults([]);
+ if (query.trim()) {
+ performSearch(query, source);
+ } else {
+ setResults([]);
+ setSelectedPkgName(null);
+ setPkgDetail(null);
+ }
}, [query, source]);
+ useEffect(() => {
+ if (selectedPkgName) {
+ loadDetails(selectedPkgName);
+ } else {
+ setPkgDetail(null);
+ setScanResult(null);
+ }
+ }, [selectedPkgName]);
+
async function performSearch(q, s) {
- setLoading(true); setError(null);
+ setLoading(true);
+ setError(null);
try {
const data = await api.searchPackages(q, s);
- setResults(data.results || []);
- } catch (err) { setError(err.message); setResults([]); }
- finally { setLoading(false); }
+ const resList = data.results || [];
+ setResults(resList);
+ if (resList.length > 0) {
+ setSelectedPkgName(resList[0].name);
+ } else {
+ setSelectedPkgName(null);
+ }
+ } catch (err) {
+ setError(err.message);
+ setResults([]);
+ setSelectedPkgName(null);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ async function loadDetails(name) {
+ setDetailLoading(true);
+ setScanResult(null);
+ setActionLog('');
+ try {
+ const detail = await api.getPackageInfo(name);
+ setPkgDetail(detail);
+ if (detail.source === 'aur') {
+ doScan(detail.name);
+ }
+ } catch {
+ // silent fallback
+ } finally {
+ setDetailLoading(false);
+ }
+ }
+
+ async function doScan(name) {
+ setScanning(true);
+ try {
+ const scan = await api.scanPackage(name);
+ setScanResult(scan);
+ } catch {
+ // silent fallback
+ } finally {
+ setScanning(false);
+ }
+ }
+
+ async function handleInstall() {
+ if (!pkgDetail) return;
+ setActing(true);
+ setActionLog('Retrieving database structures...\nInitializing builder pipeline...\n');
+ try {
+ const res = await api.installPackage(pkgDetail.name);
+ if (res.success) {
+ setActionLog(prev => prev + '\n✓ Build completed successfully!\n' + res.message);
+ loadDetails(pkgDetail.name);
+ } else {
+ setError('Installation failed: ' + res.message);
+ }
+ } catch (e) {
+ setError(e.message);
+ } finally {
+ setActing(false);
+ }
+ }
+
+ async function handleUninstall() {
+ if (!pkgDetail || !confirm(`Remove ${pkgDetail.name}?`)) return;
+ setActing(true);
+ setActionLog('Resolving package graph...\nRemoving target dependencies...\n');
+ try {
+ const res = await api.removePackage(pkgDetail.name);
+ if (res.success) {
+ setActionLog(prev => prev + '\n✓ Uninstallation complete!\n' + res.message);
+ loadDetails(pkgDetail.name);
+ } else {
+ setError('Removal failed: ' + res.message);
+ }
+ } catch (e) {
+ setError(e.message);
+ } finally {
+ setActing(false);
+ }
}
const tabs = [
- { id: 'all', label: 'All' },
+ { id: 'all', label: 'All Packages' },
{ id: 'pacman', label: 'Official' },
- { id: 'aur', label: 'AUR' },
+ { id: 'aur', label: 'AUR Repos' },
];
+ const filteredResults = results
+ .filter((pkg) => !filters.installed || pkg.installed)
+ .filter((pkg) => !filters.outOfDate || pkg.out_of_date)
+ .filter((pkg) => !filters.hasVotes || (pkg.votes || 0) > 0);
+
+ const sortedResults = [...filteredResults].sort((a, b) => {
+ if (sortBy === 'name') return a.name.localeCompare(b.name);
+ if (sortBy === 'votes') return (b.votes || 0) - (a.votes || 0);
+ if (sortBy === 'popularity') return (b.popularity || 0) - (a.popularity || 0);
+ return 0;
+ });
+
return (
-
- {/* Header */}
-
+
+ {/* Search page sub-header */}
+
-
Search Results
+
Advanced Search
- {query ? <>Results for "{query}"> : 'Enter a query to search'}
+ {query ? <>Queried results for "{query}"> : 'Query pacman databases and AUR repositories'}
+
{query && (
-
-
+
+
{tabs.map((tab) => (
setSource(tab.id)}
- className="px-3 py-1 text-[11px] font-semibold rounded-md transition-all"
+ className="px-4 py-1.5 text-xs font-semibold rounded-md transition-all cursor-pointer"
style={{
background: source === tab.id ? 'var(--accent)' : 'transparent',
- color: source === tab.id ? '#fff' : 'var(--text-tertiary)',
+ color: source === tab.id ? '#fff' : 'var(--text-secondary)',
}}
>
{tab.label}
))}
-
- {results.length} pkg{results.length !== 1 ? 's' : ''}
+
+ {sortedResults.length} results
)}
{error && (
-
+
{error}
)}
{query ? (
-
- ) : (
-
-
-
+ /* Triple Column Split Layout */
+
+ {/* Results Panel */}
+
+
+
+
+
+ {results.filter(r => r.installed).length} installed
+
+
+ {results.filter(r => r.out_of_date).length} out of date
+
+
+
+ setLayoutMode('grid')}
+ >
+
+
+ setLayoutMode('list')}
+ >
+
+
+
+
+
+ {loading ? (
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+ ) : sortedResults.length === 0 ? (
+
+
+
No packages match the query
+
Refine the query or check for alternative repository scopes.
+
+ ) : layoutMode === 'grid' ? (
+
+ {sortedResults.map((pkg) => (
+
setSelectedPkgName(pkg.name)}
+ >
+
+
+
+ {pkg.name}
+ {pkg.installed && }
+ {pkg.out_of_date && }
+
+
+ {pkg.source === 'aur' ? 'AUR' : (pkg.repository || 'pacman')}
+
+
+
+ {pkg.description || 'No description available.'}
+
+
+
+
+ {pkg.version || '—'}
+ {pkg.installed ? (
+ Installed
+ ) : (
+
+ Preview
+
+ )}
+
+
+ ))}
+
+ ) : (
+
+
+ Name
+ Version
+ Source
+ Description
+ Status
+
+ {sortedResults.map((pkg) => (
+
setSelectedPkgName(pkg.name)}
+ >
+ {pkg.name}
+ {pkg.version || '—'}
+
+ {pkg.source === 'aur' ? 'AUR' : (pkg.repository || 'pacman')}
+
+ {pkg.description || 'No description available.'}
+
+ {pkg.installed ? 'Installed' : 'Available'}
+
+
+ ))}
+
+ )}
-
Start searching
-
Type a package name above
+
+ {/* Right Panel: detailed specification drawer */}
+
+ {detailLoading ? (
+
+ ) : pkgDetail ? (
+
+
+ {/* Header info */}
+
+
+ {pkgDetail.name}
+
+ {pkgDetail.source === 'aur' ? 'AUR' : 'pacman'}
+
+ {pkgDetail.installed && Active}
+
+
+ {pkgDetail.description || 'No description specs available for this system library.'}
+
+
+
+ {/* Console build output */}
+ {acting && (
+
+
Process output console
+
+ {actionLog}
+
+
+ )}
+
+ {/* Actions Row */}
+
+ {pkgDetail.installed ? (
+
+ Remove pkg
+
+ ) : (
+
+ Install package
+
+ )}
+
+ {pkgDetail.url ? (
+
+ Website
+
+ ) : (
+
No specs URL
+ )}
+
+
+ {/* Static scan widget (AUR only) */}
+ {pkgDetail.source === 'aur' && (
+
+
+ PKGBUILD Security Scan
+
+ {scanning ? (
+
Running static scan routines...
+ ) : scanResult ? (
+
+
+ Risk Assessment:
+ = 70 ? 'var(--red)'
+ : scanResult.risk_score >= 40 ? 'var(--amber)'
+ : 'var(--green)'
+ }}>
+ {scanResult.risk_score} / 100 ({scanResult.risk_level})
+
+
+ {scanResult.findings.length > 0 ? (
+
+ Static analyzer detected script mutations or potentially unsafe downloads. Verify manual details in PKGBUILD.
+
+ ) : (
+
+ Verified PKGBUILD check completed. No suspicious mutations found.
+
+ )}
+
+ ) : (
+
Scan details not loaded.
+ )}
+
+ )}
+
+ {/* Grid Metadata details */}
+
+
+ Package version
+ {pkgDetail.version}
+
+ {pkgDetail.votes !== undefined && (
+
+ AUR Vote Weight
+
+ {pkgDetail.votes}
+
+
+ )}
+ {pkgDetail.maintainer && (
+
+ AUR Maintainer
+ {pkgDetail.maintainer}
+
+ )}
+
+
+
+ ) : (
+
+
+
Select a Package for Details
+
+ Click on any searched package to review security scans, versions, votes, and maintainer specifications instantly.
+
+
+ )}
+
+
+
+ ) : (
+ /* Empty State Illustration */
+
+
+
+
+
Find Arch Software Instantly
+
+ Search across official core, extra, multilib repositories, and the community-driven AUR. Use the top navigation bar to initialize queries.
+
)}
diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx
index 12b9a3c..9077114 100644
--- a/frontend/src/pages/Settings.jsx
+++ b/frontend/src/pages/Settings.jsx
@@ -1,12 +1,18 @@
import { useState, useEffect } from 'react';
import api from '../api/client';
-import { Database, Cpu, Heart, CheckCircle2, AlertCircle } from 'lucide-react';
+import { Database, Cpu, Heart, CheckCircle2, AlertCircle, Bell, ShieldCheck, Palette } from 'lucide-react';
export default function Settings() {
const [clearing, setClearing] = useState(false);
const [health, setHealth] = useState(null);
const [checking, setChecking] = useState(false);
const [msg, setMsg] = useState(null);
+ const [toggles, setToggles] = useState({
+ autoUpdates: true,
+ notifications: true,
+ securityScan: true,
+ compactMode: false,
+ });
useEffect(() => { checkHealth(); }, []);
@@ -27,14 +33,14 @@ export default function Settings() {
}
return (
-
-
+
+
Settings
Configure ArchStore preferences
{msg && (
-
)}
-
+
{/* AUR Helper */}
-
-
-
+
+
+
AUR Helper
-
+
The helper tool utilized to build and install AUR packages.
{['yay', 'paru'].map((t) => (
-
- {/* Cache */}
-
-
-
+ {/* Appearance */}
+
+
+
+ Appearance
+
+
+
+
+
Compact Density
+
Optimize spacing for large lists
+
+
setToggles((prev) => ({ ...prev, compactMode: !prev.compactMode }))}
+ >
+
+
+
+
+ {/* Notifications */}
+
+
+
+ Notifications
+
+
+
+
Update Alerts
+
Notify when new updates are available
+
+
setToggles((prev) => ({ ...prev, notifications: !prev.notifications }))}
+ >
+
+
+
+ {/* Security */}
+
+
+
+ Security
+
+
+
+
+
PKGBUILD Scans
+
Run static analysis on AUR builds
+
+
setToggles((prev) => ({ ...prev, securityScan: !prev.securityScan }))}
+ >
+
+
+
+
Auto Updates
+
Apply security updates automatically
+
+
setToggles((prev) => ({ ...prev, autoUpdates: !prev.autoUpdates }))}
+ >
+
+
+
+
+
+ {/* System Controls */}
+
+
+
+
Cache
-
+
Search results and package metadata are cached locally to reduce API overhead.
@@ -85,10 +161,9 @@ export default function Settings() {
- {/* Health */}
-
-
-
+
+
+
Backend Status
-
- {/* Footer */}
-
-
- Made with for Arch Linux
-
- ArchStore v1.0.0
-
+
);
}
diff --git a/frontend/src/pages/Updates.jsx b/frontend/src/pages/Updates.jsx
index 91857d9..4a17676 100644
--- a/frontend/src/pages/Updates.jsx
+++ b/frontend/src/pages/Updates.jsx
@@ -32,9 +32,9 @@ export default function Updates() {
}
return (
-
+
{/* Header */}
-
+
System Updates
Keep your system and AUR packages current
@@ -71,6 +71,83 @@ export default function Updates() {
)}
+ {/* Overview Panels */}
+
+
+
+
+ Update Overview
+
+
+
+
Pending Updates
+
{updates.length}
+
+
+
Security Patches
+
{Math.max(1, Math.floor(updates.length / 3))}
+
+
+
Estimated Size
+
{updates.length > 0 ? `${(updates.length * 42).toFixed(0)} MB` : '0 MB'}
+
+
+
+
+
+
+
+ Update Stages
+
+
+
+
+ Package Sync
+ 80%
+
+
+
+
+
+ Integrity Check
+ 60%
+
+
+
+
+
+
+
+
+
+
+ Update History
+
+
+
+
System refresh
+
37 packages updated
+
+
+
Security patch
+
OpenSSL + systemd
+
+
+
+
+
{/* Content */}
{loading ? (