Move e-payment to Settings and add ZarinPal credentials.

Gateway config now lives on the general Settings page, with Mellat fields in a 3-column layout and ZarinPal available at checkout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-16 01:45:41 +03:30
co-authored by Cursor
parent 9a59bc8f65
commit e79211e3cd
35 changed files with 614 additions and 212 deletions
@@ -82,7 +82,7 @@
display: flex;
align-items: center;
gap: 12px;
padding: 6px 12px 6px 6px;
padding: 8px 16px;
border-radius: 50px;
background: rgba(255, 255, 255, 0.45);
backdrop-filter: blur(12px);
@@ -94,6 +94,7 @@
.profileInfo {
display: flex;
flex-direction: column;
padding: 0 4px;
}
.name {
+5
View File
@@ -164,3 +164,8 @@ textarea::placeholder {
font-family: var(--font-ui);
font-weight: 400;
}
body:has([aria-modal='true']) #root {
filter: blur(var(--modal-overlay-blur, 16px));
pointer-events: none;
}
@@ -1,6 +1,11 @@
.colName,
.colRole {
padding-inline: 22px;
}
.roleBadge {
display: inline-block;
padding: 3px 8px;
padding: 3px 12px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
@@ -9,6 +14,23 @@
border: 1px solid rgba(var(--primary-rgb) / 0.18);
}
.shieldSuperAdmin {
color: #0f172a;
}
.shieldSuperAdmin:hover:not(:disabled) {
color: #0f172a;
background: rgba(15, 23, 42, 0.08);
}
.shieldOwner {
color: var(--primary);
}
.shieldOwner:hover:not(:disabled) {
color: var(--primary);
}
.inactiveRow {
opacity: 0.55;
}
+31 -6
View File
@@ -41,6 +41,18 @@ function displayName(user: UserListItem) {
return name || '—'
}
function isSuperAdminUser(user: UserListItem) {
return user.roleSlug === 'super_admin' || /\bsuper admin\b/i.test(user.roles ?? '')
}
function isBusinessOwnerUser(user: UserListItem) {
return (
user.roleSlug === 'business_owner' ||
Boolean(user.isBusinessOwner) ||
/\bbusiness owner\b/i.test(user.roles ?? '')
)
}
const TEAM_ROLE_ORDER = ['admin', 'editor', 'viewer'] as const
const TEAM_ROLE_LABELS: Record<string, string> = {
@@ -644,9 +656,9 @@ export function UsersPage() {
<table className={tableStyles.table}>
<thead>
<tr>
<th className={tableStyles.th}>Name</th>
<th className={`${tableStyles.th} ${styles.colName}`}>Name</th>
<th className={tableStyles.th}>Cell number</th>
<th className={tableStyles.th}>Role</th>
<th className={`${tableStyles.th} ${styles.colRole}`}>Role</th>
<th className={tableStyles.th}>Business</th>
<th className={tableStyles.th}>Date joined</th>
<th className={`${tableStyles.th} ${tableStyles.thActions}`}>Actions</th>
@@ -672,12 +684,12 @@ export function UsersPage() {
{!loading &&
data?.items?.map((user) => (
<tr key={user.id} className={!user.isActive ? styles.inactiveRow : undefined}>
<td className={tableStyles.td}>
<td className={`${tableStyles.td} ${styles.colName}`}>
<div>{displayName(user)}</div>
{!user.isActive && <div className={styles.statusInactive}>Inactive</div>}
</td>
<td className={tableStyles.td}>{formatCellForDisplay(user.cellNumber)}</td>
<td className={tableStyles.td}>
<td className={`${tableStyles.td} ${styles.colRole}`}>
{(() => {
const label = businessFilter
? formatBusinessUserRoleLabel(user, teamRoles)
@@ -697,7 +709,13 @@ export function UsersPage() {
<div className={tableStyles.rowActions}>
<button
type="button"
className={tableStyles.controlBtn}
className={`${tableStyles.controlBtn} ${
isSuperAdminUser(user)
? styles.shieldSuperAdmin
: isBusinessOwnerUser(user)
? styles.shieldOwner
: ''
}`}
onClick={() => openRoleChange(user)}
title={
businessFilter && user.isBusinessOwner
@@ -711,7 +729,14 @@ export function UsersPage() {
}
disabled={!user.isActive || Boolean(businessFilter && user.isBusinessOwner)}
>
<Shield size={16} />
<Shield
size={16}
fill={
isSuperAdminUser(user) || isBusinessOwnerUser(user)
? 'currentColor'
: 'none'
}
/>
</button>
<button
type="button"
@@ -52,7 +52,7 @@
}
.deployStarted {
color: #b45309;
color: #0f172a;
}
.deployFail {