tenant profile

This commit is contained in:
2026-09-02 12:29:17 +05:30
parent 52be0e30ac
commit d22b79e109
7 changed files with 651 additions and 221 deletions

View File

@@ -23,19 +23,16 @@
*/
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@astryxdesign/core/Button';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Card } from '@astryxdesign/core/Card';
import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text';
import { VStack } from '@astryxdesign/core/VStack';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { errorMessage } from '@/api/client';
import { tenantsApi } from '@/api/tenants';
import { useAuth } from '@/auth/AuthContext';
import { PageBody } from '@/components/PageBody';
import { useBranchScope } from '@/features/store-admin/BranchScope';
import { useLocationProducts, useOwnTenant } from '@/queries/hooks';
import { StepFrame } from './StepFrame';
import { Stepper } from './Stepper';
import {
completeStep,
@@ -90,6 +87,7 @@ export function OnboardingPage() {
const shop = useOwnTenant(tenantid || undefined);
const products = useLocationProducts(tenantid || undefined, undefined, 0, { allBranches: true });
const [params, setParams] = useSearchParams();
const [state, setState] = useState<OnboardingState>(() => readOnboarding(userid, tenantid));
const [errors, setErrors] = useState<Errors>({});
const [saving, setSaving] = useState(false);
@@ -122,6 +120,29 @@ export function OnboardingPage() {
});
}, [shop.data]);
/**
* Coming back from the errand, one step further on.
*
* A merchant sent to Inventory to add products used to have no way back into
* setup but the browser's Back button — which returns them to the step they
* already finished, so setup looked stuck. `SetupReturnBar` sends them here
* with `?advance=<step>` instead, and this is the half that honours it.
*
* The parameter is consumed as it is read. Left in the URL it would re-fire on
* every render and on a refresh, pushing somebody through steps they never
* looked at.
*/
useEffect(() => {
const advance = params.get('advance') as StepId | null;
if (!advance) return;
const carry = new URLSearchParams(params);
carry.delete('advance');
setParams(carry, { replace: true });
setState((prev) =>
writeOnboarding(userid, tenantid, completeStep(prev, advance, nextOf(advance))),
);
}, [params, setParams, userid, tenantid]);
const productCount = (products.data ?? []).length;
const step = resumeStep(state);
const { done, total } = progressOf(state);
@@ -215,25 +236,7 @@ export function OnboardingPage() {
return (
<PageBody measure="reading">
{!isFirst && !isLast ? (
<VStack gap={3}>
<VStack gap={0.5}>
<Text
type="display-1"
weight="semibold"
style={{ fontFamily: 'var(--font-display)', textWrap: 'balance' }}
>
{heading.title}
</Text>
{heading.blurb ? (
<Text type="body" color="secondary">
{heading.blurb}
</Text>
) : null}
</VStack>
<Stepper current={step} completed={state.completed} />
</VStack>
) : null}
{!isFirst && !isLast ? <Stepper current={step} completed={state.completed} /> : null}
{step === 'welcome' ? (
<WelcomeStep
@@ -245,6 +248,19 @@ export function OnboardingPage() {
/>
) : null}
{!isFirst && !isLast ? (
<StepFrame
step={step}
title={heading.title}
{...(heading.blurb ? { blurb: heading.blurb } : {})}
continueLabel={step === 'review' ? 'Complete setup' : 'Save & continue'}
isSaving={saving}
canSkip={canSkip}
onBack={() => goTo(backOf(step))}
{...(canSkip ? { onSkip: () => persist(skipStep(state, step, nextOf(step))) } : {})}
onContinue={() => void saveAndContinue()}
onExit={() => navigate('/admin/console')}
>
{step === 'store' ? (
<StoreInfoStep
value={state.store}
@@ -256,15 +272,18 @@ export function OnboardingPage() {
{step === 'catalogue' ? (
<CatalogueStep
productCount={productCount}
onUpload={() => navigate('/admin/inventory?tab=products&upload=1')}
onManual={() => navigate('/admin/inventory')}
onUpload={() => navigate('/admin/inventory?tab=products&upload=1&setup=catalogue')}
/* The catalogue tab, not the products list. This pointed at
`/admin/inventory` bare, which lands on Products — so "import from
the catalogue" showed a merchant their own empty product list. */
onManual={() => navigate('/admin/inventory?tab=catalogue&setup=catalogue')}
/>
) : null}
{step === 'inventory' ? (
<InventoryStep
onDownloadTemplate={() => navigate('/admin/inventory')}
onUpload={() => navigate('/admin/inventory')}
onDownloadTemplate={() => navigate('/admin/inventory?tab=stock&setup=inventory')}
onUpload={() => navigate('/admin/inventory?tab=products&upload=1&setup=inventory')}
/>
) : null}
@@ -288,57 +307,27 @@ export function OnboardingPage() {
/>
) : null}
{step === 'done' ? (
<DoneStep
productCount={productCount}
onCatalogue={() => navigate('/admin/inventory')}
onInventory={() => navigate('/admin/inventory')}
onStorefront={() => navigate('/admin/inventory')}
onDashboard={() => navigate('/admin/console')}
/>
) : null}
{saveError ? (
<Card padding={3} variant="transparent">
<Card padding={2} variant="transparent">
<Text type="body" size="sm" role="alert" style={{ color: 'var(--color-error, #d64545)' }}>
{saveError}
</Text>
</Card>
) : null}
{/* Footer navigation. Back is quiet, Continue is the one action, and Skip
sits between them so it is available without competing. */}
{!isFirst && !isLast ? (
<HStack justify="between" align="center" gap={2} wrap="wrap" style={{ paddingTop: 4 }}>
<Button
label="Back"
variant="ghost"
icon={<ArrowLeft size={14} />}
isDisabled={saving}
onClick={() => goTo(backOf(step))}
/>
<HStack gap={1.5} align="center" wrap="wrap">
{canSkip ? (
<Button
label="Skip for now"
variant="ghost"
isDisabled={saving}
onClick={() => persist(skipStep(state, step, nextOf(step)))}
/>
) : null}
<Button
label={
saving ? 'Saving…' : step === 'review' ? 'Complete setup' : 'Save & continue'
}
variant="primary"
isLoading={saving}
isDisabled={saving}
endContent={<ArrowRight size={14} />}
onClick={() => void saveAndContinue()}
/>
</HStack>
</HStack>
</StepFrame>
) : null}
{step === 'done' ? (
<DoneStep
productCount={productCount}
onCatalogue={() => navigate('/admin/inventory?tab=catalogue')}
onInventory={() => navigate('/admin/inventory?tab=stock')}
onStorefront={() => navigate('/admin/inventory?tab=products')}
onDashboard={() => navigate('/admin/console')}
/>
) : null}
</PageBody>
);
}