feature: add subscription manage

This commit is contained in:
2025-04-30 01:59:14 +08:00
parent 9fdb778330
commit 4301f1dbab
128 changed files with 2286 additions and 740 deletions

View File

@@ -0,0 +1,40 @@
import { useEffect } from 'react';
import { useStateRef } from './use-state-ref.ts';
export interface UseDebouncedSkeletonProps {
minSkeletonDuration?: number;
loading?: boolean;
}
export function useDebouncedSkeleton({
minSkeletonDuration = 100,
loading,
}: UseDebouncedSkeletonProps) {
const [showSkeleton, setShowSkeleton, showSkeletonRef] = useStateRef(loading);
useEffect(() => {
if (loading && !showSkeleton) {
setShowSkeleton(true);
}
if (!loading && showSkeleton) {
const timeout = setTimeout(() => {
if (showSkeletonRef.current) {
setShowSkeleton(false);
}
}, minSkeletonDuration);
return () => {
clearTimeout(timeout);
};
}
}, [
loading,
showSkeleton,
setShowSkeleton,
minSkeletonDuration,
showSkeletonRef,
]);
return {
showSkeleton,
};
}

View File

@@ -0,0 +1,18 @@
import { useCallback, useInsertionEffect, useRef } from 'react';
export function useEvent<
const T extends (
...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any
any[]
) => void,
>(fn: T): T {
const ref = useRef<T | null>(fn);
useInsertionEffect(() => {
ref.current = fn;
}, [fn]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return useCallback((...args: any) => {
const latestFn = ref.current!;
return latestFn(...args);
}, []) as unknown as T;
}

View File

@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}

View File

@@ -0,0 +1,28 @@
import {
type Dispatch,
type RefObject,
type SetStateAction,
useCallback,
useRef,
useState,
} from 'react';
export function useStateRef<T>(
initialValue: T
): [T, Dispatch<SetStateAction<T>>, RefObject<T>] {
const [state, _setState] = useState(initialValue);
const ref = useRef(initialValue);
const setState = useCallback((value: T | ((prev: T) => T)) => {
let nextValue: T;
if (typeof value === 'function') {
nextValue = (value as (prev: T) => T)(ref.current);
} else {
nextValue = value;
}
ref.current = nextValue;
_setState(nextValue);
}, []);
return [state, setState, ref] as const;
}

View File

