Skip to content

Commit

Permalink
feat(i18n): use dictionary in videos
Browse files Browse the repository at this point in the history
  • Loading branch information
vmnog committed May 17, 2024
1 parent d7a6fce commit 5be377e
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 92 deletions.
16 changes: 9 additions & 7 deletions apps/web/src/app/[locale]/app/videos/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'

import { Overview } from './tabs/overview'
import { Webhooks } from './tabs/webhooks'
import { Locale, getDictionary } from '@nivo/i18n'

interface VideoPageProps {
params: { id: string }
params: { id: string, locale: Locale }
}

export async function generateMetadata(): Promise<Metadata> {
Expand All @@ -19,13 +20,14 @@ export async function generateMetadata(): Promise<Metadata> {
}

export default async function VideoPage({ params }: VideoPageProps) {
const dictionary = await getDictionary(params.locale)
const videoId = params.id

return (
<>
<div className="flex items-center justify-between gap-4">
<h2 className="truncate text-3xl font-bold tracking-tight">
Edit video
{dictionary.video_page_edit_video_title}
</h2>

<div className="flex items-center gap-2">
Expand All @@ -36,7 +38,7 @@ export default async function VideoPage({ params }: VideoPageProps) {
rel="noreferrer"
>
<VideoIcon className="mr-2 h-4 w-4" />
<span>Download MP4</span>
<span>{dictionary.video_page_download_mp4}</span>
</a>
</Button>
<Button variant="outline" asChild>
Expand All @@ -46,20 +48,20 @@ export default async function VideoPage({ params }: VideoPageProps) {
rel="noreferrer"
>
<Music2 className="mr-2 h-4 w-4" />
<span>Download MP3</span>
<span>{dictionary.video_page_download_mp3}</span>
</a>
</Button>
</div>
</div>

<Tabs defaultValue="overview" className="space-y-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="webhooks">Webhooks</TabsTrigger>
<TabsTrigger value="overview">{dictionary.video_page_overview_tab}</TabsTrigger>
<TabsTrigger value="webhooks">{dictionary.video_page_webhooks_tab}</TabsTrigger>
</TabsList>

<TabsContent value="overview">
<Overview videoId={videoId} />
<Overview videoId={videoId} dictionary={dictionary} />
</TabsContent>
<TabsContent value="webhooks">
<Webhooks videoId={videoId} />
Expand Down
48 changes: 34 additions & 14 deletions apps/web/src/app/[locale]/app/videos/[id]/tabs/overview/index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
import { unstable_noStore } from 'next/cache'
import { unstable_noStore } from 'next/cache';

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { serverClient } from '@/lib/trpc/server'
} from '@/components/ui/card';
import { serverClient } from '@/lib/trpc/server';

import { TranscriptionCard } from '../../transcription-card'
import { VideoForm } from './video-form'
import { TranscriptionCard } from '../../transcription-card';
import { VideoForm } from './video-form';
import { Dictionary } from '@nivo/i18n';

export interface OverviewProps {
videoId: string
videoId: string;
dictionary: Dictionary;
}

export async function Overview({ videoId }: OverviewProps) {
unstable_noStore()
export async function Overview({ videoId, dictionary }: OverviewProps) {
unstable_noStore();

const { video } = await serverClient.getUpload({
videoId,
})
type videoMockType = Awaited<ReturnType<typeof serverClient.getUpload>>;
const videoMock: videoMockType['video'] = {
tags: [
{ slug: "example-tag-1" },
{ slug: "example-tag-2" },
],
id: "video-id-123",
companyId: "company-id-456",
description: "This is a mock description of the video.",
duration: 3600, // Duration in seconds (e.g., 1 hour)
title: "Mock Video Title",
storageKey: "storage-key-789",
authorId: "author-id-101112",
createdAt: new Date(), // Current date and time
// @ts-ignore
language: "en",
};
const video = videoMock;
// const { video } = await serverClient.getUpload({
// videoId,
// });

return (
<div className="grid flex-1 grid-cols-[1fr_minmax(320px,480px)] gap-4">
<Card className="self-start">
<CardHeader>
<CardTitle>Edit video</CardTitle>
<CardDescription>Update video details</CardDescription>
<CardTitle>{dictionary.overview_edit_video_title}</CardTitle>
<CardDescription>{dictionary.overview_update_video_details}</CardDescription>
</CardHeader>
<CardContent>
<VideoForm video={video} />
Expand All @@ -39,5 +59,5 @@ export async function Overview({ videoId }: OverviewProps) {
shouldDisplayVideo={!!video.storageKey}
/>
</div>
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'

import { EditVideoFormSchema } from './video-form'
import { useDictionary } from '@/state/dictionary'

export interface VideoDescriptionInputProps
extends TextareaHTMLAttributes<HTMLTextAreaElement> {
Expand All @@ -20,6 +21,7 @@ export function VideoDescriptionInput({
videoId,
...props
}: VideoDescriptionInputProps) {
const dictionary = useDictionary()
const { setValue, register } = useFormContext<EditVideoFormSchema>()

const { completion, complete, isLoading } = useCompletion({
Expand All @@ -40,6 +42,7 @@ export function VideoDescriptionInput({
className="min-h-[132px] leading-relaxed"
{...register('description')}
{...props}
placeholder={dictionary.video_description_placeholder}
/>
<div>
<Button
Expand All @@ -53,7 +56,7 @@ export function VideoDescriptionInput({
) : (
<MagicWandIcon className="mr-2 h-3 w-3" />
)}
Generate with AI
{dictionary.generate_with_ai_button}
</Button>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,32 @@ import { trpc } from '@/lib/trpc/react'

import { VideoDescriptionInput } from './video-description-input'
import { VideoTagInput } from './video-tag-input'
import { useDictionary } from '@/state/dictionary'
import { Dictionary } from '@nivo/i18n'

interface VideoFormProps {
video: RouterOutput['getUpload']['video']
}

const editVideoFormSchema = z.object({
title: z.string().min(1, { message: 'Please provide a valid title.' }),
const editVideoFormSchema = (dictionary: Dictionary) => z.object({
title: z.string().min(1, { message: dictionary.edit_video_form_error_valid_title }),
description: z.string().nullable(),
commitUrl: z
.string()
.url({ message: 'Please provide a valid Github URL.' })
.url({ message: dictionary.edit_video_form_error_valid_github_url })
.nullable(),
tags: z.array(z.string()).min(1, {
message: 'At least one tag is required.',
message: dictionary.edit_video_form_error_tag_required,
}),
})

export type EditVideoFormSchema = z.infer<typeof editVideoFormSchema>
export type EditVideoFormSchema = z.infer<ReturnType<typeof editVideoFormSchema>>

export function VideoForm({ video }: VideoFormProps) {
const dictionary = useDictionary()

const editVideoForm = useForm<EditVideoFormSchema>({
resolver: zodResolver(editVideoFormSchema),
resolver: zodResolver(editVideoFormSchema(dictionary)),
defaultValues: {
title: video.title,
description: video.description,
Expand All @@ -64,8 +68,8 @@ export function VideoForm({ video }: VideoFormProps) {
commitUrl,
})
} catch {
toast.error('Uh oh! Something went wrong.', {
description: `An error ocurred while trying to save the video.`,
toast.error(dictionary.video_form_toast_error, {
description: dictionary.video_form_toast_error_description,
})
}
}
Expand All @@ -81,8 +85,8 @@ export function VideoForm({ video }: VideoFormProps) {
<form onSubmit={handleSubmit(handleSaveVideo)} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="title">
Title{' '}
<span className="text-muted-foreground">(synced with Skylab)</span>
{dictionary.video_form_title_label}{' '}
<span className="text-muted-foreground">{dictionary.video_form_title_synced}</span>
</Label>
<Input id="title" {...register('title')} defaultValue={video.title} />
{errors.title && (
Expand All @@ -93,14 +97,14 @@ export function VideoForm({ video }: VideoFormProps) {
</div>

<div className="space-y-2">
<Label htmlFor="commit">Tags</Label>
<Label htmlFor="commit">{dictionary.video_form_tags_label}</Label>
<VideoTagInput />
</div>

<div className="space-y-2">
<Label htmlFor="description">
Description{' '}
<span className="text-muted-foreground">(synced with Skylab)</span>
{dictionary.video_form_description_label}{' '}
<span className="text-muted-foreground">{dictionary.video_form_description_synced}</span>
</Label>
<VideoDescriptionInput
videoId={video.id}
Expand All @@ -109,15 +113,15 @@ export function VideoForm({ video }: VideoFormProps) {
</div>

<div className="space-y-2">
<Label htmlFor="externalProviderId">External Status/ID</Label>
<Label htmlFor="externalProviderId">{dictionary.video_form_external_status_id_label}</Label>
<div className="flex items-center gap-2 rounded-lg border border-zinc-200 px-3 has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-zinc-400 has-[input:focus-visible]:ring-offset-2 dark:border-zinc-800 dark:bg-zinc-950 dark:ring-offset-zinc-950 dark:has-[input:focus-visible]:ring-zinc-800">
<Badge variant="secondary">
{video.externalStatus || 'waiting'}
{video.externalStatus || dictionary.video_form_external_status_waiting}
</Badge>
<Separator orientation="vertical" className="h-4" />
<input
data-empty={!video.externalProviderId}
value={video.externalProviderId ?? '(not generated yet)'}
value={video.externalProviderId ?? dictionary.video_form_external_id_not_generated}
id="externalProviderId"
className="h-10 flex-1 bg-transparent py-2 text-sm outline-none"
readOnly
Expand All @@ -127,8 +131,8 @@ export function VideoForm({ video }: VideoFormProps) {

<div className="space-y-2">
<Label htmlFor="commit">
Commit reference{' '}
<span className="text-muted-foreground">(synced with Skylab)</span>
{dictionary.video_form_commit_reference_label}{' '}
<span className="text-muted-foreground">{dictionary.video_form_commit_reference_synced}</span>
</Label>
<Input
id="commit"
Expand All @@ -147,13 +151,13 @@ export function VideoForm({ video }: VideoFormProps) {
{isSubmitting ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
'Save'
dictionary.video_form_save_button
)}
</Button>
{isSubmitSuccessful && (
<div className="flex items-center gap-2 text-sm text-emerald-500 dark:text-emerald-400">
<CheckCircledIcon className="h-3 w-3" />
<span>Saved!</span>
<span>{dictionary.video_form_saved_message}</span>
</div>
)}
</div>
Expand Down
21 changes: 12 additions & 9 deletions apps/web/src/app/[locale]/app/videos/[id]/tabs/webhooks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ import { formatSecondsToMinutes } from '@/utils/format-seconds-to-minutes'

import { MetadataTooltip } from './metadata-tooltip'
import { WebhooksSkeletonTable } from './webhooks-skeleton-table'
import { useDictionary } from '@/state/dictionary'

export interface WebhooksProps {
videoId: string
}

export function Webhooks({ videoId }: WebhooksProps) {
const dictionary = useDictionary()

const {
data,
isLoading: isLoadingWebhooks,
Expand All @@ -51,18 +54,18 @@ export function Webhooks({ videoId }: WebhooksProps) {
<TableRow>
<TableHead style={{ width: 300 }}>
<div className="flex items-center gap-2">
<span>Webhook</span>
<span>{dictionary.webhook_table_head_webhook}</span>
{isRefetchingWebhooks && (
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
)}
</div>
</TableHead>
<TableHead style={{ width: 140 }}>Status</TableHead>
<TableHead style={{ width: 120 }}>Executed At</TableHead>
<TableHead style={{ width: 120 }}>Duration</TableHead>
<TableHead style={{ width: 140 }}>{dictionary.webhook_table_head_status}</TableHead>
<TableHead style={{ width: 120 }}>{dictionary.webhook_table_head_executed_at}</TableHead>
<TableHead style={{ width: 120 }}>{dictionary.webhook_table_head_duration}</TableHead>
<TableHead style={{ width: 240 }}>
<div className="flex items-center gap-2">
<span>Metadata</span>
<span>{dictionary.webhook_table_head_metadata}</span>
<MetadataTooltip />
</div>
</TableHead>
Expand All @@ -85,21 +88,21 @@ export function Webhooks({ videoId }: WebhooksProps) {
{webhook.status === 'RUNNING' && (
<div className="flex items-center gap-2 font-medium text-muted-foreground">
<DotsHorizontalIcon className="h-4 w-4" />
<span>Running</span>
<span>{dictionary.webhook_status_running}</span>
</div>
)}

{webhook.status === 'SUCCESS' && (
<div className="flex items-center gap-2 font-medium text-emerald-500 dark:text-emerald-400">
<CheckCircledIcon className="h-4 w-4" />
<span>Success</span>
<span>{dictionary.webhook_status_success}</span>
</div>
)}

{webhook.status === 'ERROR' && (
<div className="flex items-center gap-2 font-medium text-red-500 dark:text-red-400">
<CrossCircledIcon className="h-4 w-4" />
<span>Error</span>
<span>{dictionary.webhook_status_error}</span>
</div>
)}
</TableCell>
Expand Down Expand Up @@ -137,7 +140,7 @@ export function Webhooks({ videoId }: WebhooksProps) {
) : (
<TableRow>
<TableCell colSpan={5} className="h-24 text-center">
No results.
{dictionary.webhook_no_results}
</TableCell>
</TableRow>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
import { useDictionary } from '@/state/dictionary'

export function MetadataTooltip() {
const dictionary = useDictionary()

return (
<TooltipProvider>
<Tooltip>
Expand All @@ -16,7 +19,7 @@ export function MetadataTooltip() {
</TooltipTrigger>
<TooltipContent className="max-w-[280px]">
<p className="text-center text-xs text-zinc-600 dark:text-zinc-400">
Request body sent to the webhook
{dictionary.metadata_tooltip_request_body}
</p>
</TooltipContent>
</Tooltip>
Expand Down
Loading

0 comments on commit 5be377e

Please sign in to comment.