@burdenoff/microfe-hookmovies 2026.628.1 → 2026.705.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ListsPage.js","names":[],"sources":["../../../../src/hookmovies/pages/library/ListsPage.tsx"],"sourcesContent":["/**\n * My Lists (Library — self-scoped)\n * @module hookmovies/pages/library\n *\n * The caller's own watchlists and playlists via `hokMyLists`, created with\n * `hokCreateList` and removed with `hokDeleteList`. All self-scoped to the\n * calling user by the subgraph.\n */\n\nimport { type FC, type FormEvent, useState } from 'react';\nimport { ListVideo, Plus, X, Trash2, Lock, Globe, Link as LinkIcon } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport {\n useMyLists,\n useCreateList,\n useDeleteList,\n type HokListKind,\n type HokListVisibility,\n type CreateListInput,\n} from '../../hooks/useHookMoviesApi';\nimport { formatRelativeTime } from '../../utils/formatters';\n\nconst KIND_OPTIONS: HokListKind[] = ['WATCHLIST', 'PLAYLIST'];\nconst VISIBILITY_OPTIONS: HokListVisibility[] = ['PRIVATE', 'UNLISTED', 'PUBLIC'];\nconst fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelClass = 'mb-1.5 block text-xs font-medium text-text-secondary';\n\nconst visibilityIcon: Record<HokListVisibility, typeof Lock> = {\n PRIVATE: Lock,\n UNLISTED: LinkIcon,\n PUBLIC: Globe,\n};\n\nexport const ListsPage: FC = () => {\n const [kindFilter, setKindFilter] = useState<HokListKind | ''>('');\n const [showCreate, setShowCreate] = useState(false);\n\n const { data, isLoading, isError, refetch } = useMyLists(kindFilter || undefined, {\n limit: 100,\n offset: 0,\n });\n const createList = useCreateList();\n const deleteList = useDeleteList();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<HokListKind>('WATCHLIST');\n const [visibility, setVisibility] = useState<HokListVisibility>('PRIVATE');\n const [error, setError] = useState<string | null>(null);\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!name.trim()) {\n setError('Name is required.');\n return;\n }\n const input: CreateListInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n kind,\n visibility,\n };\n try {\n await createList.mutateAsync(input);\n setName('');\n setDescription('');\n setKind('WATCHLIST');\n setVisibility('PRIVATE');\n setShowCreate(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create list');\n }\n };\n\n const items = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"My Library\"\n description=\"Watchlists & playlists\"\n purpose=\"Your personal collections — a watchlist of what to watch next and curated playlists you can keep private, unlisted, or public.\"\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate((v) => !v)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n <Plus className=\"h-4 w-4\" />\n New List\n </button>\n }\n >\n {showCreate && (\n <GlassCard treatment=\"glass\" className=\"mb-6 border-border-seam p-5\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Create List</h2>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"rounded-md p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <div>\n <label htmlFor=\"list-name\" className={labelClass}>\n Name *\n </label>\n <input\n id=\"list-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Weekend Watch\"\n className={fieldClass}\n required\n />\n </div>\n <div>\n <label htmlFor=\"list-desc\" className={labelClass}>\n Description\n </label>\n <textarea\n id=\"list-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={fieldClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"list-kind\" className={labelClass}>\n Kind\n </label>\n <select\n id=\"list-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as HokListKind)}\n className={fieldClass}\n >\n {KIND_OPTIONS.map((k) => (\n <option key={k} value={k}>\n {k.charAt(0) + k.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"list-visibility\" className={labelClass}>\n Visibility\n </label>\n <select\n id=\"list-visibility\"\n value={visibility}\n onChange={(e) => setVisibility(e.target.value as HokListVisibility)}\n className={fieldClass}\n >\n {VISIBILITY_OPTIONS.map((v) => (\n <option key={v} value={v}>\n {v.charAt(0) + v.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n </div>\n <button\n type=\"submit\"\n disabled={createList.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n {createList.isPending ? 'Creating…' : 'Create List'}\n </button>\n </form>\n </GlassCard>\n )}\n\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <select\n value={kindFilter}\n onChange={(e) => setKindFilter(e.target.value as HokListKind | '')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">All Lists</option>\n {KIND_OPTIONS.map((k) => (\n <option key={k} value={k}>\n {k.charAt(0) + k.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingState label=\"Loading your lists…\" />\n ) : isError ? (\n <ErrorState onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<ListVideo className=\"h-12 w-12\" />}\n title=\"No lists yet\"\n description=\"Create a watchlist or playlist to organize what you watch.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((list) => {\n const VisibilityIcon = visibilityIcon[list.visibility];\n return (\n <GlassCard\n key={list.id}\n treatment=\"metric\"\n className=\"flex flex-col border-border-seam p-4\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"line-clamp-1 text-sm font-semibold text-text-primary\">\n {list.name}\n </h3>\n <button\n type=\"button\"\n disabled={deleteList.isPending}\n onClick={() => void deleteList.mutateAsync(list.id)}\n className=\"rounded-md p-1 text-text-secondary hover:text-status-error-text disabled:opacity-50 transition-colors\"\n aria-label=\"Delete list\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n {list.description && (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">\n {list.description}\n </p>\n )}\n <div className=\"mt-3 flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {list.kind.charAt(0) + list.kind.slice(1).toLowerCase()}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <VisibilityIcon className=\"h-3 w-3\" />\n {list.visibility.charAt(0) + list.visibility.slice(1).toLowerCase()}\n </span>\n <span>{list.itemCount} items</span>\n </div>\n <p className=\"mt-2 text-[11px] text-text-muted\">\n Updated {formatRelativeTime(list.updatedAt)}\n </p>\n </GlassCard>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ListsPage.js","names":[],"sources":["../../../../src/hookmovies/pages/library/ListsPage.tsx"],"sourcesContent":["/**\n * My Lists (Library — self-scoped)\n * @module hookmovies/pages/library\n *\n * The caller's own watchlists and playlists via `hokMyLists`, created with\n * `hokCreateList` and removed with `hokDeleteList`. All self-scoped to the\n * calling user by the subgraph.\n */\n\nimport { type FC, type FormEvent, useState } from 'react';\nimport { ListVideo, Plus, X, Trash2, Lock, Globe, Link as LinkIcon } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport {\n useMyLists,\n useCreateList,\n useDeleteList,\n type HokListKind,\n type HokListVisibility,\n type CreateListInput,\n} from '../../hooks/useHookMoviesApi';\nimport { formatRelativeTime } from '../../utils/formatters';\n\nconst KIND_OPTIONS: HokListKind[] = ['WATCHLIST', 'PLAYLIST'];\nconst VISIBILITY_OPTIONS: HokListVisibility[] = ['PRIVATE', 'UNLISTED', 'PUBLIC'];\nconst fieldClass =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelClass = 'mb-1.5 block text-xs font-medium text-text-secondary';\n\nconst visibilityIcon: Record<HokListVisibility, typeof Lock> = {\n PRIVATE: Lock,\n UNLISTED: LinkIcon,\n PUBLIC: Globe,\n};\n\nexport const ListsPage: FC = () => {\n const [kindFilter, setKindFilter] = useState<HokListKind | ''>('');\n const [showCreate, setShowCreate] = useState(false);\n\n const { data, isLoading, isError, refetch } = useMyLists(kindFilter || undefined, {\n limit: 100,\n offset: 0,\n });\n const createList = useCreateList();\n const deleteList = useDeleteList();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [kind, setKind] = useState<HokListKind>('WATCHLIST');\n const [visibility, setVisibility] = useState<HokListVisibility>('PRIVATE');\n const [error, setError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const handleDelete = async (id: string) => {\n setActionError(null);\n try {\n await deleteList.mutateAsync(id);\n } catch (err) {\n setActionError(\n err instanceof Error ? err.message : 'Failed to delete list (you can only delete your own)'\n );\n }\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!name.trim()) {\n setError('Name is required.');\n return;\n }\n const input: CreateListInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n kind,\n visibility,\n };\n try {\n await createList.mutateAsync(input);\n setName('');\n setDescription('');\n setKind('WATCHLIST');\n setVisibility('PRIVATE');\n setShowCreate(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create list');\n }\n };\n\n const items = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"My Library\"\n description=\"Watchlists & playlists\"\n purpose=\"Your personal collections — a watchlist of what to watch next and curated playlists you can keep private, unlisted, or public.\"\n actions={\n <button\n type=\"button\"\n onClick={() => setShowCreate((v) => !v)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n <Plus className=\"h-4 w-4\" />\n New List\n </button>\n }\n >\n {showCreate && (\n <GlassCard treatment=\"glass\" className=\"mb-6 border-border-seam p-5\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Create List</h2>\n <button\n type=\"button\"\n onClick={() => setShowCreate(false)}\n className=\"rounded-md p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <div>\n <label htmlFor=\"list-name\" className={labelClass}>\n Name *\n </label>\n <input\n id=\"list-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Weekend Watch\"\n className={fieldClass}\n required\n />\n </div>\n <div>\n <label htmlFor=\"list-desc\" className={labelClass}>\n Description\n </label>\n <textarea\n id=\"list-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={fieldClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"list-kind\" className={labelClass}>\n Kind\n </label>\n <select\n id=\"list-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as HokListKind)}\n className={fieldClass}\n >\n {KIND_OPTIONS.map((k) => (\n <option key={k} value={k}>\n {k.charAt(0) + k.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"list-visibility\" className={labelClass}>\n Visibility\n </label>\n <select\n id=\"list-visibility\"\n value={visibility}\n onChange={(e) => setVisibility(e.target.value as HokListVisibility)}\n className={fieldClass}\n >\n {VISIBILITY_OPTIONS.map((v) => (\n <option key={v} value={v}>\n {v.charAt(0) + v.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n </div>\n <button\n type=\"submit\"\n disabled={createList.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n {createList.isPending ? 'Creating…' : 'Create List'}\n </button>\n </form>\n </GlassCard>\n )}\n\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <select\n value={kindFilter}\n onChange={(e) => setKindFilter(e.target.value as HokListKind | '')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">All Lists</option>\n {KIND_OPTIONS.map((k) => (\n <option key={k} value={k}>\n {k.charAt(0) + k.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </div>\n\n {actionError && (\n <div className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {isLoading ? (\n <LoadingState label=\"Loading your lists…\" />\n ) : isError ? (\n <ErrorState onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<ListVideo className=\"h-12 w-12\" />}\n title=\"No lists yet\"\n description=\"Create a watchlist or playlist to organize what you watch.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((list) => {\n const VisibilityIcon = visibilityIcon[list.visibility];\n return (\n <GlassCard\n key={list.id}\n treatment=\"metric\"\n className=\"flex flex-col border-border-seam p-4\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"line-clamp-1 text-sm font-semibold text-text-primary\">\n {list.name}\n </h3>\n <button\n type=\"button\"\n disabled={deleteList.isPending}\n onClick={() => void handleDelete(list.id)}\n className=\"rounded-md p-1 text-text-secondary hover:text-status-error-text disabled:opacity-50 transition-colors\"\n aria-label=\"Delete list\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n {list.description && (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">\n {list.description}\n </p>\n )}\n <div className=\"mt-3 flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {list.kind.charAt(0) + list.kind.slice(1).toLowerCase()}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <VisibilityIcon className=\"h-3 w-3\" />\n {list.visibility.charAt(0) + list.visibility.slice(1).toLowerCase()}\n </span>\n <span>{list.itemCount} items</span>\n </div>\n <p className=\"mt-2 text-[11px] text-text-muted\">\n Updated {formatRelativeTime(list.updatedAt)}\n </p>\n </GlassCard>\n );\n })}\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductionDetailPage.js","names":[],"sources":["../../../../src/hookmovies/pages/productions/ProductionDetailPage.tsx"],"sourcesContent":["/**\n * Production detail (Production lifecycle)\n * @module hookmovies/pages/productions\n *\n * Reads one production via `hokProduction` (with auditions), advances its\n * lifecycle with `hokTransitionProduction`, and links into the casting auditions\n * the production owns.\n */\n\nimport { type FC, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Calendar,\n Coins,\n Megaphone,\n ArrowRight,\n Link2,\n Plus,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useProduction,\n useTransitionProduction,\n type HokProductionStatus,\n} from '../../hooks/useHookMoviesApi';\nimport { formatDate, formatEnumLabel, formatMinorUnits } from '../../utils/formatters';\n\nconst NEXT_STATUSES: Record<HokProductionStatus, HokProductionStatus[]> = {\n DEVELOPMENT: ['GREENLIT', 'SHELVED'],\n GREENLIT: ['IN_PRODUCTION', 'SHELVED'],\n IN_PRODUCTION: ['POST_PRODUCTION', 'SHELVED'],\n POST_PRODUCTION: ['RELEASED', 'SHELVED'],\n RELEASED: [],\n SHELVED: ['DEVELOPMENT'],\n};\n\nexport const ProductionDetailPage: FC = () => {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useHookMovies();\n const { data: production, isLoading, isError, refetch } = useProduction(id);\n const transition = useTransitionProduction();\n const [actionError, setActionError] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Production\">\n <LoadingState label=\"Loading production…\" />\n </PageLayout>\n );\n }\n\n if (isError || !production) {\n return (\n <PageLayout title=\"Production\">\n <ErrorState title=\"Production not found\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n const handleTransition = async (status: HokProductionStatus) => {\n setActionError(null);\n try {\n await transition.mutateAsync({ id: production.id, status });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Transition failed');\n }\n };\n\n return (\n <PageLayout\n title={\n <span className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigateTo('productions')}\n className=\"rounded-md p-1 text-text-secondary hover:text-text-primary\"\n aria-label=\"Back to productions\"\n >\n <ArrowLeft className=\"h-5 w-5\" />\n </button>\n {production.title}\n </span>\n }\n description={production.logline ?? 'Production'}\n actions={<StatusBadge status={production.status} />}\n >\n {actionError && (\n <div className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-6 lg:col-span-2\">\n {production.synopsis && (\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Synopsis</h2>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{production.synopsis}</p>\n </GlassCard>\n )}\n\n {/* Auditions */}\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Megaphone className=\"h-4 w-4\" />\n Auditions ({production.auditions.length})\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo(`auditions/new?productionId=${production.id}`)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Post Role\n </button>\n </div>\n {production.auditions.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No open audition roles for this production.</p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {production.auditions.map((aud) => (\n <li key={aud.id}>\n <button\n type=\"button\"\n onClick={() => navigateTo(`auditions/${aud.id}`)}\n className=\"flex w-full items-center justify-between gap-3 py-3 text-left hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {aud.roleName}\n {aud.isMinorRole && (\n <span className=\"ml-2 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] text-status-warning-text\">\n Minor role\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatMinorUnits(aud.payMinorUnits, aud.currency)}\n {aud.location ? ` · ${aud.location}` : ''}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={aud.status} />\n <ArrowRight className=\"h-4 w-4 text-text-secondary\" />\n </div>\n </button>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n\n <div className=\"space-y-6\">\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Advance Lifecycle</h2>\n <div className=\"flex flex-wrap gap-2\">\n {NEXT_STATUSES[production.status].map((status) => (\n <button\n key={status}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => void handleTransition(status)}\n className=\"rounded-lg bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n {formatEnumLabel(status)}\n </button>\n ))}\n {NEXT_STATUSES[production.status].length === 0 && (\n <p className=\"text-xs text-text-secondary\">No further transitions available.</p>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h2>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex items-center justify-between\">\n <dt className=\"flex items-center gap-2 text-text-secondary\">\n <Coins className=\"h-3.5 w-3.5\" />\n Budget\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatMinorUnits(production.budgetMinorUnits, production.currency)}\n </dd>\n </div>\n {production.startDate && (\n <div className=\"flex items-center justify-between\">\n <dt className=\"flex items-center gap-2 text-text-secondary\">\n <Calendar className=\"h-3.5 w-3.5\" />\n Start\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDate(production.startDate, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n )}\n {production.endDate && (\n <div className=\"flex items-center justify-between\">\n <dt className=\"flex items-center gap-2 text-text-secondary\">\n <Calendar className=\"h-3.5 w-3.5\" />\n End\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDate(production.endDate, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n )}\n </dl>\n\n {production.resultingTitleId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`catalog/titles/${production.resultingTitleId}`)}\n className=\"mt-3 inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:opacity-80\"\n >\n <Link2 className=\"h-3.5 w-3.5\" />\n View resulting title\n </button>\n )}\n {production.artistrybaseProjectRef && (\n <p className=\"mt-3 text-xs text-text-secondary\">\n Promoted from Artistrybase project{' '}\n <span className=\"font-mono\">{production.artistrybaseProjectRef}</span>\n </p>\n )}\n </GlassCard>\n\n {production.tags.length > 0 && (\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Tags\n </h3>\n <div className=\"flex flex-wrap gap-1.5\">\n {production.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </GlassCard>\n )}\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductionDetailPage.js","names":[],"sources":["../../../../src/hookmovies/pages/productions/ProductionDetailPage.tsx"],"sourcesContent":["/**\n * Production detail (Production lifecycle)\n * @module hookmovies/pages/productions\n *\n * Reads one production via `hokProduction` (with auditions), advances its\n * lifecycle with `hokTransitionProduction`, and links into the casting auditions\n * the production owns.\n */\n\nimport { type FC, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Calendar, Coins, Megaphone, ArrowRight, Link2, Plus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useProduction,\n useTransitionProduction,\n type HokProductionStatus,\n} from '../../hooks/useHookMoviesApi';\nimport { formatDate, formatEnumLabel, formatMinorUnits } from '../../utils/formatters';\n\nconst NEXT_STATUSES: Record<HokProductionStatus, HokProductionStatus[]> = {\n DEVELOPMENT: ['GREENLIT', 'SHELVED'],\n GREENLIT: ['IN_PRODUCTION', 'SHELVED'],\n IN_PRODUCTION: ['POST_PRODUCTION', 'SHELVED'],\n POST_PRODUCTION: ['RELEASED', 'SHELVED'],\n RELEASED: [],\n SHELVED: ['DEVELOPMENT'],\n};\n\nexport const ProductionDetailPage: FC = () => {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useHookMovies();\n const { data: production, isLoading, isError, refetch } = useProduction(id);\n const transition = useTransitionProduction();\n const [actionError, setActionError] = useState<string | null>(null);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Production\">\n <LoadingState label=\"Loading production…\" />\n </PageLayout>\n );\n }\n\n if (isError || !production) {\n return (\n <PageLayout title=\"Production\">\n <ErrorState title=\"Production not found\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n const handleTransition = async (status: HokProductionStatus) => {\n setActionError(null);\n try {\n await transition.mutateAsync({ id: production.id, status });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Transition failed');\n }\n };\n\n return (\n <PageLayout\n title={\n <span className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigateTo('productions')}\n className=\"rounded-md p-1 text-text-secondary hover:text-text-primary\"\n aria-label=\"Back to productions\"\n >\n <ArrowLeft className=\"h-5 w-5\" />\n </button>\n {production.title}\n </span>\n }\n description={production.logline ?? 'Production'}\n actions={<StatusBadge status={production.status} />}\n >\n {actionError && (\n <div className=\"mb-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-6 lg:col-span-2\">\n {production.synopsis && (\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Synopsis</h2>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{production.synopsis}</p>\n </GlassCard>\n )}\n\n {/* Auditions */}\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Megaphone className=\"h-4 w-4\" />\n Auditions ({production.auditions.length})\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo(`auditions/new?productionId=${production.id}`)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Post Role\n </button>\n </div>\n {production.auditions.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No open audition roles for this production.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {production.auditions.map((aud) => (\n <li key={aud.id}>\n <button\n type=\"button\"\n onClick={() => navigateTo(`auditions/${aud.id}`)}\n className=\"flex w-full items-center justify-between gap-3 py-3 text-left hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {aud.roleName}\n {aud.isMinorRole && (\n <span className=\"ml-2 rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] text-status-warning-text\">\n Minor role\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {formatMinorUnits(aud.payMinorUnits, aud.currency)}\n {aud.location ? ` · ${aud.location}` : ''}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={aud.status} />\n <ArrowRight className=\"h-4 w-4 text-text-secondary\" />\n </div>\n </button>\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n </div>\n\n <div className=\"space-y-6\">\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Advance Lifecycle</h2>\n <div className=\"flex flex-wrap gap-2\">\n {NEXT_STATUSES[production.status].map((status) => (\n <button\n key={status}\n type=\"button\"\n disabled={transition.isPending}\n onClick={() => void handleTransition(status)}\n className=\"rounded-lg bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50 transition-opacity\"\n >\n {formatEnumLabel(status)}\n </button>\n ))}\n {NEXT_STATUSES[production.status].length === 0 && (\n <p className=\"text-xs text-text-secondary\">No further transitions available.</p>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Details</h2>\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex items-center justify-between\">\n <dt className=\"flex items-center gap-2 text-text-secondary\">\n <Coins className=\"h-3.5 w-3.5\" />\n Budget\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatMinorUnits(production.budgetMinorUnits, production.currency)}\n </dd>\n </div>\n {production.startDate && (\n <div className=\"flex items-center justify-between\">\n <dt className=\"flex items-center gap-2 text-text-secondary\">\n <Calendar className=\"h-3.5 w-3.5\" />\n Start\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDate(production.startDate, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n )}\n {production.endDate && (\n <div className=\"flex items-center justify-between\">\n <dt className=\"flex items-center gap-2 text-text-secondary\">\n <Calendar className=\"h-3.5 w-3.5\" />\n End\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatDate(production.endDate, { hour: undefined, minute: undefined })}\n </dd>\n </div>\n )}\n </dl>\n\n {production.resultingTitleId && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`catalog/titles/${production.resultingTitleId}`)}\n className=\"mt-3 inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:opacity-80\"\n >\n <Link2 className=\"h-3.5 w-3.5\" />\n View resulting title\n </button>\n )}\n {production.artistrybaseProjectRef && (\n <p className=\"mt-3 text-xs text-text-secondary\">\n Promoted from Artistrybase project{' '}\n <span className=\"font-mono\">{production.artistrybaseProjectRef}</span>\n </p>\n )}\n </GlassCard>\n\n {production.tags.length > 0 && (\n <GlassCard treatment=\"metric\" className=\"border-border-seam p-5\">\n <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Tags\n </h3>\n <div className=\"flex flex-wrap gap-1.5\">\n {production.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n </GlassCard>\n )}\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductionsPage.js","names":[],"sources":["../../../../src/hookmovies/pages/productions/ProductionsPage.tsx"],"sourcesContent":["/**\n * Productions list (Production lifecycle)\n * @module hookmovies/pages/productions\n *\n * Lists the B2B production pipeline via `hokListProductions` with status filter\n * and offset pagination. Rows link to the production detail page.\n */\n\nimport { type FC, useState } from 'react';\nimport { Clapperboard, Plus } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useProductions,\n type HokProductionStatus,\n} from '../../hooks/useHookMoviesApi';\nimport { formatEnumLabel, formatMinorUnits, formatDate } from '../../utils/formatters';\n\nconst STATUS_OPTIONS: HokProductionStatus[] = [\n 'DEVELOPMENT',\n 'GREENLIT',\n 'IN_PRODUCTION',\n 'POST_PRODUCTION',\n 'RELEASED',\n 'SHELVED',\n];\n\nconst PAGE_LIMIT = 20;\n\nexport const ProductionsPage: FC = () => {\n const { navigateTo } = useHookMovies();\n const [statusFilter, setStatusFilter] = useState<HokProductionStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const { data, isLoading, isError, refetch } = useProductions(\n { status: statusFilter || undefined },\n { limit: PAGE_LIMIT, offset: page * PAGE_LIMIT }\n );\n\n const items = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Productions\"\n description=\"Development → release lifecycle\"\n purpose=\"Track every project from development through release. Each production carries budget, schedule, and casting, and can result in a published catalog title.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('productions/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n <Plus className=\"h-4 w-4\" />\n New Production\n </button>\n }\n >\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as HokProductionStatus | '');\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Status</option>\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingState label=\"Loading productions…\" />\n ) : isError ? (\n <ErrorState onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Clapperboard className=\"h-12 w-12\" />}\n title=\"No productions yet\"\n description=\"Start a production to plan budget, schedule, and casting.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('productions/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n New Production\n </button>\n }\n />\n ) : (\n <div className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">Title</th>\n <th className=\"px-4 py-3 font-medium\">Status</th>\n <th className=\"px-4 py-3 font-medium\">Budget</th>\n <th className=\"hidden px-4 py-3 font-medium md:table-cell\">Start</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {items.map((prod) => (\n <tr\n key={prod.id}\n onClick={() => navigateTo(`productions/${prod.id}`)}\n className=\"cursor-pointer hover:bg-bg-sunken transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <p className=\"font-medium text-text-primary\">{prod.title}</p>\n {prod.logline && (\n <p className=\"line-clamp-1 text-xs text-text-secondary\">{prod.logline}</p>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge status={prod.status} />\n </td>\n <td className=\"px-4 py-3 tabular-nums text-text-secondary\">\n {formatMinorUnits(prod.budgetMinorUnits, prod.currency)}\n </td>\n <td className=\"hidden px-4 py-3 text-text-secondary md:table-cell\">\n {prod.startDate ? formatDate(prod.startDate, { hour: undefined, minute: undefined }) : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n\n {!isLoading && !isError && (\n <Pagination\n page={page}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={setPage}\n shownCount={items.length}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductionsPage.js","names":[],"sources":["../../../../src/hookmovies/pages/productions/ProductionsPage.tsx"],"sourcesContent":["/**\n * Productions list (Production lifecycle)\n * @module hookmovies/pages/productions\n *\n * Lists the B2B production pipeline via `hokListProductions` with status filter\n * and offset pagination. Rows link to the production detail page.\n */\n\nimport { type FC, useState } from 'react';\nimport { Clapperboard, Plus } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport { useProductions, type HokProductionStatus } from '../../hooks/useHookMoviesApi';\nimport { formatEnumLabel, formatMinorUnits, formatDate } from '../../utils/formatters';\n\nconst STATUS_OPTIONS: HokProductionStatus[] = [\n 'DEVELOPMENT',\n 'GREENLIT',\n 'IN_PRODUCTION',\n 'POST_PRODUCTION',\n 'RELEASED',\n 'SHELVED',\n];\n\nconst PAGE_LIMIT = 20;\n\nexport const ProductionsPage: FC = () => {\n const { navigateTo } = useHookMovies();\n const [statusFilter, setStatusFilter] = useState<HokProductionStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const { data, isLoading, isError, refetch } = useProductions(\n { status: statusFilter || undefined },\n { limit: PAGE_LIMIT, offset: page * PAGE_LIMIT }\n );\n\n const items = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Productions\"\n description=\"Development → release lifecycle\"\n purpose=\"Track every project from development through release. Each production carries budget, schedule, and casting, and can result in a published catalog title.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('productions/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n <Plus className=\"h-4 w-4\" />\n New Production\n </button>\n }\n >\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as HokProductionStatus | '');\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Status</option>\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingState label=\"Loading productions…\" />\n ) : isError ? (\n <ErrorState onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Clapperboard className=\"h-12 w-12\" />}\n title=\"No productions yet\"\n description=\"Start a production to plan budget, schedule, and casting.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('productions/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n New Production\n </button>\n }\n />\n ) : (\n <div className=\"overflow-hidden rounded-lg border border-border-seam bg-bg-surface\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">Title</th>\n <th className=\"px-4 py-3 font-medium\">Status</th>\n <th className=\"px-4 py-3 font-medium\">Budget</th>\n <th className=\"hidden px-4 py-3 font-medium md:table-cell\">Start</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {items.map((prod) => (\n <tr\n key={prod.id}\n onClick={() => navigateTo(`productions/${prod.id}`)}\n className=\"cursor-pointer hover:bg-bg-sunken transition-colors\"\n >\n <td className=\"px-4 py-3\">\n <p className=\"font-medium text-text-primary\">{prod.title}</p>\n {prod.logline && (\n <p className=\"line-clamp-1 text-xs text-text-secondary\">{prod.logline}</p>\n )}\n </td>\n <td className=\"px-4 py-3\">\n <StatusBadge status={prod.status} />\n </td>\n <td className=\"px-4 py-3 tabular-nums text-text-secondary\">\n {formatMinorUnits(prod.budgetMinorUnits, prod.currency)}\n </td>\n <td className=\"hidden px-4 py-3 text-text-secondary md:table-cell\">\n {prod.startDate\n ? formatDate(prod.startDate, { hour: undefined, minute: undefined })\n : '—'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n\n {!isLoading && !isError && (\n <Pagination\n page={page}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={setPage}\n shownCount={items.length}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.js","names":[],"sources":["../../../src/hookmovies/utils/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for the HookMovies microfrontend\n * @module hookmovies/utils/formatters\n */\n\n/**\n * Format a number as Indian Rupees (INR) currency\n *\n * @param amount - The numeric amount to format\n * @param options - Optional Intl.NumberFormat options to override defaults\n * @returns Formatted currency string (e.g., \"₹1,200.00\")\n *\n * @example\n * formatCurrency(1200) // => \"₹1,200.00\"\n * formatCurrency(0) // => \"₹0.00\"\n * formatCurrency(1200, { minimumFractionDigits: 0 }) // => \"₹1,200\"\n */\nexport function formatCurrency(\n amount: number,\n options?: Partial<Intl.NumberFormatOptions>\n): string {\n return new Intl.NumberFormat('en-IN', {\n style: 'currency',\n currency: 'INR',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n ...options,\n }).format(amount);\n}\n\n/**\n * Format a date string or Date object into a human-readable format\n *\n * @param date - ISO date string or Date object\n * @param options - Optional Intl.DateTimeFormat options\n * @returns Formatted date string (e.g., \"15 Jan 2024, 4:00 PM\")\n *\n * @example\n * formatDate('2024-01-15T10:30:00Z')\n * // => \"15 Jan 2024, 4:00 PM\" (in IST)\n */\nexport function formatDate(\n date: string | Date,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n return new Intl.DateTimeFormat('en-IN', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n ...options,\n }).format(d);\n}\n\n/**\n * Format a date into a relative time string (e.g., \"2 hours ago\")\n *\n * @param date - ISO date string or Date object\n * @returns Human-readable relative time string\n *\n * @example\n * formatRelativeTime('2024-01-15T10:30:00Z') // => \"3 days ago\"\n * formatRelativeTime(new Date()) // => \"just now\"\n */\nexport function formatRelativeTime(date: string | Date): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = new Date();\n const diffMs = now.getTime() - d.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n\n if (diffSeconds < 60) return 'just now';\n if (diffMinutes < 60) return `${diffMinutes} min${diffMinutes !== 1 ? 's' : ''} ago`;\n if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;\n if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`;\n if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`;\n\n const diffYears = Math.floor(diffDays / 365);\n return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`;\n}\n\n/**\n * Format a number with Indian locale grouping (lakhs, crores)\n *\n * @param value - The number to format\n * @param options - Optional Intl.NumberFormat options\n * @returns Formatted number string (e.g., \"12,345\" or \"1,24,500\")\n *\n * @example\n * formatNumber(1245) // => \"1,245\"\n * formatNumber(124500) // => \"1,24,500\"\n */\nexport function formatNumber(value: number, options?: Partial<Intl.NumberFormatOptions>): string {\n return new Intl.NumberFormat('en-IN', options).format(value);\n}\n\n/**\n * Format a distance value in kilometers\n *\n * @param km - Distance in kilometers\n * @returns Formatted distance string\n *\n * @example\n * formatDistance(0.5) // => \"500 m\"\n * formatDistance(1.2) // => \"1.2 km\"\n * formatDistance(15) // => \"15 km\"\n */\nexport function formatDistance(km: number): string {\n if (km < 1) {\n return `${Math.round(km * 1000)} m`;\n }\n if (km < 10) {\n return `${km.toFixed(1)} km`;\n }\n return `${Math.round(km)} km`;\n}\n\n/**\n * Format a duration in minutes into a human-readable string\n *\n * @param minutes - Duration in minutes\n * @returns Formatted duration string\n *\n * @example\n * formatDuration(5) // => \"5 min\"\n * formatDuration(90) // => \"1h 30min\"\n * formatDuration(1440) // => \"1 day\"\n * formatDuration(4320) // => \"3 days\"\n */\nexport function formatDuration(minutes: number): string {\n if (minutes < 1) return 'instant';\n if (minutes < 60) return `${minutes} min`;\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n if (hours < 24) {\n if (remainingMinutes === 0) {\n return `${hours}h`;\n }\n return `${hours}h ${remainingMinutes}min`;\n }\n\n const days = Math.floor(hours / 24);\n const remainingHours = hours % 24;\n\n if (remainingHours === 0) {\n return `${days} day${days !== 1 ? 's' : ''}`;\n }\n return `${days} day${days !== 1 ? 's' : ''} ${remainingHours}h`;\n}\n\n/**\n * Format a SCREAMING_SNAKE_CASE enum value into Title Case for display.\n *\n * @example\n * formatEnumLabel('IN_PRODUCTION') // => \"In Production\"\n * formatEnumLabel('TVOD_RENT') // => \"Tvod Rent\"\n */\nexport function formatEnumLabel(value: string): string {\n return value\n .toLowerCase()\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n/**\n * Format a \"minor units\" money string (e.g. cents) into a localized currency\n * string. The HookMovies subgraph stores money as minor-unit integers in a\n * string (to avoid precision loss); divide by 100 to get the major amount.\n *\n * @param minorUnits - Integer amount in minor units, as a string (may be \"\").\n * @param currency - ISO 4217 currency code (defaults to USD).\n *\n * @example\n * formatMinorUnits('1999', 'USD') // => \"$19.99\"\n * formatMinorUnits('', 'USD') // => \"—\"\n */\nexport function formatMinorUnits(\n minorUnits: string | null | undefined,\n currency = 'USD'\n): string {\n if (minorUnits === null || minorUnits === undefined || minorUnits.trim() === '') {\n return '—';\n }\n const parsed = Number(minorUnits);\n if (!Number.isFinite(parsed)) {\n return '—';\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: currency || 'USD',\n }).format(parsed / 100);\n } catch {\n // Unknown currency code — fall back to a plain decimal.\n return `${(parsed / 100).toFixed(2)} ${currency}`;\n }\n}\n"],"mappings":";AAiBA,SAAgB,EACd,GACA,GACQ;AACR,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;EACV,uBAAuB;EACvB,uBAAuB;EACvB,GAAG;EACJ,CAAC,CAAC,OAAO,EAAO;;AAcnB,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG;AACtD,QAAO,IAAI,KAAK,eAAe,SAAS;EACtC,KAAK;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,GAAG;EACJ,CAAC,CAAC,OAAO,EAAE;;AAad,SAAgB,EAAmB,GAA6B;CAC9D,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG,GAEhD,qBADM,IAAI,MAAM,EACH,SAAS,GAAG,EAAE,SAAS,EACpC,IAAc,KAAK,MAAM,IAAS,IAAK,EACvC,IAAc,KAAK,MAAM,IAAc,GAAG,EAC1C,IAAY,KAAK,MAAM,IAAc,GAAG,EACxC,IAAW,KAAK,MAAM,IAAY,GAAG,EACrC,IAAY,KAAK,MAAM,IAAW,EAAE,EACpC,IAAa,KAAK,MAAM,IAAW,GAAG;AAE5C,KAAI,IAAc,GAAI,QAAO;AAC7B,KAAI,IAAc,GAAI,QAAO,GAAG,EAAY,MAAM,MAAgB,IAAU,KAAN,IAAS;AAC/E,KAAI,IAAY,GAAI,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;AAC1E,KAAI,IAAW,EAAG,QAAO,GAAG,EAAS,MAAM,MAAa,IAAU,KAAN,IAAS;AACrE,KAAI,IAAY,EAAG,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;AACzE,KAAI,IAAa,GAAI,QAAO,GAAG,EAAW,QAAQ,MAAe,IAAU,KAAN,IAAS;CAE9E,IAAM,IAAY,KAAK,MAAM,IAAW,IAAI;AAC5C,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;;AAcxD,SAAgB,EAAa,GAAe,GAAqD;AAC/F,QAAO,IAAI,KAAK,aAAa,SAAS,EAAQ,CAAC,OAAO,EAAM;;AAkE9D,SAAgB,EAAgB,GAAuB;AACrD,QAAO,EACJ,aAAa,CACb,QAAQ,MAAM,IAAI,CAClB,QAAQ,UAAU,MAAS,EAAK,aAAa,CAAC;;AAenD,SAAgB,EACd,GACA,IAAW,OACH;AACR,KAAI,KAAe,QAAoC,EAAW,MAAM,KAAK,GAC3E,QAAO;CAET,IAAM,IAAS,OAAO,EAAW;AACjC,KAAI,CAAC,OAAO,SAAS,EAAO,CAC1B,QAAO;AAET,KAAI;AACF,SAAO,IAAI,KAAK,aAAa,KAAA,GAAW;GACtC,OAAO;GACP,UAAU,KAAY;GACvB,CAAC,CAAC,OAAO,IAAS,IAAI;SACjB;AAEN,SAAO,IAAI,IAAS,KAAK,QAAQ,EAAE,CAAC,GAAG"}
1
+ {"version":3,"file":"formatters.js","names":[],"sources":["../../../src/hookmovies/utils/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for the HookMovies microfrontend\n * @module hookmovies/utils/formatters\n */\n\n/**\n * Format a number as Indian Rupees (INR) currency\n *\n * @param amount - The numeric amount to format\n * @param options - Optional Intl.NumberFormat options to override defaults\n * @returns Formatted currency string (e.g., \"₹1,200.00\")\n *\n * @example\n * formatCurrency(1200) // => \"₹1,200.00\"\n * formatCurrency(0) // => \"₹0.00\"\n * formatCurrency(1200, { minimumFractionDigits: 0 }) // => \"₹1,200\"\n */\nexport function formatCurrency(\n amount: number,\n options?: Partial<Intl.NumberFormatOptions>\n): string {\n return new Intl.NumberFormat('en-IN', {\n style: 'currency',\n currency: 'INR',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n ...options,\n }).format(amount);\n}\n\n/**\n * Format a date string or Date object into a human-readable format\n *\n * @param date - ISO date string or Date object\n * @param options - Optional Intl.DateTimeFormat options\n * @returns Formatted date string (e.g., \"15 Jan 2024, 4:00 PM\")\n *\n * @example\n * formatDate('2024-01-15T10:30:00Z')\n * // => \"15 Jan 2024, 4:00 PM\" (in IST)\n */\nexport function formatDate(\n date: string | Date,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n return new Intl.DateTimeFormat('en-IN', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n ...options,\n }).format(d);\n}\n\n/**\n * Format a date into a relative time string (e.g., \"2 hours ago\")\n *\n * @param date - ISO date string or Date object\n * @returns Human-readable relative time string\n *\n * @example\n * formatRelativeTime('2024-01-15T10:30:00Z') // => \"3 days ago\"\n * formatRelativeTime(new Date()) // => \"just now\"\n */\nexport function formatRelativeTime(date: string | Date): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = new Date();\n const diffMs = now.getTime() - d.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n\n if (diffSeconds < 60) return 'just now';\n if (diffMinutes < 60) return `${diffMinutes} min${diffMinutes !== 1 ? 's' : ''} ago`;\n if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;\n if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`;\n if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`;\n\n const diffYears = Math.floor(diffDays / 365);\n return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`;\n}\n\n/**\n * Format a number with Indian locale grouping (lakhs, crores)\n *\n * @param value - The number to format\n * @param options - Optional Intl.NumberFormat options\n * @returns Formatted number string (e.g., \"12,345\" or \"1,24,500\")\n *\n * @example\n * formatNumber(1245) // => \"1,245\"\n * formatNumber(124500) // => \"1,24,500\"\n */\nexport function formatNumber(value: number, options?: Partial<Intl.NumberFormatOptions>): string {\n return new Intl.NumberFormat('en-IN', options).format(value);\n}\n\n/**\n * Format a distance value in kilometers\n *\n * @param km - Distance in kilometers\n * @returns Formatted distance string\n *\n * @example\n * formatDistance(0.5) // => \"500 m\"\n * formatDistance(1.2) // => \"1.2 km\"\n * formatDistance(15) // => \"15 km\"\n */\nexport function formatDistance(km: number): string {\n if (km < 1) {\n return `${Math.round(km * 1000)} m`;\n }\n if (km < 10) {\n return `${km.toFixed(1)} km`;\n }\n return `${Math.round(km)} km`;\n}\n\n/**\n * Format a duration in minutes into a human-readable string\n *\n * @param minutes - Duration in minutes\n * @returns Formatted duration string\n *\n * @example\n * formatDuration(5) // => \"5 min\"\n * formatDuration(90) // => \"1h 30min\"\n * formatDuration(1440) // => \"1 day\"\n * formatDuration(4320) // => \"3 days\"\n */\nexport function formatDuration(minutes: number): string {\n if (minutes < 1) return 'instant';\n if (minutes < 60) return `${minutes} min`;\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n if (hours < 24) {\n if (remainingMinutes === 0) {\n return `${hours}h`;\n }\n return `${hours}h ${remainingMinutes}min`;\n }\n\n const days = Math.floor(hours / 24);\n const remainingHours = hours % 24;\n\n if (remainingHours === 0) {\n return `${days} day${days !== 1 ? 's' : ''}`;\n }\n return `${days} day${days !== 1 ? 's' : ''} ${remainingHours}h`;\n}\n\n/**\n * Format a SCREAMING_SNAKE_CASE enum value into Title Case for display.\n *\n * @example\n * formatEnumLabel('IN_PRODUCTION') // => \"In Production\"\n * formatEnumLabel('TVOD_RENT') // => \"Tvod Rent\"\n */\nexport function formatEnumLabel(value: string): string {\n return value\n .toLowerCase()\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n/**\n * Format a \"minor units\" money string (e.g. cents) into a localized currency\n * string. The HookMovies subgraph stores money as minor-unit integers in a\n * string (to avoid precision loss); divide by 100 to get the major amount.\n *\n * @param minorUnits - Integer amount in minor units, as a string (may be \"\").\n * @param currency - ISO 4217 currency code (defaults to USD).\n *\n * @example\n * formatMinorUnits('1999', 'USD') // => \"$19.99\"\n * formatMinorUnits('', 'USD') // => \"—\"\n */\nexport function formatMinorUnits(minorUnits: string | null | undefined, currency = 'USD'): string {\n if (minorUnits === null || minorUnits === undefined || minorUnits.trim() === '') {\n return '—';\n }\n const parsed = Number(minorUnits);\n if (!Number.isFinite(parsed)) {\n return '—';\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: currency || 'USD',\n }).format(parsed / 100);\n } catch {\n // Unknown currency code — fall back to a plain decimal.\n return `${(parsed / 100).toFixed(2)} ${currency}`;\n }\n}\n"],"mappings":";AAiBA,SAAgB,EACd,GACA,GACQ;AACR,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;EACV,uBAAuB;EACvB,uBAAuB;EACvB,GAAG;EACJ,CAAC,CAAC,OAAO,EAAO;;AAcnB,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG;AACtD,QAAO,IAAI,KAAK,eAAe,SAAS;EACtC,KAAK;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,GAAG;EACJ,CAAC,CAAC,OAAO,EAAE;;AAad,SAAgB,EAAmB,GAA6B;CAC9D,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG,GAEhD,qBADM,IAAI,MAAM,EACH,SAAS,GAAG,EAAE,SAAS,EACpC,IAAc,KAAK,MAAM,IAAS,IAAK,EACvC,IAAc,KAAK,MAAM,IAAc,GAAG,EAC1C,IAAY,KAAK,MAAM,IAAc,GAAG,EACxC,IAAW,KAAK,MAAM,IAAY,GAAG,EACrC,IAAY,KAAK,MAAM,IAAW,EAAE,EACpC,IAAa,KAAK,MAAM,IAAW,GAAG;AAE5C,KAAI,IAAc,GAAI,QAAO;AAC7B,KAAI,IAAc,GAAI,QAAO,GAAG,EAAY,MAAM,MAAgB,IAAU,KAAN,IAAS;AAC/E,KAAI,IAAY,GAAI,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;AAC1E,KAAI,IAAW,EAAG,QAAO,GAAG,EAAS,MAAM,MAAa,IAAU,KAAN,IAAS;AACrE,KAAI,IAAY,EAAG,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;AACzE,KAAI,IAAa,GAAI,QAAO,GAAG,EAAW,QAAQ,MAAe,IAAU,KAAN,IAAS;CAE9E,IAAM,IAAY,KAAK,MAAM,IAAW,IAAI;AAC5C,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;;AAcxD,SAAgB,EAAa,GAAe,GAAqD;AAC/F,QAAO,IAAI,KAAK,aAAa,SAAS,EAAQ,CAAC,OAAO,EAAM;;AAkE9D,SAAgB,EAAgB,GAAuB;AACrD,QAAO,EACJ,aAAa,CACb,QAAQ,MAAM,IAAI,CAClB,QAAQ,UAAU,MAAS,EAAK,aAAa,CAAC;;AAenD,SAAgB,EAAiB,GAAuC,IAAW,OAAe;AAChG,KAAI,KAAe,QAAoC,EAAW,MAAM,KAAK,GAC3E,QAAO;CAET,IAAM,IAAS,OAAO,EAAW;AACjC,KAAI,CAAC,OAAO,SAAS,EAAO,CAC1B,QAAO;AAET,KAAI;AACF,SAAO,IAAI,KAAK,aAAa,KAAA,GAAW;GACtC,OAAO;GACP,UAAU,KAAY;GACvB,CAAC,CAAC,OAAO,IAAS,IAAI;SACjB;AAEN,SAAO,IAAI,IAAS,KAAK,QAAQ,EAAE,CAAC,GAAG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-hookmovies",
3
- "version": "2026.628.1",
3
+ "version": "2026.705.1",
4
4
  "description": "Burdenoff HookMovies Microfrontend - Mobile services marketplace platform for connecting service providers with customers",
5
5
  "type": "module",
6
6
  "files": [