-
Determine the page name and route slug. Page names use PascalCase matching the route slug (e.g. route commission_report → component CommissionReport.vue). Confirm the slug does not already exist by reading src/router/index.ts around the affiliate block (lines ~88-105). Verify no file with that name already exists under src/views/billing/affiliates/ before proceeding.
-
Add locale keys to src/locales/en/affiliate.json. Add a new nested object keyed by <pageName>Page containing at minimum title. Example: "commissionReportPage": { "title": "Commission Report", ... }. Reuse existing keys already present in src/locales/en/affiliate.json and src/locales/en/common.json — do not duplicate them. Verify the JSON parses (no trailing commas) before proceeding.
-
Create the component file under src/views/billing/affiliates/ with a PascalCase name like src/views/billing/affiliates/CommissionReport.vue. Use <script setup lang="ts">. Copy the boilerplate from src/views/billing/affiliates/Faq.vue (static content) or src/views/billing/affiliates/RichReport.vue (API-backed). Required imports:
import { watchEffect } from 'vue';
import { RouterLink } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useSiteStore } from '@/stores/site.store';
For API-backed pages, also import fetchWrapper from @/helpers/fetchWrapper, ref/onMounted from vue, and Swal from 'sweetalert2'. Set up const { t } = useI18n(); const siteStore = useSiteStore(); and (for API pages) const baseUrl = siteStore.getBaseUrl();. Put setPageHeading/setTitle/setBreadcrums inside watchEffect(() => { ... }). Verify the file compiles by running yarn ts.
-
Build the template using Admin-LTE Bootstrap 4 card markup. Wrap content in <div class="row justify-content-center"><div class="col-md-8"> (or col-md-10 for wider tables/graphs — see src/views/billing/affiliates/RichReport.vue). Inside, render a <div class="card"> with card-header (containing <h3 class="card-title py-2"> + icon + title, plus a back link to the affiliate index using <router-link> with btn btn-custom btn-sm and the fa-arrow-left icon) and card-body. Use t('...') for every label — no hard-coded strings.
-
For API-backed pages, fetch in a top-level try/catch block (not inside onMounted). Pattern from src/views/billing/affiliates/RichReport.vue:
try {
Swal.fire({ title: '', html: '<i class="fas fa-spinner fa-pulse"></i> Please wait!', allowOutsideClick: false, showConfirmButton: false });
fetchWrapper.get(`${baseUrl}/affiliate/<endpoint>`).then((response) => {
Swal.close();
table.value = response.text;
});
} catch (error: any) {
Swal.close();
Swal.fire({ icon: 'error', html: `Got error ${error.text}` });
}
Render API HTML responses with v-html="table" on the card-body.
-
Register the route in src/router/index.ts. Add a new entry inside the children: [...] array of the affiliate route block (around line 91-105). Use the format: { path: '<slug>', component: () => import('../views/billing/affiliates/<PageName>.vue') },. Keep entries grouped logically with the existing ones. Do not add a name, meta, or redirect — the parent's meta.i18n cascades. Verify the route resolves by running yarn dev and visiting the new path under the affiliate base.
-
Propagate locale keys to other languages (optional). New keys added to src/locales/en/affiliate.json should ideally be copied to the other 109 locale directories (src/locales/<lang>/affiliate.json). If the user does not request translation, leave a TODO comment in the PR description rather than translating manually.
-
Verify before completion. Run in order:
yarn ts
yarn lint
yarn dev
Confirm page heading, breadcrumb, back link, and rendered content all show correctly.