@@ -0,0 +1,663 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
// Import Routes
import { Route as R404Import } from './routes/404.tsx';
import { Route as rootRoute } from './routes/__root.tsx';
import { Route as AppExploreExploreImport } from './routes/_app/_explore/explore.tsx';
import { Route as AppExploreFeedImport } from './routes/_app/_explore/feed.tsx';
import { Route as AppBangumiManageImport } from './routes/_app/bangumi/manage.tsx';
import { Route as AppBangumiRouteImport } from './routes/_app/bangumi/route.tsx';
import { Route as AppPlaygroundGraphqlApiImport } from './routes/_app/playground/graphql-api.tsx';
import { Route as AppPlaygroundRouteImport } from './routes/_app/playground/route.tsx';
import { Route as AppRouteImport } from './routes/_app/route.tsx';
import { Route as AppSettingsDownloaderImport } from './routes/_app/settings/downloader.tsx';
import { Route as AppSettingsRouteImport } from './routes/_app/settings/route.tsx';
import { Route as AppSubscriptionsCreateImport } from './routes/_app/subscriptions/create.tsx';
import { Route as AppSubscriptionsDetailSubscriptionIdImport } from './routes/_app/subscriptions/detail.$subscriptionId.tsx';
import { Route as AppSubscriptionsEditSubscriptionIdImport } from './routes/_app/subscriptions/edit.$subscriptionId.tsx';
import { Route as AppSubscriptionsManageImport } from './routes/_app/subscriptions/manage.tsx';
import { Route as AppSubscriptionsRouteImport } from './routes/_app/subscriptions/route.tsx';
import { Route as AboutImport } from './routes/about.tsx';
import { Route as AuthOidcCallbackImport } from './routes/auth/oidc/callback.tsx';
import { Route as AuthSignInImport } from './routes/auth/sign-in.tsx';
import { Route as AuthSignUpImport } from './routes/auth/sign-up.tsx';
import { Route as IndexImport } from './routes/index.tsx';
// Create/Update Routes
const AboutRoute = AboutImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRoute,
} as any);
const R404Route = R404Import.update({
id: '/404',
path: '/404',
getParentRoute: () => rootRoute,
} as any);
const AppRouteRoute = AppRouteImport.update({
id: '/_app',
getParentRoute: () => rootRoute,
} as any);
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any);
const AuthSignUpRoute = AuthSignUpImport.update({
id: '/auth/sign-up',
path: '/auth/sign-up',
getParentRoute: () => rootRoute,
} as any);
const AuthSignInRoute = AuthSignInImport.update({
id: '/auth/sign-in',
path: '/auth/sign-in',
getParentRoute: () => rootRoute,
} as any);
const AppSubscriptionsRouteRoute = AppSubscriptionsRouteImport.update({
id: '/subscriptions',
path: '/subscriptions',
getParentRoute: () => AppRouteRoute,
} as any);
const AppSettingsRouteRoute = AppSettingsRouteImport.update({
id: '/settings',
path: '/settings',
getParentRoute: () => AppRouteRoute,
} as any);
const AppPlaygroundRouteRoute = AppPlaygroundRouteImport.update({
id: '/playground',
path: '/playground',
getParentRoute: () => AppRouteRoute,
} as any);
const AppBangumiRouteRoute = AppBangumiRouteImport.update({
id: '/bangumi',
path: '/bangumi',
getParentRoute: () => AppRouteRoute,
} as any);
const AuthOidcCallbackRoute = AuthOidcCallbackImport.update({
id: '/auth/oidc/callback',
path: '/auth/oidc/callback',
getParentRoute: () => rootRoute,
} as any);
const AppSubscriptionsManageRoute = AppSubscriptionsManageImport.update({
id: '/manage',
path: '/manage',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any);
const AppSubscriptionsCreateRoute = AppSubscriptionsCreateImport.update({
id: '/create',
path: '/create',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any);
const AppSettingsDownloaderRoute = AppSettingsDownloaderImport.update({
id: '/downloader',
path: '/downloader',
getParentRoute: () => AppSettingsRouteRoute,
} as any);
const AppPlaygroundGraphqlApiRoute = AppPlaygroundGraphqlApiImport.update({
id: '/graphql-api',
path: '/graphql-api',
getParentRoute: () => AppPlaygroundRouteRoute,
} as any).lazy(() =>
import('./routes/_app/playground/graphql-api.lazy.tsx').then((d) => d.Route)
);
const AppBangumiManageRoute = AppBangumiManageImport.update({
id: '/manage',
path: '/manage',
getParentRoute: () => AppBangumiRouteRoute,
} as any);
const AppExploreFeedRoute = AppExploreFeedImport.update({
id: '/_explore/feed',
path: '/feed',
getParentRoute: () => AppRouteRoute,
} as any);
const AppExploreExploreRoute = AppExploreExploreImport.update({
id: '/_explore/explore',
path: '/explore',
getParentRoute: () => AppRouteRoute,
} as any);
const AppSubscriptionsEditSubscriptionIdRoute =
AppSubscriptionsEditSubscriptionIdImport.update({
id: '/edit/$subscriptionId',
path: '/edit/$subscriptionId',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any);
const AppSubscriptionsDetailSubscriptionIdRoute =
AppSubscriptionsDetailSubscriptionIdImport.update({
id: '/detail/$subscriptionId',
path: '/detail/$subscriptionId',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any);
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/';
path: '/';
fullPath: '/';
preLoaderRoute: typeof IndexImport;
parentRoute: typeof rootRoute;
};
'/_app': {
id: '/_app';
path: '';
fullPath: '';
preLoaderRoute: typeof AppRouteImport;
parentRoute: typeof rootRoute;
};
'/404': {
id: '/404';
path: '/404';
fullPath: '/404';
preLoaderRoute: typeof R404Import;
parentRoute: typeof rootRoute;
};
'/about': {
id: '/about';
path: '/about';
fullPath: '/about';
preLoaderRoute: typeof AboutImport;
parentRoute: typeof rootRoute;
};
'/_app/bangumi': {
id: '/_app/bangumi';
path: '/bangumi';
fullPath: '/bangumi';
preLoaderRoute: typeof AppBangumiRouteImport;
parentRoute: typeof AppRouteImport;
};
'/_app/playground': {
id: '/_app/playground';
path: '/playground';
fullPath: '/playground';
preLoaderRoute: typeof AppPlaygroundRouteImport;
parentRoute: typeof AppRouteImport;
};
'/_app/settings': {
id: '/_app/settings';
path: '/settings';
fullPath: '/settings';
preLoaderRoute: typeof AppSettingsRouteImport;
parentRoute: typeof AppRouteImport;
};
'/_app/subscriptions': {
id: '/_app/subscriptions';
path: '/subscriptions';
fullPath: '/subscriptions';
preLoaderRoute: typeof AppSubscriptionsRouteImport;
parentRoute: typeof AppRouteImport;
};
'/auth/sign-in': {
id: '/auth/sign-in';
path: '/auth/sign-in';
fullPath: '/auth/sign-in';
preLoaderRoute: typeof AuthSignInImport;
parentRoute: typeof rootRoute;
};
'/auth/sign-up': {
id: '/auth/sign-up';
path: '/auth/sign-up';
fullPath: '/auth/sign-up';
preLoaderRoute: typeof AuthSignUpImport;
parentRoute: typeof rootRoute;
};
'/_app/_explore/explore': {
id: '/_app/_explore/explore';
path: '/explore';
fullPath: '/explore';
preLoaderRoute: typeof AppExploreExploreImport;
parentRoute: typeof AppRouteImport;
};
'/_app/_explore/feed': {
id: '/_app/_explore/feed';
path: '/feed';
fullPath: '/feed';
preLoaderRoute: typeof AppExploreFeedImport;
parentRoute: typeof AppRouteImport;
};
'/_app/bangumi/manage': {
id: '/_app/bangumi/manage';
path: '/manage';
fullPath: '/bangumi/manage';
preLoaderRoute: typeof AppBangumiManageImport;
parentRoute: typeof AppBangumiRouteImport;
};
'/_app/playground/graphql-api': {
id: '/_app/playground/graphql-api';
path: '/graphql-api';
fullPath: '/playground/graphql-api';
preLoaderRoute: typeof AppPlaygroundGraphqlApiImport;
parentRoute: typeof AppPlaygroundRouteImport;
};
'/_app/settings/downloader': {
id: '/_app/settings/downloader';
path: '/downloader';
fullPath: '/settings/downloader';
preLoaderRoute: typeof AppSettingsDownloaderImport;
parentRoute: typeof AppSettingsRouteImport;
};
'/_app/subscriptions/create': {
id: '/_app/subscriptions/create';
path: '/create';
fullPath: '/subscriptions/create';
preLoaderRoute: typeof AppSubscriptionsCreateImport;
parentRoute: typeof AppSubscriptionsRouteImport;
};
'/_app/subscriptions/manage': {
id: '/_app/subscriptions/manage';
path: '/manage';
fullPath: '/subscriptions/manage';
preLoaderRoute: typeof AppSubscriptionsManageImport;
parentRoute: typeof AppSubscriptionsRouteImport;
};
'/auth/oidc/callback': {
id: '/auth/oidc/callback';
path: '/auth/oidc/callback';
fullPath: '/auth/oidc/callback';
preLoaderRoute: typeof AuthOidcCallbackImport;
parentRoute: typeof rootRoute;
};
'/_app/subscriptions/detail/$subscriptionId': {
id: '/_app/subscriptions/detail/$subscriptionId';
path: '/detail/$subscriptionId';
fullPath: '/subscriptions/detail/$subscriptionId';
preLoaderRoute: typeof AppSubscriptionsDetailSubscriptionIdImport;
parentRoute: typeof AppSubscriptionsRouteImport;
};
'/_app/subscriptions/edit/$subscriptionId': {
id: '/_app/subscriptions/edit/$subscriptionId';
path: '/edit/$subscriptionId';
fullPath: '/subscriptions/edit/$subscriptionId';
preLoaderRoute: typeof AppSubscriptionsEditSubscriptionIdImport;
parentRoute: typeof AppSubscriptionsRouteImport;
};
}
}
// Create and export the route tree
interface AppBangumiRouteRouteChildren {
AppBangumiManageRoute: typeof AppBangumiManageRoute;
}
const AppBangumiRouteRouteChildren: AppBangumiRouteRouteChildren = {
AppBangumiManageRoute: AppBangumiManageRoute,
};
const AppBangumiRouteRouteWithChildren = AppBangumiRouteRoute._addFileChildren(
AppBangumiRouteRouteChildren
);
interface AppPlaygroundRouteRouteChildren {
AppPlaygroundGraphqlApiRoute: typeof AppPlaygroundGraphqlApiRoute;
}
const AppPlaygroundRouteRouteChildren: AppPlaygroundRouteRouteChildren = {
AppPlaygroundGraphqlApiRoute: AppPlaygroundGraphqlApiRoute,
};
const AppPlaygroundRouteRouteWithChildren =
AppPlaygroundRouteRoute._addFileChildren(AppPlaygroundRouteRouteChildren);
interface AppSettingsRouteRouteChildren {
AppSettingsDownloaderRoute: typeof AppSettingsDownloaderRoute;
}
const AppSettingsRouteRouteChildren: AppSettingsRouteRouteChildren = {
AppSettingsDownloaderRoute: AppSettingsDownloaderRoute,
};
const AppSettingsRouteRouteWithChildren =
AppSettingsRouteRoute._addFileChildren(AppSettingsRouteRouteChildren);
interface AppSubscriptionsRouteRouteChildren {
AppSubscriptionsCreateRoute: typeof AppSubscriptionsCreateRoute;
AppSubscriptionsManageRoute: typeof AppSubscriptionsManageRoute;
AppSubscriptionsDetailSubscriptionIdRoute: typeof AppSubscriptionsDetailSubscriptionIdRoute;
AppSubscriptionsEditSubscriptionIdRoute: typeof AppSubscriptionsEditSubscriptionIdRoute;
}
const AppSubscriptionsRouteRouteChildren: AppSubscriptionsRouteRouteChildren = {
AppSubscriptionsCreateRoute: AppSubscriptionsCreateRoute,
AppSubscriptionsManageRoute: AppSubscriptionsManageRoute,
AppSubscriptionsDetailSubscriptionIdRoute:
AppSubscriptionsDetailSubscriptionIdRoute,
AppSubscriptionsEditSubscriptionIdRoute:
AppSubscriptionsEditSubscriptionIdRoute,
};
const AppSubscriptionsRouteRouteWithChildren =
AppSubscriptionsRouteRoute._addFileChildren(
AppSubscriptionsRouteRouteChildren
);
interface AppRouteRouteChildren {
AppBangumiRouteRoute: typeof AppBangumiRouteRouteWithChildren;
AppPlaygroundRouteRoute: typeof AppPlaygroundRouteRouteWithChildren;
AppSettingsRouteRoute: typeof AppSettingsRouteRouteWithChildren;
AppSubscriptionsRouteRoute: typeof AppSubscriptionsRouteRouteWithChildren;
AppExploreExploreRoute: typeof AppExploreExploreRoute;
AppExploreFeedRoute: typeof AppExploreFeedRoute;
}
const AppRouteRouteChildren: AppRouteRouteChildren = {
AppBangumiRouteRoute: AppBangumiRouteRouteWithChildren,
AppPlaygroundRouteRoute: AppPlaygroundRouteRouteWithChildren,
AppSettingsRouteRoute: AppSettingsRouteRouteWithChildren,
AppSubscriptionsRouteRoute: AppSubscriptionsRouteRouteWithChildren,
AppExploreExploreRoute: AppExploreExploreRoute,
AppExploreFeedRoute: AppExploreFeedRoute,
};
const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(
AppRouteRouteChildren
);
export interface FileRoutesByFullPath {
'/': typeof IndexRoute;
'': typeof AppRouteRouteWithChildren;
'/404': typeof R404Route;
'/about': typeof AboutRoute;
'/bangumi': typeof AppBangumiRouteRouteWithChildren;
'/playground': typeof AppPlaygroundRouteRouteWithChildren;
'/settings': typeof AppSettingsRouteRouteWithChildren;
'/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren;
'/auth/sign-in': typeof AuthSignInRoute;
'/auth/sign-up': typeof AuthSignUpRoute;
'/explore': typeof AppExploreExploreRoute;
'/feed': typeof AppExploreFeedRoute;
'/bangumi/manage': typeof AppBangumiManageRoute;
'/playground/graphql-api': typeof AppPlaygroundGraphqlApiRoute;
'/settings/downloader': typeof AppSettingsDownloaderRoute;
'/subscriptions/create': typeof AppSubscriptionsCreateRoute;
'/subscriptions/manage': typeof AppSubscriptionsManageRoute;
'/auth/oidc/callback': typeof AuthOidcCallbackRoute;
'/subscriptions/detail/$subscriptionId': typeof AppSubscriptionsDetailSubscriptionIdRoute;
'/subscriptions/edit/$subscriptionId': typeof AppSubscriptionsEditSubscriptionIdRoute;
}
export interface FileRoutesByTo {
'/': typeof IndexRoute;
'': typeof AppRouteRouteWithChildren;
'/404': typeof R404Route;
'/about': typeof AboutRoute;
'/bangumi': typeof AppBangumiRouteRouteWithChildren;
'/playground': typeof AppPlaygroundRouteRouteWithChildren;
'/settings': typeof AppSettingsRouteRouteWithChildren;
'/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren;
'/auth/sign-in': typeof AuthSignInRoute;
'/auth/sign-up': typeof AuthSignUpRoute;
'/explore': typeof AppExploreExploreRoute;
'/feed': typeof AppExploreFeedRoute;
'/bangumi/manage': typeof AppBangumiManageRoute;
'/playground/graphql-api': typeof AppPlaygroundGraphqlApiRoute;
'/settings/downloader': typeof AppSettingsDownloaderRoute;
'/subscriptions/create': typeof AppSubscriptionsCreateRoute;
'/subscriptions/manage': typeof AppSubscriptionsManageRoute;
'/auth/oidc/callback': typeof AuthOidcCallbackRoute;
'/subscriptions/detail/$subscriptionId': typeof AppSubscriptionsDetailSubscriptionIdRoute;
'/subscriptions/edit/$subscriptionId': typeof AppSubscriptionsEditSubscriptionIdRoute;
}
export interface FileRoutesById {
__root__: typeof rootRoute;
'/': typeof IndexRoute;
'/_app': typeof AppRouteRouteWithChildren;
'/404': typeof R404Route;
'/about': typeof AboutRoute;
'/_app/bangumi': typeof AppBangumiRouteRouteWithChildren;
'/_app/playground': typeof AppPlaygroundRouteRouteWithChildren;
'/_app/settings': typeof AppSettingsRouteRouteWithChildren;
'/_app/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren;
'/auth/sign-in': typeof AuthSignInRoute;
'/auth/sign-up': typeof AuthSignUpRoute;
'/_app/_explore/explore': typeof AppExploreExploreRoute;
'/_app/_explore/feed': typeof AppExploreFeedRoute;
'/_app/bangumi/manage': typeof AppBangumiManageRoute;
'/_app/playground/graphql-api': typeof AppPlaygroundGraphqlApiRoute;
'/_app/settings/downloader': typeof AppSettingsDownloaderRoute;
'/_app/subscriptions/create': typeof AppSubscriptionsCreateRoute;
'/_app/subscriptions/manage': typeof AppSubscriptionsManageRoute;
'/auth/oidc/callback': typeof AuthOidcCallbackRoute;
'/_app/subscriptions/detail/$subscriptionId': typeof AppSubscriptionsDetailSubscriptionIdRoute;
'/_app/subscriptions/edit/$subscriptionId': typeof AppSubscriptionsEditSubscriptionIdRoute;
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths:
| '/'
| ''
| '/404'
| '/about'
| '/bangumi'
| '/playground'
| '/settings'
| '/subscriptions'
| '/auth/sign-in'
| '/auth/sign-up'
| '/explore'
| '/feed'
| '/bangumi/manage'
| '/playground/graphql-api'
| '/settings/downloader'
| '/subscriptions/create'
| '/subscriptions/manage'
| '/auth/oidc/callback'
| '/subscriptions/detail/$subscriptionId'
| '/subscriptions/edit/$subscriptionId';
fileRoutesByTo: FileRoutesByTo;
to:
| '/'
| ''
| '/404'
| '/about'
| '/bangumi'
| '/playground'
| '/settings'
| '/subscriptions'
| '/auth/sign-in'
| '/auth/sign-up'
| '/explore'
| '/feed'
| '/bangumi/manage'
| '/playground/graphql-api'
| '/settings/downloader'
| '/subscriptions/create'
| '/subscriptions/manage'
| '/auth/oidc/callback'
| '/subscriptions/detail/$subscriptionId'
| '/subscriptions/edit/$subscriptionId';
id:
| '__root__'
| '/'
| '/_app'
| '/404'
| '/about'
| '/_app/bangumi'
| '/_app/playground'
| '/_app/settings'
| '/_app/subscriptions'
| '/auth/sign-in'
| '/auth/sign-up'
| '/_app/_explore/explore'
| '/_app/_explore/feed'
| '/_app/bangumi/manage'
| '/_app/playground/graphql-api'
| '/_app/settings/downloader'
| '/_app/subscriptions/create'
| '/_app/subscriptions/manage'
| '/auth/oidc/callback'
| '/_app/subscriptions/detail/$subscriptionId'
| '/_app/subscriptions/edit/$subscriptionId';
fileRoutesById: FileRoutesById;
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute;
AppRouteRoute: typeof AppRouteRouteWithChildren;
R404Route: typeof R404Route;
AboutRoute: typeof AboutRoute;
AuthSignInRoute: typeof AuthSignInRoute;
AuthSignUpRoute: typeof AuthSignUpRoute;
AuthOidcCallbackRoute: typeof AuthOidcCallbackRoute;
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AppRouteRoute: AppRouteRouteWithChildren,
R404Route: R404Route,
AboutRoute: AboutRoute,
AuthSignInRoute: AuthSignInRoute,
AuthSignUpRoute: AuthSignUpRoute,
AuthOidcCallbackRoute: AuthOidcCallbackRoute,
};
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>();
/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/_app",
"/404",
"/about",
"/auth/sign-in",
"/auth/sign-up",
"/auth/oidc/callback"
]
},
"/": {
"filePath": "index.tsx"
},
"/_app": {
"filePath": "_app/route.tsx",
"children": [
"/_app/bangumi",
"/_app/playground",
"/_app/settings",
"/_app/subscriptions",
"/_app/_explore/explore",
"/_app/_explore/feed"
]
},
"/404": {
"filePath": "404.tsx"
},
"/about": {
"filePath": "about.tsx"
},
"/_app/bangumi": {
"filePath": "_app/bangumi/route.tsx",
"parent": "/_app",
"children": [
"/_app/bangumi/manage"
]
},
"/_app/playground": {
"filePath": "_app/playground/route.tsx",
"parent": "/_app",
"children": [
"/_app/playground/graphql-api"
]
},
"/_app/settings": {
"filePath": "_app/settings/route.tsx",
"parent": "/_app",
"children": [
"/_app/settings/downloader"
]
},
"/_app/subscriptions": {
"filePath": "_app/subscriptions/route.tsx",
"parent": "/_app",
"children": [
"/_app/subscriptions/create",
"/_app/subscriptions/manage",
"/_app/subscriptions/detail/$subscriptionId",
"/_app/subscriptions/edit/$subscriptionId"
]
},
"/auth/sign-in": {
"filePath": "auth/sign-in.tsx"
},
"/auth/sign-up": {
"filePath": "auth/sign-up.tsx"
},
"/_app/_explore/explore": {
"filePath": "_app/_explore/explore.tsx",
"parent": "/_app"
},
"/_app/_explore/feed": {
"filePath": "_app/_explore/feed.tsx",
"parent": "/_app"
},
"/_app/bangumi/manage": {
"filePath": "_app/bangumi/manage.tsx",
"parent": "/_app/bangumi"
},
"/_app/playground/graphql-api": {
"filePath": "_app/playground/graphql-api.tsx",
"parent": "/_app/playground"
},
"/_app/settings/downloader": {
"filePath": "_app/settings/downloader.tsx",
"parent": "/_app/settings"
},
"/_app/subscriptions/create": {
"filePath": "_app/subscriptions/create.tsx",
"parent": "/_app/subscriptions"
},
"/_app/subscriptions/manage": {
"filePath": "_app/subscriptions/manage.tsx",
"parent": "/_app/subscriptions"
},
"/auth/oidc/callback": {
"filePath": "auth/oidc/callback.tsx"
},
"/_app/subscriptions/detail/$subscriptionId": {
"filePath": "_app/subscriptions/detail.$subscriptionId.tsx",
"parent": "/_app/subscriptions"
},
"/_app/subscriptions/edit/$subscriptionId": {
"filePath": "_app/subscriptions/edit.$subscriptionId.tsx",
"parent": "/_app/subscriptions"
}
}
}
ROUTE_MANIFEST_END */

