Skip to content

Commit

Permalink
feat: ✨ API Keys and URLs.
Browse files Browse the repository at this point in the history
- Introduce feature API Keys and URLs.
- Create keys page.
- Create url-card, key-card, copy-button and connect-modal components.

Keys page create.
  • Loading branch information
RicardoGEsteves committed Dec 15, 2023
1 parent 7d21c91 commit 4d1b361
Show file tree
Hide file tree
Showing 11 changed files with 901 additions and 0 deletions.
98 changes: 98 additions & 0 deletions app/(dashboard)/u/[username]/keys/_components/connect-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client";

import { toast } from "sonner";
import { useState, useTransition, useRef, ElementRef } from "react";
import { AlertTriangle } from "lucide-react";
// import { IngressInput } from "livekit-server-sdk";

// import { createIngress } from "@/actions/ingress";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

// const RTMP = String(IngressInput.RTMP_INPUT);
// const WHIP = String(IngressInput.WHIP_INPUT);

// type IngressType = typeof RTMP | typeof WHIP;

const ConnectModal = () => {
const closeRef = useRef<ElementRef<"button">>(null);
const [isPending, startTransition] = useTransition();
// const [ingressType, setIngressType] = useState<IngressType>(RTMP);

// const onSubmit = () => {
// startTransition(() => {
// createIngress(parseInt(ingressType))
// .then(() => {
// toast.success("Ingress created");
// closeRef?.current?.click();
// })
// .catch(() => toast.error("Something went wrong"));
// });
// };

return (
<Dialog>
<DialogTrigger asChild>
<Button variant="primary">Generate connection</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Generate connection</DialogTitle>
</DialogHeader>
<Select
disabled={isPending}
// value={ingressType}
// onValueChange={(value) => setIngressType(value)}
>
<SelectTrigger className="w-full px-3 py-2 text-sm focus:outline-none focus:ring-0 focus:ring-none focus:ring-offset-0">
<SelectValue placeholder="Ingress Type" />
</SelectTrigger>
<SelectContent>
{/* <SelectItem value={RTMP}>RTMP</SelectItem> */}
{/* <SelectItem value={WHIP}>WHIP</SelectItem> */}
</SelectContent>
</Select>
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Warning!</AlertTitle>
<AlertDescription>
This action will reset all active streams using the current
connection
</AlertDescription>
</Alert>
<div className="flex justify-between">
<DialogClose
ref={closeRef}
asChild
>
<Button variant="ghost">Cancel</Button>
</DialogClose>
<Button
disabled={isPending}
// onClick={onSubmit}
variant="primary"
>
Generate
</Button>
</div>
</DialogContent>
</Dialog>
);
};

export default ConnectModal;
39 changes: 39 additions & 0 deletions app/(dashboard)/u/[username]/keys/_components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { useState } from "react";
import { CheckCheck, Copy } from "lucide-react";

import { Button } from "@/components/ui/button";

interface CopyButtonProps {
value?: string;
}

const CopyButton = ({ value }: CopyButtonProps) => {
const [isCopied, setIsCopied] = useState(false);

const onCopy = () => {
if (!value) return;

setIsCopied(true);
navigator.clipboard.writeText(value);
setTimeout(() => {
setIsCopied(false);
}, 1000);
};

const Icon = isCopied ? CheckCheck : Copy;

return (
<Button
onClick={onCopy}
disabled={!value || isCopied}
variant="primary"
size="sm"
>
<Icon className="h-4 w-4" />
</Button>
);
};

export default CopyButton;
45 changes: 45 additions & 0 deletions app/(dashboard)/u/[username]/keys/_components/key-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client";

import { useState } from "react";

import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

import CopyButton from "./copy-button";

interface KeyCardProps {
value: string | null;
}

const KeyCard = ({ value }: KeyCardProps) => {
const [show, setShow] = useState(false);

return (
<div className="rounded-xl border p-6">
<div className="flex items-start gap-x-10">
<p className="font-semibold shrink-0">Stream Key</p>
<div className="space-y-2 w-full">
<div className="w-full flex items-center gap-x-2">
<Input
value={value || ""}
type={show ? "text" : "password"}
disabled
placeholder="Stream key"
className="bg-secondary/40"
/>
<CopyButton value={value || ""} />
</div>
<Button
onClick={() => setShow(!show)}
size="sm"
variant="link"
>
{show ? "Hide" : "Show"}
</Button>
</div>
</div>
</div>
);
};

export default KeyCard;
30 changes: 30 additions & 0 deletions app/(dashboard)/u/[username]/keys/_components/url-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Input } from "@/components/ui/input";

import CopyButton from "./copy-button";

interface UrlCardProps {
value: string | null;
}

const UrlCard = ({ value }: UrlCardProps) => {
return (
<div className="rounded-xl border p-6">
<div className="flex items-center gap-x-10">
<p className="font-semibold shrink-0">Server URL</p>
<div className="space-y-2 w-full">
<div className="w-full flex items-center gap-x-2">
<Input
value={value || ""}
disabled
placeholder="Server URL"
className="bg-secondary/40"
/>
<CopyButton value={value || ""} />
</div>
</div>
</div>
</div>
);
};

export default UrlCard;
30 changes: 30 additions & 0 deletions app/(dashboard)/u/[username]/keys/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getSelf } from "@/lib/auth-service";
import { getStreamByUserId } from "@/lib/stream-service";

import UrlCard from "./_components/url-card";
import KeyCard from "./_components/key-card";
import ConnectModal from "./_components/connect-modal";

const KeysPage = async () => {
const self = await getSelf();
const stream = await getStreamByUserId(self.id);

if (!stream) {
throw new Error("Stream not found");
}

return (
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-semibold">API Keys and URLs</h1>
<ConnectModal />
</div>
<div className="space-y-4">
<UrlCard value={stream.serverUrl} />
<KeyCard value={stream.streamKey} />
</div>
</div>
);
};

export default KeysPage;
59 changes: 59 additions & 0 deletions components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"

export { Alert, AlertTitle, AlertDescription }
2 changes: 2 additions & 0 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const buttonVariants = cva(
link: "text-primary underline-offset-4 hover:underline",
primary:
"bg-primary dark:bg-blue-700 text-primary-foreground hover:bg-primary/90 dark:hover:bg-blue-700/80",
custom:
"bg-primary dark:bg-blue-700 text-secondary-foreground hover:bg-primary/90 dark:hover:bg-blue-700/80",
},
size: {
default: "h-10 px-4 py-2",
Expand Down
Loading

0 comments on commit 4d1b361

Please sign in to comment.