Manual ICO Web » History » Version 1
prin methirattanasophon, 03/17/2026 08:43 AM
| 1 | 1 | prin methirattanasophon | # Manual ICO Web |
|---|---|---|---|
| 2 | |||
| 3 | ICO Web — Project Migration Document |
||
| 4 | |||
| 5 | > **Note:** This project was originally scaffolded using [Vite](https://vitejs.dev/) (`create-vite` with the React/TypeScript template). |
||
| 6 | |||
| 7 | --- |
||
| 8 | |||
| 9 | ## 1. Stack & Prerequisites |
||
| 10 | |||
| 11 | ### Tech Stack |
||
| 12 | |||
| 13 | | Category | Technology | Version | |
||
| 14 | | -------------------- | -------------------- | ------- | |
||
| 15 | | **Language** | TypeScript | ^5.6.3 | |
||
| 16 | | **Framework** | React | ^18.3.1 | |
||
| 17 | | **Build Tool** | Vite | ^6.3.5 | |
||
| 18 | | **Styling** | Tailwind CSS | ^3.4.11 | |
||
| 19 | | **State Management** | Redux Toolkit | ^2.2.7 | |
||
| 20 | | **Routing** | React Router DOM | ^6.26.2 | |
||
| 21 | | **HTTP Client** | Axios | ^1.7.7 | |
||
| 22 | | **Form** | React Hook Form | ^7.53.0 | |
||
| 23 | | **Form Validation** | Zod | ^3.23.8 | |
||
| 24 | | **i18n** | i18next | ^25.7.4 | |
||
| 25 | | **Testing** | Jest | ^29.7.0 | |
||
| 26 | | **Linting** | ESLint | ^8.56.0 | |
||
| 27 | | **Container** | Docker (multi-stage) | — | |
||
| 28 | | **Web Server** | Nginx Alpine | — | |
||
| 29 | |||
| 30 | ### Prerequisites to Install |
||
| 31 | |||
| 32 | | Tool | Version | |
||
| 33 | | ----------- | ------------- | |
||
| 34 | | **Node.js** | LTS (v20/v22) | |
||
| 35 | | **Yarn** | Berry (v2+) | |
||
| 36 | | **Docker** | 20+ | |
||
| 37 | | **Git** | 2.x | |
||
| 38 | |||
| 39 | ### Verify Installations |
||
| 40 | |||
| 41 | ```bash |
||
| 42 | node -v # v20.x or v22.x |
||
| 43 | yarn -v # 4.x (Berry) |
||
| 44 | docker -v # Docker version 20+ |
||
| 45 | git --version # git version 2.x |
||
| 46 | ``` |
||
| 47 | |||
| 48 | --- |
||
| 49 | |||
| 50 | ## 3. Package Manager |
||
| 51 | |||
| 52 | This project uses **Yarn Berry (v2+)** with classic `node_modules` linker. |
||
| 53 | |||
| 54 | ### Configuration |
||
| 55 | |||
| 56 | File: `.yarnrc.yml` |
||
| 57 | |||
| 58 | ```yaml |
||
| 59 | nodeLinker: node-modules |
||
| 60 | ``` |
||
| 61 | |||
| 62 | ### Common Commands |
||
| 63 | |||
| 64 | ```bash |
||
| 65 | # Install all dependencies |
||
| 66 | yarn install |
||
| 67 | |||
| 68 | # Add a new dependency |
||
| 69 | yarn add <package-name> |
||
| 70 | |||
| 71 | # Add a dev dependency |
||
| 72 | yarn add -D <package-name> |
||
| 73 | |||
| 74 | # Remove a dependency |
||
| 75 | yarn remove <package-name> |
||
| 76 | ``` |
||
| 77 | |||
| 78 | ### Scripts (from package.json) |
||
| 79 | |||
| 80 | ```bash |
||
| 81 | yarn dev # Start Vite dev server (http://localhost:5173) |
||
| 82 | yarn build # TypeScript check + production build → /dist |
||
| 83 | yarn test # Run Jest tests |
||
| 84 | ``` |
||
| 85 | |||
| 86 | --- |
||
| 87 | |||
| 88 | ## 4. Project Architecture |
||
| 89 | |||
| 90 | ### 4.1 Routing (React Router DOM v6) |
||
| 91 | |||
| 92 | **Entry:** `src/app/AppRoutes.tsx` |
||
| 93 | |||
| 94 | All routes are defined in a single file using `<Routes>` and `<Route>`. The app wraps routes in a `<HelmetLayout>` for SEO. |
||
| 95 | |||
| 96 | **Route groups:** |
||
| 97 | |||
| 98 | | Group | Path Pattern | Pages | |
||
| 99 | | --------------------- | -------------------- | ------------------------------------------------------------------------------------------ | |
||
| 100 | | **Landing** | `/` | Landing page, investment campaign | |
||
| 101 | | **Auth** | `/login`, `/reset-*` | Login, reset password, Google Authenticator | |
||
| 102 | | **Sign Up (Thai)** | `/signup/*` | Type select → corporate/individual → basicinfo → suite test → OTP → ID card → verification | |
||
| 103 | | **Sign Up (Foreign)** | `/foreign/*` | Fullname → CRS → basicinfo → suite test → doc upload → OTP | |
||
| 104 | | **Re-KYC** | `/rekyc/*` | Re-KYC flow, FATCA, suite test, ID card | |
||
| 105 | | **Portfolio** | `/portfolio` | Portfolio management | |
||
| 106 | | **Marketplace** | `/marketplace` | Marketplace + TOTP confirmation | |
||
| 107 | | **Order & Trade** | `/order-trade` | Order trade + high net worth verification | |
||
| 108 | | **Subscription** | `/subscription` | Subscription page | |
||
| 109 | | **Asset Details** | `/asset-details/*` | ICO details, white paper, filing | |
||
| 110 | | **Fraction Exchange** | `/fraction-exchange` | Fraction exchange | |
||
| 111 | | **Static Pages** | `/contact-us`, etc. | Contact, policy, complaint, about us, terms | |
||
| 112 | | **Error** | `*` | 404 page | |
||
| 113 | |||
| 114 | --- |
||
| 115 | |||
| 116 | ### 4.2 State Management (Redux Toolkit) |
||
| 117 | |||
| 118 | **Store:** `src/redux/store.tsx` |
||
| 119 | |||
| 120 | ``` |
||
| 121 | Redux Store |
||
| 122 | └── redux/ |
||
| 123 | ├── store |
||
| 124 | └── slice/ |
||
| 125 | |||
| 126 | ``` |
||
| 127 | |||
| 128 | --- |
||
| 129 | |||
| 130 | ### 4.3 API Layer (Axios) |
||
| 131 | |||
| 132 | **File:** `src/api/axios.tsx` |
||
| 133 | |||
| 134 | The project creates multiple Axios instances, all with `baseURL: window.origin` (same-origin proxy): |
||
| 135 | |||
| 136 | | Instance | Purpose | Auth | |
||
| 137 | | ----------------- | ---------------------------------- | ------------ | |
||
| 138 | | `default export` | Basic API calls | Cookies only | |
||
| 139 | | `axiosCheckToken` | Authenticated calls with refresh | Auto-refresh | |
||
| 140 | | `axiosMultipart` | File uploads (multipart/form-data) | Cookies | |
||
| 141 | | `axiosNoAuth` | Public API calls | None | |
||
| 142 | |||
| 143 | **Token refresh flow:** |
||
| 144 | |||
| 145 | 1. Request interceptor checks if refresh token is expired → redirect to login if so |
||
| 146 | 2. If access token is expired, calls `POST /api/v1/authen/customers/refresh` |
||
| 147 | 3. Queues failed requests in `failedQueue` while refresh is in-flight |
||
| 148 | 4. On success, retries all queued requests with new token |
||
| 149 | 5. In dev mode (`isDev()`), token checks are skipped entirely |
||
| 150 | |||
| 151 | **Helper:** `getCustomAxios()` returns `axiosCheckToken` if access token exists, otherwise `axiosNoAuth`. |
||
| 152 | |||
| 153 | --- |
||
| 154 | |||
| 155 | ### 4.4 Form Handling |
||
| 156 | |||
| 157 | | Library | Purpose | |
||
| 158 | | ----------------------- | ---------------------------------- | |
||
| 159 | | **React Hook Form** | Form state, validation, submission | |
||
| 160 | | **Zod** | Schema-based validation | |
||
| 161 | | **@hookform/resolvers** | Connects Zod schemas to RHF | |
||
| 162 | |||
| 163 | --- |
||
| 164 | |||
| 165 | ### 4.5 Internationalization (i18n) |
||
| 166 | |||
| 167 | **File:** `src/i18n/config.ts` |
||
| 168 | |||
| 169 | | Setting | Value | |
||
| 170 | | --------------------- | -------------------------------------------------------------- | |
||
| 171 | | **Library** | i18next + react-i18next | |
||
| 172 | | **Languages** | `en` (English), `th` (Thai) | |
||
| 173 | | **Fallback** | `en` | |
||
| 174 | | **Detection** | localStorage → navigator → htmlTag | |
||
| 175 | | **Storage key** | `i18nextLng` (localStorage) | |
||
| 176 | | **Translation files** | `public/locales/{lng}/{ns}.json` | |
||
| 177 | | **Namespaces** | `common`, `footer`, `navbar`, `landing`, `signup`, `portfolio` | |
||
| 178 | | **Default ns** | `common` | |
||
| 179 | |||
| 180 | --- |
||
| 181 | |||
| 182 | ### 4.6 Styling |
||
| 183 | |||
| 184 | | Tool | Config File | Notes | |
||
| 185 | | ------------------ | ---------------------- | ------------------------------------------- | |
||
| 186 | | **Tailwind CSS** | `tailwind.config.js` | Custom font: Anuphan, custom colors/screens | |
||
| 187 | | **CSS** | `src/index.css` | Global styles | |
||
| 188 | | **PostCSS** | `postcss.config.js` | Tailwind + Autoprefixer plugins | |
||
| 189 | | **tailwind-merge** | via `src/lib/utils.ts` | `cn()` helper for conditional classes | |
||
| 190 | |||
| 191 | --- |
||
| 192 | |||
| 193 | ### 4.7 Key Libraries |
||
| 194 | |||
| 195 | | Library | Purpose | |
||
| 196 | | ----------------------- | ---------------------------------------- | |
||
| 197 | | `crypto-js` | Encryption/decryption (with pepper keys) | |
||
| 198 | | `jwt-decode` | Decode JWT tokens (expiry check) | |
||
| 199 | | `js-cookie` | Cookie management (access/refresh token) | |
||
| 200 | | `dayjs` | Date/time formatting (with UTC/timezone) | |
||
| 201 | | `lodash` | Utility functions | |
||
| 202 | | `react-toastify` | Toast notifications | |
||
| 203 | | `react-helmet-async` | SEO meta tags | |
||
| 204 | | `apexcharts` | Interactive charts | |
||
| 205 | | `chart.js` | Canvas-based charts | |
||
| 206 | | `echarts-for-react` | ECharts wrapper | |
||
| 207 | | `@tanstack/react-table` | Headless table | |
||
| 208 | | `swiper` | Carousel/slider | |
||
| 209 | | `leaflet` | Map component | |
||
| 210 | | `filepond` | File upload widget | |
||
| 211 | | `react-webcam` | Webcam capture (ID card/liveness) | |
||
| 212 | |||
| 213 | --- |
||
| 214 | |||
| 215 | ## 5. Environment Variables |
||
| 216 | |||
| 217 | ### 5.1 Variable List |
||
| 218 | |||
| 219 | All variables use the `VITE_` prefix (required by Vite to expose to client-side code): |
||
| 220 | |||
| 221 | | Variable | Required | Description | |
||
| 222 | | --------------------- | -------- | -------------------------------------------- | |
||
| 223 | | `VITE_BASE_URL` | Yes | Backend API base URL | |
||
| 224 | | `VITE_REKOGNITOR_URL` | Yes | AWS Rekognition / face liveness service URL | |
||
| 225 | | `VITE_STATIC_URL` | Yes | Static file server URL (images, documents) | |
||
| 226 | | `VITE_BUCKET_URL` | Yes | S3 bucket URL for uploads/downloads | |
||
| 227 | | `VITE_PEPER` | Yes | Encryption pepper (used with crypto-js) | |
||
| 228 | | `VITE_BACK_REGISTER` | Yes | Backend registration endpoint pepper | |
||
| 229 | | `VITE_CID_PEPER` | Yes | CID (citizen ID) encryption pepper | |
||
| 230 | | `VITE_ENVIRONMENT` | Yes | Environment name: `dev`, `uat`, `production` | |
||
| 231 | |||
| 232 | ### 5.2 Environment Variable Injection (CI/CD Pipeline) |
||
| 233 | |||
| 234 | **Workflow process:** |
||
| 235 | |||
| 236 | 1. **Build Phase:** The React app is built using Vite. During this build use config paceholder `VITE_*` (e.g., the string `"VITE_BASE_URL"` instead of a real URL). |
||
| 237 | 2. **Docker Phase:** The `Dockerfile` packages the built static files and an `entrypoint.sh` script into an Nginx image. |
||
| 238 | 3. **Environment Fetching:** Before the application spins up in the target AWS environment, the real environment values are fetched from an `.env` file securely stored in an **AWS S3 bucket**. |
||
| 239 | 4. **Runtime Injection:** When the Docker container starts, it executes `entrypoint.sh`. This shell script uses `sed` to find all the JS bundles that contain the text placeholders and replaces them with the real environment values fetched from S3. |
||
| 240 | |||
| 241 | This pattern allows the same Docker image to be promoted across Dev, UAT, and Prod environments just by fetching a different `.env` file from S3 |
||
| 242 | |||
| 243 | ### 5.3 Config File |
||
| 244 | |||
| 245 | **File:** `src/config/config.ts` |
||
| 246 | |||
| 247 | This file maps environment variables to exported constants used throughout the app: |
||
| 248 | |||
| 249 | | Constant | Source / Env Var | |
||
| 250 | | --------------------- | --------------------- | |
||
| 251 | | `BASE_URL` | `VITE_BASE_URL` | |
||
| 252 | | `BASE_REKOGNITOR_URL` | `VITE_REKOGNITOR_URL` | |
||
| 253 | | `BASE_STATIC_URL` | `VITE_STATIC_URL` | |
||
| 254 | | `BASE_BUCKET_URL` | `VITE_BUCKET_URL` | |
||
| 255 | | `PEPER` | `VITE_PEPER` | |
||
| 256 | | `PEPER_BACK_REGISTER` | `VITE_BACK_REGISTER` | |
||
| 257 | | `PEPER_CID` | `VITE_CID_PEPER` | |
||
| 258 | | `ENVIRONMENT` | `VITE_ENVIRONMENT` | |