Overview
TAIKHOANXIN is a live marketplace for licensed digital accounts. Buyers browse products, pay by bank transfer and receive their order quickly; the operators run the whole business from a dedicated admin console. It is a two-person venture where I am co-founder and own the frontend: the public storefront and the admin dashboard.
The product is not one app but a small system, and most of the interesting engineering is in how the pieces cooperate.
The system at a glance
- Clients
- StorefrontNext.js 14 · Ant Design
- Admin consoleNext.js 14 · Shadcn UI
- API
- Express APIAuth · orders · payments · warranty
- Scheduled jobsOrder expiry · reminders · warranty SLA
- Data
- MongoDBOrders, wallets, products
- Cloudflare R2Images and uploads
- SMTPOrder and OTP e-mail
- Payments
- Payment botPolls bank transactions, matches order codes
- Bank + VietQRCustomer transfer
- TelegramOperator notifications
- Client
- Compute / service
- Messaging / jobs
- Data store
- External API
The two web apps are my part of the system; the API and bots complete the order-to-payment loop.
My part is the two web apps. The backend and the bots complete the picture, and understanding them shaped the frontend decisions.
The storefront
The customer app is a Next.js 14 application using Ant Design, SCSS and Tailwind. State is held in Redux Toolkit with Redux Persist, and server data is fetched through Axios with SWR for caching and revalidation.
- Discovery: categories, search, product pages, a wishlist and a recently viewed list.
- Purchasing: cart, checkout and a dedicated payment step designed around the fast bank-transfer flow.
- Account area: order history, profile, plus help, about, contact, terms and privacy pages.
- SEO: generated sitemaps for pages and categories, and optimized images (WebP/AVIF) because product catalogs are image-heavy.
- Operations: a Dockerfile for repeatable deployment.
The admin console
The admin dashboard is a separate Next.js 14 app built with Shadcn UI (Radix + Tailwind), Redux Toolkit, SWR, React Hook Form with Zod, and Recharts.
- Dashboard: revenue today, new orders, low-stock products, new customers, a seven-day revenue chart and an order-status distribution.
- Products and stock: advanced filters (name, category, status, stock), detailed create/edit forms, and variant ("account") and inventory management.
- Orders: status tracking from pending to completed or cancelled, order details with customer information, and automated or manual processing.
- Users and reviews: customer lists with purchase history, and moderation of reviews.
- Settings: profile and password, SMTP email configuration, site information, a maintenance mode switch for the public site, and theme customization.
How an order really works
The backend models an order as an explicit state machine — pending_payment, paid, processing, completed, cancelled, failed, refunded, plus a family of warranty states. That matters for the frontend: every screen, badge and filter is driven from the same status list instead of ad-hoc strings.
Payment is confirmed without a payment gateway:
- The checkout shows a VietQR code with the amount and an order code as the transfer note.
- A Telegram bot polls the business bank account for new transactions and matches each one to an order using the order code in the transfer description.
- For a match, the bot calls the backend through an internal API-key-protected endpoint to record the payment and advance the order, and operators get a Telegram notification for every new transaction.
- Scheduled jobs expire unpaid orders, remind customers about expiring subscriptions, and track warranty requests against SLAs (auto-assign and escalate).
The admin console listens over Socket.io, so new comments and reviews reach operators without refreshing.
Decisions worth noting
Two frontends instead of one. Keeping the storefront and the admin apart lets each ship independently, keeps admin code out of the public bundle, and allows different UI stacks: the storefront uses Ant Design for its component set, while the admin uses Shadcn UI for a more custom dashboard.
SWR for server state, Redux for client state. Cart, session and preferences live in Redux (persisted); everything that comes from the API goes through SWR so lists stay fresh without hand-written refetch logic.
Operations as a feature. Maintenance mode, configurable SMTP, order templates and log settings live in the admin UI, so changing how the business runs does not require a deploy.
What I took away
Building for a real business changes priorities: reliability of the payment-to-delivery loop and tooling for the operators matter more than visual novelty. Modelling statuses once and reusing them everywhere removed a whole class of "this order looks different on this page" bugs.
