ICON Documentation
ICON is a complete, production-grade e-commerce platform: one Flutter app for iOS & Android, a NestJS REST API, and a Next.js admin panel — all sharing a single typed contract. Thank you for your purchase!
Everything is included — no external system to buy or host. Unlike app-only templates that need a separate, third-party backend or CMS to function, ICON ships its own complete backend (REST API, database schema, migrations and seed data) and its own admin panel. Clone it, add your keys, and run — front to back, it's one self-contained product you fully own.
How the pieces fit together
Flutter app ───► REST API ───► PostgreSQL
(iOS/Android) (/api/v1) (your data)
▲
Next.js admin ───────┘ ← runs your store from a browser
The app and the admin both talk to the same API under /api/v1. That API is the only contract
between clients and the server, and it is documented as committed openapi.yaml /
openapi.json and a Postman collection.
Tech stack
What's in the package
The download is a pnpm monorepo. Each part is independent but speaks the same API contract.
| Folder | What it is |
|---|---|
app/ | The Flutter customer app (iOS & Android), built with Riverpod and go_router. |
server/ | The NestJS REST API + Prisma schema, database migrations and seed data. |
admin/ | The Next.js admin panel you run your store from. |
packages/shared/ | Shared TypeScript types + Zod schemas — one source of truth for the API contract. |
openapi.yaml / .json | The generated OpenAPI 3.1 spec (the API contract of record). |
postman_collection.json | Ready-to-import Postman collection for trying the API. |
docker-compose.yml | One-command Postgres + API stack. |
.env.example | Every environment variable, documented and ready to copy. |
README.md · RELEASE.md · KNOWN_GAPS.md | Setup, store-submission checklist, and tracked follow-ups. |
Requirements
You only need these to build and run the project locally. Docker is optional — it just makes the database one command.
| Tool | Version | Used for |
|---|---|---|
| Node.js | ≥ 20 | API, admin, shared package |
| pnpm | ≥ 9 | Workspace package manager (npm i -g pnpm) |
| Flutter | ≥ 3.29 (Dart 3) | The mobile app |
| PostgreSQL | 16 | Database (via Docker or Homebrew) |
| Redis | 7 | Cache / queues (dev infra) |
| Xcode | latest | iOS builds (macOS only) |
| JDK | 17 | Android builds |
No Postgres installed? Run docker compose up --build to start the database and API together —
migrations apply automatically on first boot.
Quick start
From an empty machine to a running store and app in a few minutes.
-
Install dependencies
From the project root, install all JavaScript workspaces (API, admin, shared) at once.
pnpm install -
Start the database
Use Docker, or your local Postgres.
# Option A — Docker pnpm db:up # Option B — Homebrew Postgres (no Docker) brew services start postgresql@16 createdb icon # first time only -
Create your environment file
Copy the example. The server reads its own
server/.env.cp .env.example .env cp .env.example server/.envThe defaults run out of the box. See Configuration for going live.
-
Set up the database schema & demo data
pnpm --filter @icon/server prisma:migrate pnpm --filter @icon/server seedThe seed loads 4 brands, 4 categories, 12 products and 2 coupons, plus two logins (password
Password123!):admin@iconcommerce.app(admin) andmaya@example.com(shopper). -
Run the API
pnpm dev:server # http://localhost:3000/api/v1 # health check at /api/v1/health -
Run the admin panel
pnpm dev:admin # http://localhost:3001Sign in with
admin@iconcommerce.app/Password123!. -
Run the app
The app defaults to mock mode — a built-in in-memory backend, so it demos with zero server. To see live data from your API and admin edits, run in live mode:
cd app flutter pub get # Live mode — talks to your API flutter run --dart-define=USE_MOCK=false \ --dart-define=API_BASE_URL=http://localhost:3000/api/v1
Mock vs live. In mock mode a corner “DEMO DATA” ribbon appears and changes made in
the admin will not show in the app — because the app never contacts the server. Run with
USE_MOCK=false and an API_BASE_URL to connect to your backend.
Run the whole API stack in Docker
docker compose up --build # Postgres + API on http://localhost:3000/api/v1
Make it yours — Rebranding
Turn ICON into your brand in minutes. Each step below gives the exact file and the exact line to change — no guesswork.
Your store name, currencies and home-screen layout are edited with no code at all in Admin → Settings. The five steps here change the app's built-in identity (colour, name, font, icon, server) for the version you publish to the stores.
-
1. Change the brand / accent colour
One accent colour drives buttons, links, badges and highlights across the whole product. Change it in the app and in the admin so they match.
Light & dark use different shades. The app picks a slightly lighter accent in dark mode for contrast, so you must change two values — otherwise the colour won't appear to change in the mode you're testing. (If you only edited
accent600and saw no change, you were almost certainly in dark mode.)App — file
app/lib/design_system/tokens.dart. Replace the hex on both lines (keep the0xFFprefix, then your 6-digit colour):// LIGHT mode accent — line 22 static const Color accent600 = Color(0xFF059669); // was 0xFF4F46E5 // DARK mode accent — line 21 (use the same colour, or a slightly lighter shade) static const Color accent400 = Color(0xFF34D399); // was 0xFF818CF8Then do a full restart of the app (stop it and run again) — colour constants don't always update on hot-reload.
Admin — file
admin/app/globals.css(line 3). Use your main colour::root { --accent: #059669; /* was #4f46e5 */Want one uniform accent everywhere? Set
accent400andaccent600to the same hex. For a pixel-perfect rebrand,tokens.dartalso has the full scale (accent50…accent900) used for hover/pressed shades — replace those too. -
2. Change the app name
This is the name shown under the icon on the phone's home screen.
Android — file
app/android/app/src/main/AndroidManifest.xml(line 3):android:label="Your Store" <!-- was: android:label="icon" -->iOS — file
app/ios/Runner/Info.plist, the value under theCFBundleDisplayNamekey:<key>CFBundleDisplayName</key> <string>Your Store</string> <!-- was: Icon -->Optional — the title shown inside the app's top bar comes from your store name in Admin → Settings (no code). To change the built-in fallback, edit
"appTitle"(line 3) inapp/lib/l10n/app_en.arb(and theapp_es.arb/app_ar.arbcopies). -
3. Change the app font
One line restyles the typography everywhere. File
app/lib/design_system/tokens.dart(line 213):static const String fontFamily = 'Poppins'; // was 'Manrope'If your font is not a built-in system font, bundle it: drop the
.ttffiles intoapp/assets/fonts/and declare them inapp/pubspec.yamlunderfonts:(a commented template is already there, around line 114):flutter: fonts: - family: Poppins fonts: - asset: assets/fonts/Poppins-Regular.ttf - asset: assets/fonts/Poppins-Bold.ttf weight: 700Then run
cd app && flutter pub get. -
4. Replace the app logo & launcher icon
Drop in two images, then regenerate every iOS/Android size automatically with
flutter_launcher_icons(already configured).Replace these files with your artwork (keep the same names):
app/assets/branding/app_icon.png— your square app icon, 1024×1024.app/assets/branding/splash_logo.png— your splash-screen logo.
The generator config lives in
app/pubspec.yaml— set the icon background to your brand colour:flutter_launcher_icons: image_path: assets/branding/app_icon.png background_color_ios: "#059669" # your brand colourRegenerate the native icons and splash:
cd app dart run flutter_launcher_icons dart run flutter_native_splash:createNo artwork yet? ICON can generate placeholder branding for you: edit the colours/shape in
app/tool/generate_icon.dart, rundart run tool/generate_icon.dart, then the two commands above. -
5. Set the API base URL
Point the published app at your live server (instead of
localhost).Recommended — set it at build time (no code change), and turn off demo mode:
flutter run --dart-define=USE_MOCK=false \ --dart-define=API_BASE_URL=https://api.yourdomain.com/api/v1Or change the built-in default — file
app/lib/core/env.dart(line 15):static const String apiBaseUrl = String.fromEnvironment( 'API_BASE_URL', defaultValue: 'https://api.yourdomain.com/api/v1', // was http://localhost:3000/api/v1 );Remember to ship with
USE_MOCK=falseso the app uses your real backend — see Quick start.
That's the full rebrand. Colour, name, font, icon and server — five files, a few minutes, and the app is yours. For store-submission specifics (signing, bundle id) continue to Building for release.
Configuration — environment & keys
All server configuration lives in server/.env (copied from
.env.example). Secrets stay on the server and are never committed or bundled into the app — the
app fetches only its one public key (the Stripe publishable key) at runtime from GET /config.
Core settings
| Variable | What it does |
|---|---|
DATABASE_URL | Postgres connection string. |
JWT_ACCESS_SECRETJWT_REFRESH_SECRET | Sign-in tokens. Use strong, distinct values — generate with openssl rand -base64 48. |
JWT_ACCESS_TTL / JWT_REFRESH_TTL | Token lifetimes (e.g. 15m / 30d). |
CORS_ORIGINS | Comma-separated list of origins allowed to call the API (your admin URL goes here). |
PORT · API_PREFIX · NODE_ENV | Server port, API path prefix, and environment. |
Safe by default. Payments, social login and email all ship fully coded and run in a simulated mode until you add credentials — so the demo works end-to-end out of the box. Adding a key switches that feature to the real provider with no code changes.
Payments
ICON supports card payments (Stripe), PayPal, a Buy-Now-Pay-Later option, and Cash on Delivery. Merchants can turn each method on or off from the admin. Card payments via Stripe are fully integrated:
server/.env key | Where to get it | Status |
|---|---|---|
STRIPE_SECRET_KEY | Stripe dashboard → Developers → API keys (sk_test_…) | Real when set |
STRIPE_PUBLISHABLE_KEY | Stripe dashboard → API keys (pk_test_…) | Served to the app |
STRIPE_WEBHOOK_SECRET | Stripe → Webhooks → Signing secret (whsec_…) | Confirms payment |
- Webhook: point Stripe at
https://YOUR_API_HOST/api/v1/payments/webhook/stripeand subscribe topayment_intent.succeededandpayment_intent.payment_failed. - No key? Checkout returns a simulated payment so orders still flow end-to-end for demos. With a key, the app opens the real native Payment Sheet and the webhook marks the order paid.
- PayPal & BNPL ship as documented redirect-flow stubs behind the same payment interface — drop in your provider's call to go live. Cash on Delivery needs no keys and works out of the box.
Social login
Google, Apple and Facebook sign-in are coded against POST /api/v1/auth/oauth, which verifies the
provider token server-side and links (or creates) a passwordless account. Each button stays visible but returns
“Sign in with X is not configured” until both the env value and the matching native
client config are in place.
server/.env key | Provider console |
|---|---|
GOOGLE_CLIENT_IDS | Google Cloud Console → OAuth client IDs (comma-separate iOS/Android/Web) |
APPLE_CLIENT_IDS | Apple Developer → your Bundle ID / Services ID |
FACEBOOK_APP_ID · FACEBOOK_APP_SECRET | Meta for Developers → app → Settings → Basic |
The per-platform native steps (Info.plist / manifest snippets) are in RELEASE.md under
Buyer setup.
Email — password reset & marketing
Transactional and marketing email run through one provider seam.
| Variable | What it does |
|---|---|
RESEND_API_KEY | Empty → emails are logged, not sent (the reset & marketing flows still work end-to-end in dev). Add the key to send for real. Add to go live |
EMAIL_FROM | The “from” name and address on outgoing mail. |
APP_WEB_URL | The host of the web page the password-reset link points at (/reset-password?token=…). |
The provider call lives in one method (EmailService) — swap Resend for SendGrid, SES, Postmark,
etc. without touching the callers.
Admin panel guide
Run your whole store from the browser at http://localhost:3001. Sign in with a staff or admin account (shopper accounts are rejected). The admin opens on a Dashboard that explains, in one line each, what every section is for — and forms include plain-language helper text so a non-technical owner can use it confidently.
Dashboard
Store totals (products, orders, customers, revenue) and a guide to each section.
Products
Add & edit items — photos, description, status (draft / active / archived), and variants with their own SKU, price, compare-at price and stock.
Categories
Group products so shoppers can browse. New categories appear in the app automatically.
Brands
The makers/labels you tag products with.
Orders
Every order with a full detail view (items, address, totals). Advance the status — cancelling or refunding restocks the items and reverses the payment.
Customers
Who signed up or ordered, with lifetime spend; promote staff/admin roles.
Coupons
Percent or fixed-amount discount codes, optional minimum-subtotal, with an active toggle.
Marketing
Send a promotional email to customers who opted in.
Settings
Store name, supported currencies & exchange rates, and the app's home-screen layout.
Audit log
A history of changes made in the admin.
App features
The Flutter app is a complete storefront for iOS and Android.
Browse & discover
- Home — a merchandisable home screen (hero banner, featured & deals rails) plus an automatic “New arrivals” rail, a “Shop by category” rail and an “Explore the collection” grid.
- Badges — a NEW badge on recently-added products and a discount badge (e.g. -17%) on sale items.
- Search, Shop all & category pages with sorting and an in-stock filter.
- Product detail — image gallery, optional product video, variant picker, ratings & reviews, wishlist, and product comparison (up to four items).
Cart & checkout
- Cart with quantity controls and coupon-code entry.
- Shipping address form with country and state/region dropdowns.
- Choose a payment method — card / Apple Pay / Google Pay (Stripe), PayPal, Buy-Now-Pay-Later, or Cash on Delivery (whichever the merchant has enabled).
Account & orders
- Sign in / register, social login, and password reset.
- Order history with tracking, and customer-initiated cancellation (restocks & refunds).
- Address book, wishlist, and a notifications inbox.
- Language & currency switchers, and privacy controls (export your data, delete your account).
Multi-currency, RTL & languages
Currencies
USD, EUR and GBP. Exchange rates are set in the admin, and shoppers switch currency in-app.
Languages
Fully localized in English, Spanish and Arabic.
Right-to-left
Complete RTL layout support for Arabic — the whole UI mirrors correctly.
Money is stored as integer minor units with an ISO currency code, so totals are always exact. To add a language, add a translation file (ARB) and regenerate localizations; to add a currency, extend the supported set and provide a rate in Settings.
Building for release
Follow RELEASE.md for the complete, top-to-bottom store-submission checklist. The essentials:
Pre-flight gates
pnpm --filter @icon/shared build && pnpm --filter @icon/shared test
pnpm --filter @icon/server lint && pnpm --filter @icon/server typecheck && pnpm --filter @icon/server test
pnpm --filter @icon/admin lint && pnpm --filter @icon/admin typecheck && pnpm --filter @icon/admin build
cd app && flutter analyze && flutter test
iOS — App Store
- After any clone/pull, run
app/tool/ios_dev_setup.sh, thenflutter pub get. - In Xcode, set the Release signing team for the bundle id
app.iconcommerce.icon. - Point the app at your production API and do not ship
USE_MOCK=true. - Build and upload:
flutter build ipa --release
Android — Google Play
- Configure Play App Signing; the
applicationIdisapp.iconcommerce.icon. - Build the app bundle:
flutter build appbundle --release - Complete the Play Console listing (data-safety form, content rating, screenshots) and roll out.
The app icon and splash are generated from app/tool/generate_icon.dart; replace the source
artwork (or edit the colours) and re-run the launcher-icon and splash generators to rebrand.
Troubleshooting
The app shows a “DEMO DATA” ribbon and my admin edits don't appear
You're in mock mode. Run the app with --dart-define=USE_MOCK=false and
--dart-define=API_BASE_URL=http://localhost:3000/api/v1 to use your live backend.
iOS build fails with “Undefined symbol: _Flutter…”
Stale build state after a clone/pull/merge. Run app/tool/ios_dev_setup.sh, then
flutter pub get, and rebuild.
The admin can't reach the API / CORS errors in the browser console
Add your admin origin (e.g. http://localhost:3001) to CORS_ORIGINS in
server/.env and restart the API.
Database connection errors on boot
Check that Postgres is running and DATABASE_URL is correct. With Homebrew, create the database
once with createdb icon. With Docker, use pnpm db:up or
docker compose up --build.
Payments aren't charging a real card
With no STRIPE_SECRET_KEY, checkout runs in simulated mode. Add your Stripe keys and register the
webhook to take real payments.
Password-reset / marketing emails aren't arriving
With no RESEND_API_KEY, emails are logged to the server console instead of sent. Add the key to
deliver real mail.
Signed out of the admin unexpectedly
Access tokens are short-lived and refresh automatically; if the refresh token has expired, just sign in again.
Changelog
v1.0.0 — Initial release
- Flutter app (iOS & Android): catalog, search, product detail with video & reviews, wishlist, comparison, cart with coupons, multi-method checkout, orders with cancellation, account & notifications.
- NestJS REST API with PostgreSQL, JWT auth with refresh-token rotation, and a documented OpenAPI 3.1 contract.
- Next.js admin panel: products, categories, brands, orders (with detail), customers, coupons, marketing email, settings and an audit log.
- Payments: Stripe (card / Apple Pay / Google Pay), PayPal & BNPL seams, and Cash on Delivery.
- Social login (Google / Apple / Facebook) and email/password with password reset.
- Multi-currency (USD/EUR/GBP), three languages (English / Spanish / Arabic) and full RTL.
- Docker compose stack, seed data, and a store-submission checklist.
Support
If something isn't covered here, these files in the package go deeper:
README.md— project overview and developer commands.RELEASE.md— full store-submission & buyer-setup checklist.SECURITY.md·PRIVACY.md— the policies the code enforces.KNOWN_GAPS.md— tracked, deferred follow-ups.openapi.yaml+postman_collection.json— try the API directly.
For purchase-related questions, please reach out through your CodeCanyon item page (the Comments tab) or the author's profile. Include your environment (OS, Node, Flutter versions) and the exact command and error so we can help quickly.
Thank you for choosing ICON. We hope it ships your store fast. ⭐