parity(apps+api): complete_profile in both apps [ref: henza_server Major/CustomerController::complete]
Ports "تکمیل پروفایل" end to end (D22). API - POST customer/me/complete-profile — three required document ids, the customer taken from the token, verification step set to the reference's LIVE value (approved outright; its `= 1` line is commented out at :83 in favour of `= 2` at :85). Both SMS messages byte-identical and non-blocking per D20. - POST customer/me/documents — the reference let the app upload through the same file/create the admin used, inside its auth:api-customer group (routes/api.php:95); the target's copy is admin-guarded, so the page could not have uploaded at all. Reuses AdminUploadsService unchanged, so both paths share one size limit, MIME whitelist and naming scheme (UPL-01). - PROFILE_ADMIN_MOBILES replaces the reference's four hardcoded personal numbers; blank means nobody is notified and the upload still succeeds. Three reference behaviours deliberately NOT ported, all in ISSUES.md: - SEC-14: `$customer->update($request->all())` mass-assigned the whole request body, letting the app write its own credit limit, making charge, gold debt, active flag or mobile. Replaced with a three-field whitelist. - SEC-15: four real personal mobiles hardcoded in the source. - APP-03: refresh()'s unterminated setInterval page reload. Apps (both, template+script byte-identical, per-app styling only) - CompleteProfileView + SingleImageUploader - route /complete_profile, redirecting home once the documents are in, as the reference's guard did (router/index.ts:266) - the entry point on the profile screen, shown only while they are missing (profile.vue:96-102) Also (D23): JWT_CUSTOMER_SECRET now warns loudly at boot when unset. It falls back to JWT_SECRET, which signs app and admin tokens with the same key and leaves the `typ` check as the only barrier — and an expert's app token carries their AdminUser id in `sub`. Warn rather than refuse to boot (D19, lowest risk); DELIVERY.md lists it as the first pre-release step (REL-03). Verified: build 5/5 · jest 45/45 · complete-profile 38/38 (new) · expert login 36/36 · apps smoke 43/43 · admin smoke 43/43 · B2B scan 34 ok/0 broken · typecheck clean on all four apps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f71fd5bfe1
commit
5ad1083aee
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { useSessionStore } from '../stores/session';
|
||||
import { useNavStore } from '../stores/navStore';
|
||||
import { useTransitionStore } from '../stores/transitionStore';
|
||||
import { api } from '../api/client';
|
||||
|
||||
// Legacy lazy-loaded every route component (`component: () => import(...)`) so
|
||||
// the initial bundle only carries the entry route. Matched here.
|
||||
@@ -52,6 +53,15 @@ export const router = createRouter({
|
||||
component: () => import('../views/expert/CustomerChoiceView.vue'),
|
||||
meta: { title: 'انتخاب مشتری', layout: 'FullPage', requiresAuth: true, requiresExpert: true },
|
||||
},
|
||||
// Reference path and name kept (henza_pwa router/index.ts:208-215). The
|
||||
// guard below mirrors the reference's: once the documents are in, the page
|
||||
// redirects home (henza_pwa router/index.ts:266).
|
||||
{
|
||||
path: '/complete_profile',
|
||||
name: 'completeProfile',
|
||||
component: () => import('../views/user/CompleteProfileView.vue'),
|
||||
meta: { title: 'تکمیل پروفایل', layout: 'FullPage', requiresAuth: true, requiresIncompleteProfile: true },
|
||||
},
|
||||
{ path: '/notifications', name: 'notifications', component: () => import('../views/user/NotificationsView.vue') },
|
||||
{ path: '/search', name: 'search', component: () => import('../views/search/SearchView.vue') },
|
||||
{ path: '/collection/:id?', name: 'collection', component: () => import('../views/search/CollectionView.vue') },
|
||||
@@ -65,7 +75,7 @@ export const router = createRouter({
|
||||
// user away from /login, and blocks unauthenticated access to routes that
|
||||
// require a session (legacy blocked ordersList/transactionsList/profileData/
|
||||
// favoritesList/completeProfile the same way).
|
||||
router.beforeEach((to) => {
|
||||
router.beforeEach(async (to) => {
|
||||
const session = useSessionStore();
|
||||
const nav = useNavStore();
|
||||
|
||||
@@ -83,6 +93,23 @@ router.beforeEach((to) => {
|
||||
return { name: 'userPage::hasSubRoute' };
|
||||
}
|
||||
|
||||
// Ported from henza_pwa router/index.ts:266 — once the documents are in,
|
||||
// /complete_profile is not reachable any more and redirects home. The
|
||||
// reference read its cached store; this asks the server, so a profile
|
||||
// completed on another device is respected too. A failed lookup lets the
|
||||
// navigation through: the page itself is harmless, and the server is the
|
||||
// one that decides whether the write is allowed.
|
||||
if (to.meta.requiresIncompleteProfile && session.isLoggedIn) {
|
||||
try {
|
||||
const me = await api.getMe();
|
||||
if (me.verificationStep === 'VERIFIED' || me.verificationStep === 'STEP_TWO_SUBMITTED') {
|
||||
return { name: 'home' };
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
// legacy router/index.ts:268-270 — the product page slides down into view.
|
||||
if (to.name === 'singleProduct') nav.setPgaTransition('slideDown');
|
||||
else nav.setPgaTransition(undefined);
|
||||
|
||||
Reference in New Issue
Block a user