View File

@@ -0,0 +1,6 @@
import { AppNotFoundComponent } from '@/components/layout/app-not-found';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/404')({
component: AppNotFoundComponent,
});

View File

@@ -0,0 +1,26 @@
import type {
RouteStateDataOption,
RouterContext,
} from '@/infra/routes/traits';
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router';
import { Home } from 'lucide-react';
import { memo } from 'react';
import { Toaster } from 'sonner';
export const RootRouteComponent = memo(() => {
return (
<>
<Outlet />
<Toaster position="top-right" />
</>
);
});
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootRouteComponent,
staticData: {
breadcrumb: {
icon: Home,
},
} satisfies RouteStateDataOption,
});

View File

@@ -0,0 +1,15 @@
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/_explore/explore')({
component: ExploreRouteComponent,
staticData: {
breadcrumb: {
label: 'Explore',
},
} satisfies RouteStateDataOption,
});
function ExploreRouteComponent() {
return <div>Hello "/_app/explore"!</div>;
}

View File

@@ -0,0 +1,15 @@
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/_explore/feed')({
component: FeedRouteComponent,
staticData: {
breadcrumb: {
label: 'Feed',
},
} satisfies RouteStateDataOption,
});
function FeedRouteComponent() {
return <div>Hello "/_app/feed"!</div>;
}

