Improve business edit: names/about, and git URL on domain edit.

Replace color/locale in Edit business with EN/FA name and about; show git repo on Edit domain to provision storefront.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-08 14:18:31 +03:30
co-authored by Cursor
parent 7d6c1cd4e3
commit f4a087089e
3 changed files with 84 additions and 88 deletions
+55 -67
View File
@@ -43,17 +43,13 @@ import {
import { listBusinessCategories } from '../services/categoryService' import { listBusinessCategories } from '../services/categoryService'
import { Modal } from '../components/Modal' import { Modal } from '../components/Modal'
import { MultiSelectDropdown } from '../components/MultiSelectDropdown' import { MultiSelectDropdown } from '../components/MultiSelectDropdown'
import { PrimaryColorPicker } from '../components/PrimaryColorPicker'
import { PrimaryColorSwatchControl } from '../components/PrimaryColorSwatchControl' import { PrimaryColorSwatchControl } from '../components/PrimaryColorSwatchControl'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
import { import {
DEFAULT_BUSINESS_PRIMARY_COLOR_ID,
normalizeBusinessPrimaryColorId, normalizeBusinessPrimaryColorId,
type BusinessPrimaryColorId, type BusinessPrimaryColorId,
} from '../utils/businessPrimaryColors' } from '../utils/businessPrimaryColors'
import { import {
getBusinessSettings,
updateBusinessBranding,
updateBusinessDefaultLocale, updateBusinessDefaultLocale,
updateBusinessModules, updateBusinessModules,
updateBusinessPrimaryColor, updateBusinessPrimaryColor,
@@ -215,11 +211,8 @@ export function BusinessesPage() {
const [editOpen, setEditOpen] = useState(false) const [editOpen, setEditOpen] = useState(false)
const [editBusiness, setEditBusiness] = useState<BusinessListItem | null>(null) const [editBusiness, setEditBusiness] = useState<BusinessListItem | null>(null)
const [editName, setEditName] = useState('') const [editName, setEditName] = useState('')
const [editPrimaryColor, setEditPrimaryColor] = useState<BusinessPrimaryColorId>( const [editNameFa, setEditNameFa] = useState('')
DEFAULT_BUSINESS_PRIMARY_COLOR_ID, const [editAbout, setEditAbout] = useState('')
)
const [editDefaultLocale, setEditDefaultLocale] = useState<DashboardLocale>(DEFAULT_LOCALE)
const [editLoadingSettings, setEditLoadingSettings] = useState(false)
const [editSubmitting, setEditSubmitting] = useState(false) const [editSubmitting, setEditSubmitting] = useState(false)
const [editError, setEditError] = useState('') const [editError, setEditError] = useState('')
@@ -370,25 +363,10 @@ export function BusinessesPage() {
function openEdit(b: BusinessListItem) { function openEdit(b: BusinessListItem) {
setEditBusiness(b) setEditBusiness(b)
setEditName(b.name) setEditName(b.name)
setEditPrimaryColor(normalizeBusinessPrimaryColorId(b.primaryColor)) setEditNameFa(b.nameFa ?? '')
setEditDefaultLocale(normalizeDefaultLocale(b.defaultLocale)) setEditAbout(b.about ?? '')
setEditError('') setEditError('')
setEditOpen(true) setEditOpen(true)
setEditLoadingSettings(true)
const controller = new AbortController()
void getBusinessSettings(b.id, controller.signal)
.then((data) => {
setEditPrimaryColor(data.settings.branding.primaryColor)
setEditDefaultLocale(normalizeDefaultLocale(data.settings.branding.defaultLocale))
})
.catch((err) => {
if (isAbortError(err)) return
setEditError(err instanceof ApiError ? err.message : 'Unable to load business theme.')
})
.finally(() => {
if (!controller.signal.aborted) setEditLoadingSettings(false)
})
} }
function goToBusinessUsers(b: BusinessListItem, membership: 'staff' | 'all') { function goToBusinessUsers(b: BusinessListItem, membership: 'staff' | 'all') {
@@ -557,13 +535,17 @@ export function BusinessesPage() {
async function submitEdit() { async function submitEdit() {
if (!editBusiness) return if (!editBusiness) return
const name = editName.trim()
const nameFa = editNameFa.trim()
if (!name || !nameFa) return
setEditSubmitting(true) setEditSubmitting(true)
setEditError('') setEditError('')
try { try {
await updateBusiness(editBusiness.id, { name: editName }) await updateBusiness(editBusiness.id, {
await updateBusinessBranding(editBusiness.id, { name,
primaryColor: editPrimaryColor, nameFa,
defaultLocale: editDefaultLocale, about: editAbout.trim(),
}) })
setData((prev) => { setData((prev) => {
if (!prev) return prev if (!prev) return prev
@@ -573,9 +555,9 @@ export function BusinessesPage() {
item.id === editBusiness.id item.id === editBusiness.id
? { ? {
...item, ...item,
name: editName.trim(), name,
primaryColor: editPrimaryColor, nameFa,
defaultLocale: editDefaultLocale, about: editAbout.trim() || null,
} }
: item, : item,
), ),
@@ -596,7 +578,7 @@ export function BusinessesPage() {
setDomainBusiness(b) setDomainBusiness(b)
setDomainId(b.domainId) setDomainId(b.domainId)
setDomainHost(b.domain ?? '') setDomainHost(b.domain ?? '')
setDomainGitRepoUrl('') setDomainGitRepoUrl(b.gitRepoUrl ?? '')
setDomainError('') setDomainError('')
setDomainOpen(true) setDomainOpen(true)
} }
@@ -736,7 +718,15 @@ export function BusinessesPage() {
setDomainError('') setDomainError('')
try { try {
if (domainId) { if (domainId) {
await updateBusinessDomain(domainBusiness.id, domainId, { host }) const updated = (await updateBusinessDomain(domainBusiness.id, domainId, {
host,
...(gitRepoUrl ? { gitRepoUrl } : {}),
})) as {
id: number
host: string
deploySlug?: string | null
provisionError?: string | null
}
setData((prev) => { setData((prev) => {
if (!prev) return prev if (!prev) return prev
return { return {
@@ -746,7 +736,17 @@ export function BusinessesPage() {
), ),
} }
}) })
if (updated.provisionError) {
showToast(`Domain updated to "${host}", but storefront setup failed.`, 'error')
showToast(updated.provisionError, 'error')
} else if (gitRepoUrl && updated.deploySlug) {
showToast(
`Domain updated. Storefront deploy is ready on Websites.`,
'success',
)
} else {
showToast(`Domain updated to "${host}".`, 'success') showToast(`Domain updated to "${host}".`, 'success')
}
} else { } else {
const created = (await addBusinessDomain(domainBusiness.id, { const created = (await addBusinessDomain(domainBusiness.id, {
host, host,
@@ -1231,45 +1231,37 @@ export function BusinessesPage() {
</p> </p>
) : null} ) : null}
<div className={styles.field}> <div className={styles.field}>
<label htmlFor="edit-name">Business name</label> <label htmlFor="edit-name-en">Name (EN)</label>
<input <input
id="edit-name" id="edit-name-en"
value={editName} value={editName}
onChange={(e) => setEditName(e.target.value)} onChange={(e) => setEditName(e.target.value)}
placeholder="Business name"
disabled={editSubmitting} disabled={editSubmitting}
/> />
</div> </div>
<div className={styles.field}> <div className={styles.field}>
<label>Dashboard primary color</label> <label htmlFor="edit-name-fa">Name (FA)</label>
{editLoadingSettings ? ( <input
<p className={styles.helperText}>Loading theme...</p> id="edit-name-fa"
) : ( className="faText"
<PrimaryColorPicker value={editNameFa}
value={editPrimaryColor} onChange={(e) => setEditNameFa(e.target.value)}
onChange={setEditPrimaryColor} placeholder="نام فارسی"
dir="rtl"
lang="fa"
disabled={editSubmitting} disabled={editSubmitting}
/> />
)}
</div> </div>
<div className={styles.field}> <div className={styles.field}>
<label htmlFor="edit-default-locale">Default dashboard language</label> <label htmlFor="edit-about">About business</label>
{editLoadingSettings ? ( <textarea
<p className={styles.helperText}>Loading language...</p> id="edit-about"
) : ( value={editAbout}
<select onChange={(e) => setEditAbout(e.target.value)}
id="edit-default-locale" placeholder="Short description about the business"
className={styles.localeSelect}
value={editDefaultLocale}
disabled={editSubmitting} disabled={editSubmitting}
onChange={(e) => setEditDefaultLocale(normalizeDefaultLocale(e.target.value))} />
>
<option value="fa">Farsi (FA)</option>
<option value="en">English (EN)</option>
</select>
)}
<p className={styles.helperText}>
Business and customer dashboards open in this language.
</p>
</div> </div>
<div style={{ height: 12 }} /> <div style={{ height: 12 }} />
<div className={styles.actionsRow}> <div className={styles.actionsRow}>
@@ -1285,7 +1277,7 @@ export function BusinessesPage() {
type="button" type="button"
className={`${styles.btn} ${styles.btnPrimary}`} className={`${styles.btn} ${styles.btnPrimary}`}
onClick={() => void submitEdit()} onClick={() => void submitEdit()}
disabled={editSubmitting || !editName.trim() || editLoadingSettings} disabled={editSubmitting || !editName.trim() || !editNameFa.trim()}
> >
Save Save
</button> </button>
@@ -1318,8 +1310,6 @@ export function BusinessesPage() {
disabled={domainSubmitting} disabled={domainSubmitting}
/> />
</div> </div>
{!domainId ? (
<>
<div style={{ height: 12 }} /> <div style={{ height: 12 }} />
<div className={styles.field}> <div className={styles.field}>
<label htmlFor="domain-git-repo">Git repo URL (optional)</label> <label htmlFor="domain-git-repo">Git repo URL (optional)</label>
@@ -1337,8 +1327,6 @@ export function BusinessesPage() {
(dashboards). (dashboards).
</p> </p>
</div> </div>
</>
) : null}
<div style={{ height: 12 }} /> <div style={{ height: 12 }} />
<div className={styles.actionsRow}> <div className={styles.actionsRow}>
<button <button
@@ -19,6 +19,8 @@ export interface ListBusinessesParams {
export interface UpdateBusinessPayload { export interface UpdateBusinessPayload {
name?: string name?: string
nameFa?: string
about?: string
slug?: string slug?: string
oldBusinessId?: number | null oldBusinessId?: number | null
} }
@@ -36,6 +38,8 @@ export interface DisableBusinessPayload {
export interface UpdateDomainPayload { export interface UpdateDomainPayload {
host: string host: string
/** When set, provisions storefront deploy on the websites VM. */
gitRepoUrl?: string
} }
export async function listBusinesses(params: ListBusinessesParams, signal?: AbortSignal) { export async function listBusinesses(params: ListBusinessesParams, signal?: AbortSignal) {
+4
View File
@@ -15,10 +15,14 @@ export interface BusinessDomainInfo {
export interface BusinessListItem { export interface BusinessListItem {
id: string id: string
name: string name: string
nameFa: string | null
about: string | null
createdAt: string createdAt: string
domainId: number | null domainId: number | null
domain: string | null domain: string | null
sslEnabled: boolean | null sslEnabled: boolean | null
gitRepoUrl: string | null
deploySlug: string | null
ownerName: string | null ownerName: string | null
ownerNameEn: string | null ownerNameEn: string | null
ownerCellNumber: string | null ownerCellNumber: string | null