Files
k-tv/k-tv-frontend/app/(main)/dashboard/components/create-channel-dialog.tsx
Gabriel Kaszewski 81df6eb8ff feat: add access control to channels with various modes
- Introduced AccessMode enum to define channel access levels: Public, PasswordProtected, AccountRequired, and OwnerOnly.
- Updated Channel and ProgrammingBlock entities to include access_mode and access_password_hash fields.
- Enhanced create and update channel functionality to handle access mode and password.
- Implemented access checks in channel routes based on the defined access modes.
- Modified frontend components to support channel creation and editing with access control options.
- Added ChannelPasswordModal for handling password input when accessing restricted channels.
- Updated API calls to include channel and block passwords as needed.
- Created database migrations to add access_mode and access_password_hash columns to channels table.
2026-03-14 01:45:10 +01:00

162 lines
5.5 KiB
TypeScript

"use client";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import type { AccessMode } from "@/lib/types";
interface CreateChannelDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (data: {
name: string;
timezone: string;
description: string;
access_mode?: AccessMode;
access_password?: string;
}) => void;
isPending: boolean;
error?: string | null;
}
export function CreateChannelDialog({
open,
onOpenChange,
onSubmit,
isPending,
error,
}: CreateChannelDialogProps) {
const [name, setName] = useState("");
const [timezone, setTimezone] = useState("UTC");
const [description, setDescription] = useState("");
const [accessMode, setAccessMode] = useState<AccessMode>("public");
const [accessPassword, setAccessPassword] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit({
name,
timezone,
description,
access_mode: accessMode !== "public" ? accessMode : undefined,
access_password: accessMode === "password_protected" && accessPassword ? accessPassword : undefined,
});
};
const handleOpenChange = (next: boolean) => {
if (!isPending) {
onOpenChange(next);
if (!next) {
setName("");
setTimezone("UTC");
setDescription("");
setAccessMode("public");
setAccessPassword("");
}
}
};
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="bg-zinc-900 border-zinc-800 text-zinc-100 sm:max-w-md">
<DialogHeader>
<DialogTitle>New channel</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 py-2">
<div className="space-y-1.5">
<label className="block text-xs font-medium text-zinc-400">
Name <span className="text-red-400">*</span>
</label>
<input
required
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="90s Sitcom Network"
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-medium text-zinc-400">
Timezone <span className="text-red-400">*</span>
</label>
<input
required
value={timezone}
onChange={(e) => setTimezone(e.target.value)}
placeholder="America/New_York"
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
/>
<p className="text-[11px] text-zinc-600">
IANA timezone, e.g. America/New_York, Europe/London, UTC
</p>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-medium text-zinc-400">
Description
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Nothing but classic sitcoms, all day"
rows={2}
className="w-full resize-none rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
/>
</div>
<div className="space-y-1.5">
<label className="block text-xs font-medium text-zinc-400">Access</label>
<select
value={accessMode}
onChange={(e) => setAccessMode(e.target.value as AccessMode)}
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-zinc-500 focus:outline-none"
>
<option value="public">Public</option>
<option value="password_protected">Password protected</option>
<option value="account_required">Account required</option>
<option value="owner_only">Owner only</option>
</select>
</div>
{accessMode === "password_protected" && (
<div className="space-y-1.5">
<label className="block text-xs font-medium text-zinc-400">Password</label>
<input
type="password"
value={accessPassword}
onChange={(e) => setAccessPassword(e.target.value)}
placeholder="Channel password"
className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
/>
</div>
)}
{error && <p className="text-xs text-red-400">{error}</p>}
<DialogFooter>
<Button
type="button"
variant="ghost"
onClick={() => handleOpenChange(false)}
disabled={isPending}
>
Cancel
</Button>
<Button type="submit" disabled={isPending}>
{isPending ? "Creating…" : "Create channel"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}