View File

@@ -0,0 +1,13 @@
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/bangumi/manage')({
component: BangumiManageRouteComponent,
staticData: {
breadcrumb: { label: 'Manage' },
} satisfies RouteStateDataOption,
});
function BangumiManageRouteComponent() {
return <div>Hello "/_app/bangumi/manage"!</div>;
}

View File

@@ -0,0 +1,8 @@
import { buildVirtualBranchRouteOptions } from '@/infra/routes/utils';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/bangumi')(
buildVirtualBranchRouteOptions({
title: 'Bangumi',
})
);

View File

@@ -0,0 +1,35 @@
import { useAuth } from '@/app/auth/hooks';
import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
import { createLazyFileRoute } from '@tanstack/react-router';
import { GraphiQL } from 'graphiql';
import { useCallback } from 'react';
import 'graphiql/graphiql.css';
import { firstValueFrom } from 'rxjs';
export const Route = createLazyFileRoute('/_app/playground/graphql-api')({
component: PlaygroundGraphQLApiRouteComponent,
});
function PlaygroundGraphQLApiRouteComponent() {
const { authProvider } = useAuth();
const fetcher: Fetcher = useCallback(
async (props) => {
const authHeaders = await firstValueFrom(authProvider.getAuthHeaders());
return createGraphiQLFetcher({
url: '/api/graphql',
headers: authHeaders,
})(props);
},
[authProvider]
);
return (
<div
data-id="graphiql-playground-container"
className="h-full overflow-hidden rounded-lg"
>
<GraphiQL fetcher={fetcher} />
</div>
);
}

View File

@@ -0,0 +1,8 @@
import { AppSkeleton } from '@/components/layout/app-skeleton';
import { buildLeafRouteStaticData } from '@/infra/routes/utils';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/playground/graphql-api')({
staticData: buildLeafRouteStaticData({ title: 'GraphQL Api' }),
pendingComponent: AppSkeleton,
});

View File

@@ -0,0 +1,8 @@
import { buildVirtualBranchRouteOptions } from '@/infra/routes/utils';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/playground')(
buildVirtualBranchRouteOptions({
title: 'Playground',
})
);

View File

@@ -0,0 +1,16 @@
import { beforeLoadGuard } from '@/app/auth/guard';
import { AppAside } from '@/components/layout/app-layout';
import { Outlet, createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app')({
component: AppLayoutRoute,
beforeLoad: beforeLoadGuard,
});
function AppLayoutRoute() {
return (
<AppAside extractBreadcrumbFromRoutes>
<Outlet />
</AppAside>
);
}

View File

@@ -0,0 +1,13 @@
import { buildLeafRouteStaticData } from '@/infra/routes/utils';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/settings/downloader')({
component: SettingsDownloaderRouteComponent,
staticData: buildLeafRouteStaticData({
title: 'Downloader',
}),
});
function SettingsDownloaderRouteComponent() {
return <div>Hello "/_app/settings/downloader"!</div>;
}

View File

@@ -0,0 +1,8 @@
import { buildVirtualBranchRouteOptions } from '@/infra/routes/utils';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/settings')(
buildVirtualBranchRouteOptions({
title: 'Settings',
})
);

View File

@@ -0,0 +1,108 @@
import { gql } from '@apollo/client';
import type {
GetSubscriptionDetailQuery,
GetSubscriptionsQuery,
} from '@/infra/graphql/gql/graphql';
export const GET_SUBSCRIPTIONS = gql`
query GetSubscriptions(
$page: PageInput!,
$filters: SubscriptionsFilterInput!,
$orderBy: SubscriptionsOrderInput!
) {
subscriptions(
pagination: {
page: $page
}
filters: $filters
orderBy: $orderBy
) {
nodes {
id
createdAt
updatedAt
displayName
category
sourceUrl
enabled
}
paginationInfo {
total
pages
}
}
}
`;
export type SubscriptionDto =
GetSubscriptionsQuery['subscriptions']['nodes'][number];
export const UPDATE_SUBSCRIPTIONS = gql`
mutation UpdateSubscriptions(
$data: SubscriptionsUpdateInput!,
$filters: SubscriptionsFilterInput!,
) {
subscriptionsUpdate (
data: $data
filter: $filters
) {
id
createdAt
updatedAt
displayName
category
sourceUrl
enabled
}
}
`;
export const DELETE_SUBSCRIPTIONS = gql`
mutation DeleteSubscriptions($filters: SubscriptionsFilterInput) {
subscriptionsDelete(filter: $filters)
}
`;
export const GET_SUBSCRIPTION_DETAIL = gql`
query GetSubscriptionDetail ($id: Int!) {
subscriptions(filters: { id: {
eq: $id
} }) {
nodes {
id
displayName
createdAt
updatedAt
category
sourceUrl
enabled
bangumi {
nodes {
createdAt
updatedAt
id
mikanBangumiId
displayName
rawName
season
seasonRaw
fansub
mikanFansubId
rssLink
posterLink
savePath
deleted
homepage
}
}
}
}
}
`;
export type SubscriptionDetailDto =
GetSubscriptionDetailQuery['subscriptions']['nodes'][number];
export type SubscriptionDetailBangumiDto =
SubscriptionDetailDto['bangumi']['nodes'][number];

View File

