diff --git a/frontend/src/api/consent.ts b/frontend/src/api/consent.ts new file mode 100644 index 0000000..40fa562 --- /dev/null +++ b/frontend/src/api/consent.ts @@ -0,0 +1,78 @@ +/** + * Consent API client — per-video consent settings and audit history. + */ + +import { request, BASE } from "./client"; + +// ── Types ──────────────────────────────────────────────────────────────────── + +export interface VideoConsentRead { + source_video_id: string; + video_filename: string; + creator_id: string; + kb_inclusion: boolean; + training_usage: boolean; + public_display: boolean; + updated_at: string; +} + +export interface VideoConsentUpdate { + kb_inclusion?: boolean; + training_usage?: boolean; + public_display?: boolean; +} + +export interface ConsentAuditEntry { + version: number; + field_name: string; + old_value: boolean | null; + new_value: boolean; + changed_by: string; + created_at: string; +} + +export interface ConsentListResponse { + items: VideoConsentRead[]; + total: number; +} + +export interface ConsentSummary { + total_videos: number; + kb_inclusion_granted: number; + training_usage_granted: number; + public_display_granted: number; +} + +// ── Functions ──────────────────────────────────────────────────────────────── + +export async function fetchConsentList(): Promise { + return request(`${BASE}/consent/videos`); +} + +export async function fetchVideoConsent( + videoId: string, +): Promise { + return request(`${BASE}/consent/videos/${videoId}`); +} + +export async function updateVideoConsent( + videoId: string, + updates: VideoConsentUpdate, +): Promise { + return request(`${BASE}/consent/videos/${videoId}`, { + method: "PUT", + body: JSON.stringify(updates), + }); +} + +export async function fetchConsentHistory( + videoId: string, +): Promise { + return request( + `${BASE}/consent/videos/${videoId}/history`, + ); +} + +export async function fetchConsentSummary(): Promise { + return request(`${BASE}/consent/summary`); +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a4b2d58..e7e27c4 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -14,3 +14,4 @@ export * from "./admin-pipeline"; export * from "./admin-techniques"; export * from "./auth"; export * from "./creator-dashboard"; +export * from "./consent"; diff --git a/frontend/src/components/ToggleSwitch.module.css b/frontend/src/components/ToggleSwitch.module.css new file mode 100644 index 0000000..98cb6f6 --- /dev/null +++ b/frontend/src/components/ToggleSwitch.module.css @@ -0,0 +1,71 @@ +/* ToggleSwitch — sliding toggle with accessible checkbox */ + +.wrapper { + display: inline-flex; + align-items: center; + gap: 0.625rem; + cursor: pointer; + user-select: none; +} + +.wrapper[data-disabled] { + opacity: 0.45; + cursor: not-allowed; + pointer-events: none; +} + +.label { + font-size: 0.875rem; + color: var(--color-text-primary); +} + +/* Track (pill shape) */ +.track { + position: relative; + display: inline-flex; + align-items: center; + width: 40px; + height: 22px; + border-radius: 11px; + background: var(--color-border); + transition: background 200ms ease; + flex-shrink: 0; +} + +.track[data-checked] { + background: var(--color-accent); +} + +/* Visually hidden native checkbox */ +.input { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Sliding thumb */ +.thumb { + position: absolute; + left: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--color-text-primary); + transition: transform 200ms ease; + pointer-events: none; +} + +.track[data-checked] .thumb { + transform: translateX(18px); +} + +/* Focus ring on the track when input is focused */ +.input:focus-visible ~ .thumb { + box-shadow: 0 0 0 2px var(--color-accent-focus); +} diff --git a/frontend/src/components/ToggleSwitch.tsx b/frontend/src/components/ToggleSwitch.tsx new file mode 100644 index 0000000..aa7018b --- /dev/null +++ b/frontend/src/components/ToggleSwitch.tsx @@ -0,0 +1,44 @@ +/** + * Reusable toggle switch — accessible checkbox styled as a sliding toggle. + */ + +import { useId } from "react"; +import styles from "./ToggleSwitch.module.css"; + +export interface ToggleSwitchProps { + checked: boolean; + onChange: (checked: boolean) => void; + label: string; + disabled?: boolean; + id?: string; +} + +export function ToggleSwitch({ + checked, + onChange, + label, + disabled = false, + id, +}: ToggleSwitchProps) { + const autoId = useId(); + const inputId = id ?? autoId; + + return ( + + ); +}