feat: Created TypeScript consent API client with 5 fetch functions and…
- "frontend/src/api/consent.ts" - "frontend/src/components/ToggleSwitch.tsx" - "frontend/src/components/ToggleSwitch.module.css" - "frontend/src/api/index.ts" GSD-Task: S03/T01
This commit is contained in:
parent
2e7fa224bc
commit
2162385c6b
4 changed files with 194 additions and 0 deletions
78
frontend/src/api/consent.ts
Normal file
78
frontend/src/api/consent.ts
Normal file
|
|
@ -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<ConsentListResponse> {
|
||||
return request<ConsentListResponse>(`${BASE}/consent/videos`);
|
||||
}
|
||||
|
||||
export async function fetchVideoConsent(
|
||||
videoId: string,
|
||||
): Promise<VideoConsentRead> {
|
||||
return request<VideoConsentRead>(`${BASE}/consent/videos/${videoId}`);
|
||||
}
|
||||
|
||||
export async function updateVideoConsent(
|
||||
videoId: string,
|
||||
updates: VideoConsentUpdate,
|
||||
): Promise<VideoConsentRead> {
|
||||
return request<VideoConsentRead>(`${BASE}/consent/videos/${videoId}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(updates),
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchConsentHistory(
|
||||
videoId: string,
|
||||
): Promise<ConsentAuditEntry[]> {
|
||||
return request<ConsentAuditEntry[]>(
|
||||
`${BASE}/consent/videos/${videoId}/history`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchConsentSummary(): Promise<ConsentSummary> {
|
||||
return request<ConsentSummary>(`${BASE}/consent/summary`);
|
||||
}
|
||||
|
|
@ -14,3 +14,4 @@ export * from "./admin-pipeline";
|
|||
export * from "./admin-techniques";
|
||||
export * from "./auth";
|
||||
export * from "./creator-dashboard";
|
||||
export * from "./consent";
|
||||
|
|
|
|||
71
frontend/src/components/ToggleSwitch.module.css
Normal file
71
frontend/src/components/ToggleSwitch.module.css
Normal file
|
|
@ -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);
|
||||
}
|
||||
44
frontend/src/components/ToggleSwitch.tsx
Normal file
44
frontend/src/components/ToggleSwitch.tsx
Normal file
|
|
@ -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 (
|
||||
<label className={styles.wrapper} htmlFor={inputId} data-disabled={disabled || undefined}>
|
||||
<span className={styles.label}>{label}</span>
|
||||
<span className={styles.track} data-checked={checked || undefined}>
|
||||
<input
|
||||
id={inputId}
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
className={styles.input}
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
aria-label={label}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
/>
|
||||
<span className={styles.thumb} />
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue