mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat: Social page — aggregated post queues + X/video history (#345)
* feat(api): x/video post history endpoints Approved or rejected drafts vanished from both queues permanently -- the listers exclude terminal statuses and no history surface existed, so a posted tweet or video was only findable in the raw task list. GET /x/posts/history and GET /video/posts/history (CEO-gated, bounded) return acted-on drafts newest-first with the posted platform ids and reject reasons from the draft markers. Route tests assert by identity, not emptiness: approve/reject commits the whole session, so prior tests' rows legitimately persist in the shared test DB. * feat(panel): Social page aggregating post queues and history New dashboard page composing the X and video post queues with one unified history section beneath them -- both platforms interleaved newest-first, kind and outcome badges, posted X ids linking to the live tweet, reject reasons shown. The command center's two full queue cards become a compact pending-counts card linking to the page, so the queues have one home instead of duplicated surfaces. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -28,15 +28,21 @@ export type {
|
||||
XPost,
|
||||
XMentionRef,
|
||||
XPostExecuteResult,
|
||||
XPostHistoryEntry,
|
||||
XCredentialsStatus,
|
||||
} from "./x";
|
||||
export { roadmapApi } from "./roadmap";
|
||||
export type { RoadmapCycle, RoadmapItem, RoadmapItemActionResult } from "./roadmap";
|
||||
export type {
|
||||
RoadmapCycle,
|
||||
RoadmapItem,
|
||||
RoadmapItemActionResult,
|
||||
} from "./roadmap";
|
||||
export { videoApi, videoMediaUrl } from "./video";
|
||||
export type {
|
||||
VideoCut,
|
||||
VideoPost,
|
||||
VideoPostExecuteResult,
|
||||
VideoPostHistoryEntry,
|
||||
VideoRequestResult,
|
||||
TikTokCredentialsStatus,
|
||||
} from "./video";
|
||||
|
||||
@@ -30,6 +30,22 @@ export interface VideoPostExecuteResult {
|
||||
detail: string;
|
||||
}
|
||||
|
||||
// One acted-on draft (posted or rejected) — the CEO's history view.
|
||||
export interface VideoPostHistoryEntry {
|
||||
task_id: string;
|
||||
source: string; // "video_post"
|
||||
title: string;
|
||||
status: string; // "completed" | "cancelled"
|
||||
occasion: string;
|
||||
script: string;
|
||||
platforms: string[];
|
||||
x_caption?: string | null;
|
||||
tiktok_caption?: string | null;
|
||||
reject_reason?: string | null;
|
||||
posted: Record<string, string>; // platform -> posted id
|
||||
acted_at: string;
|
||||
}
|
||||
|
||||
export interface VideoRequestResult {
|
||||
status: string; // "opened" | "disabled" | "not_opened"
|
||||
task_id?: string | null;
|
||||
@@ -56,6 +72,15 @@ export const videoApi = {
|
||||
const { data } = await api.get<VideoPost[]>("/video/posts");
|
||||
return data;
|
||||
},
|
||||
// Posted or rejected drafts, newest-acted-first. Fixed default limit (50);
|
||||
// no "load more" — pass a higher limit if the panel ever needs it.
|
||||
listHistory: async (limit = 50): Promise<VideoPostHistoryEntry[]> => {
|
||||
const { data } = await api.get<VideoPostHistoryEntry[]>(
|
||||
"/video/posts/history",
|
||||
{ params: { limit } },
|
||||
);
|
||||
return data;
|
||||
},
|
||||
// Fetches the rendered cut as a Blob via axios (carrying the auth headers
|
||||
// a plain <video src> GET can't) so the caller can drive <video> off an
|
||||
// object URL instead of pointing it at the route directly.
|
||||
@@ -88,10 +113,7 @@ export const videoApi = {
|
||||
brief: string;
|
||||
platforms: string[];
|
||||
}): Promise<VideoRequestResult> => {
|
||||
const { data } = await api.post<VideoRequestResult>(
|
||||
"/video/request",
|
||||
body,
|
||||
);
|
||||
const { data } = await api.post<VideoRequestResult>("/video/request", body);
|
||||
return data;
|
||||
},
|
||||
getCredentialsStatus: async (): Promise<TikTokCredentialsStatus> => {
|
||||
|
||||
@@ -37,6 +37,22 @@ export interface XPostExecuteResult {
|
||||
detail: string;
|
||||
}
|
||||
|
||||
// One acted-on draft (posted or rejected) — the CEO's history view.
|
||||
export interface XPostHistoryEntry {
|
||||
task_id: string;
|
||||
source: "x_post" | "x_reply" | "x_feature";
|
||||
title: string;
|
||||
status: string; // "completed" | "cancelled"
|
||||
body: string;
|
||||
char_count: number;
|
||||
release_version?: string | null;
|
||||
mention?: XMentionRef | null;
|
||||
feature?: XFeatureRef | null;
|
||||
tweet_id?: string | null;
|
||||
reject_reason?: string | null;
|
||||
acted_at: string;
|
||||
}
|
||||
|
||||
export interface XCredentialsStatus {
|
||||
has_credentials: boolean;
|
||||
}
|
||||
@@ -46,6 +62,14 @@ export const xApi = {
|
||||
const { data } = await api.get<XPost[]>("/x/posts");
|
||||
return data;
|
||||
},
|
||||
// Posted or rejected drafts, newest-acted-first. Fixed default limit (50);
|
||||
// no "load more" — pass a higher limit if the panel ever needs it.
|
||||
listHistory: async (limit = 50): Promise<XPostHistoryEntry[]> => {
|
||||
const { data } = await api.get<XPostHistoryEntry[]>("/x/posts/history", {
|
||||
params: { limit },
|
||||
});
|
||||
return data;
|
||||
},
|
||||
approve: async (
|
||||
taskId: string,
|
||||
editedBody?: string,
|
||||
|
||||
Reference in New Issue
Block a user