Pripomienky: - odkaz /review/<token> so snimkou newslettra: nic sa neda upravit, da sa komentovat (spendlik), pisat text do newslettra a kreslit; meno autora sa pamata - pripomienky su ukotvene k modulu a ulozene k odkazu - vidi ich kazdy s odkazom - v editore: tlacidlo Pripomienky (pocet otvorenych), vytvorenie a kopirovanie odkazu, aktualizacia nahladu v odkaze, zoznam pripomienok, Hotovo a mazanie; pripomienky sa zobrazuju aj priamo na platne a daju sa tam oznacit ako hotove - supabase/migrations/002_reviews.sql (tabulky reviews, review_comments) - pismo Poppins v layoute (nahlad vernejsi e-mailu) Stitky predajni: - predvolena poloha -15 vodorovne / +5 zvisle od praveho horneho rohu pozadia cenovky - tlacidlo Polohu stitkov pouzit na vsetky produkty Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
2.7 KiB
TypeScript
95 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
// Pripomienky aktuálnej kampane v editore: odkaz, zoznam pripomienok (pravidelne obnovovaný), akcie
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
import { NewsletterCampaign } from '@/types/newsletter';
|
|
import {
|
|
Review,
|
|
ReviewComment,
|
|
createReview,
|
|
deleteReviewComment,
|
|
getReviewForTemplate,
|
|
listReviewComments,
|
|
setReviewCommentDone,
|
|
updateReviewSnapshot
|
|
} from '@/lib/reviews';
|
|
|
|
const POLL_MS = 20000;
|
|
|
|
export function useCampaignReview(campaign: NewsletterCampaign) {
|
|
const [review, setReview] = useState<Review | null>(null);
|
|
const [comments, setComments] = useState<ReviewComment[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
const templateId = campaign.id;
|
|
|
|
const refresh = useCallback(async (reviewId: string) => {
|
|
try {
|
|
setComments(await listReviewComments(reviewId));
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : String(e));
|
|
}
|
|
}, []);
|
|
|
|
// Odkaz kampane sa načíta pri otvorení kampane; pripomienky sa obnovujú pravidelne
|
|
useEffect(() => {
|
|
let timer: ReturnType<typeof setInterval> | undefined;
|
|
let cancelled = false;
|
|
if (!templateId) return;
|
|
(async () => {
|
|
try {
|
|
const r = await getReviewForTemplate(templateId);
|
|
if (cancelled) return;
|
|
setReview(r);
|
|
setComments([]);
|
|
setError(null);
|
|
if (!r) return;
|
|
await refresh(r.id);
|
|
timer = setInterval(() => refresh(r.id), POLL_MS);
|
|
} catch (e) {
|
|
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
clearInterval(timer);
|
|
};
|
|
}, [templateId, refresh]);
|
|
|
|
const run = async (action: () => Promise<void>) => {
|
|
setBusy(true);
|
|
setError(null);
|
|
try {
|
|
await action();
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : String(e));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
review,
|
|
comments,
|
|
error,
|
|
busy,
|
|
openCount: comments.filter((c) => !c.done).length,
|
|
create: () => run(async () => setReview(await createReview(campaign))),
|
|
updateSnapshot: () =>
|
|
run(async () => {
|
|
if (review) setReview(await updateReviewSnapshot(review.id, campaign));
|
|
}),
|
|
toggleDone: (c: ReviewComment) =>
|
|
run(async () => {
|
|
await setReviewCommentDone(c.id, !c.done);
|
|
setComments((prev) => prev.map((x) => (x.id === c.id ? { ...x, done: !c.done } : x)));
|
|
}),
|
|
remove: (c: ReviewComment) =>
|
|
run(async () => {
|
|
await deleteReviewComment(c.id);
|
|
setComments((prev) => prev.filter((x) => x.id !== c.id));
|
|
}),
|
|
refresh: () => review && refresh(review.id)
|
|
};
|
|
}
|