chore: improve ssr data loading
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
This commit is contained in:
parent
0ad4d9ab81
commit
f3eb57dbc7
8 changed files with 88 additions and 58 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { createIsomorphicFn } from "@tanstack/react-start";
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import { config } from "~/server/core/config";
|
||||
|
||||
export const getOrigin = createIsomorphicFn()
|
||||
.server(() => config.baseUrl)
|
||||
.client(() => window.location.origin);
|
||||
export const getOrigin = createServerFn().handler(() => {
|
||||
return config.baseUrl;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
|||
import { Shield, ShieldAlert, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { GetOrgMembersResponse } from "~/client/api-client";
|
||||
import {
|
||||
getOrgMembersOptions,
|
||||
removeOrgMemberMutation,
|
||||
|
|
@ -24,13 +25,17 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~
|
|||
import { useOrganizationContext } from "~/client/hooks/use-org-context";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
|
||||
export function OrgMembersSection() {
|
||||
type OrgMembersSectionProps = {
|
||||
initialMembers: GetOrgMembersResponse;
|
||||
};
|
||||
|
||||
export function OrgMembersSection({ initialMembers }: OrgMembersSectionProps) {
|
||||
const { activeMember } = useOrganizationContext();
|
||||
const [memberToRemove, setMemberToRemove] = useState<{ id: string; name: string } | null>(null);
|
||||
|
||||
const orgMembersQuery = useSuspenseQuery({
|
||||
...getOrgMembersOptions(),
|
||||
});
|
||||
const {
|
||||
data: { members },
|
||||
} = useSuspenseQuery({ ...getOrgMembersOptions(), initialData: initialMembers });
|
||||
|
||||
const updateRole = useMutation({
|
||||
...updateMemberRoleMutation(),
|
||||
|
|
@ -53,8 +58,6 @@ export function OrgMembersSection() {
|
|||
},
|
||||
});
|
||||
|
||||
const members = orgMembersQuery.data.members;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-md border">
|
||||
|
|
|
|||
|
|
@ -32,19 +32,25 @@ import { formatDateWithMonth } from "~/client/lib/datetime";
|
|||
import { getOrigin } from "~/client/functions/get-origin";
|
||||
import { authClient } from "~/client/lib/auth-client";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
import { useServerFn } from "@tanstack/react-start";
|
||||
import type { GetSsoSettingsResponse } from "~/client/api-client";
|
||||
|
||||
type InvitationRole = "member" | "admin" | "owner";
|
||||
|
||||
export function SsoSettingsSection() {
|
||||
const origin = getOrigin();
|
||||
type SsoSettingsSectionProps = {
|
||||
initialSettings: GetSsoSettingsResponse;
|
||||
};
|
||||
|
||||
export function SsoSettingsSection({ initialSettings }: SsoSettingsSectionProps) {
|
||||
const originQuery = useServerFn(getOrigin);
|
||||
const { data } = useSuspenseQuery({ queryKey: ["app-origin"], queryFn: originQuery });
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { activeOrganization } = useOrganizationContext();
|
||||
const [inviteEmail, setInviteEmail] = useState("");
|
||||
const [inviteRole, setInviteRole] = useState<InvitationRole>("member");
|
||||
|
||||
const ssoSettingsQuery = useSuspenseQuery({
|
||||
...getSsoSettingsOptions(),
|
||||
});
|
||||
const ssoSettingsQuery = useSuspenseQuery({ ...getSsoSettingsOptions(), initialData: initialSettings });
|
||||
|
||||
const updateProviderAutoLinkingMutation = useMutation({
|
||||
...updateSsoProviderAutoLinkingMutation(),
|
||||
|
|
@ -195,7 +201,7 @@ export function SsoSettingsSection() {
|
|||
<Input
|
||||
type="text"
|
||||
readOnly
|
||||
value={`${origin}/api/auth/sso/callback/${provider.providerId}`}
|
||||
value={`${data}/api/auth/sso/callback/${provider.providerId}`}
|
||||
className="h-8 max-w-62.5 font-mono text-xs text-muted-foreground"
|
||||
onClick={(e) => e.currentTarget.select()}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
|
|||
import { SsoSettingsSection } from "../components/sso/sso-settings-section";
|
||||
import { OrgMembersSection } from "../components/org-members-section";
|
||||
import { useOrganizationContext } from "~/client/hooks/use-org-context";
|
||||
import type { GetOrgMembersResponse, GetSsoSettingsResponse } from "~/client/api-client";
|
||||
|
||||
type Props = {
|
||||
appContext: AppContext;
|
||||
initialMembers: GetOrgMembersResponse;
|
||||
initialSsoSettings: GetSsoSettingsResponse;
|
||||
};
|
||||
|
||||
export function SettingsPage({ appContext }: Props) {
|
||||
export function SettingsPage({ appContext, initialMembers, initialSsoSettings }: Props) {
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
|
|
@ -296,7 +299,7 @@ export function SettingsPage({ appContext }: Props) {
|
|||
<CardDescription className="mt-1.5">Manage organization members and roles</CardDescription>
|
||||
</div>
|
||||
<CardContent className="p-6">
|
||||
<OrgMembersSection />
|
||||
<OrgMembersSection initialMembers={initialMembers} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
|
@ -309,7 +312,7 @@ export function SettingsPage({ appContext }: Props) {
|
|||
<CardDescription className="mt-1.5">Configure OIDC provider settings</CardDescription>
|
||||
</div>
|
||||
<CardContent className="p-6">
|
||||
<SsoSettingsSection />
|
||||
<SsoSettingsSection initialSettings={initialSsoSettings} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { VolumeIcon } from "~/client/components/volume-icon";
|
|||
import { listVolumesOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import type { VolumeStatus } from "~/client/lib/types";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { cn } from "~/client/lib/utils";
|
||||
|
||||
const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | "error" | "warning" => {
|
||||
const statusMap = {
|
||||
|
|
@ -119,38 +120,35 @@ export function VolumesPage() {
|
|||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{hasNoFilteredVolumes ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center py-12">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<p className="text-muted-foreground">No volumes match your filters.</p>
|
||||
<Button onClick={clearFilters} variant="outline" size="sm">
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
<TableRow className={cn({ hidden: !hasNoFilteredVolumes })}>
|
||||
<TableCell colSpan={4} className="text-center py-12">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<p className="text-muted-foreground">No volumes match your filters.</p>
|
||||
<Button onClick={clearFilters} variant="outline" size="sm">
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{filteredVolumes.map((volume) => (
|
||||
<TableRow
|
||||
key={volume.name}
|
||||
className="hover:bg-white/2 hover:cursor-pointer transition-colors h-12"
|
||||
onClick={() => navigate({ to: `/volumes/${volume.shortId}` })}
|
||||
>
|
||||
<TableCell className="font-medium font-mono text-strong-accent">{volume.name}</TableCell>
|
||||
<TableCell className="font-mono text-muted-foreground">
|
||||
<VolumeIcon backend={volume.type} />
|
||||
</TableCell>
|
||||
<TableCell className="text-center font-mono">
|
||||
<StatusDot
|
||||
variant={getVolumeStatusVariant(volume.status)}
|
||||
label={volume.status[0].toUpperCase() + volume.status.slice(1)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
filteredVolumes.map((volume) => (
|
||||
<TableRow
|
||||
key={volume.name}
|
||||
className="hover:bg-white/2 hover:cursor-pointer transition-colors border-l-2 border-r-2 border-transparent hover:border-white/10 h-12"
|
||||
onClick={() => navigate({ to: `/volumes/${volume.shortId}` })}
|
||||
>
|
||||
<TableCell className="font-medium font-mono text-strong-accent">{volume.name}</TableCell>
|
||||
<TableCell className="font-mono text-muted-foreground">
|
||||
<VolumeIcon backend={volume.type} />
|
||||
</TableCell>
|
||||
<TableCell className="text-center font-mono">
|
||||
<StatusDot
|
||||
variant={getVolumeStatusVariant(volume.status)}
|
||||
label={volume.status[0].toUpperCase() + volume.status.slice(1)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { type } from "arktype";
|
|||
import { fetchUser } from "../route";
|
||||
import type { AppContext } from "~/context";
|
||||
import { AdminPage } from "~/client/modules/admin/routes/admin-page";
|
||||
import { getAdminUsersOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import { getAdminUsersOptions, getRegistrationStatusOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
|
||||
export const Route = createFileRoute("/(dashboard)/admin/")({
|
||||
validateSearch: type({ tab: "string?" }),
|
||||
|
|
@ -15,7 +15,10 @@ export const Route = createFileRoute("/(dashboard)/admin/")({
|
|||
throw redirect({ to: "/settings" });
|
||||
}
|
||||
|
||||
await context.queryClient.ensureQueryData(getAdminUsersOptions());
|
||||
await Promise.all([
|
||||
context.queryClient.ensureQueryData({ ...getAdminUsersOptions() }),
|
||||
context.queryClient.ensureQueryData({ ...getRegistrationStatusOptions() }),
|
||||
]);
|
||||
|
||||
return authContext as AppContext;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Layout } from "~/client/components/layout";
|
|||
import { SIDEBAR_COOKIE_NAME } from "~/client/components/ui/sidebar";
|
||||
import { authMiddleware } from "~/middleware/auth";
|
||||
import { auth } from "~/server/lib/auth";
|
||||
import { getOrganizationContext } from "~/server/lib/functions/organization-context";
|
||||
import { authService } from "~/server/modules/auth/auth.service";
|
||||
|
||||
export const fetchUser = createServerFn({ method: "GET" }).handler(async () => {
|
||||
|
|
@ -24,8 +25,15 @@ export const Route = createFileRoute("/(dashboard)")({
|
|||
server: {
|
||||
middleware: [authMiddleware],
|
||||
},
|
||||
loader: async () => {
|
||||
const authContext = await fetchUser();
|
||||
loader: async ({ context }) => {
|
||||
const [authContext] = await Promise.all([
|
||||
fetchUser(),
|
||||
context.queryClient.ensureQueryData({
|
||||
queryKey: ["organization-context"],
|
||||
queryFn: () => getOrganizationContext(),
|
||||
}),
|
||||
]);
|
||||
|
||||
return authContext;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,13 +3,21 @@ import { fetchUser } from "../route";
|
|||
import type { AppContext } from "~/context";
|
||||
import { SettingsPage } from "~/client/modules/settings/routes/settings";
|
||||
import { type } from "arktype";
|
||||
import { getOrgMembersOptions, getSsoSettingsOptions } from "~/client/api-client/@tanstack/react-query.gen";
|
||||
import { getOrigin } from "~/client/functions/get-origin";
|
||||
|
||||
export const Route = createFileRoute("/(dashboard)/settings/")({
|
||||
component: RouteComponent,
|
||||
validateSearch: type({ tab: "string?" }),
|
||||
loader: async () => {
|
||||
const authContext = await fetchUser();
|
||||
return authContext as AppContext;
|
||||
loader: async ({ context }) => {
|
||||
const [authContext, org, members] = await Promise.all([
|
||||
fetchUser(),
|
||||
context.queryClient.ensureQueryData({ ...getSsoSettingsOptions() }),
|
||||
context.queryClient.ensureQueryData({ ...getOrgMembersOptions() }),
|
||||
context.queryClient.ensureQueryData({ queryKey: ["app-origin"], queryFn: () => getOrigin() }),
|
||||
]);
|
||||
|
||||
return { authContext: authContext as AppContext, org, members };
|
||||
},
|
||||
staticData: {
|
||||
breadcrumb: () => [{ label: "Settings" }],
|
||||
|
|
@ -17,6 +25,7 @@ export const Route = createFileRoute("/(dashboard)/settings/")({
|
|||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const appContext = Route.useLoaderData();
|
||||
return <SettingsPage appContext={appContext} />;
|
||||
const { authContext, org, members } = Route.useLoaderData();
|
||||
|
||||
return <SettingsPage appContext={authContext} initialMembers={members} initialSsoSettings={org} />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue