@burdenoff/microfe-hookmovies 2026.705.1 → 2026.705.2

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":"ReviewsPage.js","names":[],"sources":["../../../../src/hookmovies/pages/engagement/ReviewsPage.tsx"],"sourcesContent":["/**\n * Reviews (Audience engagement)\n * @module hookmovies/pages/engagement\n *\n * Lists reviews via `hokListReviews` (status + title filters), creates them via\n * `hokCreateReview`, and removes the caller's own via `hokDeleteReview`.\n */\n\nimport { type FC, type FormEvent, useState } from 'react';\nimport { Star, Plus, X, Trash2, ShieldCheck } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\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 useReviews,\n useCreateReview,\n useDeleteReview,\n useTitles,\n type HokReviewStatus,\n type CreateReviewInput,\n} from '../../hooks/useHookMoviesApi';\nimport { formatRelativeTime } from '../../utils/formatters';\n\nconst STATUS_OPTIONS: HokReviewStatus[] = ['PENDING', 'PUBLISHED', 'HIDDEN', 'REMOVED'];\nconst PAGE_LIMIT = 20;\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\nexport const ReviewsPage: FC = () => {\n const { navigateTo } = useHookMovies();\n const [statusFilter, setStatusFilter] = useState<HokReviewStatus | ''>('');\n const [page, setPage] = useState(0);\n const [showCreate, setShowCreate] = useState(false);\n\n const { data, isLoading, isError, refetch } = useReviews(\n { status: statusFilter || undefined },\n { limit: PAGE_LIMIT, offset: page * PAGE_LIMIT }\n );\n const titles = useTitles({}, { limit: 200, offset: 0 });\n const createReview = useCreateReview();\n const deleteReview = useDeleteReview();\n\n const [titleId, setTitleId] = useState('');\n const [score, setScore] = useState('8');\n const [body, setBody] = useState('');\n const [hasSpoilers, setHasSpoilers] = useState(false);\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 deleteReview.mutateAsync(id);\n } catch (err) {\n setActionError(\n err instanceof Error ? err.message : 'Failed to delete review (you can only delete your own)'\n );\n }\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!titleId) {\n setError('Select a title to review.');\n return;\n }\n const scoreNum = Number(score);\n if (!Number.isInteger(scoreNum) || scoreNum < 1 || scoreNum > 10) {\n setError('Score must be a whole number from 1 to 10.');\n return;\n }\n const input: CreateReviewInput = {\n titleId,\n score: scoreNum,\n body: body.trim() || undefined,\n hasSpoilers,\n };\n try {\n await createReview.mutateAsync(input);\n setTitleId('');\n setScore('8');\n setBody('');\n setHasSpoilers(false);\n setShowCreate(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create review');\n }\n };\n\n const items = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Reviews\"\n description=\"Ratings & audience reviews\"\n purpose=\"Audience scores and written reviews per title, with verified-watch badges and moderation status. Write a review or moderate the queue.\"\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 Write Review\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\">Write a Review</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 className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"review-title\" className={labelClass}>\n Title *\n </label>\n <select\n id=\"review-title\"\n value={titleId}\n onChange={(e) => setTitleId(e.target.value)}\n className={fieldClass}\n required\n >\n <option value=\"\">Select a title…</option>\n {(titles.data?.items ?? []).map((t) => (\n <option key={t.id} value={t.id}>\n {t.title}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"review-score\" className={labelClass}>\n Score (1–10) *\n </label>\n <input\n id=\"review-score\"\n type=\"number\"\n min=\"1\"\n max=\"10\"\n value={score}\n onChange={(e) => setScore(e.target.value)}\n className={fieldClass}\n required\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"review-body\" className={labelClass}>\n Review\n </label>\n <textarea\n id=\"review-body\"\n value={body}\n onChange={(e) => setBody(e.target.value)}\n rows={3}\n placeholder=\"Share your thoughts…\"\n className={fieldClass}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={hasSpoilers}\n onChange={(e) => setHasSpoilers(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n This review contains spoilers\n </label>\n <button\n type=\"submit\"\n disabled={createReview.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 {createReview.isPending ? 'Publishing…' : 'Publish Review'}\n </button>\n </form>\n </GlassCard>\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 HokReviewStatus | '');\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 {s.charAt(0) + s.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 reviews…\" />\n ) : isError ? (\n <ErrorState onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Star className=\"h-12 w-12\" />}\n title=\"No reviews\"\n description=\"No reviews match this filter yet.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {items.map((review) => (\n <GlassCard key={review.id} treatment=\"metric\" className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`catalog/titles/${review.titleId}`)}\n className=\"truncate text-sm font-semibold text-text-primary hover:text-action-primary-bg transition-colors\"\n >\n {review.title?.title ?? 'Title'}\n </button>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"inline-flex items-center gap-1 text-sm font-semibold text-text-primary\">\n <Star className=\"h-3.5 w-3.5 fill-status-warning-text text-status-warning-text\" />\n {review.score}/10\n </span>\n {review.verifiedWatch && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] text-status-success-text\">\n <ShieldCheck className=\"h-3 w-3\" />\n Verified watch\n </span>\n )}\n {review.hasSpoilers && (\n <span className=\"rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] text-status-warning-text\">\n Spoilers\n </span>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={review.status} />\n <button\n type=\"button\"\n disabled={deleteReview.isPending}\n onClick={() => void handleDelete(review.id)}\n className=\"rounded-md p-1.5 text-text-secondary hover:text-status-error-text disabled:opacity-50 transition-colors\"\n aria-label=\"Delete review\"\n title=\"Delete (only your own reviews)\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {review.body && <p className=\"mt-2 text-sm text-text-secondary\">{review.body}</p>}\n <p className=\"mt-2 text-xs text-text-muted\">\n {review.helpfulCount} found this helpful · {formatRelativeTime(review.createdAt)}\n </p>\n </GlassCard>\n ))}\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":"ReviewsPage.js","names":[],"sources":["../../../../src/hookmovies/pages/engagement/ReviewsPage.tsx"],"sourcesContent":["/**\n * Reviews (Audience engagement)\n * @module hookmovies/pages/engagement\n *\n * Lists reviews via `hokListReviews` (status + title filters), creates them via\n * `hokCreateReview`, and removes the caller's own via `hokDeleteReview`.\n */\n\nimport { type FC, type FormEvent, useState } from 'react';\nimport { Star, Plus, X, Trash2, ShieldCheck } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\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 useReviews,\n useCreateReview,\n useDeleteReview,\n useTitles,\n type HokReviewStatus,\n type CreateReviewInput,\n} from '../../hooks/useHookMoviesApi';\nimport { formatRelativeTime } from '../../utils/formatters';\n\nconst STATUS_OPTIONS: HokReviewStatus[] = ['PENDING', 'PUBLISHED', 'HIDDEN', 'REMOVED'];\nconst PAGE_LIMIT = 20;\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\nexport const ReviewsPage: FC = () => {\n const { navigateTo } = useHookMovies();\n const [statusFilter, setStatusFilter] = useState<HokReviewStatus | ''>('');\n const [page, setPage] = useState(0);\n const [showCreate, setShowCreate] = useState(false);\n\n const { data, isLoading, isError, refetch } = useReviews(\n { status: statusFilter || undefined },\n { limit: PAGE_LIMIT, offset: page * PAGE_LIMIT }\n );\n const titles = useTitles({}, { limit: 200, offset: 0 });\n const createReview = useCreateReview();\n const deleteReview = useDeleteReview();\n\n const [titleId, setTitleId] = useState('');\n const [score, setScore] = useState('8');\n const [body, setBody] = useState('');\n const [hasSpoilers, setHasSpoilers] = useState(false);\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 deleteReview.mutateAsync(id);\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : 'Failed to delete review (you can only delete your own)'\n );\n }\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!titleId) {\n setError('Select a title to review.');\n return;\n }\n const scoreNum = Number(score);\n if (!Number.isInteger(scoreNum) || scoreNum < 1 || scoreNum > 10) {\n setError('Score must be a whole number from 1 to 10.');\n return;\n }\n const input: CreateReviewInput = {\n titleId,\n score: scoreNum,\n body: body.trim() || undefined,\n hasSpoilers,\n };\n try {\n await createReview.mutateAsync(input);\n setTitleId('');\n setScore('8');\n setBody('');\n setHasSpoilers(false);\n setShowCreate(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create review');\n }\n };\n\n const items = data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Reviews\"\n description=\"Ratings & audience reviews\"\n purpose=\"Audience scores and written reviews per title, with verified-watch badges and moderation status. Write a review or moderate the queue.\"\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 Write Review\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\">Write a Review</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 className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"review-title\" className={labelClass}>\n Title *\n </label>\n <select\n id=\"review-title\"\n value={titleId}\n onChange={(e) => setTitleId(e.target.value)}\n className={fieldClass}\n required\n >\n <option value=\"\">Select a title…</option>\n {(titles.data?.items ?? []).map((t) => (\n <option key={t.id} value={t.id}>\n {t.title}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"review-score\" className={labelClass}>\n Score (1–10) *\n </label>\n <input\n id=\"review-score\"\n type=\"number\"\n min=\"1\"\n max=\"10\"\n value={score}\n onChange={(e) => setScore(e.target.value)}\n className={fieldClass}\n required\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"review-body\" className={labelClass}>\n Review\n </label>\n <textarea\n id=\"review-body\"\n value={body}\n onChange={(e) => setBody(e.target.value)}\n rows={3}\n placeholder=\"Share your thoughts…\"\n className={fieldClass}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={hasSpoilers}\n onChange={(e) => setHasSpoilers(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n This review contains spoilers\n </label>\n <button\n type=\"submit\"\n disabled={createReview.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 {createReview.isPending ? 'Publishing…' : 'Publish Review'}\n </button>\n </form>\n </GlassCard>\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 HokReviewStatus | '');\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 {s.charAt(0) + s.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 reviews…\" />\n ) : isError ? (\n <ErrorState onRetry={() => void refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Star className=\"h-12 w-12\" />}\n title=\"No reviews\"\n description=\"No reviews match this filter yet.\"\n />\n ) : (\n <div className=\"space-y-3\">\n {items.map((review) => (\n <GlassCard key={review.id} treatment=\"metric\" className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`catalog/titles/${review.titleId}`)}\n className=\"truncate text-sm font-semibold text-text-primary hover:text-action-primary-bg transition-colors\"\n >\n {review.title?.title ?? 'Title'}\n </button>\n <div className=\"mt-1 flex items-center gap-2\">\n <span className=\"inline-flex items-center gap-1 text-sm font-semibold text-text-primary\">\n <Star className=\"h-3.5 w-3.5 fill-status-warning-text text-status-warning-text\" />\n {review.score}/10\n </span>\n {review.verifiedWatch && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] text-status-success-text\">\n <ShieldCheck className=\"h-3 w-3\" />\n Verified watch\n </span>\n )}\n {review.hasSpoilers && (\n <span className=\"rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] text-status-warning-text\">\n Spoilers\n </span>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={review.status} />\n <button\n type=\"button\"\n disabled={deleteReview.isPending}\n onClick={() => void handleDelete(review.id)}\n className=\"rounded-md p-1.5 text-text-secondary hover:text-status-error-text disabled:opacity-50 transition-colors\"\n aria-label=\"Delete review\"\n title=\"Delete (only your own reviews)\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {review.body && <p className=\"mt-2 text-sm text-text-secondary\">{review.body}</p>}\n <p className=\"mt-2 text-xs text-text-muted\">\n {review.helpfulCount} found this helpful · {formatRelativeTime(review.createdAt)}\n </p>\n </GlassCard>\n ))}\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-hookmovies",
3
- "version": "2026.705.1",
3
+ "version": "2026.705.2",
4
4
  "description": "Burdenoff HookMovies Microfrontend - Mobile services marketplace platform for connecting service providers with customers",
5
5
  "type": "module",
6
6
  "files": [