MVP-1: helpdesk core with dashboard, Telegram channel, customer portal

Next.js 16 + Drizzle/SQLite (matching ~/project-claude conventions), SSE-based
realtime updates, admin dashboard with status columns, ticket thread view,
Telegram long-polling integration (inbound + outbound), token-based customer
portal, Docker Compose packaging.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-07-26 12:40:56 +00:00
1 parent 888b7b82ba
commit cf0b6cf58e
59 files changed
+12556

No files matched your search

+234
View File
@@ -0,0 +1,234 @@
@import "tailwindcss";
@theme {
--font-sans: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
--font-mono: var(--font-mono), ui-monospace, monospace;
--color-bg: var(--bg);
--color-surface: var(--surface);
--color-surface-hover: var(--surface-hover);
--color-border: var(--border);
--color-border-strong: var(--border-strong);
--color-text: var(--text);
--color-text-muted: var(--text-muted);
--color-text-faint: var(--text-faint);
--color-accent: var(--accent);
--color-accent-hover: var(--accent-hover);
--color-accent-soft: var(--accent-soft);
--color-accent-soft-text: var(--accent-soft-text);
--color-danger: var(--danger);
--color-danger-soft: var(--danger-soft);
--color-danger-soft-text: var(--danger-soft-text);
--color-warning: var(--warning);
--color-warning-soft: var(--warning-soft);
--color-warning-soft-text: var(--warning-soft-text);
--color-success: var(--success);
--color-success-soft: var(--success-soft);
--color-success-soft-text: var(--success-soft-text);
--radius-sm: var(--radius-sm-val);
--radius-md: var(--radius-md-val);
--radius-lg: var(--radius-lg-val);
}
:root {
--bg: #fafaf9;
--surface: #ffffff;
--surface-hover: #f6f5ff;
--border: #e8e6f5;
--border-strong: #d6d1ee;
--text: #191524;
--text-muted: #6c6685;
--text-faint: #a29cc0;
--accent: #7c3aed;
--accent-hover: #6d28d9;
--accent-soft: #f3edff;
--accent-soft-text: #6d28d9;
--danger: #dc2626;
--danger-soft: #fef2f2;
--danger-soft-text: #b91c1c;
--warning: #d97706;
--warning-soft: #fffbeb;
--warning-soft-text: #b45309;
--success: #0d9488;
--success-soft: #ecfdf9;
--success-soft-text: #0f766e;
--radius-sm-val: 8px;
--radius-md-val: 12px;
--radius-lg-val: 18px;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #0f0d17;
--surface: #17141f;
--surface-hover: #1e1a2b;
--border: #2b2438;
--border-strong: #3a3150;
--text: #f1eefb;
--text-muted: #a79fc4;
--text-faint: #6f6790;
--accent: #a78bfa;
--accent-hover: #c4b5fd;
--accent-soft: #241b3d;
--accent-soft-text: #c4b5fd;
--danger: #f87171;
--danger-soft: #2a1517;
--danger-soft-text: #fca5a5;
--warning: #fbbf24;
--warning-soft: #2a2110;
--warning-soft-text: #fcd34d;
--success: #2dd4bf;
--success-soft: #0f2320;
--success-soft-text: #5eead4;
color-scheme: dark;
}
}
:root[data-theme="dark"] {
--bg: #0f0d17;
--surface: #17141f;
--surface-hover: #1e1a2b;
--border: #2b2438;
--border-strong: #3a3150;
--text: #f1eefb;
--text-muted: #a79fc4;
--text-faint: #6f6790;
--accent: #a78bfa;
--accent-hover: #c4b5fd;
--accent-soft: #241b3d;
--accent-soft-text: #c4b5fd;
--danger: #f87171;
--danger-soft: #2a1517;
--danger-soft-text: #fca5a5;
--warning: #fbbf24;
--warning-soft: #2a2110;
--warning-soft-text: #fcd34d;
--success: #2dd4bf;
--success-soft: #0f2320;
--success-soft-text: #5eead4;
color-scheme: dark;
}
* {
border-color: var(--border);
}
html,
body {
height: 100%;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
::selection {
background: var(--accent-soft);
color: var(--accent-soft-text);
}
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 4px;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--border-strong);
border-radius: 999px;
}
::-webkit-scrollbar-track {
background: transparent;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
border-radius: var(--radius-sm);
font-weight: 600;
font-size: 0.875rem;
padding: 0.5rem 0.9rem;
transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease, transform 100ms ease;
}
.btn:active {
transform: translateY(1px);
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
}
.btn-ghost {
background: transparent;
color: var(--text-muted);
border: 1px solid var(--border);
}
.btn-ghost:hover {
background: var(--surface-hover);
color: var(--text);
}
.badge {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.75rem;
font-weight: 600;
padding: 0.15rem 0.55rem;
border-radius: 999px;
}
.badge-new {
background: var(--accent-soft);
color: var(--accent-soft-text);
}
.badge-open {
background: var(--success-soft);
color: var(--success-soft-text);
}
.badge-pending {
background: var(--warning-soft);
color: var(--warning-soft-text);
}
.badge-closed {
background: var(--border);
color: var(--text-muted);
}