@burdenoff/microfe-theculturepost 2026.1110.3 → 2026.1112.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.
@@ -12,12 +12,14 @@ import { ErrorView as d, LoadingView as f } from "../../components/StateViews.js
12
12
  import { Pagination as p } from "../../components/Pagination.js";
13
13
  import "../../hooks/index.js";
14
14
  import "../../context/index.js";
15
- import { useState as m } from "react";
16
- import { BookOpen as h, Plus as g } from "lucide-react";
17
- import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
18
- import { Button as b, Select as x, SelectContent as S, SelectItem as C, SelectTrigger as w, SelectValue as T } from "@burdenoff/fe-libs/ui";
15
+ import { resolveStoryPermissions as m, resolveStoryStatusFilter as h, visibleStoryStatusFilters as g } from "../../utils/storyVisibility.js";
16
+ import { useState as _ } from "react";
17
+ import { BookOpen as v, Plus as y } from "lucide-react";
18
+ import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
19
+ import { Button as C, Select as w, SelectContent as T, SelectItem as E, SelectTrigger as D, SelectValue as O } from "@burdenoff/fe-libs/ui";
20
+ import { useSafePermissions as k } from "@burdenoff/fe-libs/shared/providers/shell";
19
21
  // src/theculturepost/pages/stories/StoriesPage.tsx