@@ -0,0 +1,234 @@
import { useAuth } from '@/app/auth/hooks';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { gql, useMutation } from '@apollo/client';
import { createFileRoute } from '@tanstack/react-router';
import { useNavigate } from '@tanstack/react-router';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
export const Route = createFileRoute('/_app/subscriptions/create')({
component: SubscriptionCreateRouteComponent,
staticData: {
breadcrumb: { label: 'Create' },
} satisfies RouteStateDataOption,
});
type SubscriptionFormValues = {
displayName: string;
sourceUrl: string;
category: string;
enabled: boolean;
};
const CREATE_SUBSCRIPTION_MUTATION = gql`
mutation CreateSubscription($input: SubscriptionsInsertInput!) {
subscriptionsCreateOne(data: $input) {
id
displayName
sourceUrl
enabled
category
}
}
`;
function SubscriptionCreateRouteComponent() {
const { authData } = useAuth();
console.log(JSON.stringify(authData, null, 2));
const [isSubmitting, setIsSubmitting] = useState(false);
const navigate = useNavigate();
const form = useForm<SubscriptionFormValues>({
defaultValues: {
displayName: '',
sourceUrl: '',
category: 'mikan',
enabled: true,
},
});
const [createSubscription] = useMutation(CREATE_SUBSCRIPTION_MUTATION);
const onSubmit = async (data: SubscriptionFormValues) => {
try {
setIsSubmitting(true);
const response = await createSubscription({
variables: {
input: {
category: data.category,
displayName: data.displayName,
sourceUrl: data.sourceUrl,
enabled: data.enabled,
},
},
});
if (response.errors) {
throw new Error(
response.errors[0]?.message || 'Failed to create subscription'
);
}
toast.success('Subscription created successfully');
navigate({ to: '/subscriptions/manage' });
} catch (error) {
console.error('Failed to create subscription:', error);
toast.error(
`Subscription creation failed: ${
error instanceof Error ? error.message : 'Unknown error'
}`
);
} finally {
setIsSubmitting(false);
}
};
return (
<Card>
<CardHeader>
<CardTitle>Create Bangumi Subscription</CardTitle>
<CardDescription>Add a new bangumi subscription source</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>Source Type</FormLabel>
<Select
disabled
value={field.value}
onValueChange={field.onChange}
defaultValue="mikan"
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select source type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="mikan">mikan</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Currently only mikan source is supported
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="displayName"
render={({ field }) => (
<FormItem>
<FormLabel>Display Name</FormLabel>
<FormControl>
<Input
placeholder="Enter subscription display name"
{...field}
/>
</FormControl>
<FormDescription>
Set an easily recognizable name for this subscription
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="sourceUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Source URL</FormLabel>
<FormControl>
<Input
placeholder="Enter subscription source URL"
{...field}
/>
</FormControl>
<FormDescription>
Copy the RSS subscription link from the source website, e.g.
https://mikanani.me/RSS/Bangumi?bangumiId=3141&subgroupid=370
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="enabled"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Enable Subscription
</FormLabel>
<FormDescription>
Enable this subscription immediately after creation
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</form>
</Form>
</CardContent>
<CardFooter className="flex justify-between">
<Button
variant="outline"
onClick={() => navigate({ to: '/subscriptions/manage' })}
>
Cancel
</Button>
<Button
type="submit"
onClick={form.handleSubmit(onSubmit)}
disabled={isSubmitting}
>
{isSubmitting ? 'Creating...' : 'Create Subscription'}
</Button>
</CardFooter>
</Card>
);
}

View File

@@ -0,0 +1,38 @@
import type { GetSubscriptionDetailQuery } from '@/infra/graphql/gql/graphql';
import { useQuery } from '@apollo/client';
import { createFileRoute } from '@tanstack/react-router';
import { GET_SUBSCRIPTION_DETAIL } from './-defs.ts';
export const Route = createFileRoute(
'/_app/subscriptions/detail/$subscriptionId'
)({
component: DetailRouteComponent,
});
function DetailRouteComponent() {
const { subscriptionId } = Route.useParams();
const { data, loading, error } = useQuery<GetSubscriptionDetailQuery>(
GET_SUBSCRIPTION_DETAIL,
{
variables: {
id: Number.parseInt(subscriptionId),
},
}
);
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
const detail = data?.subscriptions?.nodes?.[0];
return (
<div
dangerouslySetInnerHTML={{ __html: JSON.stringify(detail, null, 2) }}
/>
);
}

View File

@@ -0,0 +1,15 @@
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute(
'/_app/subscriptions/edit/$subscriptionId'
)({
component: RouteComponent,
staticData: {
breadcrumb: { label: 'Edit' },
} satisfies RouteStateDataOption,
});
function RouteComponent() {
return <div>Hello "/subscriptions/edit/$subscription-id"!</div>;
}

View File

@@ -0,0 +1,289 @@
import { DataTablePagination } from '@/components/ui/data-table-pagination';
import { DataTableViewOptions } from '@/components/ui/data-table-view-options';
import { QueryErrorView } from '@/components/ui/query-error-view.tsx';
import { Skeleton } from '@/components/ui/skeleton.tsx';
import { Switch } from '@/components/ui/switch';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import type {
GetSubscriptionsQuery,
SubscriptionsUpdateInput,
} from '@/infra/graphql/gql/graphql';
import type { RouteStateDataOption } from '@/infra/routes/traits';
import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton.ts';
import { useEvent } from '@/presentation/hooks/use-event.ts';
import { useMutation, useQuery } from '@apollo/client';
import { createFileRoute } from '@tanstack/react-router';
import { useNavigate } from '@tanstack/react-router';
import {
type ColumnDef,
type PaginationState,
type Row,
type SortingState,
type VisibilityState,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from '@tanstack/react-table';
import { useMemo, useState } from 'react';
import { toast } from 'sonner';
import { DataTableRowActions } from '../../../../components/ui/data-table-row-actions.tsx';
import {
DELETE_SUBSCRIPTIONS,
GET_SUBSCRIPTIONS,
type SubscriptionDto,
UPDATE_SUBSCRIPTIONS,
} from './-defs.ts';
export const Route = createFileRoute('/_app/subscriptions/manage')({
component: SubscriptionManageRouteComponent,
staticData: {
breadcrumb: { label: 'Manage' },
} satisfies RouteStateDataOption,
});
function SubscriptionManageRouteComponent() {
const navigate = useNavigate();
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [sorting, setSorting] = useState<SortingState>([]);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const { loading, error, data, refetch } = useQuery<GetSubscriptionsQuery>(
GET_SUBSCRIPTIONS,
{
variables: {
page: {
page: pagination.pageIndex,
limit: pagination.pageSize,
},
filters: {},
orderBy: {},
},
refetchWritePolicy: 'overwrite',
nextFetchPolicy: 'network-only',
}
);
const [updateSubscription] = useMutation(UPDATE_SUBSCRIPTIONS);
const [deleteSubscription] = useMutation(DELETE_SUBSCRIPTIONS);
const { showSkeleton } = useDebouncedSkeleton({ loading });
const subscriptions = data?.subscriptions;
const handleUpdateRecord = useEvent(
(row: Row<SubscriptionDto>) => async (data: SubscriptionsUpdateInput) => {
const result = await updateSubscription({
variables: {
data,
filters: {
id: {
eq: row.original.id,
},
},
},
});
if (result.errors) {
toast.error(result.errors[0].message);
return;
}
const refetchResult = await refetch();
if (refetchResult.errors) {
toast.error(refetchResult.errors[0].message);
return;
}
toast.success('Subscription updated');
}
);
const handleDeleteRecord = useEvent(
(row: Row<SubscriptionDto>) => async () => {
const result = await deleteSubscription({
variables: { filters: { id: { eq: row.original.id } } },
});
if (result.errors) {
toast.error(result.errors[0].message);
return;
}
const refetchResult = await refetch();
if (refetchResult.errors) {
toast.error(refetchResult.errors[0].message);
return;
}
toast.success('Subscription deleted');
}
);
const columns = useMemo(() => {
const cs: ColumnDef<SubscriptionDto>[] = [
{
header: 'Enabled',
accessorKey: 'enabled',
cell: ({ row }) => {
const enabled = row.original.enabled;
return (
<div className="px-1">
<Switch
checked={enabled}
onCheckedChange={(enabled) =>
handleUpdateRecord(row)({ enabled: enabled })
}
/>
</div>
);
},
enableResizing: true,
},
{
header: 'Name',
accessorKey: 'displayName',
cell: ({ row }) => {
const displayName = row.original.displayName;
return (
<div className="whitespace-normal break-words">{displayName}</div>
);
},
},
{
header: 'Category',
accessorKey: 'category',
},
{
header: 'Source URL',
accessorKey: 'sourceUrl',
cell: ({ row }) => {
const sourceUrl = row.original.sourceUrl;
return (
<div className="whitespace-normal break-words">{sourceUrl}</div>
);
},
},
{
id: 'actions',
cell: ({ row }) => (
<DataTableRowActions
row={row}
getId={(row) => row.original.id}
showDetail
showEdit
showDelete
onDetail={() => {
navigate({
to: '/subscriptions/detail/$subscriptionId',
params: { subscriptionId: `${row.original.id}` },
});
}}
onEdit={() => {
navigate({
to: '/subscriptions/edit/$subscriptionId',
params: { subscriptionId: `${row.original.id}` },
});
}}
onDelete={handleDeleteRecord(row)}
/>
),
},
];
return cs;
}, [handleUpdateRecord, handleDeleteRecord, navigate]);
const table = useReactTable({
data: data?.subscriptions?.nodes ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onPaginationChange: setPagination,
onSortingChange: setSorting,
onColumnVisibilityChange: setColumnVisibility,
pageCount: subscriptions?.paginationInfo?.pages,
rowCount: subscriptions?.paginationInfo?.total,
state: {
pagination,
sorting,
columnVisibility,
},
});
if (error) {
return <QueryErrorView message={error.message} onRetry={refetch} />;
}
return (
<div className="container mx-auto space-y-4 rounded-md">
<div className="flex items-center py-4">
<DataTableViewOptions table={table} />
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{showSkeleton &&
Array.from(new Array(pagination.pageSize)).map((_, index) => (
<TableRow key={index}>
{table.getVisibleLeafColumns().map((column) => (
<TableCell key={column.id}>
<Skeleton className="h-8" />
</TableCell>
))}
</TableRow>
))}
{!showSkeleton &&
(table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<DataTablePagination table={table} showSelectedRowCount={false} />
</div>
);
}

View File

@@ -0,0 +1,8 @@
import { buildVirtualBranchRouteOptions } from '@/infra/routes/utils';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/_app/subscriptions')(
buildVirtualBranchRouteOptions({
title: 'Subscriptions',
})
);

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/about')({
component: About,
});
function About() {
return <div class="p-2">Hello from About!</div>;
}

View File

@@ -0,0 +1,94 @@
import { authContextFromInjector } from '@/app/auth/context';
import { useAuth } from '@/app/auth/hooks';
import { ProLink } from '@/components/ui/pro-link';
import { Spinner } from '@/components/ui/spinner';
import { AUTH_METHOD } from '@/infra/auth/defs';
import { createFileRoute, redirect } from '@tanstack/react-router';
import { useAtom } from 'jotai/react';
import { atomWithObservable } from 'jotai/utils';
import { EventTypes } from 'oidc-client-rx';
import { useMemo } from 'react';
import { filter, map } from 'rxjs';
export const Route = createFileRoute('/auth/oidc/callback')({
component: OidcCallbackRouteComponent,
beforeLoad: ({ context }) => {
const { authProvider } = authContextFromInjector(context.injector);
if (authProvider.authMethod !== AUTH_METHOD.OIDC) {
throw redirect({ to: '/' });
}
},
});
function OidcCallbackRouteComponent() {
const { authService } = useAuth();
const isLoading = useAtom(
useMemo(
() =>
atomWithObservable(() =>
authService.checkAuthResultEvent$.pipe(map(Boolean))
),
[authService.checkAuthResultEvent$]
)
);
const checkAuthResultError = useAtom(
useMemo(
() =>
atomWithObservable(() =>
authService.checkAuthResultEvent$.pipe(
filter(
(
e
): e is {
type: EventTypes.CheckingAuthFinishedWithError;
value: string;
} => e.type === EventTypes.CheckingAuthFinishedWithError
),
map((e) => e.value)
)
),
[authService.checkAuthResultEvent$]
)
);
const renderBackToHome = () => {
return (
<ProLink
to="/"
className="inline-flex h-10 items-center rounded-md border border-gray-200 bg-white px-8 font-medium text-sm shadow-sm transition-colors hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:focus-visible:ring-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50"
>
Return to website
</ProLink>
);
};
return (
<div className="flex h-svh items-center px-4 py-12 sm:px-6 md:px-8 lg:px-12 xl:px-16">
<div className="w-full space-y-6 text-center">
<div className="space-y-6">
<div className="flex justify-center font-bold text-4xl tracking-tighter sm:text-5xl">
<Spinner variant="circle-filled" size="48" />
</div>
{isLoading && (
<p className="text-gray-500">
Processing OIDC authentication callback...
</p>
)}
{checkAuthResultError && (
<p className="text-gray-500">
Failed to handle OIDC callback: {checkAuthResultError}
</p>
)}
{!isLoading && !checkAuthResultError && (
<p className="text-gray-500">
Succeed to handle OIDC authentication callback.
</p>
)}
{renderBackToHome()}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/auth/sign-in')({
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/auth/sign-in"!</div>;
}

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/auth/sign-up')({
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/auth/sign-up"!</div>;
}

View File

@@ -0,0 +1,15 @@
import { AppAside } from '@/components/layout/app-layout';
import { AppSkeleton } from '@/components/layout/app-skeleton';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: HomeRouteComponent,
});
function HomeRouteComponent() {
return (
<AppAside extractBreadcrumbFromRoutes>
<AppSkeleton />
</AppAside>
);
}

