Skip to content

Commit

Permalink
patch placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
sphinxrave committed Jun 30, 2024
1 parent e293f80 commit b57d82f
Show file tree
Hide file tree
Showing 10 changed files with 480 additions and 537 deletions.
189 changes: 189 additions & 0 deletions packages/react/src/components/about/placeholder/FormFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { useMemo, useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import {
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
} from "@/shadcn/ui/form";
import { Input } from "@/shadcn/ui/input";
import { RadioGroup, RadioGroupItem } from "@/shadcn/ui/radio-group";
import { DatePicker } from "@/components/common/DatePicker";
import { ChannelPicker } from "@/components/channel/ChannelPicker";
import { useTranslation } from "react-i18next";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shadcn/ui/select";
import { allTimezones } from "../../settings/timezones";
import { localeAtom } from "@/store/i18n";
import { useAtomValue } from "jotai";

export const FormInput = ({
name,
label,
description,
...props
}: {
name: string;
label: string;
description?: string;
} & Omit<Parameters<typeof Input>[0], "id">) => {
const { control } = useFormContext();

return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<Input {...field} {...props} />
</FormControl>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage />
</FormItem>
)}
/>
);
};

export const FormRadioGroup = ({
name,
label,
options,
...props
}: {
name: string;
label: string;
options: { value: string; label: string }[];
} & Omit<
Parameters<typeof RadioGroup>[0],
"onValueChange" | "defaultValue"
>) => {
const { control } = useFormContext();
return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
{...props}
>
{options.map(({ value, label }) => (
<FormItem
key={value}
className="flex items-center space-x-3 space-y-0"
>
<FormControl>
<RadioGroupItem value={value} />
</FormControl>
<FormLabel className="font-normal">{label}</FormLabel>
</FormItem>
))}
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
};

export const FormDatePicker = ({
name,
label,
...props
}: Parameters<typeof DatePicker>[0] & { name: string; label: string }) => {
const { t } = useTranslation();
const { control } = useFormContext();
const { dayjs } = useAtomValue(localeAtom);
const [timezone, setTimezone] = useState("Asia/Tokyo");
const timezoneOptions = useMemo(() => {
return Object.keys(allTimezones).map((tz) => {
const now = dayjs().tz(tz);
const offset = now.format("Z");
return {
value: tz,
label: tz,
offset: offset,
};
});
}, [dayjs]);

return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>{label}</FormLabel>
<FormControl>
<div className="flex items-center gap-2">
<Select value={timezone} onValueChange={setTimezone}>
<SelectTrigger className="h-9 w-auto shrink">
<SelectValue placeholder="Select timezone" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{timezoneOptions.map(({ label, value, offset }) => (
<SelectItem key={value} value={value}>
{label} ({offset})
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<DatePicker
selected={field.value ? new Date(field.value) : undefined}
timezone={timezone}
{...props}
onSelect={(date: Date) => field.onChange(date.toISOString())}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
};
export const FormChannelPicker = ({
name,
label,
...props
}: Parameters<typeof ChannelPicker>[0] & { name: string; label: string }) => {
const { control } = useFormContext();

return (
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormLabel>{label}</FormLabel>
<FormControl>
<ChannelPicker
{...props}
value={field.value}
onSelect={({ id }) => field.onChange(id)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
};
28 changes: 28 additions & 0 deletions packages/react/src/components/about/placeholder/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { z } from "zod";

export const placeholderSchema = z.object({
id: z.string().optional(),
channel_id: z.string().min(1, "Channel ID is required"),
duration: z.number().min(1, "Duration must be at least 1 minute"),
liveTime: z.string().min(1, "Live time is required"),
title: z.object({
credits: z.object({
editor: z.object({
user: z.string().optional(),
name: z.string().min(1, "Editor name is required"),
}),
}),
name: z.string().min(1, "Title is required"),
jp_name: z.string().optional(),
link: z.string().url("Invalid URL"),
thumbnail: z.string().url("Invalid URL"),
placeholderType: z.enum([
"event",
"external-stream",
"scheduled-yt-stream",
]),
certainty: z.enum(["certain", "likely"]),
}),
});

export type PlaceholderFormData = z.infer<typeof placeholderSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { usePlaceholderMutation } from "@/services/video.service";
import { useToast } from "@/shadcn/ui/use-toast";
import { useTranslation } from "react-i18next";
import { PlaceholderFormData } from "./schema";

export const usePlaceholderSubmit = (token: string | null) => {
const { t } = useTranslation();
const { toast } = useToast();
const { mutate } = usePlaceholderMutation();

const onSubmit = (data: PlaceholderFormData) => {
mutate(
{
body: { ...data, duration: data.duration * 60 },
token,
},
{
onSuccess: () => {
toast({
title: t("component.addPlaceholder.success"),
});
},
onError: (error) => {
console.error(error);
toast({
title: t("component.addPlaceholder.error"),
description: error.message,
variant: "error",
});
},
},
);
};

return onSubmit;
};
9 changes: 8 additions & 1 deletion packages/react/src/components/channel/ChannelPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import atomWithDebounce from "@/lib/atomWithDebounce";
import { useChannel } from "@/services/channel.service";
import { useSearchAutoCompleteMutation } from "@/services/search.service";
import { Button } from "@/shadcn/ui/button";
import {
Expand All @@ -10,6 +11,7 @@ import {
CommandList,
} from "@/shadcn/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/shadcn/ui/popover";
import { usePreferredName } from "@/store/settings";
import { useAtom, useAtomValue } from "jotai";
import { useEffect } from "react";
import {
Expand Down Expand Up @@ -50,6 +52,9 @@ export function ChannelPicker<
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue]);

const { data: selectedChannel } = useChannel(value, { enabled: !!value });
const preferredSelectedName = usePreferredName(selectedChannel || {});

return (
<Popover>
<PopoverTrigger asChild>
Expand All @@ -59,7 +64,9 @@ export function ChannelPicker<
variant="outline"
role="combobox"
>
{value || t("channelRequest.ChannelPickerLabel")}
{preferredSelectedName ||
value ||
t("channelRequest.ChannelPickerLabel")}
<div className="i-lucide:chevrons-up-down text-sm opacity-50" />
</Button>
</PopoverTrigger>
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/components/common/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ export function DatePicker(
<PopoverTrigger asChild>
<Button
variant="outline"
size="lg"
className={cn(
"w-full justify-start border-base-6 text-left font-normal focus:border-blue-6",
"h-9 justify-start border-base-6 text-left text-sm font-normal focus:border-blue-6 ",
!date && "text-base-11",
)}
>
Expand Down
Loading

0 comments on commit b57d82f

Please sign in to comment.