Add @sofa/native Expo React Native app (#7)

This commit is contained in:
2026-03-12 19:39:54 -04:00
committed by GitHub
parent a326c968b7
commit 83839a0cd7
130 changed files with 9015 additions and 971 deletions
+15 -15
View File
@@ -18,13 +18,13 @@
"@fontsource/dm-serif-display": "5.2.8",
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/tanstack-query": "1.13.6",
"@orpc/tanstack-query": "catalog:",
"@player.style/sutro": "0.2.1",
"@sofa/api": "workspace:*",
"@tabler/icons-react": "3.40.0",
"@tanstack/react-hotkeys": "0.4.1",
"@tanstack/react-query": "5.90.21",
"@tanstack/react-router": "1.166.6",
"@tanstack/react-query": "catalog:",
"@tanstack/react-router": "1.166.7",
"better-auth": "catalog:",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
@@ -32,14 +32,14 @@
"date-fns": "catalog:",
"jotai": "2.18.1",
"media-chrome": "4.18.0",
"motion": "12.35.2",
"react": "19.2.4",
"motion": "12.36.0",
"react": "catalog:",
"react-day-picker": "9.14.0",
"react-dom": "19.2.4",
"react-dom": "catalog:",
"recharts": "3.8.0",
"shadcn": "4.0.3",
"shadcn": "4.0.5",
"sonner": "2.0.7",
"tailwind-merge": "3.5.0",
"tailwind-merge": "catalog:",
"tw-animate-css": "1.4.0",
"vaul": "1.1.2",
"youtube-video-element": "1.9.0",
@@ -47,18 +47,18 @@
},
"devDependencies": {
"@tailwindcss/vite": "4.2.1",
"@tanstack/devtools-vite": "0.5.3",
"@tanstack/react-devtools": "0.9.10",
"@tanstack/devtools-vite": "0.5.5",
"@tanstack/react-devtools": "0.9.13",
"@tanstack/react-query-devtools": "5.91.3",
"@tanstack/react-router-devtools": "1.166.6",
"@tanstack/router-plugin": "1.166.6",
"@tanstack/react-router-devtools": "1.166.7",
"@tanstack/router-plugin": "1.166.7",
"@types/bun": "catalog:",
"@types/node": "25.4.0",
"@types/react": "19.2.14",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "19.2.3",
"@vitejs/plugin-react": "5.1.4",
"babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "4.2.1",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "7.3.1",
"vite-tsconfig-paths": "6.1.1"
+6 -6
View File
@@ -72,12 +72,12 @@ interface SearchResult {
tmdbId: number;
type: "movie" | "tv" | "person";
title: string;
posterPath?: string | null;
profilePath?: string | null;
releaseDate?: string | null;
voteAverage?: number;
knownFor?: string[];
knownForDepartment?: string;
posterPath: string | null;
profilePath: string | null;
releaseDate: string | null;
voteAverage: number | null;
knownFor: string[] | null;
knownForDepartment: string | null;
}
export function CommandPalette() {
@@ -148,11 +148,13 @@ export function StatsDisplay({ stats }: { stats: DashboardStats }) {
const [episodePeriod, setEpisodePeriod] = useState<TimePeriod>("this_week");
const { data: movieStats } = useQuery(
orpc.stats.queryOptions({ input: { type: "movies", period: moviePeriod } }),
orpc.dashboard.watchHistory.queryOptions({
input: { type: "movie", period: moviePeriod },
}),
);
const { data: episodeStats } = useQuery(
orpc.stats.queryOptions({
input: { type: "episodes", period: episodePeriod },
orpc.dashboard.watchHistory.queryOptions({
input: { type: "episode", period: episodePeriod },
}),
);
@@ -8,6 +8,7 @@ interface TitleGridItem {
title: string;
posterPath: string | null;
releaseDate?: string | null;
firstAirDate?: string | null;
voteAverage?: number | null;
userStatus?: "watchlist" | "in_progress" | "completed" | null;
}
@@ -45,7 +46,7 @@ export function TitleGrid({ items }: { items: TitleGridItem[] }) {
type={t.type}
title={t.title}
posterPath={t.posterPath}
releaseDate={t.releaseDate}
releaseDate={t.releaseDate ?? t.firstAirDate}
voteAverage={t.voteAverage}
userStatus={t.userStatus}
/>
@@ -16,7 +16,8 @@ interface TitleRowItem {
title: string;
posterPath: string | null;
releaseDate: string | null;
voteAverage: number;
firstAirDate: string | null;
voteAverage: number | null;
}
type TitleStatus = "watchlist" | "in_progress" | "completed";
@@ -45,7 +46,7 @@ export function FilterableTitleRow({
orpc.discover.queryOptions({
input:
selectedGenre != null
? { mediaType, genreId: selectedGenre }
? { type: mediaType, genreId: selectedGenre }
: skipToken,
}),
);
@@ -140,7 +141,7 @@ export function FilterableTitleRow({
type={item.type}
title={item.title}
posterPath={item.posterPath}
releaseDate={item.releaseDate}
releaseDate={item.releaseDate ?? item.firstAirDate}
voteAverage={item.voteAverage}
userStatus={userStatuses[`${item.tmdbId}-${item.type}`]}
episodeProgress={
@@ -7,7 +7,8 @@ interface TitleRowItem {
title: string;
posterPath: string | null;
releaseDate: string | null;
voteAverage: number;
firstAirDate: string | null;
voteAverage: number | null;
}
interface TitleRowProps {
@@ -51,7 +52,7 @@ export function TitleRow({
type={item.type}
title={item.title}
posterPath={item.posterPath}
releaseDate={item.releaseDate}
releaseDate={item.releaseDate ?? item.firstAirDate}
voteAverage={item.voteAverage}
userStatus={userStatuses?.[`${item.tmdbId}-${item.type}`]}
episodeProgress={
@@ -1,4 +1,4 @@
import type { SystemHealthData } from "@sofa/api/schemas";
import type { CronJobName, SystemHealthData } from "@sofa/api/schemas";
import {
IconActivity,
IconAlertTriangle,
@@ -129,15 +129,12 @@ function LiveTimeAgo({
/** Hydrates system health state and renders the 3 cards */
export function SystemHealthCards() {
const queryClient = useQueryClient();
const {
data: statusData,
isPending,
isFetching,
} = useQuery(orpc.systemStatus.queryOptions());
const data = statusData?.health ?? null;
const { data, isPending, isFetching } = useQuery(
orpc.system.health.queryOptions(),
);
const isRefreshing = isFetching;
const refresh = () =>
queryClient.invalidateQueries({ queryKey: orpc.systemStatus.key() });
queryClient.invalidateQueries({ queryKey: orpc.system.health.key() });
if (isPending || !data) return <SkeletonCards />;
@@ -519,7 +516,9 @@ function BackgroundJobsCard({
className="size-6"
disabled={isRunning || job.disabled}
onClick={() =>
triggerJobMutation.mutate({ name: job.jobName })
triggerJobMutation.mutate({
name: job.jobName as CronJobName,
})
}
/>
}
+1 -1
View File
@@ -98,7 +98,7 @@ function QuickAddButton({
}, [userStatus]);
const quickAddMutation = useMutation(
orpc.watchlist.quickAdd.mutationOptions({
orpc.titles.quickAdd.mutationOptions({
onSuccess: () => setAddedStatus("watchlist"),
}),
);