Initial commit: Hedena Gold monorepo.

First commit of the Hedena Gold product (separate from gold-bank): branding, package names, and deploy defaults aligned to the new brand; legacy Henza trees stay local-only via .gitignore.
This commit is contained in:
s.esmaeili
2026-10-15 17:19:10 +03:30
commit ea86636027
1121 changed files with 401065 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+184
View File
@@ -0,0 +1,184 @@
// Ported from legacy henza_admin/src/router/branch.js
//
// Converted MECHANICALLY (see scratchpad/convert_routes.js) so every path, name,
// meta.role, breadcrumb and pageTitle survives byte-exact. Only the component
// imports are rewritten to point at the ported views. As of Batch 15 every
// referenced view exists, so no placeholder is emitted.
//
// NOTE: legacy also shipped 8 router files with zero importers (apps.js,
// category.js, customer.js, dashboard.js, full.js, manage.js, order.js,
// product.js, temp.js). They are dead code and are not ported.
import type { RouteRecordRaw } from 'vue-router';
export const branchRoute: RouteRecordRaw[] = [
{
path: '/',
name: 'main',
redirect: '/dashboard'
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../views/branch/dashboard.vue'),
meta: {
rule: 'admin',
// was 'dashboard.omde' — merged into the single 'dashboard.view' rid
// when the two admin dashboards became one screen.
role: 'dashboard.view'
}
},
{
path: '/products',
name: 'product-list',
component: () => import('../views/apps/product/ProductList.vue'),
meta: {
rule: 'editor',
role: 'product.list',
parent: 'product',
no_scroll: true
}
},
/*{
path: '/order',
redirect: '/order/list',
name: 'order'
},*/
{
path: '/order/list',
component: () => import('../views/order/List.vue'),
meta: {
rule: 'editor',
role: 'order.list',
parent: 'order',
no_scroll: true
}
},
{
path: '/buy/order/list',
component: () => import('../views/branch/buyList.vue'),
meta: {
rule: 'editor',
role: 'order.list',
parent: 'order',
no_scroll: true
}
},
/*{
path: '/buy/order/list',
component: () => import('../views/order/List.vue'),
meta: {
rule: 'editor',
role: 'buy.order.list',
no_scroll: true
}
},*/
{
path: '/order/create',
component: () => import('../views/branch/order/create.vue'),
meta: {
rule: 'editor',
role: 'order.create',
parent: 'order',
no_scroll: true
}
},
/*{
path: '/singleCustomer/edit/:id',
name: 'singleCustomer-edit',
component: () => import('../views/apps/customer/EditSingleCustomer.vue'),
meta: {
rule: 'editor',
role: 'persons.list.client.single.edit',
parent: 'product',
no_scroll: true
}
},*/
{
path: '/singleCustomer/view/:id',
name: 'singleCustomer-view',
component: () => import('../views/customer/single-customer/ViewSingleCustomer.vue'),
meta: {
rule: 'editor',
role: 'persons.list.client.single.view',
parent: 'product',
no_scroll: true
}
},
{
path: '/singleCustomer/edit/:id',
name: 'singleCustomer-edit',
component: () => import('../views/apps/customer/EditSingleCustomer.vue'),
meta: {
rule: 'editor',
role: 'persons.list.client.single.edit',
parent: 'product',
no_scroll: true
}
},
{
path: '/customer/add',
name: 'add-customer',
component: () => import('../views/branch/addCustomer.vue'),
meta: {
role: 'persons.create',
rule: 'editor'
}
},
{
path: '/customer/list',
name: 'list-customer',
component: () => import('../views/customer/single-customer/SingleCustomer.vue'),
meta: {
role: 'persons.list.client.single',
rule: 'editor'
}
},
/*{
path: '/report/order_item',
name: 'report_order_item',
component: () => import('../views/report/order_item.vue'),
meta: {
/!* breadcrumb: [
{title: 'خانه', url: '/'},
{title: 'مشتریان'}
],*!/
//pageTitle: 'لیست مشتریان',
role: 'report.order.item',
rule: 'editor'
}
},*/
/*{
path: '/report/product',
name: 'report_product',
component: () => import('../views/report/product.vue'),
meta: {
role: 'report.product',
rule: 'editor'
}
},*/
/*{
path: '/finical/list',
name: 'finical_list',
component: () => import('../views/finical/list.vue'),
meta: {
/!* breadcrumb: [
{title: 'خانه', url: '/'},
{title: 'مشتریان'}
],*!/
//pageTitle: 'لیست مشتریان',
role: 'document.list',
rule: 'editor'
}
},*/
]
export const fullPageBranchRoute: RouteRecordRaw[] = [ {
path: '/auth/login',
name: 'page-login',
//component: () => import('../views/auth/Login.vue'),
component: () => import('../views/branch/login.vue'),
meta: {
rule: 'editor'
}
},
]
+174
View File
@@ -0,0 +1,174 @@
// Ported from legacy henza_admin/src/router.js
import { createRouter, createWebHistory } from 'vue-router';
import { adminRoute, fullPageAdminRoute } from './admin';
import { branchRoute, fullPageBranchRoute } from './branch';
import { iamAppUrl, isBranch, tokenName, userName } from '../utils';
import { useUsersStore } from '../stores/users';
import api from '../api/client';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
// legacy router.js:17-19
scrollBehavior() {
return { left: 0, top: 0 };
},
routes: [
// ── MAIN LAYOUT ROUTES ──────────────────────────────────────────
{
path: '',
component: () => import('../layouts/main/Main.vue'),
children: isBranch() ? branchRoute : adminRoute,
},
// ── FULL PAGE LAYOUTS ───────────────────────────────────────────
{
path: '',
component: () => import('../layouts/full-page/FullPage.vue'),
children: [
...(isBranch() ? fullPageBranchRoute : fullPageAdminRoute),
{
path: '/auth/iam/callback',
name: 'iam-callback',
component: () => import('../views/auth/IamCallback.vue'),
meta: { rule: 'editor' },
},
{
path: '/pages/error-404',
name: 'page-error-404',
component: () => import('../views/pages/Error404.vue'),
meta: { rule: 'editor' },
},
{
path: '/pages/error-500',
name: 'page-error-500',
component: () => import('../views/pages/Error500.vue'),
meta: { rule: 'editor' },
},
{
path: '/pages/not-authorized',
name: 'page-not-authorized',
component: () => import('../views/pages/NotAuthorized.vue'),
meta: { rule: 'editor' },
},
{
path: '/pages/maintenance',
name: 'page-maintenance',
component: () => import('../views/pages/Maintenance.vue'),
meta: { rule: 'editor' },
},
],
},
// Redirect to 404 page, if no match found
{
path: '/:pathMatch(.*)*',
redirect: '/pages/error-404',
},
],
});
// legacy router.js:147-153
router.afterEach(() => {
const appLoading = document.getElementById('loading-bg');
if (appLoading) {
appLoading.style.display = 'none';
}
});
// legacy router.js:156-161
const noneAuthUrl = ['/auth/login', '/auth/forgot-password', '/auth/register', '/reg', '/auth/iam/callback'];
/**
* Port of legacy getPermission() (router.js:163-182).
*
* Legacy cached the result in a MODULE-SCOPED `let roles`, which was never reset
* on logout — so the next user to sign in on the same tab inherited the previous
* user's permissions (bug #2 in docs/ADMIN-PORT-PLAN.md). Here the cache is the
* Pinia users store, which auth.logout() clears; `roles === undefined` still
* means "not fetched", exactly as legacy's check did.
*/
async function getPermission(): Promise<string[] | undefined> {
const users = useUsersStore();
if (users.roles) return users.roles;
try {
const { data } = await api.get('users/show');
users.getRoles(data.permission);
users.setSuperAdmin(data.is_super_admin);
// Legacy stripped these before caching the user blob.
delete data.permission;
delete data.ut;
if (data.api_token) {
localStorage.setItem(tokenName, data.api_token);
}
localStorage.setItem(userName, JSON.stringify(data));
} catch (e) {
console.log(e);
}
return users.roles;
}
/**
* Port of legacy router.beforeEach (router.js:184-199).
*
* One deliberate change: legacy's final line was a bare
* router.push({path: '/auth/login'})
* with no next() call, which in Vue Router 4 leaves the navigation permanently
* pending and hangs the app (bug #1). Returning the redirect is the Vue Router 4
* equivalent and produces the same user-visible result.
*/
let silentSsoAttempted = false;
function skipSilentSso(): boolean {
if (sessionStorage.getItem('gb_admin_iam_sso_skip') === '1') {
sessionStorage.removeItem('gb_admin_iam_sso_skip');
silentSsoAttempted = true;
return true;
}
return silentSsoAttempted;
}
router.beforeEach(async (to) => {
if (!localStorage.getItem(tokenName) && to.path !== '/auth/iam/callback' && !skipSilentSso()) {
silentSsoAttempted = true;
sessionStorage.removeItem('gb_admin_iam_sso_checked');
try {
const { data } = await api.get('admin/auth/sso/start', {
params: { redirect_uri: iamAppUrl('auth/iam/callback') },
});
if (data?.enabled && data.authorize_url) {
window.location.assign(data.authorize_url);
return false;
}
} catch {
// سامانه هویت برای این پنل تنظیم نشده
}
}
if (noneAuthUrl.indexOf(to.path) > -1) {
return true;
}
const token = localStorage.getItem(tokenName);
if (token) {
const roles = await getPermission();
// The stored token could not be confirmed (API down, still booting, or any
// non-401 failure). Letting the route through here showed the whole panel to
// whoever held a stale token, so fail closed and send them to sign in.
if (!roles) {
return { path: '/auth/login' };
}
if (to.meta?.superAdminOnly && !useUsersStore().isSuperAdmin) {
return { name: 'page-not-authorized' };
}
if (roles && to.meta && to.meta.role && !roles.includes(to.meta.role as string)) {
if (to.name === 'dashboard-ecommerce' || to.name === 'dashboard-analytics') {
return { name: 'my-data' };
}
return { name: 'page-not-authorized' };
}
return true;
}
return { path: '/auth/login' };
});
export default router;