@fayz-ai/admin 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CommandPalette-ANZWU27U.js → CommandPalette-B5PMQFDG.js} +28 -4
- package/dist/CommandPalette-B5PMQFDG.js.map +1 -0
- package/dist/SubscriptionPage-2JPALG53.js +9 -0
- package/dist/{SubscriptionPage-4EJCCS4I.js.map → SubscriptionPage-2JPALG53.js.map} +1 -1
- package/dist/billing/index.js +2 -1
- package/dist/chunk-OD7P4L3D.js +74 -0
- package/dist/chunk-OD7P4L3D.js.map +1 -0
- package/dist/{chunk-LB5SV4ZZ.js → chunk-OVVTLRDD.js} +3 -3
- package/dist/{chunk-LB5SV4ZZ.js.map → chunk-OVVTLRDD.js.map} +1 -1
- package/dist/chunk-P5UIGQFY.js +57 -0
- package/dist/chunk-P5UIGQFY.js.map +1 -0
- package/dist/chunk-VX3WJLKK.js +54 -0
- package/dist/chunk-VX3WJLKK.js.map +1 -0
- package/dist/{chunk-U3VX52SE.js → chunk-ZOUIXHKI.js} +5 -50
- package/dist/chunk-ZOUIXHKI.js.map +1 -0
- package/dist/components/layout/CommandPalette.d.ts.map +1 -1
- package/dist/crud/archetype-lookup.d.ts +10 -0
- package/dist/crud/archetype-lookup.d.ts.map +1 -1
- package/dist/entity-routes-LXOBW5IB.js +4 -0
- package/dist/entity-routes-LXOBW5IB.js.map +1 -0
- package/dist/hooks/useListMemory.d.ts +39 -0
- package/dist/hooks/useListMemory.d.ts.map +1 -0
- package/dist/hooks/useRecentRecords.d.ts +16 -0
- package/dist/hooks/useRecentRecords.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +114 -81
- package/dist/index.js.map +1 -1
- package/dist/lib/recent-records.d.ts +53 -0
- package/dist/lib/recent-records.d.ts.map +1 -0
- package/package.json +6 -6
- package/dist/CommandPalette-ANZWU27U.js.map +0 -1
- package/dist/SubscriptionPage-4EJCCS4I.js +0 -8
- package/dist/chunk-U3VX52SE.js.map +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O que a pessoa abriu por último.
|
|
3
|
+
*
|
|
4
|
+
* ── por que no navegador, e não no banco ────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* Isto é conveniência de quem está olhando, não informação da empresa. Uma
|
|
7
|
+
* tabela custaria migração, RLS, escrita a cada navegação e uma pergunta que
|
|
8
|
+
* ninguém faz ("o que o Julio andou abrindo?" é vigilância, não produto).
|
|
9
|
+
*
|
|
10
|
+
* O preço é que a lista não atravessa máquinas: quem abre no notebook não vê
|
|
11
|
+
* no celular. É o preço certo — a alternativa é gravar a pegada de navegação
|
|
12
|
+
* de cada vendedor num servidor para resolver um atalho.
|
|
13
|
+
*
|
|
14
|
+
* ── por que a TELA registra, e não o clique ─────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* `openEntity` parece o lugar óbvio (é a porta de todo registro), e não é: o
|
|
17
|
+
* CRM navega por rota interna própria e nunca passa por lá, então metade das
|
|
18
|
+
* visitas sumiria. Pior, um clique que cai numa rota quebrada contaria como
|
|
19
|
+
* visita — e a lista de "recentes" passaria a guardar justamente os lugares
|
|
20
|
+
* onde a pessoa NÃO conseguiu chegar.
|
|
21
|
+
*
|
|
22
|
+
* Visita é a tela de detalhe ter renderizado com o registro carregado. É lá
|
|
23
|
+
* também que o NOME existe: `openEntity` só recebe o id, e uma lista de
|
|
24
|
+
* recentes mostrando UUID não serve para nada.
|
|
25
|
+
*/
|
|
26
|
+
/** Um registro aberto. `href` é resolvido na gravação, não na leitura — a rota
|
|
27
|
+
* de hoje é a que funcionava quando a pessoa esteve lá. */
|
|
28
|
+
export interface RecentRecord {
|
|
29
|
+
/** `${kind}:${id}` — um lead e uma empresa podem repetir id entre tabelas. */
|
|
30
|
+
uid: string;
|
|
31
|
+
id: string;
|
|
32
|
+
/** `lead`, `company`, `quote`… o que a tela chama de si mesma. */
|
|
33
|
+
kind: string;
|
|
34
|
+
/** Rótulo do grupo, já traduzido pela tela que gravou. */
|
|
35
|
+
kindLabel: string;
|
|
36
|
+
label: string;
|
|
37
|
+
subtitle?: string;
|
|
38
|
+
/** Quando foi aberto (epoch ms). */
|
|
39
|
+
at: number;
|
|
40
|
+
/** Para reabrir: os mesmos argumentos que a paleta passa a `openEntity`. */
|
|
41
|
+
archetype?: string;
|
|
42
|
+
archetypeKind?: string;
|
|
43
|
+
entityKey?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Marca um registro como visto agora. Idempotente: reabrir sobe a entrada em
|
|
46
|
+
* vez de duplicá-la, porque "os últimos 20 que abri" são vinte REGISTROS, não
|
|
47
|
+
* vinte aberturas do mesmo. */
|
|
48
|
+
export declare function rememberVisit(entrada: Omit<RecentRecord, 'uid' | 'at'>): void;
|
|
49
|
+
/** A lista, mais recente primeiro. */
|
|
50
|
+
export declare function listRecentRecords(): RecentRecord[];
|
|
51
|
+
export declare function clearRecentRecords(): void;
|
|
52
|
+
export declare function subscribeRecentRecords(f: () => void): () => void;
|
|
53
|
+
//# sourceMappingURL=recent-records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recent-records.d.ts","sourceRoot":"","sources":["../../src/lib/recent-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;4DAC4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,EAAE,EAAE,MAAM,CAAA;IACV,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwCD;;gCAEgC;AAChC,wBAAgB,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAM7E;AAED,sCAAsC;AACtC,wBAAgB,iBAAiB,IAAI,YAAY,EAAE,CAElD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAGhE"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"fayz": {
|
|
4
4
|
"status": "beta"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.29.0",
|
|
7
7
|
"description": "Fayz SDK admin layer — the admin shell, CRUD engine, multi-tenancy, billing, permissions and org management",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": [
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"@radix-ui/react-dialog": "^1.1.0",
|
|
60
60
|
"@tanstack/react-table": "^8.20.0",
|
|
61
61
|
"cmdk": "^1.0.0",
|
|
62
|
-
"@fayz-ai/auth": "^0.
|
|
63
|
-
"@fayz-ai/core": "^0.
|
|
64
|
-
"@fayz-ai/db": "^0.
|
|
65
|
-
"@fayz-ai/plugin-auth": "^0.12.
|
|
62
|
+
"@fayz-ai/auth": "^0.28.0",
|
|
63
|
+
"@fayz-ai/core": "^0.28.0",
|
|
64
|
+
"@fayz-ai/db": "^0.17.0",
|
|
65
|
+
"@fayz-ai/plugin-auth": "^0.12.9",
|
|
66
66
|
"@fayz-ai/sdk": "^0.19.1",
|
|
67
|
-
"@fayz-ai/ui": "^0.
|
|
67
|
+
"@fayz-ai/ui": "^0.28.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@supabase/supabase-js": "^2.45.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/layout/CommandPalette.tsx"],"names":["Fragment"],"mappings":"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","file":"CommandPalette-ANZWU27U.js","sourcesContent":["import * as React from 'react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { Command } from 'cmdk'\nimport * as Dialog from '@radix-ui/react-dialog'\nimport {\n Home, Users, Settings, CreditCard, Bell, Calendar, CalendarCheck2,\n CalendarClock, CalendarX, Package, Activity, BarChart3, FileText, FileCheck2, Mail, Search,\n DollarSign, BadgeDollarSign, CircleDollarSign, Megaphone, ShoppingCart, ShoppingBag, Target,\n Wrench, ClipboardList, ListChecks, Briefcase, UserCog, BookOpen, BookOpenCheck, MessageCircle,\n Globe, Percent, Tag, Tags, Camera, UtensilsCrossed, MapPin, Map, Handshake,\n Contact, Building2, Filter, Plus, List, Shield, User, Box, Sparkles, Loader2, X,\n ListPlus, FolderOpen, Boxes, Landmark, Receipt, Ban, Clock, Clock3, Inbox,\n Star, TrendingUp, UserCheck, UserPlus, UserX, Zap,\n type LucideIcon,\n} from 'lucide-react'\nimport {\n MIN_QUERY_LENGTH, highlightRanges, normalizeQuery, scoreCandidate, foldText,\n type GlobalSearchResult, type SearchHit,\n} from '@fayz-ai/core'\nimport { RefreshButton } from '@fayz-ai/ui'\nimport { useTranslation } from '../../hooks/useTranslation'\n\nexport interface CommandItem {\n id: string\n label: string\n icon?: string\n action: () => void\n group?: string\n keywords?: string\n subtitle?: string\n}\n\n/** Runs one global search. `onPartial` paints a cheap early answer, if there is one.\n * `opts.fresh` bypasses every cache — the footer's refresh button uses it. */\nexport type EntitySearchFn = (\n query: string,\n onPartial?: (result: GlobalSearchResult) => void,\n opts?: { fresh?: boolean },\n) => Promise<GlobalSearchResult>\n\ninterface CommandPaletteProps {\n commands?: CommandItem[]\n open: boolean\n onOpenChange: (open: boolean) => void\n onEntitySearch?: EntitySearchFn\n onEntitySelect?: (hit: SearchHit) => void\n /** O que já estava digitado quando a paleta foi aberta a partir da busca do\n * trilho. Escalar de \"onde estou\" para \"em tudo\" não pode custar digitar a\n * mesma palavra duas vezes. */\n initialQuery?: string\n}\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n Home, Users, Settings, CreditCard, Bell, Calendar, CalendarClock, Package, Activity, BarChart3,\n CalendarCheck2, CalendarX, FileText, FileCheck2, Mail, Search, DollarSign, BadgeDollarSign, CircleDollarSign, Megaphone, ShoppingCart, ShoppingBag, Target,\n Wrench, ClipboardList, ListChecks, Briefcase, UserCog, BookOpen, BookOpenCheck, MessageCircle,\n Globe, Percent, Tag, Tags, Camera, UtensilsCrossed, MapPin, Map, Handshake,\n Contact, Building2, Filter, Plus, List, Shield, ListPlus, FolderOpen, User, Box, Boxes, Sparkles, Landmark, Receipt, Ban, Clock, Clock3, Inbox,\n Star, TrendingUp, UserCheck, UserPlus, UserX, Zap,\n}\n\n/** Long enough that a fast typist issues one request per word, not per letter. */\nconst DEBOUNCE_MS = 140\n\n// Highlighting — the matched run, in the record's own spelling\n\nfunction Highlight({ text, query }: { text: string; query: string }) {\n const parts = useMemo(() => {\n const normalized = normalizeQuery(query)\n const ranges = highlightRanges(text, normalized)\n if (ranges.length === 0) return [{ text, hit: false }]\n const out: Array<{ text: string; hit: boolean }> = []\n let cursor = 0\n for (const [from, to] of ranges) {\n if (from > cursor) out.push({ text: text.slice(cursor, from), hit: false })\n out.push({ text: text.slice(from, to), hit: true })\n cursor = to\n }\n if (cursor < text.length) out.push({ text: text.slice(cursor), hit: false })\n return out\n }, [text, query])\n\n return (\n <>\n {parts.map((part, i) =>\n part.hit\n ? <mark key={i} className=\"bg-transparent font-semibold text-foreground\">{part.text}</mark>\n : <React.Fragment key={i}>{part.text}</React.Fragment>,\n )}\n </>\n )\n}\n\n// Records arrive from the server; commands are already in memory. Same scorer,\n// different clocks — commands re-rank per keystroke, records refine as they land.\n\nfunction useEntitySearch(searchFn: EntitySearchFn | undefined, query: string, open: boolean) {\n const [result, setResult] = useState<GlobalSearchResult | null>(null)\n const [loading, setLoading] = useState(false)\n const generation = useRef(0)\n\n useEffect(() => {\n const trimmed = query.trim()\n if (!open || !searchFn || foldText(trimmed).length < MIN_QUERY_LENGTH) {\n setResult(null)\n setLoading(false)\n return\n }\n\n const token = ++generation.current\n setLoading(true)\n\n const timer = setTimeout(() => {\n searchFn(trimmed, (partial) => {\n // A partial must never replace a final answer for this same query.\n if (token === generation.current) setResult((prev) => (prev?.partial === false && prev.query === trimmed ? prev : partial))\n })\n .then((final) => {\n if (token !== generation.current) return\n setResult(final)\n setLoading(false)\n })\n .catch(() => {\n // Keep what is on screen: a failed refresh is not an empty result.\n if (token !== generation.current) return\n setLoading(false)\n })\n }, DEBOUNCE_MS)\n\n return () => clearTimeout(timer)\n }, [query, searchFn, open])\n\n // The footer refresh: same query, every cache bypassed. Returned as a\n // promise so the button spins until the network answer lands.\n const refresh = React.useCallback(async () => {\n const trimmed = query.trim()\n if (!searchFn || foldText(trimmed).length < MIN_QUERY_LENGTH) return\n const token = ++generation.current\n setLoading(true)\n try {\n const final = await searchFn(trimmed, undefined, { fresh: true })\n if (token === generation.current) setResult(final)\n } finally {\n if (token === generation.current) setLoading(false)\n }\n }, [query, searchFn])\n\n return { result, loading, refresh }\n}\n\n/**\n * Commands, ranked by the same ladder records use. cmdk's own filter is OFF:\n * it re-filtered the SERVER's results with its own substring rules, so a record\n * matched by phone, by a note or by an accent-folded name was fetched and then\n * dropped — the list said \"no results\" while the footer counted them.\n */\nfunction useRankedCommands(commands: CommandItem[], query: string): CommandItem[] {\n return useMemo(() => {\n const trimmed = query.trim()\n if (!trimmed) return commands\n const normalized = normalizeQuery(trimmed)\n if (normalized.tokens.length === 0) return commands\n return commands\n .map((cmd) => ({\n cmd,\n score: scoreCandidate(normalized, {\n title: foldText(cmd.label),\n haystack: foldText(`${cmd.label} ${cmd.group ?? ''} ${cmd.keywords ?? ''} ${cmd.subtitle ?? ''}`),\n }),\n }))\n .filter((c) => c.score > 0)\n .sort((a, b) => b.score - a.score)\n .map((c) => c.cmd)\n }, [commands, query])\n}\n\nconst GROUP_HEADING =\n '[&_[cmdk-group-heading]]:px-3 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-[11px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wider [&_[cmdk-group-heading]]:text-muted-foreground'\n\nconst ITEM =\n 'flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2.5 text-sm outline-none transition-colors aria-selected:bg-muted'\n\nexport function CommandPalette({ commands = [], open, onOpenChange, onEntitySearch, onEntitySelect, initialQuery = '' }: CommandPaletteProps) {\n const { t } = useTranslation()\n const [search, setSearch] = useState(initialQuery)\n const { result, loading, refresh } = useEntitySearch(onEntitySearch, search, open)\n const rankedCommands = useRankedCommands(commands, search)\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'k') {\n e.preventDefault()\n onOpenChange(!open)\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [open, onOpenChange])\n\n // Abre com o que o trilho já tinha, e zera ao fechar — o ⌘K seco abre limpo\n // porque quem não escalou de lugar nenhum manda string vazia.\n useEffect(() => {\n setSearch(open ? initialQuery : '')\n // `initialQuery` fora das deps de propósito: ele muda no MESMO commit em\n // que `open` vira true, e reagir a ele depois sobrescreveria o que a\n // pessoa já está digitando dentro da paleta.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n const grouped = rankedCommands.reduce<Record<string, CommandItem[]>>((acc, cmd) => {\n const group = cmd.group ?? 'Actions'\n if (!acc[group]) acc[group] = []\n acc[group].push(cmd)\n return acc\n }, {})\n\n const hasQuery = foldText(search).length >= MIN_QUERY_LENGTH\n // A lookup wants the client, not the page that lists clients.\n const recordGroups = hasQuery ? (result?.groups ?? []) : []\n const recordCount = recordGroups.reduce((n, g) => n + g.hits.length, 0)\n const totalResults = rankedCommands.length + recordCount\n const nothingYet = totalResults === 0\n\n return (\n <Dialog.Root open={open} onOpenChange={onOpenChange}>\n <Dialog.Portal>\n <Dialog.Overlay className=\"saas-overlay fixed inset-0 z-[100] bg-black/40 backdrop-blur-sm\" />\n {/* Search is a PLACE on a phone, not a card floating over one. A centred\n dialog leaves the page showing round the edges, puts the field under\n the thumb's reach, and gives the results a 360px window inside a\n 660px screen. Full-bleed below sm; the desktop card is unchanged. */}\n <Dialog.Content // Desktop position lives in styles.css beside the animation that depends on\n // it — see the note there. Utilities here are the mobile full-bleed only.\n className=\"fayz-glass-surface saas-cmd-palette fixed inset-0 z-[100] flex flex-col overflow-hidden border-border/50 bg-popover shadow-2xl\">\n <Dialog.Title className=\"sr-only\">{t('layout.commandPalette.title')}</Dialog.Title>\n <Dialog.Description className=\"sr-only\">{t('layout.commandPalette.description')}</Dialog.Description>\n\n <Command className=\"flex min-h-0 flex-1 flex-col\" label=\"Command palette\" shouldFilter={false}>\n <div className=\"fz-safe-t flex shrink-0 items-center gap-3 border-b border-border/50 px-4\">\n <Search className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <Command.Input\n placeholder={t('layout.commandPalette.placeholder')}\n className=\"flex h-12 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n autoFocus\n value={search}\n onValueChange={setSearch}\n />\n {loading && <Loader2 className=\"h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground\" />}\n <kbd className=\"hidden shrink-0 rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground sm:inline-block\">ESC</kbd>\n {/* Full-bleed on a phone means the page behind is gone, and with it\n every hint that tapping outside would close this. ESC is the\n desktop's way out; this is the phone's. */}\n <Dialog.Close\n aria-label={t('common.close')}\n className=\"fz-tap -mr-2 flex shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors active:bg-muted sm:hidden\"\n >\n <X className=\"h-5 w-5\" />\n </Dialog.Close>\n </div>\n\n <Command.List className=\"fz-scroll-contain min-h-0 flex-1 overflow-y-auto p-1.5 sm:max-h-[360px] sm:flex-none\">\n {nothingYet && (\n <Command.Empty className=\"py-8 text-center text-sm text-muted-foreground\">\n {loading ? t('layout.commandPalette.searching') : t('layout.commandPalette.noResults')}\n </Command.Empty>\n )}\n\n {/* Records first */}\n {recordGroups.map((group) => {\n const GroupIcon = group.icon ? (ICON_MAP[group.icon] ?? User) : User\n return (\n <Command.Group key={`entity:${group.key}`} heading={group.label} className={GROUP_HEADING}>\n {group.hits.map((hit) => {\n const Icon = hit.icon ? (ICON_MAP[hit.icon] ?? GroupIcon) : GroupIcon\n return (\n <Command.Item\n key={hit.uid}\n value={hit.uid}\n onSelect={() => { onEntitySelect?.(hit); onOpenChange(false) }}\n className={ITEM}\n >\n <Icon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <span className=\"block truncate\"><Highlight text={hit.title} query={search} /></span>\n {hit.subtitle && (\n <span className=\"block truncate text-[11px] text-muted-foreground\">\n <Highlight text={hit.subtitle} query={search} />\n </span>\n )}\n </div>\n <span className=\"shrink-0 text-[11px] text-muted-foreground/40\">{group.label}</span>\n </Command.Item>\n )\n })}\n </Command.Group>\n )\n })}\n\n {/* Pages and actions */}\n {Object.entries(grouped).map(([group, items]) => (\n <Command.Group key={group} heading={group} className={GROUP_HEADING}>\n {items.map((cmd, i) => {\n const Icon = cmd.icon ? (ICON_MAP[cmd.icon] ?? null) : null\n // Command ids are not unique — \"Clientes\" and its \"Lista\"\n // child both mint `page:/clients`. Also the cmdk value.\n const uid = `${group}:${cmd.id}:${i}`\n return (\n <Command.Item\n key={uid}\n value={uid}\n onSelect={() => { cmd.action(); onOpenChange(false) }}\n className={ITEM}\n >\n {Icon && <Icon className=\"h-4 w-4 shrink-0 text-muted-foreground\" />}\n <span className=\"flex-1\"><Highlight text={cmd.label} query={search} /></span>\n <span className=\"text-[11px] text-muted-foreground/40\">{cmd.group}</span>\n </Command.Item>\n )\n })}\n </Command.Group>\n ))}\n </Command.List>\n\n <div className=\"flex items-center justify-between border-t border-border/50 px-4 py-2\">\n <span className=\"text-xs text-muted-foreground\">\n {loading\n ? t('layout.commandPalette.searching')\n : hasQuery\n ? t('layout.commandPalette.resultCount', { count: String(totalResults), plural: totalResults !== 1 ? 's' : '' })\n : t('layout.commandPalette.commandCount', { count: String(commands.length), plural: commands.length !== 1 ? 's' : '' })}\n </span>\n <div className=\"flex items-center gap-2 text-[10px] text-muted-foreground\">\n {hasQuery && result && (\n <RefreshButton\n onRefresh={refresh}\n fromCache={result.cached}\n label={t('layout.commandPalette.refresh')}\n labelCached={t('layout.commandPalette.refreshCached')}\n className=\"rounded p-1 text-muted-foreground hover:text-foreground\"\n />\n )}\n {/* Arrow-key and Enter hints on a device with neither. They are\n the desktop's affordance; a finger just taps the row. */}\n <span className=\"hidden items-center gap-2 sm:flex\">\n <kbd className=\"rounded border border-border bg-muted px-1 py-0.5 font-medium\">↑↓</kbd>\n <span>{t('layout.commandPalette.navigate')}</span>\n <kbd className=\"rounded border border-border bg-muted px-1 py-0.5 font-medium\">↵</kbd>\n <span>{t('layout.commandPalette.open')}</span>\n </span>\n </div>\n </div>\n </Command>\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n )\n}\n"]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { SubscriptionPage } from './chunk-LB5SV4ZZ.js';
|
|
2
|
-
import './chunk-YUHCC7JY.js';
|
|
3
|
-
import './chunk-U3VX52SE.js';
|
|
4
|
-
import './chunk-R4HQ2HS2.js';
|
|
5
|
-
import './chunk-UQ2NTFQC.js';
|
|
6
|
-
import './chunk-W5IBKM3K.js';
|
|
7
|
-
//# sourceMappingURL=SubscriptionPage-4EJCCS4I.js.map
|
|
8
|
-
//# sourceMappingURL=SubscriptionPage-4EJCCS4I.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/billing/store.ts","../src/billing/plan-badge.ts","../src/billing/trial.ts","../src/app/routing.tsx","../src/billing/TrialWidget.tsx"],"names":["paidPlanIds","named","React2"],"mappings":"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","file":"chunk-U3VX52SE.js","sourcesContent":["import { create } from 'zustand'\nimport type { Plan, BillingCheckoutFn } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface Subscription {\n id: string\n orgId: string\n planId: string\n status: 'active' | 'trialing' | 'past_due' | 'canceled' | 'incomplete'\n currentPeriodStart: string\n currentPeriodEnd: string\n cancelAtPeriodEnd?: boolean\n stripeSubscriptionId?: string\n}\n\nexport interface Invoice {\n id: string\n orgId: string\n amount: number\n currency: string\n status: 'paid' | 'open' | 'void' | 'uncollectible'\n pdfUrl?: string\n createdAt: string\n}\n\nexport interface BillingState {\n subscription: Subscription | null\n invoices: Invoice[]\n plans: Plan[]\n loading: boolean\n /** Payment-gateway seam seeded from `config.billing.onCheckout` (see core\n * BillingConfig). When set, the Subscription page routes plan changes through\n * it instead of writing `plan` straight onto the org. */\n checkout: BillingCheckoutFn | null\n}\n\n// ---------------------------------------------------------------------------\n// Store\n// ---------------------------------------------------------------------------\n\nexport interface BillingStore extends BillingState {\n setSubscription: (subscription: Subscription | null) => void\n setInvoices: (invoices: Invoice[]) => void\n setPlans: (plans: Plan[]) => void\n setLoading: (loading: boolean) => void\n setCheckout: (checkout: BillingCheckoutFn | null) => void\n reset: () => void\n}\n\nconst initialState: BillingState = {\n subscription: null,\n invoices: [],\n plans: [],\n loading: false,\n checkout: null,\n}\n\nexport const useBillingStore = create<BillingStore>((set) => ({\n ...initialState,\n setSubscription: (subscription) => set({ subscription }),\n setInvoices: (invoices) => set({ invoices }),\n setPlans: (plans) => set({ plans }),\n setLoading: (loading) => set({ loading }),\n setCheckout: (checkout) => set({ checkout }),\n reset: () => set(initialState),\n}))\n","import type { Plan } from '@fayz-ai/core'\n\nexport interface PlanBadgeInfo {\n /** Display label, e.g. 'Free' or 'Pro'. */\n label: string\n /** Resolved plan id (falls back to the raw org.plan string). */\n planId: string\n /** Paid plan (price > 0) — drives the Crown icon + gold styling in the menus. */\n paid: boolean\n}\n\nfunction capitalize(s: string): string {\n return s ? s.charAt(0).toUpperCase() + s.slice(1) : s\n}\n\n/**\n * Resolve an org's `plan` string against the configured billing plans into the\n * badge shown next to the user in both menus. Falls back to a capitalized\n * version of the raw string when the id isn't in config (defensive — e.g. a\n * legacy 'free' tenant before billing was wired).\n */\nexport function resolvePlanBadge(\n planId: string | undefined | null,\n plans: Plan[],\n): PlanBadgeInfo {\n const id = planId || 'free'\n const match = plans.find((p) => p.id === id)\n if (match) {\n return { label: match.name, planId: match.id, paid: match.price > 0 }\n }\n return { label: capitalize(id), planId: id, paid: false }\n}\n","import * as React from 'react'\nimport type { Organization, Plan } from '@fayz-ai/core'\nimport { useOrganizationStore } from '../org/store'\nimport { useBillingStore } from './store'\n\n/**\n * The free trial, as one derived fact.\n *\n * A trial is not a fourth plan or a state machine — it is a WINDOW during which\n * a tenant on the free plan is entitled to a paid one. Modelling it that way is\n * what keeps it out of every gate in the product: the access provider resolves\n * the effective plan once, and the nav crowns, the entitlement gates, the limit\n * caps and the assistant's lock all follow without knowing a trial exists.\n *\n * `trialEndsAt` is written by the database when the tenant is created (see\n * migration 147) and is never written by the client — a trial the browser could\n * extend is not a trial. A tenant with no `trialEndsAt` predates the column and\n * is left alone: status `none`, plan exactly as stored.\n */\n\nexport type TrialStatus = 'none' | 'active' | 'expired'\n\nexport interface TrialState {\n status: TrialStatus\n /** ISO date the window closes. */\n endsAt?: string\n /** Whole days remaining, floored at 0. `0` on the last day, not a negative. */\n daysLeft: number\n /** The last 48 hours, when the banner stops being informational. */\n endingSoon: boolean\n}\n\nexport const NO_TRIAL: TrialState = { status: 'none', daysLeft: 0, endingSoon: false }\n\nconst DAY_MS = 86_400_000\n\n/** Pure — the part worth testing, and it needs neither React nor a clock. */\nexport function resolveTrial(\n org: Pick<Organization, 'plan' | 'trialEndsAt'> | null | undefined,\n paidPlanIds: ReadonlySet<string>,\n now: number = Date.now(),\n): TrialState {\n if (!org?.trialEndsAt) return NO_TRIAL\n // Somebody who is paying is not on a trial, whatever the column still says.\n if (org.plan && paidPlanIds.has(org.plan)) return NO_TRIAL\n\n const ends = Date.parse(org.trialEndsAt)\n if (Number.isNaN(ends)) return NO_TRIAL\n\n const remaining = ends - now\n if (remaining <= 0) return { status: 'expired', endsAt: org.trialEndsAt, daysLeft: 0, endingSoon: false }\n\n const daysLeft = Math.ceil(remaining / DAY_MS)\n return {\n status: 'active',\n endsAt: org.trialEndsAt,\n daysLeft,\n endingSoon: remaining <= 2 * DAY_MS,\n }\n}\n\n/**\n * The plan whose entitlements apply RIGHT NOW.\n *\n * During the trial that is the trial plan — by default the most expensive one\n * configured, because the point of a trial is to be worth losing. After it, the\n * tenant's own plan, which for a free tenant is the free one: that fall is the\n * paywall, and it needs no separate enforcement path.\n *\n * ── Por que um plano desconhecido cai no piso, e não em `null` ──────────────\n * A frase acima (\"essa queda é o paywall\") só era verdade quando o id gravado\n * em `tenants.plan` existia no catálogo do app. Quando não existia — o\n * ChefControl vendia `starter/professional/enterprise` enquanto o pool grava\n * `free/pro/business` — isto devolvia `null`, e `null` não é \"sem plano\": é SEM\n * TETO. `resolveLimit` lê teto ausente como ilimitado e `isEntitledByPlan` só\n * nega com um `false` explícito. O resultado é que vencer o trial LIBERAVA em\n * vez de travar, que é o oposto do paywall.\n *\n * Então um id que o catálogo não conhece cai no plano mais barato — o piso é a\n * resposta segura para \"não sei em que plano esta conta está\". `null` fica\n * reservado para o único caso em que ilimitado é a leitura certa: o app que não\n * vende plano nenhum e portanto não tem o que travar.\n */\nexport function effectivePlan(\n org: Pick<Organization, 'plan'> | null | undefined,\n plans: Plan[],\n trial: TrialState,\n trialPlanId?: string,\n): Plan | null {\n if (trial.status === 'active') {\n const named = trialPlanId ? plans.find((p) => p.id === trialPlanId) : undefined\n if (named) return named\n const paid = plans.filter((p) => p.price > 0)\n if (paid.length) return paid.reduce((best, p) => (p.price > best.price ? p : best))\n }\n if (plans.length === 0) return null\n\n const named = org?.plan ? plans.find((p) => p.id === org.plan) : undefined\n if (named) return named\n\n const floor = plans.reduce((cheapest, p) => (p.price < cheapest.price ? p : cheapest))\n if (org?.plan) {\n // Avisa alto: é um defeito de dados (catálogo e pool discordando), e quem\n // vê primeiro é quem estiver com o console aberto.\n console.warn(\n `[billing] tenant plan \"${org.plan}\" não existe neste catálogo (${plans.map((p) => p.id).join(', ')}) — aplicando o piso \"${floor.id}\"`,\n )\n }\n return floor\n}\n\n/** Ids of the plans somebody actually pays for. */\nexport function paidPlanIds(plans: Plan[]): Set<string> {\n return new Set(plans.filter((p) => p.price > 0).map((p) => p.id))\n}\n\n/**\n * Live trial state for the current workspace.\n *\n * Ticks once an hour rather than on a timer per second: nothing on screen\n * counts down in real time, and a banner that says \"2 days left\" only has to be\n * right the next time somebody looks at it.\n */\nexport function useTrial(): TrialState {\n const org = useOrganizationStore((s) => s.currentOrg)\n const plans = useBillingStore((s) => s.plans)\n const [now, setNow] = React.useState(() => Date.now())\n\n React.useEffect(() => {\n const id = window.setInterval(() => setNow(Date.now()), 3_600_000)\n return () => window.clearInterval(id)\n }, [])\n\n return React.useMemo(\n () => resolveTrial(org, paidPlanIds(plans), now),\n [org?.plan, org?.trialEndsAt, plans, now],\n )\n}\n","import * as React from 'react'\nimport { hashRouterAdapter } from '@fayz-ai/core'\n\n// ---------------------------------------------------------------------------\n// Admin hash router — the same lightweight approach the storefront uses. The\n// admin shell collects routes from the plugin runtime + manifest pages and\n// switches on the current hash path. Deliberately not react-router: an SDK\n// shell shipping a router creates singleton hazards under source-alias dev.\n// ---------------------------------------------------------------------------\n\nconst adapter = hashRouterAdapter()\n\nexport function normalizeAdminPath(path: string): string {\n const clean = `/${path}`.replace(/\\/+/g, '/')\n return clean.length > 1 ? clean.replace(/\\/+$/, '') : clean\n}\n\nexport function navigateTo(to: string): void {\n adapter.navigate(normalizeAdminPath(to))\n}\n\nexport function useAdminPath(): string {\n const [path, setPath] = React.useState(() => normalizeAdminPath(adapter.getCurrentPath() || '/'))\n React.useEffect(() => {\n return adapter.onPathChange((p) => {\n setPath(normalizeAdminPath(p || '/'))\n if (typeof window !== 'undefined') window.scrollTo(0, 0)\n })\n }, [])\n return path\n}\n\n/** Match '/courses/:id' against '/courses/abc' → { id: 'abc' }; supports a\n * trailing '/*' wildcard (matches the base and any deeper path). */\nexport function matchRoute(pattern: string, path: string): Record<string, string> | null {\n const cleanPath = (path.split('?')[0] ?? '').replace(/\\/+$/, '') || '/'\n if (pattern.endsWith('/*')) {\n const base = pattern.slice(0, -2)\n if (cleanPath === base || cleanPath.startsWith(`${base}/`)) return {}\n return null\n }\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = cleanPath.split('/').filter(Boolean)\n if (patternParts.length !== pathParts.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pat = patternParts[i]!\n const part = pathParts[i]!\n if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)\n else if (pat !== part) return null\n }\n return params\n}\n\n/** Score a route pattern for specificity so the most specific match wins\n * (static segments beat params; longer beats shorter; wildcard loses). */\nexport function routeScore(pattern: string): number {\n const isWildcard = pattern.endsWith('/*')\n const routePattern = isWildcard ? pattern.slice(0, -2) : pattern\n const parts = routePattern.split('/').filter(Boolean)\n let score = parts.length * 10\n for (const p of parts) if (!p.startsWith(':')) score += 5\n if (isWildcard) score -= 1\n return score\n}\n","import * as React from 'react'\nimport { Clock, Crown } from 'lucide-react'\nimport { tl } from '@fayz-ai/core'\nimport { cn } from '../lib/cn'\nimport { navigateTo } from '../app/routing'\nimport { useLayoutStore } from '@fayz-ai/ui'\nimport { useTrial } from './trial'\n\nexport interface TrialWidgetProps {\n /** Length of the window, for the progress bar. Matches the DB column default (migration 044). */\n totalDays?: number\n /** Where \"see the plans\" goes. */\n route?: string\n className?: string\n}\n\n/**\n * The trial, in the rail.\n *\n * A countdown is a promise about the future, so it belongs where somebody looks\n * at their own progress — one line under the getting-started checklist, in the\n * menu's own colours, not a banner across the app. Two states only:\n *\n * • RUNNING — informational, and quiet until the last two days.\n * • ENDED — the paywall's face. It stops being a note and becomes a button,\n * because at that point every gated feature in the product is already\n * answering \"not on your plan\" and this is the one place that says why.\n *\n * There is no third state: a paying tenant, or one that predates trials, gets\n * nothing at all.\n */\nexport function TrialWidget({ totalDays = 30, route = '/settings/subscription', className }: TrialWidgetProps) {\n const trial = useTrial()\n const collapsed = useLayoutStore((s) => s.sidebarCollapsed)\n\n if (trial.status === 'none') return null\n\n const expired = trial.status === 'expired'\n const pct = Math.max(0, Math.min(100, (trial.daysLeft / totalDays) * 100))\n\n const label = expired\n ? tl('Trial ended', 'Seu teste terminou')\n : trial.daysLeft <= 1\n ? tl('Last day of your trial', 'Último dia do teste')\n : tl(`${trial.daysLeft} days of trial left`, `${trial.daysLeft} dias de teste`)\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n onClick={() => navigateTo(route)}\n title={label}\n aria-label={label}\n data-tour=\"billing.trial\"\n className={cn(\n 'flex w-full items-center justify-center rounded-button p-1.5 transition-colors',\n expired\n ? 'bg-primary/15 text-primary hover:bg-primary/25'\n : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/60',\n className,\n )}\n >\n {expired ? <Crown className=\"h-4 w-4\" /> : <Clock className=\"h-4 w-4\" />}\n </button>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={() => navigateTo(route)}\n data-tour=\"billing.trial\"\n className={cn(\n 'w-full rounded-button px-1.5 py-1.5 text-left transition-colors',\n expired ? 'bg-primary/15 hover:bg-primary/25' : 'hover:bg-sidebar-accent/50',\n className,\n )}\n >\n <span className=\"flex items-center gap-2\">\n {expired\n ? <Crown className=\"h-3.5 w-3.5 shrink-0 text-primary\" />\n : <Clock className={cn('h-3.5 w-3.5 shrink-0', trial.endingSoon ? 'text-warning' : 'text-sidebar-muted')} />}\n <span\n className={cn(\n 'min-w-0 flex-1 truncate text-[12px] font-medium',\n expired ? 'text-sidebar-foreground' : 'text-sidebar-foreground/90',\n )}\n >\n {label}\n </span>\n </span>\n {expired ? (\n <span className=\"mt-1 block pl-[22px] text-[11px] leading-snug text-sidebar-muted\">\n {tl('Choose a plan to keep the assistant', 'Escolha um plano para manter o assistente')}\n </span>\n ) : (\n <span className=\"mt-1.5 block h-1 overflow-hidden rounded-full bg-sidebar-accent\">\n <span\n className={cn(\n 'block h-full rounded-full transition-all',\n trial.endingSoon ? 'bg-warning' : 'bg-sidebar-foreground/70',\n )}\n style={{ width: `${pct}%` }}\n />\n </span>\n )}\n </button>\n )\n}\n"]}
|