This commit is contained in:
2026-09-09 11:19:05 +05:30
parent a94c23c20e
commit 32c612a10d
8 changed files with 335 additions and 127 deletions

View File

@@ -1,6 +1,8 @@
import { useState, useRef, useEffect } from 'react';
import { Check, ChevronDown, FileSpreadsheet, Monitor, Store, Users } from 'lucide-react';
import { AppShell, type MenuEntry, type NavEntry } from '@/components/shell/AppShell';
import { QrCode } from 'lucide-react';
import { StoreQrDrawer } from '@/features/store-user/StoreQrDrawer';
import { AppShell, IconButton, type MenuEntry, type NavEntry } from '@/components/shell/AppShell';
import { OnboardingGate } from '@/features/onboarding/OnboardingGate';
import { AssistantScope } from '@/features/console/AssistantScope';
import { BranchScopeProvider, useBranchScope } from './BranchScope';
@@ -86,19 +88,48 @@ export function StoreAdminShell() {
<LiveWatch />
<OnboardingGate />
<AssistantScope>
<AppShell
nav={NAV}
home="/admin/console"
navLabel="Store Admin"
scopeControl={<BranchSelector />}
manageItems={MANAGE}
/>
<AdminShell />
</AssistantScope>
</BranchScopeProvider>
</DateScopeProvider>
);
}
/**
* The shell itself, inside the providers so it can read the branch in scope.
*
* Split out for the QR button: it needs open/closed state and it needs to know
* which outlet is selected, and both of those have to sit under
* `BranchScopeProvider`.
*/
function AdminShell() {
const [isQrOpen, setQrOpen] = useState(false);
return (
<>
<AppShell
nav={NAV}
home="/admin/console"
navLabel="Store Admin"
scopeControl={<BranchSelector />}
manageItems={MANAGE}
/* The storefront code, in the same place the branch login has it.
A merchant runs the outlets — they are the one who prints the poster
for a new shop and reprints it when it is lost — and until now the
code existed only on the branch user's header, so an owner with five
shops could not reach any of them. It follows the branch selector, so
the code on screen is always the one for the outlet in scope. */
headerActions={
<IconButton label="Store QR code" onClick={() => setQrOpen(true)}>
<QrCode size={16} />
</IconButton>
}
/>
{isQrOpen ? <StoreQrDrawer onClose={() => setQrOpen(false)} /> : null}
</>
);
}
/**
* Opens the live stream for the branch currently in scope.
*

View File

@@ -18,6 +18,7 @@ import { PageBody } from '@/components/PageBody';
import { PageHeader } from '@/components/PageHeader';
import { SectionHeader } from '@/components/SectionHeader';
import { queryKeys } from '@/queries/keys';
import { StoreQrPanel } from '@/features/qr/StoreQrPanel';
import { useStaff, useTenants } from '@/queries/hooks';
import type { TenantInfo } from '@/api/types';
import { isUnplaced } from '../staffPlacement';
@@ -199,6 +200,22 @@ export function OnboardBranchPage() {
? 'The person you chose now runs it and can sign in with their own account. The branch has no catalogue yet — products are published to it per store.'
: 'A login was created for the outlet itself, using the email above. The branch has no catalogue yet — products are published to it per store.'}
</Text>
{/* Its own storefront code, and it has to be its own: the payload is
{'{'}tenantid, locationid{'}'}, so a second outlet that reused the
first one's poster would send every shopper to the first shop.
`createtenantlocation` is used precisely because it returns the
created row — the new locationid is what this needs. */}
{mutation.data?.locationid ? (
<Card padding={3} elevation="low" variant="transparent">
<StoreQrPanel
tenantid={Number(form.tenantid)}
locationid={mutation.data.locationid}
locationname={form.locationname}
where={[form.suburb, form.city].filter(Boolean).join(', ')}
/>
</Card>
) : null}
<HStack gap={1.5} wrap="wrap">
<Button
label="Commission another"