22 KiB
Worklog
Task 5 — Dashboard API Route & Dashboard Page Component
Date: 2025-06-24
What was done
-
Created Dashboard API route at
/src/app/api/stats/dashboard/route.ts- GET handler with auth verification via
getAuth - Returns comprehensive dashboard data:
stats: totalUsers, totalProducts, totalPurchases, totalRevenue, totalSubcategories, aov, conversionRate, completedPurchases, pendingPurchases, cancelledPurchaseschartData: 7-day and 30-day revenue + 7-day new user datatopProducts: top 5 by quantity from completed purchases (name, qty, revenue)topSpenders: top 5 users by total spent (username, spent)revenueByCategory: revenue grouped by category name using$queryRawtopCountries: top 5 countries with product counts using$queryRawactivities: merged last 10 completed purchases + last 10 audit logs, sorted by datewalletSummary: per wallet type (BTC/LTC/ETH/USDT/USDC) with count, total balance, mock USD balance
- Uses parallel
Promise.allfor basic counts,$queryRawfor complex aggregations (revenue by category, top countries) - Wrapped in try/catch returning 500 on error
- GET handler with auth verification via
-
Created Dashboard Page component at
/src/components/dashboard/dashboard-page.tsx- Named export
DashboardPage(not default) - "use client" directive, fetches data from
/api/stats/dashboardvia useState/useEffect - Loading skeleton state, error state handling
- 10 KPI Cards in a responsive grid (2/3/5 cols): Total Users, Total Products, Total Purchases, Revenue, AOV, Conversion Rate, Completed, Pending, Cancelled, Subcategories — each with lucide-react icon and color accent
- 7 Charts in a 2-col grid (1-col mobile):
- Revenue 7 days (AreaChart, orange gradient)
- Revenue 30 days (AreaChart, cyan gradient)
- New Users 7 days (BarChart, violet)
- Top 5 Products (horizontal BarChart, yellow)
- Top 5 Spenders (horizontal BarChart, pink)
- Revenue by Category (donut PieChart, 5 colors)
- Top 5 Countries (BarChart, orange)
- Uses recharts with ResponsiveContainer, proper tooltips styled with shadcn CSS variables
- Activity Feed: merged list with purchase/audit icons, scrollable max-h-96
- Wallet Summary Table: type, count, crypto balance (8 decimal), USD mock balance
- Empty states shown when no data
- Chart colors: orange=#f97316, cyan=#06b6d4, violet=#8b5cf6, yellow=#eab308, pink=#ec4899
- Named export
Files created
src/app/api/stats/dashboard/route.tssrc/components/dashboard/dashboard-page.tsx
Notes
- Pre-existing errors in dev.log (admin-header.tsx missing
@/import prefix, LoginPage not named export, other missing page components) are unrelated to this task - ESLint passes cleanly with no errors
Task 7 — Users API Routes & Users Page Components
Date: 2025-06-24
What was done
-
Created Users Bulk API route at
src/app/api/users/bulk/route.ts- GET: Returns paginated users with
search(username/telegramId),page,limit(default 50, max 100) query params - Uses Prisma
findManywithinclude: { _count: { select: { wallets: true, purchases: true } } } - Orders by id DESC, returns
{ data, total, page, limit } - Auth check via
getAuth
- GET: Returns paginated users with
-
Created User Detail API route at
src/app/api/users/[id]/route.ts- GET: Returns single user with
_countof wallets/purchases,walletsarray, andpurchases(last 20 with product name) - POST: Toggles user status (0↔2). Uses
$transactionto update user and create AuditLog entry with actionstatus_toggle. Returns{ ok: true, user } - Next.js 16 async params pattern (
params: Promise<{ id: string }>)
- GET: Returns single user with
-
Created Adjust Balance API route at
src/app/api/users/[id]/adjust-balance/route.ts- POST: Accepts
{ amount: number, currency: "total_balance" | "bonus_balance" } - Validates amount is number and currency is one of the two allowed values
- Uses
$transactionto update balance and create AuditLog with actionbalance_adjust(includes old/new balance in details) - Returns
{ ok: true, newBalance }
- POST: Accepts
-
Created UsersPage component at
src/components/users/users-page.tsx- Named export
UsersPage(not default), "use client" - Fetches from
/api/users/bulk?limit=50with useState/useEffect - Search input with 300ms debounce, filters via
?search=API param - Table columns: ID, Telegram ID, Username, Country, City, Status (Badge: green=Active, red=Blocked, gray=Deleted), Balance ($X.XX), Wallets count, Purchases count, Actions (View button)
- View button navigates via
window.location.hash = '/users/${id}' - Loading skeleton table, empty state "No users found", error state
- Max height scroll:
max-h-[calc(100vh-12rem)] overflow-y-auto - Pagination with Previous/Next buttons, page X of Y display
- Uses shadcn Table, Input, Badge, Button; lucide-react Search, Eye icons
- Named export
-
Created UserDetailPage component at
src/components/users/user-detail-page.tsx- Named export
UserDetailPagewith prop{ userId: string }(not default), "use client" - Fetches from
/api/users/${userId} - Back button navigates to
window.location.hash = '/users' - Profile card: ID, Telegram ID, Username, Status badge, Country, City, District, Language, Registered date, Main Balance, Bonus Balance (with icons)
- Purchases table (last 20): ID, Product name, Qty, Total Price, Status badge (green=Completed, yellow=Pending, red=Cancelled), Date
- Wallets table: Type (badge), Address (truncated mono), Balance (8 decimals)
- Balance Adjustment form: Amount input (number, step 0.01), currency Select (Main/Bonus), Submit button. Calls
/api/users/${userId}/adjust-balance. Refetches user on success - Ban/Unban button with AlertDialog confirmation. Dynamic title/description based on current status. Calls POST
/api/users/${userId}. Refetches on success - Toast notifications (sonner) on success/error for all actions
- Loading skeleton, error states, responsive grid layout (1-col mobile, 3-col desktop)
- Named export
Files created
src/app/api/users/bulk/route.tssrc/app/api/users/[id]/route.tssrc/app/api/users/[id]/adjust-balance/route.tssrc/components/users/users-page.tsxsrc/components/users/user-detail-page.tsx
Notes
- ESLint passes cleanly with 0 errors, 0 warnings
- Pre-existing dev.log error (admin-header.tsx missing
@/import prefix) is unrelated to this task - All API routes follow the established pattern:
getAuthcheck, try/catch with 500 fallback, Prisma ORM
Task 6 — Catalog API Routes & Catalog Page Component
Date: 2025-06-24
What was done
-
Created Catalog Tree API route at
src/app/api/catalog/tree/route.ts- GET: Returns full catalog tree as 3 flat arrays:
locations,categories,subcategories - Each location includes
categoryCountandproductCount - Each category includes
subcategoryCount,productCount, and nestedlocationinfo - Each subcategory includes
productCountand nestedcategoryinfo - Uses parallel
Promise.allto fetch all three tables simultaneously - Auth check via
getAuth
- GET: Returns full catalog tree as 3 flat arrays:
-
Created Products Bulk API route at
src/app/api/products/bulk/route.ts- GET: Paginated products with filters:
loc(locationId),cat(categoryId),sub(subcategoryId),search(name contains),page,limit - Includes
category(id, name),subcategory(id, name),location(id, country, city, district) - Returns
{ data, total, page, limit } - Auth check via
getAuth
- GET: Paginated products with filters:
-
Created Products Add API route at
src/app/api/products/add/route.ts- POST: Creates product with all fields from JSON body
isMono=1(or true) forcesquantityInStock=999999- Validates required fields:
locationId,categoryId,name,price - Returns created product with relations included (201)
- Auth check via
getAuth
-
Created Product [id] API route at
src/app/api/products/[id]/route.ts- GET: Single product with full
category,subcategory,locationrelations - PUT: Updates product fields, same mono logic (isMono=1 → stock=999999)
- DELETE: Deletes product, returns
{ ok: true } - Uses Next.js 16 async params pattern
- Auth check via
getAuth
- GET: Single product with full
-
Created Locations CRUD API routes:
src/app/api/locations/bulk/route.ts: GET (all with_countcategories/products), POST (create with unique constraint handling, 409 on duplicate)src/app/api/locations/[id]/route.ts: PUT (update name fields), PATCH (toggle is_active 0↔1), DELETE (only if no categories/products, 400 otherwise)
-
Created Categories CRUD API routes:
src/app/api/categories/bulk/route.ts: GET (all with location info,_count), POST (create, 409 on duplicate within location)src/app/api/categories/[id]/route.ts: PUT (update name/locationId), PATCH (toggle is_active), DELETE (only if no products, cascades subcategories)
-
Created Subcategories CRUD API routes:
src/app/api/subcategories/bulk/route.ts: GET (all with category info,_count), POST (create, 409 on duplicate within category)src/app/api/subcategories/[id]/route.ts: PUT (update name), PATCH (toggle is_active), DELETE (only if no products)
-
Created CatalogPage component at
src/components/catalog/catalog-page.tsx- Named export
CatalogPage(not default), "use client" - Two-column layout using
ResizablePanelGroup(30% left / 70% right) - Left Panel — Tree View:
- Collapsible accordion: Country → City → District (location) → Category → Subcategory
- Each node shows name, active/inactive badge, item count
- Hover actions on each node: inline rename (Enter/blur save), toggle active (Switch), delete (AlertDialog), add child
- Click a node to filter products on the right
- Add forms inline at each level (location: country+city+district inputs; category/subcategory: name input)
- Scrollable:
overflow-y-auto max-h-[calc(100vh-14rem)] - Uses shadcn Accordion, Switch, Button, Badge, AlertDialog, Input
- Right Panel — Products Table:
- Columns: ID, Photo (40x40 img or placeholder), Name, Category, Subcategory, Price ($X.XX), Stock (∞ for mono), Actions (Edit, Delete)
- Search input with 300ms debounce
- Clear filter button when filter active
- Product count display, pagination (Previous/Next)
- Max height scroll with overflow-auto
- Product Modal (Dialog):
- Three cascading Select components: Location (grouped as "Country > City > District"), Category (filtered by location), Subcategory (filtered by category, with "None" option)
- Changing location/category reloads dependent selects
- Checkbox: "Digital Product (infinite stock)" → disables quantity field, shows ∞
- Price input (number, min 0.01)
- Description textarea
- Photo URL and Hidden Photo URL text inputs
- Collapsible "Hidden Content Fields" section: hidden_description, hidden_coordinates, private_data
- Save and Cancel buttons, loading state during save
- Edit mode pre-fills all fields from existing product
- Delete Confirmation: AlertDialog for all destructive actions (locations, categories, subcategories, products)
- Toast notifications (sonner) on all success/error actions
- Loading skeleton state, error state with retry button
- Responsive: category/subcategory columns hidden on smaller screens
- Colors: orange (MapPin), amber (FolderOpen), emerald (Tag)
- Named export
Files created
src/app/api/catalog/tree/route.tssrc/app/api/products/bulk/route.tssrc/app/api/products/add/route.tssrc/app/api/products/[id]/route.tssrc/app/api/locations/bulk/route.tssrc/app/api/locations/[id]/route.tssrc/app/api/categories/bulk/route.tssrc/app/api/categories/[id]/route.tssrc/app/api/subcategories/bulk/route.tssrc/app/api/subcategories/[id]/route.tssrc/components/catalog/catalog-page.tsx
Notes
- ESLint passes cleanly with 0 errors, 0 warnings
- Pre-existing dev.log error (admin-header.tsx missing
@/import prefix) is unrelated to this task - All 10 API routes follow the established pattern:
getAuthcheck, try/catch with 500 fallback, Prisma ORM - Unique constraint violations return 409 status code
- DELETE endpoints enforce referential integrity (no delete if children exist)
- All [id] routes use Next.js 16 async params pattern (
params: Promise<{ id: string }>)
Task 8 — Wallets API Routes & Wallets Page Component
Date: 2025-06-24
What was done
-
Created Wallets Overview API route at
src/app/api/wallets/overview/route.ts- GET: Returns owner summary data with auth check
- Aggregates all CryptoWallet records to compute:
totals(balance per walletType),walletCounts,totalUsd,totalWallets,totalUsers(unique users with wallets) - Commission data:
commissionEnabled(true),commissionRate(0.05/5%),currentCommission(totalUsd × rate),payments(last 20 CommissionPayment records),lastPaidAmount,commissionDue(max(0, current - paid))
-
Created Wallets Bulk API route at
src/app/api/wallets/bulk/route.ts- GET: Returns all users who have wallets, with wallet counts
- Query param
?search=filters by username or telegramId (contains) - Each user includes: id, username, telegramId, status, totalBalance, bonusBalance, walletCount, country, city
-
Created Wallets [userId] API route at
src/app/api/wallets/[userId]/route.ts- GET: Returns user profile + wallets array for a specific user
- Wallets include: id, walletType, address, balance, createdAt
- Uses Next.js 16 async params pattern
- 400 on invalid userId, 404 if user not found
-
Created Record Payment API route at
src/app/api/wallets/record-payment/route.ts- POST: Accepts
{paidAmount: number, note?: string} - Validates paidAmount is positive number
- Creates CommissionPayment record with computed fields
- Returns
{ok: true}
- POST: Accepts
-
Created Seeds API route at
src/app/api/wallets/seeds/route.ts- GET: Returns all seed phrases (mnemonic field) — super admin only via
requireSuperAuth - Filters to wallets where mnemonic is not null
- Includes user username in response
- Creates AuditLog entry with action
seed_phrase_viewed
- GET: Returns all seed phrases (mnemonic field) — super admin only via
-
Created Export Seeds API route at
src/app/api/wallets/export-seeds/route.ts- GET: Returns CSV download — super admin only
- CSV columns: WalletId, UserId, Username, WalletType, Address, DerivationPath, Mnemonic
- Content-Type: text/csv, Content-Disposition: attachment
- Creates AuditLog entry with action
csv_seed_export
-
Created WalletsPage component at
src/components/wallets/wallets-page.tsx- Named export
WalletsPage(not default), "use client" - Three-section layout with shadcn Tabs:
- Tab 1: User Wallets — Left panel: searchable user list with status badges and wallet counts; Right panel: selected user profile card (balances, location) + wallets table (type badge, clickable truncated address with copy-to-clipboard, 8-decimal balance, created date). "Refresh Balances" button (mock toast). Debounced search, loading skeletons, empty states.
- Tab 2: Owner Summary — 4 KPI cards (Total USD Value, Total Users, Active Wallets, Commission Due). "Balances by Currency" table (BTC/LTC/ETH/USDT/USDC with wallet count and total balance). Commission section with stats (Rate, Total Balance, Commission Amount, Last Paid), commission-due warning banner, Record Payment form (amount + note inputs), Payment History table (last 20 with date, amount, note).
- Tab 3: Seed Phrases (super_admin only) — Commission-due warning disables unlock. AlertDialog confirmation before loading seeds. Seeds table: User, Type, Address (click to copy), Derivation, Mnemonic (masked by default with eye toggle reveal, click to copy). "Export All Seeds as CSV" button with AlertDialog confirmation.
- All copy-to-clipboard uses sonner toast "Copied!"
- Responsive design, max-height scrollable tables
- Uses shadcn: Tabs, Card, Table, Badge, Button, Input, Label, AlertDialog, Skeleton
- Named export
Files created
src/app/api/wallets/overview/route.tssrc/app/api/wallets/bulk/route.tssrc/app/api/wallets/[userId]/route.tssrc/app/api/wallets/record-payment/route.tssrc/app/api/wallets/seeds/route.tssrc/app/api/wallets/export-seeds/route.tssrc/components/wallets/wallets-page.tsx
Notes
- ESLint passes cleanly with 0 errors, 0 warnings
- Pre-existing dev.log error (admin-header.tsx missing
@/import prefix) is unrelated to this task - All 6 API routes follow the established pattern:
getAuth/requireSuperAuthcheck, try/catch with 500 fallback, Prisma ORM - Seed/export routes create AuditLog entries for security tracking
- [userId] route uses Next.js 16 async params pattern (
params: Promise<{ userId: string }>)
Task 9-11 — All Remaining API Routes & Page Components
Date: 2025-06-24
What was done
-
Purchases API at
src/app/api/purchases/bulk/route.ts- GET: Paginated purchases with
?status=&page=&limit=50 - Includes user (username, telegramId) and product (name)
- Returns
{data, total, page, limit}
- GET: Paginated purchases with
-
PurchasesPage at
src/components/purchases/purchases-page.tsx- Status filter tabs: All, Pending, Completed, Cancelled
- Table: ID, User (link to #/users/id), Product, Qty, Total Price ($X.XX), Currency, TX Hash (truncated, click to copy), Date, Status Badge
- Pagination, loading skeleton, empty state, max-h scroll
-
Audit Log API at
src/app/api/audit/bulk/route.ts- GET: Paginated audit log with
?page=&limit=100, ordered id DESC - Returns
{data, total, page, limit}
- GET: Paginated audit log with
-
AuditPage at
src/components/audit/audit-page.tsx- Table: ID, Action (color-coded Badge), Admin ID, Details (Collapsible JSON), Date
- Action badge colors: login=blue, balance_adjust=orange, status_toggle=red, seed_phrase_viewed/csv_seed_export=purple, seed_demo=amber, clear_all=dark-red
- Pagination, loading skeleton, empty state
-
CategoriesPage at
src/components/categories/categories-page.tsx- Table: ID, Name, Location (country > city > district), Subcategories count, Products count, Active (Switch), Actions (Edit, Delete)
- Add/Edit Dialog with name input + location Select (grouped by country/city/district)
- Delete AlertDialog with product-count protection
- Uses existing categories/bulk and categories/[id] APIs
-
LocationsPage at
src/components/locations/locations-page.tsx- Table: ID, Country, City, District, Categories count, Products count, Active (Switch), Actions (Edit, Delete)
- Add/Edit Dialog with country, city, district inputs
- Delete AlertDialog with category/product count protection
- Uses existing locations/bulk and locations/[id] APIs
-
Settings API at
src/app/api/settings/route.ts- GET: Hardcoded settings object with
_maskedarray - PUT: Accepts
{key, value}, returns{ok: true, message}(no persistence)
- GET: Hardcoded settings object with
-
SettingsPage at
src/components/settings/settings-page.tsx- Sections: Bot, WireGuard, Admin — each setting with Label, Input, per-field Save button
- Masked secrets shown as disabled inputs with bullet placeholders
- WG_ENABLED as Switch, warning banner about restart requirement
-
Locales API at
src/app/api/locales/route.ts- GET: Hardcoded 3-language (en, es, de) demo data
- PUT: Accepts
{lang, key, value}, returns{ok: true}(no persistence)
-
LocalesPage at
src/components/locales/locales-page.tsx- Table: rows = flattened locale keys, columns = en, es, de
- Each cell is inline-editable Input, blur triggers save
- Grouped by section (menu, common, products) with rowSpan headers
-
Seed Data API at
src/app/api/seed/data/route.ts- GET: Returns
{seeded: boolean}based on user count > 0
- GET: Returns
-
Seed Demo API at
src/app/api/seed/demo/route.ts- POST: Verifies reauth token, creates audit log, clears all data, seeds demo:
- 4 locations (USA/New York/Manhattan, Brooklyn; UK/London; Germany/Berlin)
- 2-3 categories per location, 1-2 subcategories per category
- 5-10 products per category with random prices
- 10-20 users with random usernames and balances
- 10-15 wallets across users, 5-10 purchases (mixed statuses)
- 3-5 audit log entries
- Resets autoincrement via sqlite_sequence
- POST: Verifies reauth token, creates audit log, clears all data, seeds demo:
-
Seed Clear API at
src/app/api/seed/clear/route.ts- POST: Verifies reauth token, creates audit log BEFORE deleting
- Deletes all tables in order, resets autoincrement
-
SeedPage at
src/components/seed/seed-page.tsx- Role gate: shows "Access Denied" if not super_admin
- DB status indicator (Contains Data / Empty)
- Two red danger cards: Seed Demo Data, Clear All Data
- Each action opens AlertDialog with warning text + reauth token password input
- Confirm disabled until token entered, loading spinner during operation
- Toast on success/error
Files created
src/app/api/purchases/bulk/route.tssrc/app/api/audit/bulk/route.tssrc/app/api/settings/route.tssrc/app/api/locales/route.tssrc/app/api/seed/data/route.tssrc/app/api/seed/demo/route.tssrc/app/api/seed/clear/route.tssrc/components/purchases/purchases-page.tsxsrc/components/audit/audit-page.tsxsrc/components/categories/categories-page.tsxsrc/components/locations/locations-page.tsxsrc/components/settings/settings-page.tsxsrc/components/locales/locales-page.tsxsrc/components/seed/seed-page.tsx
Notes
- ESLint passes cleanly with 0 errors, 0 warnings
- Pre-existing dev.log error (admin-header.tsx missing
@/import prefix) is unrelated - Categories and Locations APIs were already created by Task 6 (catalog agent)
- All 7 new API routes use
getAuthorverifyReAuthfor auth - All page components use named exports (not default), "use client" directive
- Seed demo/clear routes use
verifyReAuthfrom@/lib/authfor double-confirmation security