Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:29:26 +08:00
commit a0db888440
20 changed files with 1989 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
// Grey Haven Studio - Protected Route Layout Template
// Copy this template for _authenticated/_layout.tsx
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
import { Header } from "~/lib/components/layout/Header";
import { Sidebar } from "~/lib/components/layout/Sidebar";
import { getSession } from "~/lib/server/functions/auth";
// TODO: Update route path to match your file location
export const Route = createFileRoute("/_authenticated/_layout")({
// Auth check runs before loading
beforeLoad: async ({ context }) => {
const session = await getSession();
// Redirect to login if not authenticated
if (!session) {
throw redirect({
to: "/auth/login",
search: {
redirect: context.location.href, // Save redirect URL
},
});
}
// Make session available to child routes
return { session };
},
component: AuthenticatedLayout,
});
function AuthenticatedLayout() {
const { session } = Route.useRouteContext();
return (
<div className="flex min-h-screen">
{/* TODO: Update layout structure */}
<Sidebar user={session.user} />
<div className="flex-1">
<Header user={session.user} />
<main className="p-6">
<Outlet /> {/* Child routes render here */}
</main>
</div>
</div>
);
}

View File

@@ -0,0 +1,39 @@
// Grey Haven Studio - Custom Query Hook Template
// Copy this template for reusable query logic
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
// TODO: Import your server functions
// import { getResource, updateResource } from "~/lib/server/functions/resources";
// TODO: Update hook name and parameter types
export function useResource(resourceId: string, tenantId: string) {
const queryClient = useQueryClient();
// Query for fetching data
const query = useQuery({
queryKey: ["resource", resourceId], // Include resourceId in key
queryFn: () => getResource(resourceId, tenantId),
staleTime: 60000, // Grey Haven default: 1 minute
});
// Mutation for updating data
const updateMutation = useMutation({
mutationFn: (data: ResourceUpdate) => updateResource(resourceId, data, tenantId),
onSuccess: (updatedResource) => {
// Update cache with new data
queryClient.setQueryData(["resource", resourceId], updatedResource);
},
});
// Return simplified interface
return {
resource: query.data,
isLoading: query.isLoading,
error: query.error,
update: updateMutation.mutate,
isUpdating: updateMutation.isPending,
};
}
// Usage in component:
// const { resource, isLoading, update, isUpdating } = useResource(id, tenantId);

View File

@@ -0,0 +1,30 @@
// Grey Haven Studio - Page Route Template
// Copy this template for any page route
import { createFileRoute } from "@tanstack/react-router";
// TODO: Import your server functions
// import { getPageData } from "~/lib/server/functions/page";
// TODO: Update route path to match your file location
export const Route = createFileRoute("/_authenticated/page")({
// Loader fetches data on server before rendering
loader: async ({ context }) => {
const tenantId = context.session.tenantId;
// TODO: Replace with your data fetching
// return await getPageData(tenantId);
return { data: "Replace me" };
},
component: PageComponent,
});
function PageComponent() {
const data = Route.useLoaderData(); // Type-safe loader data
return (
<div>
{/* TODO: Build your page UI */}
<h1 className="text-2xl font-bold">Page Title</h1>
<div>{JSON.stringify(data)}</div>
</div>
);
}

View File

@@ -0,0 +1,35 @@
// Grey Haven Studio - TanStack Router Root Route Template
// Copy this template for __root.tsx
import { Outlet, createRootRoute } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
// Create QueryClient with Grey Haven defaults
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60000, // 1 minute default
retry: 1,
refetchOnWindowFocus: false,
},
},
});
export const Route = createRootRoute({
component: RootComponent,
});
function RootComponent() {
return (
<QueryClientProvider client={queryClient}>
<div className="min-h-screen bg-background">
<Outlet /> {/* Child routes render here */}
</div>
{/* Dev tools (only in development) */}
<ReactQueryDevtools initialIsOpen={false} />
<TanStackRouterDevtools position="bottom-right" />
</QueryClientProvider>
);
}

View File

@@ -0,0 +1,61 @@
// Grey Haven Studio - Server Function Template
// Copy this template for creating server functions
import { createServerFn } from "@tanstack/start";
import { db } from "~/lib/server/db";
// TODO: Import your database schema
// import { resources } from "~/lib/server/schema/resources";
import { eq, and } from "drizzle-orm";
// GET server function (for fetching data)
// TODO: Update function name and return type
export const getResource = createServerFn("GET", async (
resourceId: string,
tenantId: string // ALWAYS include tenant_id!
) => {
// TODO: Replace with your query
const resource = await db.query.resources.findFirst({
where: and(
eq(resources.id, resourceId),
eq(resources.tenant_id, tenantId) // Multi-tenant isolation!
),
});
if (!resource) {
throw new Error("Resource not found");
}
return resource;
});
// POST server function (for creating data)
// TODO: Update function name and parameters
export const createResource = createServerFn("POST", async (
data: { name: string; description?: string },
tenantId: string // ALWAYS include tenant_id!
) => {
// TODO: Replace with your insert
const resource = await db.insert(resources).values({
...data,
tenant_id: tenantId, // Include tenant_id in insert!
}).returning();
return resource[0];
});
// DELETE server function (for deleting data)
// TODO: Update function name
export const deleteResource = createServerFn("DELETE", async (
resourceId: string,
tenantId: string // ALWAYS include tenant_id!
) => {
// TODO: Replace with your delete
await db.delete(resources).where(
and(
eq(resources.id, resourceId),
eq(resources.tenant_id, tenantId) // Ensure tenant isolation!
)
);
return { success: true };
});