View File

@@ -0,0 +1,689 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
// Import Routes
import { Route as rootRoute } from './__root'
import { Route as AboutImport } from './about'
import { Route as R404Import } from './404'
import { Route as AppRouteImport } from './_app/route'
import { Route as IndexImport } from './index'
import { Route as RouteTreeGenImport } from './routeTree.gen'
import { Route as AuthSignUpImport } from './auth/sign-up'
import { Route as AuthSignInImport } from './auth/sign-in'
import { Route as AppSubscriptionsRouteImport } from './_app/subscriptions/route'
import { Route as AppSettingsRouteImport } from './_app/settings/route'
import { Route as AppPlaygroundRouteImport } from './_app/playground/route'
import { Route as AppBangumiRouteImport } from './_app/bangumi/route'
import { Route as AuthOidcCallbackImport } from './auth/oidc/callback'
import { Route as AppSubscriptionsManageImport } from './_app/subscriptions/manage'
import { Route as AppSubscriptionsCreateImport } from './_app/subscriptions/create'
import { Route as AppSettingsDownloaderImport } from './_app/settings/downloader'
import { Route as AppPlaygroundGraphqlApiImport } from './_app/playground/graphql-api'
import { Route as AppBangumiManageImport } from './_app/bangumi/manage'
import { Route as AppExploreFeedImport } from './_app/_explore/feed'
import { Route as AppExploreExploreImport } from './_app/_explore/explore'
import { Route as AppSubscriptionsEditSubscriptionIdImport } from './_app/subscriptions/edit.$subscriptionId'
import { Route as AppSubscriptionsDetailSubscriptionIdImport } from './_app/subscriptions/detail.$subscriptionId'
// Create/Update Routes
const AboutRoute = AboutImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRoute,
} as any)
const R404Route = R404Import.update({
id: '/404',
path: '/404',
getParentRoute: () => rootRoute,
} as any)
const AppRouteRoute = AppRouteImport.update({
id: '/_app',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any)
const RouteTreeGenRoute = RouteTreeGenImport.update({
id: '/routeTree/gen',
path: '/routeTree/gen',
getParentRoute: () => rootRoute,
} as any)
const AuthSignUpRoute = AuthSignUpImport.update({
id: '/auth/sign-up',
path: '/auth/sign-up',
getParentRoute: () => rootRoute,
} as any)
const AuthSignInRoute = AuthSignInImport.update({
id: '/auth/sign-in',
path: '/auth/sign-in',
getParentRoute: () => rootRoute,
} as any)
const AppSubscriptionsRouteRoute = AppSubscriptionsRouteImport.update({
id: '/subscriptions',
path: '/subscriptions',
getParentRoute: () => AppRouteRoute,
} as any)
const AppSettingsRouteRoute = AppSettingsRouteImport.update({
id: '/settings',
path: '/settings',
getParentRoute: () => AppRouteRoute,
} as any)
const AppPlaygroundRouteRoute = AppPlaygroundRouteImport.update({
id: '/playground',
path: '/playground',
getParentRoute: () => AppRouteRoute,
} as any)
const AppBangumiRouteRoute = AppBangumiRouteImport.update({
id: '/bangumi',
path: '/bangumi',
getParentRoute: () => AppRouteRoute,
} as any)
const AuthOidcCallbackRoute = AuthOidcCallbackImport.update({
id: '/auth/oidc/callback',
path: '/auth/oidc/callback',
getParentRoute: () => rootRoute,
} as any)
const AppSubscriptionsManageRoute = AppSubscriptionsManageImport.update({
id: '/manage',
path: '/manage',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any)
const AppSubscriptionsCreateRoute = AppSubscriptionsCreateImport.update({
id: '/create',
path: '/create',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any)
const AppSettingsDownloaderRoute = AppSettingsDownloaderImport.update({
id: '/downloader',
path: '/downloader',
getParentRoute: () => AppSettingsRouteRoute,
} as any)
const AppPlaygroundGraphqlApiRoute = AppPlaygroundGraphqlApiImport.update({
id: '/graphql-api',
path: '/graphql-api',
getParentRoute: () => AppPlaygroundRouteRoute,
} as any).lazy(() =>
import('./_app/playground/graphql-api.lazy').then((d) => d.Route),
)
const AppBangumiManageRoute = AppBangumiManageImport.update({
id: '/manage',
path: '/manage',
getParentRoute: () => AppBangumiRouteRoute,
} as any)
const AppExploreFeedRoute = AppExploreFeedImport.update({
id: '/_explore/feed',
path: '/feed',
getParentRoute: () => AppRouteRoute,
} as any)
const AppExploreExploreRoute = AppExploreExploreImport.update({
id: '/_explore/explore',
path: '/explore',
getParentRoute: () => AppRouteRoute,
} as any)
const AppSubscriptionsEditSubscriptionIdRoute =
AppSubscriptionsEditSubscriptionIdImport.update({
id: '/edit/$subscriptionId',
path: '/edit/$subscriptionId',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any)
const AppSubscriptionsDetailSubscriptionIdRoute =
AppSubscriptionsDetailSubscriptionIdImport.update({
id: '/detail/$subscriptionId',
path: '/detail/$subscriptionId',
getParentRoute: () => AppSubscriptionsRouteRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/_app': {
id: '/_app'
path: ''
fullPath: ''
preLoaderRoute: typeof AppRouteImport
parentRoute: typeof rootRoute
}
'/404': {
id: '/404'
path: '/404'
fullPath: '/404'
preLoaderRoute: typeof R404Import
parentRoute: typeof rootRoute
}
'/about': {
id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutImport
parentRoute: typeof rootRoute
}
'/_app/bangumi': {
id: '/_app/bangumi'
path: '/bangumi'
fullPath: '/bangumi'
preLoaderRoute: typeof AppBangumiRouteImport
parentRoute: typeof AppRouteImport
}
'/_app/playground': {
id: '/_app/playground'
path: '/playground'
fullPath: '/playground'
preLoaderRoute: typeof AppPlaygroundRouteImport
parentRoute: typeof AppRouteImport
}
'/_app/settings': {
id: '/_app/settings'
path: '/settings'
fullPath: '/settings'
preLoaderRoute: typeof AppSettingsRouteImport
parentRoute: typeof AppRouteImport
}
'/_app/subscriptions': {
id: '/_app/subscriptions'
path: '/subscriptions'
fullPath: '/subscriptions'
preLoaderRoute: typeof AppSubscriptionsRouteImport
parentRoute: typeof AppRouteImport
}
'/auth/sign-in': {
id: '/auth/sign-in'
path: '/auth/sign-in'
fullPath: '/auth/sign-in'
preLoaderRoute: typeof AuthSignInImport
parentRoute: typeof rootRoute
}
'/auth/sign-up': {
id: '/auth/sign-up'
path: '/auth/sign-up'
fullPath: '/auth/sign-up'
preLoaderRoute: typeof AuthSignUpImport
parentRoute: typeof rootRoute
}
'/routeTree/gen': {
id: '/routeTree/gen'
path: '/routeTree/gen'
fullPath: '/routeTree/gen'
preLoaderRoute: typeof RouteTreeGenImport
parentRoute: typeof rootRoute
}
'/_app/_explore/explore': {
id: '/_app/_explore/explore'
path: '/explore'
fullPath: '/explore'
preLoaderRoute: typeof AppExploreExploreImport
parentRoute: typeof AppRouteImport
}
'/_app/_explore/feed': {
id: '/_app/_explore/feed'
path: '/feed'
fullPath: '/feed'
preLoaderRoute: typeof AppExploreFeedImport
parentRoute: typeof AppRouteImport
}
'/_app/bangumi/manage': {
id: '/_app/bangumi/manage'
path: '/manage'
fullPath: '/bangumi/manage'
preLoaderRoute: typeof AppBangumiManageImport
parentRoute: typeof AppBangumiRouteImport
}
'/_app/playground/graphql-api': {
id: '/_app/playground/graphql-api'
path: '/graphql-api'
fullPath: '/playground/graphql-api'
preLoaderRoute: typeof AppPlaygroundGraphqlApiImport
parentRoute: typeof AppPlaygroundRouteImport
}
'/_app/settings/downloader': {
id: '/_app/settings/downloader'
path: '/downloader'
fullPath: '/settings/downloader'
preLoaderRoute: typeof AppSettingsDownloaderImport
parentRoute: typeof AppSettingsRouteImport
}
'/_app/subscriptions/create': {
id: '/_app/subscriptions/create'
path: '/create'
fullPath: '/subscriptions/create'
preLoaderRoute: typeof AppSubscriptionsCreateImport
parentRoute: typeof AppSubscriptionsRouteImport
}
'/_app/subscriptions/manage': {
id: '/_app/subscriptions/manage'
path: '/manage'
fullPath: '/subscriptions/manage'
preLoaderRoute: typeof AppSubscriptionsManageImport
parentRoute: typeof AppSubscriptionsRouteImport
}
'/auth/oidc/callback': {
id: '/auth/oidc/callback'
path: '/auth/oidc/callback'
fullPath: '/auth/oidc/callback'
preLoaderRoute: typeof AuthOidcCallbackImport
parentRoute: typeof rootRoute
}
'/_app/subscriptions/detail/$subscriptionId': {
id: '/_app/subscriptions/detail/$subscriptionId'
path: '/detail/$subscriptionId'
fullPath: '/subscriptions/detail/$subscriptionId'
preLoaderRoute: typeof AppSubscriptionsDetailSubscriptionIdImport
parentRoute: typeof AppSubscriptionsRouteImport
}
'/_app/subscriptions/edit/$subscriptionId': {
id: '/_app/subscriptions/edit/$subscriptionId'
path: '/edit/$subscriptionId'
fullPath: '/subscriptions/edit/$subscriptionId'
preLoaderRoute: typeof AppSubscriptionsEditSubscriptionIdImport
parentRoute: typeof AppSubscriptionsRouteImport
}
}
}
// Create and export the route tree
interface AppBangumiRouteRouteChildren {
AppBangumiManageRoute: typeof AppBangumiManageRoute
}
const AppBangumiRouteRouteChildren: AppBangumiRouteRouteChildren = {
AppBangumiManageRoute: AppBangumiManageRoute,
}
const AppBangumiRouteRouteWithChildren = AppBangumiRouteRoute._addFileChildren(
AppBangumiRouteRouteChildren,
)
interface AppPlaygroundRouteRouteChildren {
AppPlaygroundGraphqlApiRoute: typeof AppPlaygroundGraphqlApiRoute
}
const AppPlaygroundRouteRouteChildren: AppPlaygroundRouteRouteChildren = {
AppPlaygroundGraphqlApiRoute: AppPlaygroundGraphqlApiRoute,
}
const AppPlaygroundRouteRouteWithChildren =
AppPlaygroundRouteRoute._addFileChildren(AppPlaygroundRouteRouteChildren)
interface AppSettingsRouteRouteChildren {
AppSettingsDownloaderRoute: typeof AppSettingsDownloaderRoute
}
const AppSettingsRouteRouteChildren: AppSettingsRouteRouteChildren = {
AppSettingsDownloaderRoute: AppSettingsDownloaderRoute,
}
const AppSettingsRouteRouteWithChildren =
AppSettingsRouteRoute._addFileChildren(AppSettingsRouteRouteChildren)
interface AppSubscriptionsRouteRouteChildren {
AppSubscriptionsCreateRoute: typeof AppSubscriptionsCreateRoute
AppSubscriptionsManageRoute: typeof AppSubscriptionsManageRoute
AppSubscriptionsDetailSubscriptionIdRoute: typeof AppSubscriptionsDetailSubscriptionIdRoute
AppSubscriptionsEditSubscriptionIdRoute: typeof AppSubscriptionsEditSubscriptionIdRoute
}
const AppSubscriptionsRouteRouteChildren: AppSubscriptionsRouteRouteChildren = {
AppSubscriptionsCreateRoute: AppSubscriptionsCreateRoute,
AppSubscriptionsManageRoute: AppSubscriptionsManageRoute,
AppSubscriptionsDetailSubscriptionIdRoute:
AppSubscriptionsDetailSubscriptionIdRoute,
AppSubscriptionsEditSubscriptionIdRoute:
AppSubscriptionsEditSubscriptionIdRoute,
}
const AppSubscriptionsRouteRouteWithChildren =
AppSubscriptionsRouteRoute._addFileChildren(
AppSubscriptionsRouteRouteChildren,
)
interface AppRouteRouteChildren {
AppBangumiRouteRoute: typeof AppBangumiRouteRouteWithChildren
AppPlaygroundRouteRoute: typeof AppPlaygroundRouteRouteWithChildren
AppSettingsRouteRoute: typeof AppSettingsRouteRouteWithChildren
AppSubscriptionsRouteRoute: typeof AppSubscriptionsRouteRouteWithChildren
AppExploreExploreRoute: typeof AppExploreExploreRoute
AppExploreFeedRoute: typeof AppExploreFeedRoute
}
const AppRouteRouteChildren: AppRouteRouteChildren = {
AppBangumiRouteRoute: AppBangumiRouteRouteWithChildren,
AppPlaygroundRouteRoute: AppPlaygroundRouteRouteWithChildren,
AppSettingsRouteRoute: AppSettingsRouteRouteWithChildren,
AppSubscriptionsRouteRoute: AppSubscriptionsRouteRouteWithChildren,
AppExploreExploreRoute: AppExploreExploreRoute,
AppExploreFeedRoute: AppExploreFeedRoute,
}
const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(
AppRouteRouteChildren,
)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'': typeof AppRouteRouteWithChildren
'/404': typeof R404Route
'/about': typeof AboutRoute
'/bangumi': typeof AppBangumiRouteRouteWithChildren
'/playground': typeof AppPlaygroundRouteRouteWithChildren
'/settings': typeof AppSettingsRouteRouteWithChildren
'/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren
'/auth/sign-in': typeof AuthSignInRoute
'/auth/sign-up': typeof AuthSignUpRoute
'/routeTree/gen': typeof RouteTreeGenRoute
'/explore': typeof AppExploreExploreRoute
'/feed': typeof AppExploreFeedRoute
'/bangumi/manage': typeof AppBangumiManageRoute
'/playground/graphql-api': typeof AppPlaygroundGraphqlApiRoute
'/settings/downloader': typeof AppSettingsDownloaderRoute
'/subscriptions/create': typeof AppSubscriptionsCreateRoute
'/subscriptions/manage': typeof AppSubscriptionsManageRoute
'/auth/oidc/callback': typeof AuthOidcCallbackRoute
'/subscriptions/detail/$subscriptionId': typeof AppSubscriptionsDetailSubscriptionIdRoute
'/subscriptions/edit/$subscriptionId': typeof AppSubscriptionsEditSubscriptionIdRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'': typeof AppRouteRouteWithChildren
'/404': typeof R404Route
'/about': typeof AboutRoute
'/bangumi': typeof AppBangumiRouteRouteWithChildren
'/playground': typeof AppPlaygroundRouteRouteWithChildren
'/settings': typeof AppSettingsRouteRouteWithChildren
'/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren
'/auth/sign-in': typeof AuthSignInRoute
'/auth/sign-up': typeof AuthSignUpRoute
'/routeTree/gen': typeof RouteTreeGenRoute
'/explore': typeof AppExploreExploreRoute
'/feed': typeof AppExploreFeedRoute
'/bangumi/manage': typeof AppBangumiManageRoute
'/playground/graphql-api': typeof AppPlaygroundGraphqlApiRoute
'/settings/downloader': typeof AppSettingsDownloaderRoute
'/subscriptions/create': typeof AppSubscriptionsCreateRoute
'/subscriptions/manage': typeof AppSubscriptionsManageRoute
'/auth/oidc/callback': typeof AuthOidcCallbackRoute
'/subscriptions/detail/$subscriptionId': typeof AppSubscriptionsDetailSubscriptionIdRoute
'/subscriptions/edit/$subscriptionId': typeof AppSubscriptionsEditSubscriptionIdRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/_app': typeof AppRouteRouteWithChildren
'/404': typeof R404Route
'/about': typeof AboutRoute
'/_app/bangumi': typeof AppBangumiRouteRouteWithChildren
'/_app/playground': typeof AppPlaygroundRouteRouteWithChildren
'/_app/settings': typeof AppSettingsRouteRouteWithChildren
'/_app/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren
'/auth/sign-in': typeof AuthSignInRoute
'/auth/sign-up': typeof AuthSignUpRoute
'/routeTree/gen': typeof RouteTreeGenRoute
'/_app/_explore/explore': typeof AppExploreExploreRoute
'/_app/_explore/feed': typeof AppExploreFeedRoute
'/_app/bangumi/manage': typeof AppBangumiManageRoute
'/_app/playground/graphql-api': typeof AppPlaygroundGraphqlApiRoute
'/_app/settings/downloader': typeof AppSettingsDownloaderRoute
'/_app/subscriptions/create': typeof AppSubscriptionsCreateRoute
'/_app/subscriptions/manage': typeof AppSubscriptionsManageRoute
'/auth/oidc/callback': typeof AuthOidcCallbackRoute
'/_app/subscriptions/detail/$subscriptionId': typeof AppSubscriptionsDetailSubscriptionIdRoute
'/_app/subscriptions/edit/$subscriptionId': typeof AppSubscriptionsEditSubscriptionIdRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| ''
| '/404'
| '/about'
| '/bangumi'
| '/playground'
| '/settings'
| '/subscriptions'
| '/auth/sign-in'
| '/auth/sign-up'
| '/routeTree/gen'
| '/explore'
| '/feed'
| '/bangumi/manage'
| '/playground/graphql-api'
| '/settings/downloader'
| '/subscriptions/create'
| '/subscriptions/manage'
| '/auth/oidc/callback'
| '/subscriptions/detail/$subscriptionId'
| '/subscriptions/edit/$subscriptionId'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| ''
| '/404'
| '/about'
| '/bangumi'
| '/playground'
| '/settings'
| '/subscriptions'
| '/auth/sign-in'
| '/auth/sign-up'
| '/routeTree/gen'
| '/explore'
| '/feed'
| '/bangumi/manage'
| '/playground/graphql-api'
| '/settings/downloader'
| '/subscriptions/create'
| '/subscriptions/manage'
| '/auth/oidc/callback'
| '/subscriptions/detail/$subscriptionId'
| '/subscriptions/edit/$subscriptionId'
id:
| '__root__'
| '/'
| '/_app'
| '/404'
| '/about'
| '/_app/bangumi'
| '/_app/playground'
| '/_app/settings'
| '/_app/subscriptions'
| '/auth/sign-in'
| '/auth/sign-up'
| '/routeTree/gen'
| '/_app/_explore/explore'
| '/_app/_explore/feed'
| '/_app/bangumi/manage'
| '/_app/playground/graphql-api'
| '/_app/settings/downloader'
| '/_app/subscriptions/create'
| '/_app/subscriptions/manage'
| '/auth/oidc/callback'
| '/_app/subscriptions/detail/$subscriptionId'
| '/_app/subscriptions/edit/$subscriptionId'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AppRouteRoute: typeof AppRouteRouteWithChildren
R404Route: typeof R404Route
AboutRoute: typeof AboutRoute
AuthSignInRoute: typeof AuthSignInRoute
AuthSignUpRoute: typeof AuthSignUpRoute
RouteTreeGenRoute: typeof RouteTreeGenRoute
AuthOidcCallbackRoute: typeof AuthOidcCallbackRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AppRouteRoute: AppRouteRouteWithChildren,
R404Route: R404Route,
AboutRoute: AboutRoute,
AuthSignInRoute: AuthSignInRoute,
AuthSignUpRoute: AuthSignUpRoute,
RouteTreeGenRoute: RouteTreeGenRoute,
AuthOidcCallbackRoute: AuthOidcCallbackRoute,
}
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/_app",
"/404",
"/about",
"/auth/sign-in",
"/auth/sign-up",
"/routeTree/gen",
"/auth/oidc/callback"
]
},
"/": {
"filePath": "index.tsx"
},
"/_app": {
"filePath": "_app/route.tsx",
"children": [
"/_app/bangumi",
"/_app/playground",
"/_app/settings",
"/_app/subscriptions",
"/_app/_explore/explore",
"/_app/_explore/feed"
]
},
"/404": {
"filePath": "404.tsx"
},
"/about": {
"filePath": "about.tsx"
},
"/_app/bangumi": {
"filePath": "_app/bangumi/route.tsx",
"parent": "/_app",
"children": [
"/_app/bangumi/manage"
]
},
"/_app/playground": {
"filePath": "_app/playground/route.tsx",
"parent": "/_app",
"children": [
"/_app/playground/graphql-api"
]
},
"/_app/settings": {
"filePath": "_app/settings/route.tsx",
"parent": "/_app",
"children": [
"/_app/settings/downloader"
]
},
"/_app/subscriptions": {
"filePath": "_app/subscriptions/route.tsx",
"parent": "/_app",
"children": [
"/_app/subscriptions/create",
"/_app/subscriptions/manage",
"/_app/subscriptions/detail/$subscriptionId",
"/_app/subscriptions/edit/$subscriptionId"
]
},
"/auth/sign-in": {
"filePath": "auth/sign-in.tsx"
},
"/auth/sign-up": {
"filePath": "auth/sign-up.tsx"
},
"/routeTree/gen": {
"filePath": "routeTree.gen.ts"
},
"/_app/_explore/explore": {
"filePath": "_app/_explore/explore.tsx",
"parent": "/_app"
},
"/_app/_explore/feed": {
"filePath": "_app/_explore/feed.tsx",
"parent": "/_app"
},
"/_app/bangumi/manage": {
"filePath": "_app/bangumi/manage.tsx",
"parent": "/_app/bangumi"
},
"/_app/playground/graphql-api": {
"filePath": "_app/playground/graphql-api.tsx",
"parent": "/_app/playground"
},
"/_app/settings/downloader": {
"filePath": "_app/settings/downloader.tsx",
"parent": "/_app/settings"
},
"/_app/subscriptions/create": {
"filePath": "_app/subscriptions/create.tsx",
"parent": "/_app/subscriptions"
},
"/_app/subscriptions/manage": {
"filePath": "_app/subscriptions/manage.tsx",
"parent": "/_app/subscriptions"
},
"/auth/oidc/callback": {
"filePath": "auth/oidc/callback.tsx"
},
"/_app/subscriptions/detail/$subscriptionId": {
"filePath": "_app/subscriptions/detail.$subscriptionId.tsx",
"parent": "/_app/subscriptions"
},
"/_app/subscriptions/edit/$subscriptionId": {
"filePath": "_app/subscriptions/edit.$subscriptionId.tsx",
"parent": "/_app/subscriptions"
}
}
}
ROUTE_MANIFEST_END */

View File

@@ -0,0 +1,7 @@
import type { ClassValue } from 'clsx';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}