import { DetailCardSkeleton } from '@/components/detail-card-skeleton'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { ContainerHeader } from '@/components/ui/container-header'; import { DetailEmptyView } from '@/components/ui/detail-empty-view'; import { FormFieldErrors } from '@/components/ui/form-field-errors'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { QueryErrorView } from '@/components/ui/query-error-view'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { useAppForm } from '@/components/ui/tanstack-form'; import { MikanSeasonEnum } from '@/domains/recorder/schema/mikan'; import { GET_SUBSCRIPTION_DETAIL, type SubscriptionForm, SubscriptionFormSchema, UPDATE_SUBSCRIPTIONS, } from '@/domains/recorder/schema/subscriptions'; import { SubscriptionService } from '@/domains/recorder/services/subscription.service'; import { useInject } from '@/infra/di/inject'; import { apolloErrorToMessage, getApolloQueryError, } from '@/infra/errors/apollo'; import { Credential3rdTypeEnum, type GetSubscriptionDetailQuery, SubscriptionCategoryEnum, type UpdateSubscriptionsMutation, type UpdateSubscriptionsMutationVariables, } from '@/infra/graphql/gql/graphql'; import type { RouteStateDataOption } from '@/infra/routes/traits'; import { useMutation, useQuery } from '@apollo/client'; import { createFileRoute } from '@tanstack/react-router'; import { Save } from 'lucide-react'; import { useCallback, useMemo } from 'react'; import { toast } from 'sonner'; import { Credential3rdSelectContent } from './-credential3rd-select'; export const Route = createFileRoute('/_app/subscriptions/edit/$id')({ component: SubscriptionEditRouteComponent, staticData: { breadcrumb: { label: 'Edit' }, } satisfies RouteStateDataOption, }); type SubscriptionDetailDto = NonNullable< GetSubscriptionDetailQuery['subscriptions']['nodes'][0] >; function FormView({ subscription, onCompleted, }: { subscription: SubscriptionDetailDto; onCompleted: VoidFunction; }) { const subscriptionService = useInject(SubscriptionService); const [updateSubscription, { loading: updating }] = useMutation< UpdateSubscriptionsMutation, UpdateSubscriptionsMutationVariables >(UPDATE_SUBSCRIPTIONS, { onCompleted, onError: (error) => { toast.error('Update subscription failed', { description: error.message, }); }, }); // Extract source URL metadata for form initialization const sourceUrlMeta = useMemo( () => subscriptionService.extractSourceUrlMeta( subscription.category, subscription.sourceUrl ), [subscription.category, subscription.sourceUrl, subscriptionService] ); // Initialize form with current subscription data const defaultValues = useMemo(() => { const base = { displayName: subscription.displayName, category: subscription.category, enabled: subscription.enabled, sourceUrl: subscription.sourceUrl, credentialId: subscription.credential3rd?.id || '', }; if ( subscription.category === SubscriptionCategoryEnum.MikanSeason && sourceUrlMeta?.category === SubscriptionCategoryEnum.MikanSeason ) { return { ...base, year: sourceUrlMeta.year, seasonStr: sourceUrlMeta.seasonStr, }; } return base; }, [subscription, sourceUrlMeta]); const form = useAppForm({ defaultValues: defaultValues as unknown as SubscriptionForm, validators: { onChangeAsync: SubscriptionFormSchema, onChangeAsyncDebounceMs: 300, onSubmit: SubscriptionFormSchema, }, onSubmit: async (form) => { const input = subscriptionService.transformInsertFormToInput(form.value); await updateSubscription({ variables: { data: input, filter: { id: { eq: subscription.id, }, }, }, }); }, }); return (
form.handleSubmit()} disabled={updating}> {updating ? 'Saving...' : 'Save'} } />
Subscription information Edit subscription information
{subscription.category}
{ e.preventDefault(); e.stopPropagation(); form.handleSubmit(); }} className="space-y-6" > {(field) => (
field.handleChange(e.target.value)} placeholder="Please enter display name" autoComplete="off" /> {field.state.meta.errors && ( )}
)}
{/* Category is read-only in edit mode */}
{subscription.category}
{/* Conditional fields based on category */} {subscription.category === SubscriptionCategoryEnum.MikanSeason ? ( <> {(field) => (
{field.state.meta.errors && ( )}
)}
{(field) => (
field.handleChange(Number.parseInt(e.target.value)) } placeholder={`Please enter full year (e.g. ${new Date().getFullYear()})`} autoComplete="off" /> {field.state.meta.errors && ( )}
)}
{(field) => (
{field.state.meta.errors && ( )}
)}
) : ( {(field) => (
field.handleChange(e.target.value)} placeholder="Please enter source URL" autoComplete="off" /> {field.state.meta.errors && ( )}
)}
)} {(field) => (
Enable this subscription
field.handleChange(checked)} />
)}
); } function SubscriptionEditRouteComponent() { const { id } = Route.useParams(); const { loading, error, data, refetch } = useQuery(GET_SUBSCRIPTION_DETAIL, { variables: { id: Number.parseInt(id), }, }); const subscription = data?.subscriptions?.nodes?.[0]; const onCompleted = useCallback(async () => { const refetchResult = await refetch(); const error = getApolloQueryError(refetchResult); if (error) { toast.error('Update subscription failed', { description: apolloErrorToMessage(error), }); } else { toast.success('Update subscription successfully'); } }, [refetch]); if (loading) { return ; } if (error) { return ; } if (!subscription) { return ; } return ; }