diff --git a/packages/frontend/src/lib/components/recording-card/recording-card.svelte b/packages/frontend/src/lib/components/recording-card/recording-card.svelte index e4aa691..d7292c6 100644 --- a/packages/frontend/src/lib/components/recording-card/recording-card.svelte +++ b/packages/frontend/src/lib/components/recording-card/recording-card.svelte @@ -8,10 +8,12 @@ interface Props { recording: Recording; onPlay?: (id: string) => void; + onPublish?: (id: string) => void; + onUnpublish?: (id: string) => void; onDelete?: (id: string) => void; } - let { recording, onPlay, onDelete }: Props = $props(); + let { recording, onPlay, onPublish, onUnpublish, onDelete }: Props = $props(); function formatDuration(ms: number): string { const totalSeconds = Math.floor(ms / 1000); @@ -149,12 +151,35 @@ {$_("recording_card.play")} {/if} + {#if onPublish && recording.status === "draft"} + + {/if} + {#if onUnpublish && recording.status === "published"} + + {/if} {#if onDelete} diff --git a/packages/frontend/src/lib/i18n/locales/en.ts b/packages/frontend/src/lib/i18n/locales/en.ts index f4cfcdd..5fcf141 100644 --- a/packages/frontend/src/lib/i18n/locales/en.ts +++ b/packages/frontend/src/lib/i18n/locales/en.ts @@ -151,6 +151,10 @@ export default { delete_confirm: "Are you sure you want to delete this recording?", delete_success: "Recording deleted successfully", delete_error: "Failed to delete recording", + publish_success: "Recording published successfully", + publish_error: "Failed to publish recording", + unpublish_success: "Recording unpublished", + unpublish_error: "Failed to unpublish recording", }, }, recording_card: { @@ -161,6 +165,8 @@ export default { status_draft: "Draft", status_published: "Published", play: "Play", + publish: "Publish", + unpublish: "Unpublish", edit: "Edit", delete: "Delete", public: "Public", diff --git a/packages/frontend/src/lib/services.ts b/packages/frontend/src/lib/services.ts index 9fe0c32..559cea2 100644 --- a/packages/frontend/src/lib/services.ts +++ b/packages/frontend/src/lib/services.ts @@ -902,6 +902,32 @@ export async function createRecording( ); } +const UPDATE_RECORDING_MUTATION = gql` + mutation UpdateRecording($id: String!, $status: String, $public: Boolean) { + updateRecording(id: $id, status: $status, public: $public) { + id + status + public + } + } +`; + +export async function updateRecording( + id: string, + fields: { status?: string; public?: boolean }, +) { + return loggedApiCall( + "updateRecording", + async () => { + const data = await getGraphQLClient().request<{ updateRecording: Recording }>( + UPDATE_RECORDING_MUTATION, + { id, ...fields }, + ); + return data.updateRecording; + }, + ); +} + const DELETE_RECORDING_MUTATION = gql` mutation DeleteRecording($id: String!) { deleteRecording(id: $id) diff --git a/packages/frontend/src/routes/me/recordings/+page.svelte b/packages/frontend/src/routes/me/recordings/+page.svelte index 8ca33cd..023048f 100644 --- a/packages/frontend/src/routes/me/recordings/+page.svelte +++ b/packages/frontend/src/routes/me/recordings/+page.svelte @@ -3,7 +3,7 @@ import { _ } from "svelte-i18n"; import { goto } from "$app/navigation"; import { toast } from "svelte-sonner"; - import { deleteRecording } from "$lib/services"; + import { deleteRecording, updateRecording } from "$lib/services"; import { Button } from "$lib/components/ui/button"; import * as Empty from "$lib/components/ui/empty"; import * as Dialog from "$lib/components/ui/dialog"; @@ -38,6 +38,26 @@ } } + async function handlePublishRecording(id: string) { + try { + await updateRecording(id, { status: "published" }); + recordings = recordings.map((r) => (r.id === id ? { ...r, status: "published" } : r)); + toast.success($_("me.recordings.publish_success")); + } catch { + toast.error($_("me.recordings.publish_error")); + } + } + + async function handleUnpublishRecording(id: string) { + try { + await updateRecording(id, { status: "draft" }); + recordings = recordings.map((r) => (r.id === id ? { ...r, status: "draft" } : r)); + toast.success($_("me.recordings.unpublish_success")); + } catch { + toast.error($_("me.recordings.unpublish_error")); + } + } + function handlePlayRecording(id: string) { goto(`/play?recording=${id}`); } @@ -46,15 +66,8 @@
-
+

{$_("me.recordings.title")}

-
{#if recordings.length === 0} @@ -82,6 +95,8 @@ {/each} diff --git a/packages/frontend/src/routes/play/+page.svelte b/packages/frontend/src/routes/play/+page.svelte index 49a0387..20bb266 100644 --- a/packages/frontend/src/routes/play/+page.svelte +++ b/packages/frontend/src/routes/play/+page.svelte @@ -10,6 +10,7 @@ import DeviceMappingDialog from "./components/device-mapping-dialog.svelte"; import type { BluetoothDevice, RecordedEvent, DeviceInfo } from "$lib/types"; import { toast } from "svelte-sonner"; + import { createRecording } from "$lib/services"; import SexyBackground from "$lib/components/background/background.svelte"; // Runtime buttplug values — loaded dynamically from the buttplug nginx container @@ -173,26 +174,16 @@ })); try { - const response = await fetch("/api/sexy/recordings", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - title: data.title, - description: data.description, - duration: recordingDuration, - events: recordedEvents, - device_info: deviceInfo, - tags: data.tags, - status: "draft", - }), + await createRecording({ + title: data.title, + description: data.description, + duration: Math.round(recordingDuration), + events: recordedEvents, + device_info: deviceInfo, + tags: data.tags, + status: "draft", }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - toast.success("Recording saved successfully!"); showSaveDialog = false; recordedEvents = [];