20
- var E = 12, D = [
22
+ var A = 12, j = [
21
23
  {
22
24
  value: "ALL",
23
25
  label: "All"
@@ -47,91 +49,91 @@ var E = 12, D = [
47
49
  label: "Archived"
48
50
  }
49
51
  ];
50
- function O() {
51
- let { navigateTo: O } = e(), k = o(), A = a(), [j, M] = m("ALL"), [N, P] = m("ALL"), [F, I] = m(!1), L = i({ pagination: { limit: 100 } }), { data: R, isLoading: z, isError: B, refetch: V } = r({
52
- status: j === "ALL" || j === "FEATURED" ? void 0 : j,
53
- featured: j === "FEATURED" || void 0,
54
- topicId: N === "ALL" ? void 0 : N,
52
+ function M() {
53
+ let { navigateTo: M, workspaceId: N } = e(), P = o(), F = a(), I = k(), { canEdit: L, canCreate: R } = m(I, N), [z, B] = _(null), V = h(z, L), H = g(L), [U, W] = _("ALL"), [G, K] = _(!1), q = i({ pagination: { limit: 100 } }), { data: J, isLoading: Y, isError: X, refetch: Z } = r({
54
+ status: L ? V === "ALL" || V === "FEATURED" ? void 0 : V : "PUBLISHED",
55
+ featured: V === "FEATURED" || void 0,
56
+ topicId: U === "ALL" ? void 0 : U,
55
57
  pagination: {
56
- limit: E,
57
- cursor: A.cursor
58
+ limit: A,
59
+ cursor: F.cursor
58
60
  }
59
- }), H = R?.items ?? [];
60
- return /* @__PURE__ */ y(t, {
61
- title: k("stories.title", "Stories"),
62
- description: k("stories.description", "Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle."),
63
- actions: /* @__PURE__ */ y(b, {
64
- onClick: () => I(!0),
61
+ }), Q = J?.items ?? [];
62
+ return /* @__PURE__ */ S(t, {
63
+ title: P("stories.title", "Stories"),
64
+ description: P("stories.description", "Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle."),
65
+ actions: R && /* @__PURE__ */ S(C, {
66
+ onClick: () => K(!0),
65
67
  "data-tour": "theculturepost-stories-create",
66
68
  children: [
67
- /* @__PURE__ */ v(g, { className: "mr-1.5 h-4 w-4" }),
69
+ /* @__PURE__ */ x(y, { className: "mr-1.5 h-4 w-4" }),
68
70
  " ",
69
- k("stories.newStory", "New story")
71
+ P("stories.newStory", "New story")
70
72
  ]
71
73
  }),
72
74
  children: [
73
- /* @__PURE__ */ v(l, { className: "mb-6" }),
74
- /* @__PURE__ */ y("div", {
75
+ /* @__PURE__ */ x(l, { className: "mb-6" }),
76
+ /* @__PURE__ */ S("div", {
75
77
  className: "mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
76
78
  "data-tour": "theculturepost-stories-filters",
77
- children: [/* @__PURE__ */ v(n, {
78
- options: D,
79
- value: j,
79
+ children: [/* @__PURE__ */ x(n, {
80
+ options: j.filter((e) => H.includes(e.value)),
81
+ value: V,
80
82
  onChange: (e) => {
81
- M(e), A.reset();
83
+ B(e), F.reset();
82
84
  }
83
- }), /* @__PURE__ */ y(x, {
84
- value: N,
85
+ }), /* @__PURE__ */ S(w, {
86
+ value: U,
85
87
  onValueChange: (e) => {
86
- P(e), A.reset();
88
+ W(e), F.reset();
87
89
  },
88
- children: [/* @__PURE__ */ v(w, {
90
+ children: [/* @__PURE__ */ x(D, {
89
91
  className: "w-full sm:w-[13rem]",
90
92
  "aria-label": "Filter by topic",
91
- children: /* @__PURE__ */ v(T, { placeholder: "All topics" })
92
- }), /* @__PURE__ */ y(S, { children: [/* @__PURE__ */ v(C, {
93
+ children: /* @__PURE__ */ x(O, { placeholder: "All topics" })
94
+ }), /* @__PURE__ */ S(T, { children: [/* @__PURE__ */ x(E, {
93
95
  value: "ALL",
94
96
  children: "All topics"
95
- }), (L.data?.items ?? []).map((e) => /* @__PURE__ */ v(C, {
97
+ }), (q.data?.items ?? []).map((e) => /* @__PURE__ */ x(E, {
96
98
  value: e.id,
97
99
  children: e.name
98
100
  }, e.id))] })]
99
101
  })]
100
102
  }),
101
- z ? /* @__PURE__ */ v(f, { label: "Loading stories…" }) : B ? /* @__PURE__ */ v(d, {
103
+ Y ? /* @__PURE__ */ x(f, { label: "Loading stories…" }) : X ? /* @__PURE__ */ x(d, {
102
104
  message: "We couldn't load stories.",
103
- onRetry: () => V()
104
- }) : H.length === 0 && A.page === 0 ? /* @__PURE__ */ v(u, {
105
+ onRetry: () => Z()
106
+ }) : Q.length === 0 && F.page === 0 ? /* @__PURE__ */ x(u, {
105
107
  illustration: "empty-data",
106
- icon: /* @__PURE__ */ v(h, { className: "h-10 w-10" }),
107
- title: j === "ALL" && N === "ALL" ? "No stories yet" : "No stories match these filters",
108
- description: j === "ALL" && N === "ALL" ? "Draft the first story — it enters the editorial lifecycle you can publish from." : "Try widening the filters, or draft a new story.",
109
- action: /* @__PURE__ */ y(b, {
110
- onClick: () => I(!0),
111
- children: [/* @__PURE__ */ v(g, { className: "mr-1.5 h-4 w-4" }), " New story"]
108
+ icon: /* @__PURE__ */ x(v, { className: "h-10 w-10" }),
109
+ title: V === "ALL" && U === "ALL" ? "No stories yet" : "No stories match these filters",
110
+ description: V === "ALL" && U === "ALL" ? "Draft the first story — it enters the editorial lifecycle you can publish from." : "Try widening the filters, or draft a new story.",
111
+ action: R && /* @__PURE__ */ S(C, {
112
+ onClick: () => K(!0),
113
+ children: [/* @__PURE__ */ x(y, { className: "mr-1.5 h-4 w-4" }), " New story"]
112
114
  })
113
- }) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
115
+ }) : /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("div", {
114
116
  className: "grid gap-5 sm:grid-cols-2 lg:grid-cols-3",
115
- children: H.map((e) => /* @__PURE__ */ v(s, {
117
+ children: Q.map((e) => /* @__PURE__ */ x(s, {
116
118
  story: e,
117
- onOpen: () => O(`stories/${e.id}`)
119
+ onOpen: () => M(`stories/${e.id}`)
118
120
  }, e.id))
119
- }), /* @__PURE__ */ v(p, {
120
- page: A.page,
121
- pageSize: E,
122
- itemCount: H.length,
123
- hasMore: R?.pageInfo.hasMore ?? !1,
124
- onPageChange: (e) => A.onPageChange(e, R?.pageInfo.cursor)
121
+ }), /* @__PURE__ */ x(p, {
122
+ page: F.page,
123
+ pageSize: A,
124
+ itemCount: Q.length,
125
+ hasMore: J?.pageInfo.hasMore ?? !1,
126
+ onPageChange: (e) => F.onPageChange(e, J?.pageInfo.cursor)
125
127
  })] }),
126
- /* @__PURE__ */ v(c, {
127
- open: F,
128
- onOpenChange: I,
129
- topicId: N === "ALL" ? void 0 : N,
130
- onCreated: (e) => O(`stories/${e.id}`)
128
+ /* @__PURE__ */ x(c, {
129
+ open: R && G,
130
+ onOpenChange: K,
131
+ topicId: U === "ALL" ? void 0 : U,
132
+ onCreated: (e) => M(`stories/${e.id}`)
131
133
  })
132
134
  ]
133
135
  });
134
136
  }
135
- export { O as StoriesPage };
137
+ export { M as StoriesPage };
136
138
 
137
139
  //# sourceMappingURL=StoriesPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StoriesPage.js","names":["_Fragment","Fragment$1","PAGE_SIZE","STATUS_FILTERS","StoriesPage","useTheCulturePost","tr","useTr","pager","useCursorPager","status","setStatus","useState","topicId","setTopicId","createOpen","setCreateOpen","topicsQuery","useTcpTopics","useTcpStories","stories","data","PageLayout","Button","Plus","AiReviewQueue","FilterTabs","next","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","topic","isLoading","LoadingView","isError","ErrorView","refetch","EmptyState","BookOpen","StoryCard","navigateTo","story","Pagination","target","CreateStoryDialog"],"sources":["../../../../src/theculturepost/pages/stories/StoriesPage.tsx"],"sourcesContent":["/**\n * StoriesPage — browse the newsroom.\n * @module theculturepost/pages/stories\n *\n * Reads `tcpListStories` (filtered by status + optional topic). \"New Story\"\n * opens {@link CreateStoryDialog}. Cursor-paginated.\n */\n\nimport { useState } from 'react';\nimport { BookOpen, Plus } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { StoryCard } from '../../components/StoryCard';\nimport { CreateStoryDialog } from '../../components/CreateStoryDialog';\nimport { AiReviewQueue } from '../../components/AiContextPanel';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useTcpStories, useTcpTopics, useCursorPager, useTr } from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport type { TcpStoryStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = 'ALL' | 'FEATURED' | TcpStoryStatus;\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'FEATURED', label: 'Featured' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'IN_REVIEW', label: 'In Review' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IDEA', label: 'Idea' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function StoriesPage() {\n const { navigateTo } = useTheCulturePost();\n const tr = useTr();\n const pager = useCursorPager();\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [topicId, setTopicId] = useState<string>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const topicsQuery = useTcpTopics({ pagination: { limit: 100 } });\n const { data, isLoading, isError, refetch } = useTcpStories({\n status: status === 'ALL' || status === 'FEATURED' ? undefined : status,\n featured: status === 'FEATURED' ? true : undefined,\n topicId: topicId === 'ALL' ? undefined : topicId,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const stories = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const changeTopic = (next: string) => {\n setTopicId(next);\n pager.reset();\n };\n\n return (\n <PageLayout\n title={tr('stories.title', 'Stories')}\n description={tr(\n 'stories.description',\n 'Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle.'\n )}\n actions={\n <Button onClick={() => setCreateOpen(true)} data-tour=\"theculturepost-stories-create\">\n <Plus className=\"mr-1.5 h-4 w-4\" /> {tr('stories.newStory', 'New story')}\n </Button>\n }\n >\n <AiReviewQueue className=\"mb-6\" />\n\n <div\n className=\"mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"theculturepost-stories-filters\"\n >\n <FilterTabs options={STATUS_FILTERS} value={status} onChange={changeStatus} />\n <Select value={topicId} onValueChange={changeTopic}>\n <SelectTrigger className=\"w-full sm:w-[13rem]\" aria-label=\"Filter by topic\">\n <SelectValue placeholder=\"All topics\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All topics</SelectItem>\n {(topicsQuery.data?.items ?? []).map((topic) => (\n <SelectItem key={topic.id} value={topic.id}>\n {topic.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading stories…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load stories.\" onRetry={() => refetch()} />\n ) : stories.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BookOpen className=\"h-10 w-10\" />}\n title={\n status === 'ALL' && topicId === 'ALL'\n ? 'No stories yet'\n : 'No stories match these filters'\n }\n description={\n status === 'ALL' && topicId === 'ALL'\n ? 'Draft the first story — it enters the editorial lifecycle you can publish from.'\n : 'Try widening the filters, or draft a new story.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New story\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {stories.map((story) => (\n <StoryCard\n key={story.id}\n story={story}\n onOpen={() => navigateTo(`stories/${story.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={stories.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </>\n )}\n\n <CreateStoryDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n topicId={topicId === 'ALL' ? undefined : topicId}\n onCreated={(story) => navigateTo(`stories/${story.id}`)}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StoriesPage.js","names":["_Fragment","Fragment$1","PAGE_SIZE","STATUS_FILTERS","StoriesPage","useTheCulturePost","tr","useTr","pager","useCursorPager","permissions","useSafePermissions","resolveStoryPermissions","workspaceId","selectedStatus","setStatus","useState","status","resolveStoryStatusFilter","canEdit","visibleStatuses","visibleStoryStatusFilters","topicId","setTopicId","createOpen","setCreateOpen","topicsQuery","useTcpTopics","useTcpStories","stories","data","PageLayout","canCreate","Button","Plus","AiReviewQueue","FilterTabs","filter","next","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","topic","isLoading","LoadingView","isError","ErrorView","refetch","EmptyState","BookOpen","StoryCard","navigateTo","story","Pagination","target","CreateStoryDialog"],"sources":["../../../../src/theculturepost/pages/stories/StoriesPage.tsx"],"sourcesContent":["/**\n * StoriesPage — browse the newsroom.\n * @module theculturepost/pages/stories\n *\n * Reads `tcpListStories` (filtered by status + optional topic). \"New Story\"\n * opens {@link CreateStoryDialog}. Cursor-paginated.\n */\n\nimport { useState } from 'react';\nimport { useSafePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { BookOpen, Plus } from 'lucide-react';\nimport {\n Button,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { StoryCard } from '../../components/StoryCard';\nimport { CreateStoryDialog } from '../../components/CreateStoryDialog';\nimport { AiReviewQueue } from '../../components/AiContextPanel';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useTcpStories, useTcpTopics, useCursorPager, useTr } from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport {\n resolveStoryStatusFilter,\n resolveStoryPermissions,\n visibleStoryStatusFilters,\n type StoryStatusFilter,\n} from '../../utils/storyVisibility';\n\nconst PAGE_SIZE = 12;\n\ntype StatusFilter = StoryStatusFilter;\n\nconst STATUS_FILTERS: FilterOption<StatusFilter>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'FEATURED', label: 'Featured' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'IN_REVIEW', label: 'In Review' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IDEA', label: 'Idea' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function StoriesPage() {\n const { navigateTo, workspaceId } = useTheCulturePost();\n const tr = useTr();\n const pager = useCursorPager();\n const permissions = useSafePermissions();\n const { canEdit, canCreate } = resolveStoryPermissions(permissions, workspaceId);\n const [selectedStatus, setStatus] = useState<StatusFilter | null>(null);\n const status = resolveStoryStatusFilter(selectedStatus, canEdit);\n const visibleStatuses = visibleStoryStatusFilters(canEdit);\n const [topicId, setTopicId] = useState<string>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n\n const topicsQuery = useTcpTopics({ pagination: { limit: 100 } });\n const { data, isLoading, isError, refetch } = useTcpStories({\n status: !canEdit ? 'PUBLISHED' : status === 'ALL' || status === 'FEATURED' ? undefined : status,\n featured: status === 'FEATURED' ? true : undefined,\n topicId: topicId === 'ALL' ? undefined : topicId,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n const stories = data?.items ?? [];\n\n const changeStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const changeTopic = (next: string) => {\n setTopicId(next);\n pager.reset();\n };\n\n return (\n <PageLayout\n title={tr('stories.title', 'Stories')}\n description={tr(\n 'stories.description',\n 'Articles, essays, explainers, photo stories and timelines — across the full editorial lifecycle.'\n )}\n actions={\n canCreate && (\n <Button onClick={() => setCreateOpen(true)} data-tour=\"theculturepost-stories-create\">\n <Plus className=\"mr-1.5 h-4 w-4\" /> {tr('stories.newStory', 'New story')}\n </Button>\n )\n }\n >\n <AiReviewQueue className=\"mb-6\" />\n\n <div\n className=\"mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"theculturepost-stories-filters\"\n >\n <FilterTabs\n options={STATUS_FILTERS.filter((filter) => visibleStatuses.includes(filter.value))}\n value={status}\n onChange={changeStatus}\n />\n <Select value={topicId} onValueChange={changeTopic}>\n <SelectTrigger className=\"w-full sm:w-[13rem]\" aria-label=\"Filter by topic\">\n <SelectValue placeholder=\"All topics\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All topics</SelectItem>\n {(topicsQuery.data?.items ?? []).map((topic) => (\n <SelectItem key={topic.id} value={topic.id}>\n {topic.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading stories…\" />\n ) : isError ? (\n <ErrorView message=\"We couldn't load stories.\" onRetry={() => refetch()} />\n ) : stories.length === 0 && pager.page === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BookOpen className=\"h-10 w-10\" />}\n title={\n status === 'ALL' && topicId === 'ALL'\n ? 'No stories yet'\n : 'No stories match these filters'\n }\n description={\n status === 'ALL' && topicId === 'ALL'\n ? 'Draft the first story — it enters the editorial lifecycle you can publish from.'\n : 'Try widening the filters, or draft a new story.'\n }\n action={\n canCreate && (\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New story\n </Button>\n )\n }\n />\n ) : (\n <>\n <div className=\"grid gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {stories.map((story) => (\n <StoryCard\n key={story.id}\n story={story}\n onOpen={() => navigateTo(`stories/${story.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={stories.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo.cursor)}\n />\n </>\n )}\n\n <CreateStoryDialog\n open={canCreate && createOpen}\n onOpenChange={setCreateOpen}\n topicId={topicId === 'ALL' ? undefined : topicId}\n onCreated={(story) => navigateTo(`stories/${story.id}`)}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { TcpStoryStatus } from '../types';
2
+ export type StoryStatusFilter = 'ALL' | 'FEATURED' | TcpStoryStatus;
3
+ interface StoryPermissionState {
4
+ isLoading: boolean;
5
+ error: Error | null;
6
+ scopeId: string | null;
7
+ hasPermission: (permission: string) => boolean;
8
+ }
9
+ /** Cached grants from another scope, or an unsuccessful refresh, are not editorial authority. */
10
+ export declare function resolveStoryPermissions(permissions: StoryPermissionState | null, workspaceId: string | undefined): {
11
+ canEdit: boolean;
12
+ canCreate: boolean;
13
+ };
14
+ /** Fail closed while permissions load, and immediately after an editor grant is revoked. */
15
+ export declare function resolveStoryStatusFilter(selected: StoryStatusFilter | null, canEdit: boolean): StoryStatusFilter;
16
+ export declare function visibleStoryStatusFilters(canEdit: boolean): readonly StoryStatusFilter[];
17
+ export {};
@@ -0,0 +1,27 @@
1
+ // src/theculturepost/utils/storyVisibility.ts
2
+ function e(e, t) {
3
+ return !e || e.isLoading || e.error || !t || e.scopeId !== t ? {
4
+ canEdit: !1,
5
+ canCreate: !1
6
+ } : {
7
+ canEdit: e.hasPermission("theculturepost-story:update"),
8
+ canCreate: e.hasPermission("theculturepost-story:create")
9
+ };
10
+ }
11
+ function t(e, t) {
12
+ return t ? e ?? "ALL" : e === "FEATURED" ? "FEATURED" : "PUBLISHED";
13
+ }
14
+ function n(e) {
15
+ return e ? [
16
+ "ALL",
17
+ "FEATURED",
18
+ "PUBLISHED",
19
+ "IN_REVIEW",
20
+ "DRAFT",
21
+ "IDEA",
22
+ "ARCHIVED"
23
+ ] : ["PUBLISHED", "FEATURED"];
24
+ }
25
+ export { e as resolveStoryPermissions, t as resolveStoryStatusFilter, n as visibleStoryStatusFilters };
26
+
27
+ //# sourceMappingURL=storyVisibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storyVisibility.js","names":["resolveStoryPermissions","permissions","workspaceId","resolveStoryStatusFilter","canEdit","selected","visibleStoryStatusFilters"],"sources":["../../../src/theculturepost/utils/storyVisibility.ts"],"sourcesContent":["import type { TcpStoryStatus } from '../types';\n\nexport type StoryStatusFilter = 'ALL' | 'FEATURED' | TcpStoryStatus;\n\ninterface StoryPermissionState {\n isLoading: boolean;\n error: Error | null;\n scopeId: string | null;\n hasPermission: (permission: string) => boolean;\n}\n\n/** Cached grants from another scope, or an unsuccessful refresh, are not editorial authority. */\nexport function resolveStoryPermissions(\n permissions: StoryPermissionState | null,\n workspaceId: string | undefined\n): { canEdit: boolean; canCreate: boolean } {\n if (\n !permissions ||\n permissions.isLoading ||\n permissions.error ||\n !workspaceId ||\n permissions.scopeId !== workspaceId\n ) {\n return { canEdit: false, canCreate: false };\n }\n return {\n canEdit: permissions.hasPermission('theculturepost-story:update'),\n canCreate: permissions.hasPermission('theculturepost-story:create'),\n };\n}\n\n/** Fail closed while permissions load, and immediately after an editor grant is revoked. */\nexport function resolveStoryStatusFilter(\n selected: StoryStatusFilter | null,\n canEdit: boolean\n): StoryStatusFilter {\n if (canEdit) return selected ?? 'ALL';\n return selected === 'FEATURED' ? 'FEATURED' : 'PUBLISHED';\n}\n\nexport function visibleStoryStatusFilters(canEdit: boolean): readonly StoryStatusFilter[] {\n return canEdit\n ? ['ALL', 'FEATURED', 'PUBLISHED', 'IN_REVIEW', 'DRAFT', 'IDEA', 'ARCHIVED']\n : ['PUBLISHED', 'FEATURED'];\n}\n"],"mappings":";AAYA,SAAgBA,EACd,GACA,GAC0C;CAU1C,OARE,CAACC,KACDA,EAAY,aACZA,EAAY,SACZ,CAACC,KACDD,EAAY,YAAYC,IAEjB;EAAE,SAAS;EAAO,WAAW;CAAM,IAErC;EACL,SAASD,EAAY,cAAc,6BAA6B;EAChE,WAAWA,EAAY,cAAc,6BAA6B;CACpE;AACF;AAGA,SAAgBE,EACd,GACA,GACmB;CAEnB,OADIC,IAAgBC,KAAY,QACzBA,MAAa,aAAa,aAAa;AAChD;AAEA,SAAgBC,EAA0B,GAAgD;CACxF,OAAOF,IACH;EAAC;EAAO;EAAY;EAAa;EAAa;EAAS;EAAQ;CAAU,IACzE,CAAC,aAAa,UAAU;AAC9B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-theculturepost",
3
- "version": "2026.1110.3",
3
+ "version": "2026.1112.1",
4
4
  "description": "Burdenoff TheCulturePost Microfrontend",
5
5
  "type": "module",
6
6
  "files": [