mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 02:45:39 -04:00
feat: reorganize API around operation domains
Restructure the oRPC contract from 14 resource-oriented routers to 8 domain-oriented routers for a cleaner public API surface. - Consolidate 7 watch procedures into unified tracking.watch/unwatch with scope + ids input (movie, episode, season, series) - Split dashboard across tracking (stats, history), library (continueWatching, upcoming), and discover (recommendations) - Merge explore + search + discover into single discover router - Absorb integrations into account.integrations - Merge system.authConfig into system.publicInfo - Collapse 6 admin setting endpoints into admin.settings.get/update - Deduplicate platforms.list + explore.watchProviders into discover.platforms - Add symmetric unwatchMovie/unwatchSeries core functions - Rename titles.detail→get, titles.recommendations→similar, people.detail→get BREAKING CHANGE: All client API paths have changed. REST paths now mirror router structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,7 +101,7 @@ export function CommandPalette() {
|
||||
const debouncedQuery = useDebounce(query, 300);
|
||||
const trimmedQuery = debouncedQuery.trim();
|
||||
const { data: searchData, isLoading: loading } = useQuery(
|
||||
orpc.search.queryOptions({
|
||||
orpc.discover.search.queryOptions({
|
||||
input: trimmedQuery ? { query: trimmedQuery } : skipToken,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ContinueWatchingList, ContinueWatchingSectionSkeleton } from "./continu
|
||||
import { FeedSection } from "./feed-section";
|
||||
|
||||
export function ContinueWatchingSection() {
|
||||
const { data, isPending } = useQuery(orpc.dashboard.continueWatching.queryOptions());
|
||||
const { data, isPending } = useQuery(orpc.library.continueWatching.queryOptions());
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { FeedSection } from "./feed-section";
|
||||
import { TitleGrid, TitleGridSectionSkeleton } from "./title-grid";
|
||||
|
||||
export function RecommendationsSection() {
|
||||
const { data, isPending } = useQuery(orpc.dashboard.recommendations.queryOptions());
|
||||
const { data, isPending } = useQuery(orpc.discover.recommendations.queryOptions());
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
|
||||
@@ -158,12 +158,12 @@ export function StatsDisplay({ stats }: { stats: DashboardStats }) {
|
||||
const [episodePeriod, setEpisodePeriod] = useState<TimePeriod>("this_week");
|
||||
|
||||
const { data: movieStats } = useQuery(
|
||||
orpc.dashboard.watchHistory.queryOptions({
|
||||
orpc.tracking.history.queryOptions({
|
||||
input: { type: "movie", period: moviePeriod },
|
||||
}),
|
||||
);
|
||||
const { data: episodeStats } = useQuery(
|
||||
orpc.dashboard.watchHistory.queryOptions({
|
||||
orpc.tracking.history.queryOptions({
|
||||
input: { type: "episode", period: episodePeriod },
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { orpc } from "@/lib/orpc/client";
|
||||
import { StatsDisplay, StatsSectionSkeleton } from "./stats-display";
|
||||
|
||||
export function StatsSection() {
|
||||
const { data: stats, isPending } = useQuery(orpc.dashboard.stats.queryOptions());
|
||||
const { data: stats, isPending } = useQuery(orpc.tracking.stats.queryOptions());
|
||||
|
||||
if (isPending) return <StatsSectionSkeleton />;
|
||||
if (!stats) return null;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { UpcomingRow } from "./upcoming-item";
|
||||
|
||||
export function UpcomingSection() {
|
||||
const { data, isPending } = useQuery(
|
||||
orpc.dashboard.upcoming.queryOptions({ input: { days: 7, limit: 5 } }),
|
||||
orpc.library.upcoming.queryOptions({ input: { days: 7, limit: 5 } }),
|
||||
);
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
@@ -69,11 +69,11 @@ export function DiscoverSection() {
|
||||
const [language, setLanguage] = useState<string | undefined>(undefined);
|
||||
const [platformId, setPlatformId] = useState<string | undefined>(undefined);
|
||||
|
||||
const { data: genreData } = useQuery(orpc.explore.genres.queryOptions({ input: { type } }));
|
||||
const { data: providerData } = useQuery(orpc.platforms.list.queryOptions());
|
||||
const { data: genreData } = useQuery(orpc.discover.genres.queryOptions({ input: { type } }));
|
||||
const { data: providerData } = useQuery(orpc.discover.platforms.queryOptions());
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isPending } = useInfiniteQuery(
|
||||
orpc.discover.infiniteOptions({
|
||||
orpc.discover.browse.infiniteOptions({
|
||||
input: (pageParam: number) => ({
|
||||
type,
|
||||
genreId,
|
||||
|
||||
@@ -54,7 +54,7 @@ export function FilterableTitleRow({
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
} = useInfiniteQuery(
|
||||
orpc.discover.infiniteOptions({
|
||||
orpc.discover.browse.infiniteOptions({
|
||||
input:
|
||||
selectedGenre != null
|
||||
? (pageParam: number) => ({
|
||||
|
||||
@@ -33,7 +33,7 @@ export function PersonDetailSkeleton() {
|
||||
|
||||
export function PersonDetailClient({ id }: { id: string }) {
|
||||
const { data, isPending, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
|
||||
orpc.people.detail.infiniteOptions({
|
||||
orpc.people.get.infiniteOptions({
|
||||
input: (pageParam: number) => ({ id, page: pageParam, limit: 20 }),
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage) =>
|
||||
|
||||
@@ -83,7 +83,7 @@ export function IntegrationCard({
|
||||
const providerInput = provider as "plex" | "jellyfin" | "emby" | "sonarr" | "radarr";
|
||||
|
||||
const connectMutation = useMutation(
|
||||
orpc.integrations.create.mutationOptions({
|
||||
orpc.account.integrations.create.mutationOptions({
|
||||
onSuccess: (result) => {
|
||||
setConnections((prev) => [...prev, { ...result, recentEvents: [] }]);
|
||||
toast.success(t`${label} connected`);
|
||||
@@ -93,7 +93,7 @@ export function IntegrationCard({
|
||||
);
|
||||
|
||||
const deleteMutation = useMutation(
|
||||
orpc.integrations.delete.mutationOptions({
|
||||
orpc.account.integrations.delete.mutationOptions({
|
||||
onMutate: () => {
|
||||
let previous: IntegrationConnection[] = [];
|
||||
setConnections((prev) => {
|
||||
@@ -111,7 +111,7 @@ export function IntegrationCard({
|
||||
);
|
||||
|
||||
const regenerateTokenMutation = useMutation(
|
||||
orpc.integrations.regenerateToken.mutationOptions({
|
||||
orpc.account.integrations.regenerateToken.mutationOptions({
|
||||
onSuccess: (result) => {
|
||||
setConnections((prev) =>
|
||||
prev.map((c) => (c.provider === provider ? { ...c, token: result.token } : c)),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IntegrationCard, type IntegrationConnection } from "./integration-card"
|
||||
import { INTEGRATION_CONFIGS } from "./integration-configs";
|
||||
|
||||
export function IntegrationsSection() {
|
||||
const { data, isPending } = useQuery(orpc.integrations.list.queryOptions());
|
||||
const { data, isPending } = useQuery(orpc.account.integrations.list.queryOptions());
|
||||
const [localConnections, setLocalConnections] = useState<IntegrationConnection[] | null>(null);
|
||||
|
||||
// Use local state if user has modified connections, else use query data
|
||||
|
||||
@@ -11,12 +11,12 @@ import { orpc } from "@/lib/orpc/client";
|
||||
|
||||
export function RegistrationSection() {
|
||||
const { t } = useLingui();
|
||||
const { data, isPending: isLoading } = useQuery(orpc.admin.registration.queryOptions());
|
||||
const { data, isPending: isLoading } = useQuery(orpc.admin.settings.get.queryOptions());
|
||||
const [registrationOpen, setRegistrationOpen] = useState<boolean | null>(null);
|
||||
const currentOpen = registrationOpen ?? data?.open ?? false;
|
||||
const currentOpen = registrationOpen ?? data?.registration.open ?? false;
|
||||
const [optimisticOpen, setOptimisticOpen] = useOptimistic(currentOpen);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const toggleMutation = useMutation(orpc.admin.toggleRegistration.mutationOptions());
|
||||
const toggleMutation = useMutation(orpc.admin.settings.update.mutationOptions());
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -30,7 +30,7 @@ export function RegistrationSection() {
|
||||
startTransition(async () => {
|
||||
setOptimisticOpen(checked);
|
||||
try {
|
||||
await toggleMutation.mutateAsync({ open: checked });
|
||||
await toggleMutation.mutateAsync({ registration: { open: checked } });
|
||||
setRegistrationOpen(checked);
|
||||
toast.success(checked ? t`Registration opened` : t`Registration closed`);
|
||||
} catch {
|
||||
|
||||
@@ -20,7 +20,7 @@ export function StreamingServicesSection() {
|
||||
const saveCounterRef = useRef(0);
|
||||
const initialized = useRef(false);
|
||||
|
||||
const platformsQuery = useQuery(orpc.platforms.list.queryOptions());
|
||||
const platformsQuery = useQuery(orpc.discover.platforms.queryOptions());
|
||||
const userPlatformsQuery = useQuery(orpc.account.platforms.queryOptions());
|
||||
|
||||
// Initialize selected IDs from server data
|
||||
|
||||
@@ -11,12 +11,12 @@ import { orpc } from "@/lib/orpc/client";
|
||||
|
||||
export function UpdateCheckSection() {
|
||||
const { t } = useLingui();
|
||||
const { data, isPending: isLoading } = useQuery(orpc.admin.updateCheck.queryOptions());
|
||||
const { data, isPending: isLoading } = useQuery(orpc.admin.settings.get.queryOptions());
|
||||
const [localEnabled, setLocalEnabled] = useState<boolean | null>(null);
|
||||
const currentEnabled = localEnabled ?? data?.enabled ?? true;
|
||||
const currentEnabled = localEnabled ?? data?.updateCheck.enabled ?? true;
|
||||
const [optimisticEnabled, setOptimisticEnabled] = useOptimistic(currentEnabled);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const toggleMutation = useMutation(orpc.admin.toggleUpdateCheck.mutationOptions());
|
||||
const toggleMutation = useMutation(orpc.admin.settings.update.mutationOptions());
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -30,7 +30,7 @@ export function UpdateCheckSection() {
|
||||
startTransition(async () => {
|
||||
setOptimisticEnabled(checked);
|
||||
try {
|
||||
await toggleMutation.mutateAsync({ enabled: checked });
|
||||
await toggleMutation.mutateAsync({ updateCheck: { enabled: checked } });
|
||||
setLocalEnabled(checked);
|
||||
toast.success(checked ? t`Update checks enabled` : t`Update checks disabled`);
|
||||
} catch {
|
||||
|
||||
@@ -89,7 +89,7 @@ function QuickAddButton({ id, userStatus }: { id: string; userStatus?: TitleStat
|
||||
const [optimisticStatus, setOptimisticStatus] = useState<TitleStatus | null>(null);
|
||||
|
||||
const quickAddMutation = useMutation(
|
||||
orpc.titles.quickAdd.mutationOptions({
|
||||
orpc.tracking.quickAdd.mutationOptions({
|
||||
onSuccess: () => setOptimisticStatus("in_watchlist"),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export function useTitleUserInfo() {
|
||||
const { titleId } = useTitleContext();
|
||||
const { data: session } = useSession();
|
||||
const { data } = useQuery({
|
||||
...orpc.titles.userInfo.queryOptions({ input: { id: titleId } }),
|
||||
...orpc.tracking.userInfo.queryOptions({ input: { id: titleId } }),
|
||||
enabled: !!session,
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -27,7 +27,7 @@ function RecommendationsSkeleton() {
|
||||
|
||||
export function TitleRecommendations({ titleId }: { titleId: string }) {
|
||||
const { data, isLoading } = useQuery(
|
||||
orpc.titles.recommendations.queryOptions({ input: { id: titleId } }),
|
||||
orpc.titles.similar.queryOptions({ input: { id: titleId } }),
|
||||
);
|
||||
|
||||
if (isLoading) return <RecommendationsSkeleton />;
|
||||
|
||||
@@ -19,7 +19,7 @@ export function useTitleActions() {
|
||||
const { t } = useLingui();
|
||||
const { titleId, titleName, seasons, setWatchingEp } = useTitleContext();
|
||||
const queryClient = useQueryClient();
|
||||
const userInfoKey = orpc.titles.userInfo.queryKey({ input: { id: titleId } });
|
||||
const userInfoKey = orpc.tracking.userInfo.queryKey({ input: { id: titleId } });
|
||||
|
||||
const getUserInfo = useCallback(
|
||||
() =>
|
||||
@@ -40,15 +40,10 @@ export function useTitleActions() {
|
||||
[queryClient, userInfoKey],
|
||||
);
|
||||
|
||||
const { mutateAsync: batchWatch } = useMutation(orpc.episodes.batchWatch.mutationOptions());
|
||||
const { mutateAsync: updateStatus } = useMutation(orpc.titles.updateStatus.mutationOptions());
|
||||
const { mutateAsync: updateRating } = useMutation(orpc.titles.updateRating.mutationOptions());
|
||||
const { mutateAsync: watchMovie } = useMutation(orpc.titles.watchMovie.mutationOptions());
|
||||
const { mutateAsync: unwatchEp } = useMutation(orpc.episodes.unwatch.mutationOptions());
|
||||
const { mutateAsync: watchEp } = useMutation(orpc.episodes.watch.mutationOptions());
|
||||
const { mutateAsync: watchSeason } = useMutation(orpc.seasons.watch.mutationOptions());
|
||||
const { mutateAsync: unwatchSeason } = useMutation(orpc.seasons.unwatch.mutationOptions());
|
||||
const { mutateAsync: watchAll } = useMutation(orpc.titles.watchAll.mutationOptions());
|
||||
const { mutateAsync: watch } = useMutation(orpc.tracking.watch.mutationOptions());
|
||||
const { mutateAsync: unwatch } = useMutation(orpc.tracking.unwatch.mutationOptions());
|
||||
const { mutateAsync: updateStatus } = useMutation(orpc.tracking.updateStatus.mutationOptions());
|
||||
const { mutateAsync: updateRating } = useMutation(orpc.tracking.rate.mutationOptions());
|
||||
|
||||
const catchUp = useCallback(
|
||||
async (episodeIds: string[]) => {
|
||||
@@ -64,7 +59,7 @@ export function useTitleActions() {
|
||||
}));
|
||||
|
||||
try {
|
||||
await batchWatch({ episodeIds });
|
||||
await watch({ scope: "episode", ids: episodeIds });
|
||||
await queryClient.invalidateQueries({ queryKey: userInfoKey });
|
||||
const count = episodeIds.length;
|
||||
toast.success(
|
||||
@@ -79,7 +74,7 @@ export function useTitleActions() {
|
||||
toast.error(t`Failed to catch up`);
|
||||
}
|
||||
},
|
||||
[getUserInfo, setUserInfo, batchWatch, queryClient, userInfoKey, t],
|
||||
[getUserInfo, setUserInfo, watch, queryClient, userInfoKey, t],
|
||||
);
|
||||
|
||||
const handleStatusChange = useCallback(
|
||||
@@ -132,13 +127,13 @@ export function useTitleActions() {
|
||||
const prevStatus = getUserInfo().status;
|
||||
setUserInfo((old) => ({ ...old, status: "completed" }));
|
||||
try {
|
||||
await watchMovie({ id: titleId });
|
||||
await watch({ scope: "movie", ids: [titleId] });
|
||||
toast.success(t`Marked "${titleName}" as watched`);
|
||||
} catch {
|
||||
setUserInfo((old) => ({ ...old, status: prevStatus }));
|
||||
toast.error(t`Failed to mark as watched`);
|
||||
}
|
||||
}, [getUserInfo, setUserInfo, queryClient, userInfoKey, titleId, titleName, watchMovie, t]);
|
||||
}, [getUserInfo, setUserInfo, queryClient, userInfoKey, titleId, titleName, watch, t]);
|
||||
|
||||
const handleWatchEpisode = useCallback(
|
||||
async (episodeId: string, seasonNum: number, epNum: number, isWatched: boolean) => {
|
||||
@@ -156,7 +151,7 @@ export function useTitleActions() {
|
||||
}));
|
||||
|
||||
try {
|
||||
await unwatchEp({ id: episodeId });
|
||||
await unwatch({ scope: "episode", ids: [episodeId] });
|
||||
toast.success(t`Unwatched S${seasonNum} E${epNum}`);
|
||||
} catch {
|
||||
setUserInfo((old) => ({
|
||||
@@ -181,7 +176,7 @@ export function useTitleActions() {
|
||||
}));
|
||||
|
||||
try {
|
||||
await watchEp({ id: episodeId });
|
||||
await watch({ scope: "episode", ids: [episodeId] });
|
||||
|
||||
const watchedSet = new Set(getUserInfo().episodeWatches);
|
||||
const previousUnwatched: string[] = [];
|
||||
@@ -231,8 +226,8 @@ export function useTitleActions() {
|
||||
setWatchingEp,
|
||||
seasons,
|
||||
catchUp,
|
||||
unwatchEp,
|
||||
watchEp,
|
||||
unwatch,
|
||||
watch,
|
||||
t,
|
||||
],
|
||||
);
|
||||
@@ -257,7 +252,7 @@ export function useTitleActions() {
|
||||
}));
|
||||
|
||||
try {
|
||||
await watchSeason({ id: season.id });
|
||||
await watch({ scope: "season", ids: [season.id] });
|
||||
await queryClient.invalidateQueries({ queryKey: userInfoKey });
|
||||
|
||||
const currentWatchSet = new Set(getUserInfo().episodeWatches);
|
||||
@@ -296,7 +291,7 @@ export function useTitleActions() {
|
||||
toast.error(t`Failed to mark some episodes`);
|
||||
}
|
||||
},
|
||||
[getUserInfo, setUserInfo, seasons, catchUp, watchSeason, queryClient, userInfoKey, t],
|
||||
[getUserInfo, setUserInfo, seasons, catchUp, watch, queryClient, userInfoKey, t],
|
||||
);
|
||||
|
||||
const handleUnmarkSeason = useCallback(
|
||||
@@ -312,7 +307,7 @@ export function useTitleActions() {
|
||||
}));
|
||||
|
||||
try {
|
||||
await unwatchSeason({ id: season.id });
|
||||
await unwatch({ scope: "season", ids: [season.id] });
|
||||
const seasonNumber = season.seasonNumber;
|
||||
const seasonLabel = season.name ?? t`Season ${seasonNumber}`;
|
||||
toast.success(t`Unwatched all of ${seasonLabel}`);
|
||||
@@ -325,7 +320,7 @@ export function useTitleActions() {
|
||||
toast.error(t`Failed to unmark some episodes`);
|
||||
}
|
||||
},
|
||||
[getUserInfo, setUserInfo, queryClient, userInfoKey, unwatchSeason, t],
|
||||
[getUserInfo, setUserInfo, queryClient, userInfoKey, unwatch, t],
|
||||
);
|
||||
|
||||
const handleMarkAllWatched = useCallback(async () => {
|
||||
@@ -339,7 +334,7 @@ export function useTitleActions() {
|
||||
status: old.status ?? "watching",
|
||||
}));
|
||||
try {
|
||||
await watchAll({ id: titleId });
|
||||
await watch({ scope: "series", ids: [titleId] });
|
||||
// Refresh to get server-derived display status (caught_up / completed)
|
||||
await queryClient.invalidateQueries({ queryKey: userInfoKey });
|
||||
toast.success(t`Marked all episodes as watched`);
|
||||
@@ -351,7 +346,7 @@ export function useTitleActions() {
|
||||
}));
|
||||
toast.error(t`Failed to mark all episodes as watched`);
|
||||
}
|
||||
}, [getUserInfo, setUserInfo, seasons, titleId, watchAll, queryClient, userInfoKey, t]);
|
||||
}, [getUserInfo, setUserInfo, seasons, titleId, watch, queryClient, userInfoKey, t]);
|
||||
|
||||
return {
|
||||
handleStatusChange,
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { updateToastDismissedVersionAtom } from "@/lib/atoms/update-check";
|
||||
import type { UpdateCheckResult } from "@sofa/api/schemas";
|
||||
import type { AdminSettings } from "@sofa/api/schemas";
|
||||
|
||||
export function UpdateToast({ data }: { data: UpdateCheckResult | null }) {
|
||||
export function UpdateToast({ data }: { data: AdminSettings["updateCheck"] | null }) {
|
||||
const { t } = useLingui();
|
||||
const [dismissedVersion, setDismissedVersion] = useAtom(updateToastDismissedVersionAtom);
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ export const Route = createFileRoute("/_app")({
|
||||
let updateCheck = null;
|
||||
if (session.user.role === "admin") {
|
||||
try {
|
||||
({ updateCheck } = await client.admin.updateCheck({}));
|
||||
const settings = await client.admin.settings.get({});
|
||||
updateCheck = settings.updateCheck;
|
||||
} catch {
|
||||
// Silently ignore — update check is non-critical
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ export const Route = createFileRoute("/_app/dashboard")({
|
||||
staleTime: 30_000,
|
||||
loader: async ({ context }) => {
|
||||
await Promise.all([
|
||||
context.queryClient.ensureQueryData(orpc.dashboard.stats.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.dashboard.continueWatching.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.dashboard.recommendations.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.tracking.stats.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.library.continueWatching.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.discover.recommendations.queryOptions()),
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.dashboard.upcoming.queryOptions({ input: { days: 7, limit: 5 } }),
|
||||
orpc.library.upcoming.queryOptions({ input: { days: 7, limit: 5 } }),
|
||||
),
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.library.list.queryOptions({ input: { page: 1, limit: 10 } }),
|
||||
|
||||
@@ -17,7 +17,7 @@ export const Route = createFileRoute("/_app/explore")({
|
||||
loader: async ({ context }) => {
|
||||
await Promise.all([
|
||||
context.queryClient.ensureInfiniteQueryData(
|
||||
orpc.explore.trending.infiniteOptions({
|
||||
orpc.discover.trending.infiniteOptions({
|
||||
input: (pageParam: number) => ({ type: "all" as const, page: pageParam }),
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage) =>
|
||||
@@ -26,16 +26,16 @@ export const Route = createFileRoute("/_app/explore")({
|
||||
}),
|
||||
),
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.explore.popular.queryOptions({ input: { type: "movie" } }),
|
||||
orpc.discover.popular.queryOptions({ input: { type: "movie" } }),
|
||||
),
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.explore.popular.queryOptions({ input: { type: "tv" } }),
|
||||
orpc.discover.popular.queryOptions({ input: { type: "tv" } }),
|
||||
),
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.explore.genres.queryOptions({ input: { type: "movie" } }),
|
||||
orpc.discover.genres.queryOptions({ input: { type: "movie" } }),
|
||||
),
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.explore.genres.queryOptions({ input: { type: "tv" } }),
|
||||
orpc.discover.genres.queryOptions({ input: { type: "tv" } }),
|
||||
),
|
||||
]);
|
||||
},
|
||||
@@ -81,7 +81,7 @@ function ExplorePage() {
|
||||
hasNextPage: hasNextTrending,
|
||||
isFetchingNextPage: isFetchingNextTrending,
|
||||
} = useInfiniteQuery(
|
||||
orpc.explore.trending.infiniteOptions({
|
||||
orpc.discover.trending.infiniteOptions({
|
||||
input: (pageParam: number) => ({ type: "all" as const, page: pageParam }),
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage) =>
|
||||
@@ -91,16 +91,16 @@ function ExplorePage() {
|
||||
);
|
||||
|
||||
const { data: popularMoviesData, isPending: moviesPending } = useQuery(
|
||||
orpc.explore.popular.queryOptions({ input: { type: "movie" } }),
|
||||
orpc.discover.popular.queryOptions({ input: { type: "movie" } }),
|
||||
);
|
||||
const { data: popularTvData, isPending: tvPending } = useQuery(
|
||||
orpc.explore.popular.queryOptions({ input: { type: "tv" } }),
|
||||
orpc.discover.popular.queryOptions({ input: { type: "tv" } }),
|
||||
);
|
||||
const { data: movieGenreData } = useQuery(
|
||||
orpc.explore.genres.queryOptions({ input: { type: "movie" } }),
|
||||
orpc.discover.genres.queryOptions({ input: { type: "movie" } }),
|
||||
);
|
||||
const { data: tvGenreData } = useQuery(
|
||||
orpc.explore.genres.queryOptions({ input: { type: "tv" } }),
|
||||
orpc.discover.genres.queryOptions({ input: { type: "tv" } }),
|
||||
);
|
||||
|
||||
const isPending = trendingPending || moviesPending || tvPending;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { client, orpc } from "@/lib/orpc/client";
|
||||
|
||||
export const Route = createFileRoute("/_app/onboarding")({
|
||||
loader: async ({ context }) => {
|
||||
await context.queryClient.ensureQueryData(orpc.platforms.list.queryOptions());
|
||||
await context.queryClient.ensureQueryData(orpc.discover.platforms.queryOptions());
|
||||
},
|
||||
head: () => ({ meta: [{ title: "Get Started — Sofa" }] }),
|
||||
errorComponent: RouteError,
|
||||
@@ -23,7 +23,7 @@ function OnboardingPage() {
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const platformsQuery = useQuery(orpc.platforms.list.queryOptions());
|
||||
const platformsQuery = useQuery(orpc.discover.platforms.queryOptions());
|
||||
const platforms = platformsQuery.data?.platforms ?? [];
|
||||
|
||||
const handleToggle = useCallback((id: string) => {
|
||||
|
||||
@@ -11,7 +11,7 @@ export const Route = createFileRoute("/_app/people/$id")({
|
||||
loader: async ({ params, context }) => {
|
||||
try {
|
||||
const data = await context.queryClient.ensureInfiniteQueryData(
|
||||
orpc.people.detail.infiniteOptions({
|
||||
orpc.people.get.infiniteOptions({
|
||||
input: (pageParam: number) => ({
|
||||
id: params.id,
|
||||
page: pageParam,
|
||||
|
||||
@@ -35,9 +35,9 @@ export const Route = createFileRoute("/_app/settings")({
|
||||
staleTime: 30_000,
|
||||
loader: async ({ context }) => {
|
||||
const promises: Promise<unknown>[] = [
|
||||
context.queryClient.ensureQueryData(orpc.integrations.list.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.account.integrations.list.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.system.status.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.platforms.list.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.discover.platforms.queryOptions()),
|
||||
context.queryClient.ensureQueryData(orpc.account.platforms.queryOptions()),
|
||||
];
|
||||
const isAdmin = context.session.user.role === "admin";
|
||||
|
||||
@@ -21,10 +21,10 @@ export const Route = createFileRoute("/_app/titles/$id")({
|
||||
try {
|
||||
const [titleResult, userInfo] = await Promise.all([
|
||||
context.queryClient.ensureQueryData(
|
||||
orpc.titles.detail.queryOptions({ input: { id: params.id } }),
|
||||
orpc.titles.get.queryOptions({ input: { id: params.id } }),
|
||||
),
|
||||
context.queryClient
|
||||
.ensureQueryData(orpc.titles.userInfo.queryOptions({ input: { id: params.id } }))
|
||||
.ensureQueryData(orpc.tracking.userInfo.queryOptions({ input: { id: params.id } }))
|
||||
.catch(() => null),
|
||||
]);
|
||||
return { ...titleResult, userInfo };
|
||||
|
||||
@@ -23,7 +23,7 @@ export const Route = createFileRoute("/_app/upcoming")({
|
||||
staleTime: 30_000,
|
||||
loader: async ({ context }) => {
|
||||
await context.queryClient.ensureInfiniteQueryData(
|
||||
orpc.dashboard.upcoming.infiniteOptions({
|
||||
orpc.library.upcoming.infiniteOptions({
|
||||
input: (pageParam: string | undefined) => ({
|
||||
days: 90,
|
||||
limit: 20,
|
||||
@@ -70,7 +70,7 @@ function UpcomingPage() {
|
||||
const statusFilter = search.status ?? "all";
|
||||
|
||||
const { data, isPending, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
|
||||
orpc.dashboard.upcoming.infiniteOptions({
|
||||
orpc.library.upcoming.infiniteOptions({
|
||||
input: (pageParam: string | undefined) => ({
|
||||
days: 90,
|
||||
limit: 20,
|
||||
|
||||
@@ -5,25 +5,25 @@ import { client } from "@/lib/orpc/client";
|
||||
|
||||
export const Route = createFileRoute("/_auth/login")({
|
||||
beforeLoad: async () => {
|
||||
const authConfig = await client.system.authConfig({});
|
||||
if (authConfig.userCount === 0) throw redirect({ to: "/register" });
|
||||
const publicInfo = await client.system.publicInfo({});
|
||||
if (publicInfo.userCount === 0) throw redirect({ to: "/register" });
|
||||
|
||||
return { authConfig };
|
||||
return { publicInfo };
|
||||
},
|
||||
head: () => ({ meta: [{ title: "Sign in — Sofa" }] }),
|
||||
component: LoginPage,
|
||||
});
|
||||
|
||||
function LoginPage() {
|
||||
const { authConfig } = Route.useRouteContext();
|
||||
const { publicInfo } = Route.useRouteContext();
|
||||
return (
|
||||
<AuthForm
|
||||
mode="login"
|
||||
authConfig={{
|
||||
oidcEnabled: authConfig.oidcEnabled,
|
||||
oidcProviderName: authConfig.oidcProviderName,
|
||||
passwordLoginDisabled: authConfig.passwordLoginDisabled,
|
||||
registrationOpen: authConfig.registrationOpen,
|
||||
oidcEnabled: publicInfo.oidcEnabled,
|
||||
oidcProviderName: publicInfo.oidcProviderName,
|
||||
passwordLoginDisabled: publicInfo.passwordLoginDisabled,
|
||||
registrationOpen: publicInfo.registrationOpen,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,17 +7,17 @@ import { client } from "@/lib/orpc/client";
|
||||
|
||||
export const Route = createFileRoute("/_auth/register")({
|
||||
beforeLoad: async () => {
|
||||
const authConfig = await client.system.authConfig({});
|
||||
return { authConfig };
|
||||
const publicInfo = await client.system.publicInfo({});
|
||||
return { publicInfo };
|
||||
},
|
||||
head: () => ({ meta: [{ title: "Create account — Sofa" }] }),
|
||||
component: RegisterPage,
|
||||
});
|
||||
|
||||
function RegisterPage() {
|
||||
const { authConfig } = Route.useRouteContext();
|
||||
const { publicInfo } = Route.useRouteContext();
|
||||
|
||||
if (!authConfig.registrationOpen) {
|
||||
if (!publicInfo.registrationOpen) {
|
||||
return (
|
||||
<div className="relative mx-auto w-full max-w-sm">
|
||||
<div className="bg-primary/3 absolute -inset-4 rounded-2xl blur-2xl" />
|
||||
@@ -50,9 +50,9 @@ function RegisterPage() {
|
||||
<AuthForm
|
||||
mode="register"
|
||||
authConfig={{
|
||||
oidcEnabled: authConfig.oidcEnabled,
|
||||
oidcProviderName: authConfig.oidcProviderName,
|
||||
passwordLoginDisabled: authConfig.passwordLoginDisabled,
|
||||
oidcEnabled: publicInfo.oidcEnabled,
|
||||
oidcProviderName: publicInfo.oidcProviderName,
|
||||
passwordLoginDisabled: publicInfo.passwordLoginDisabled,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user