Skip to content

Commit

Permalink
refactor: settings fully client side
Browse files Browse the repository at this point in the history
  • Loading branch information
kdurek committed Apr 2, 2024
1 parent fbafb6a commit 79822d2
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 118 deletions.
33 changes: 2 additions & 31 deletions src/app/(app)/(settings)/ustawienia/page.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,18 @@
import { User2 } from 'lucide-react';
import Link from 'next/link';
import { redirect } from 'next/navigation';

import { LogoutButton } from '@/components/auth/logout-button';
import { GroupSelect } from '@/components/group/group-select';
import { Section } from '@/components/layout/section';
import { MembersList } from '@/components/settings/members-list';
import { buttonVariants } from '@/components/ui/button';
import { Heading } from '@/components/ui/heading';
import { cn } from '@/lib/utils';
import { Settings } from '@/components/settings/settings';
import { validateRequest } from '@/server/auth';
import { api } from '@/trpc/server';

export default async function SettingsPage() {
const { user } = await validateRequest();
if (!user) {
return redirect('/logowanie');
}

const group = await api.group.current();

return (
<Section title="Ustawienia">
<div className="space-y-4">
<div className="flex flex-col gap-4 rounded-md bg-white p-4">
<Link href={'/ustawienia/profil'} className={cn(buttonVariants({ variant: 'outline' }))}>
<User2 className="mr-2" /> Profil
</Link>
<LogoutButton />
</div>

<div className="space-y-2 rounded-md bg-white p-4">
<Heading variant="h2">Aktywna grupa</Heading>
<GroupSelect user={user} />
</div>

{user.id === group.adminId && (
<div className="rounded-md bg-white p-4">
<Heading variant="h2">Członkowie</Heading>
<MembersList />
</div>
)}
</div>
<Settings user={user} />
</Section>
);
}
34 changes: 5 additions & 29 deletions src/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,24 @@
import { redirect } from 'next/navigation';

import { CreateGroupForm } from '@/components/group/create-group-form';
import { GroupSelect } from '@/components/group/group-select';
import { AppInit } from '@/components/layout/app-init';
import { MobileNav } from '@/components/layout/mobile-nav';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Separator } from '@/components/ui/separator';
import { GenderSelectForm } from '@/components/user/gender-select-form';
import { validateRequest } from '@/server/auth';
import { api } from '@/trpc/server';

export default async function AppLayout({ children }: { children: React.ReactNode }) {
const { user } = await validateRequest();
if (!user) {
return redirect('/logowanie');
}

if (!user.gender) {
return (
<div className="p-4">
<GenderSelectForm userId={user.id} />
</div>
);
}

if (!user.activeGroupId) {
return (
<div className="space-y-4 p-4">
<GroupSelect user={user} />
<Separator />
<Collapsible>
<CollapsibleTrigger className="w-full text-center text-muted-foreground">Stwórz grupę</CollapsibleTrigger>
<CollapsibleContent>
<CreateGroupForm />
</CollapsibleContent>
</Collapsible>
</div>
);
}
const groups = await api.group.list();

return (
<div>
<AppInit user={user} groups={groups}>
<div className="min-h-dvh pb-20">{children}</div>
<div className="fixed bottom-0 z-40 h-20 w-full rounded-t-md bg-background shadow-[0px_4px_16px_rgba(17,17,26,0.1),_0px_8px_24px_rgba(17,17,26,0.1),_0px_16px_56px_rgba(17,17,26,0.1)]">
<MobileNav />
</div>
</div>
</AppInit>
);
}
48 changes: 21 additions & 27 deletions src/components/auth/logout-button.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
'use client';

import { useQueryClient } from '@tanstack/react-query';
import { LogOut } from 'lucide-react';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { useRouter } from 'next/navigation';

import { Button } from '@/components/ui/button';
import { lucia, validateRequest } from '@/server/auth';

async function logout(): Promise<ActionResult> {
'use server';
const { session } = await validateRequest();
if (!session) {
return {
error: 'Unauthorized',
};
}
import { api } from '@/trpc/react';

await lucia.invalidateSession(session.id);

const sessionCookie = lucia.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return redirect('/logowanie');
}
export function LogoutButton() {
const router = useRouter();
const queryClient = useQueryClient();
const { mutate: logout } = api.auth.logout.useMutation({
onSuccess() {
queryClient.clear();
router.push('/logowanie');
router.refresh();
},
});

interface ActionResult {
error: string | null;
}
const handleLogout = () => {
logout();
};

export function LogoutButton() {
return (
<form action={logout}>
<Button variant="outline" className="w-full">
<LogOut className="mr-2" /> Wyloguj
</Button>
</form>
<Button type="button" onClick={handleLogout} variant="outline" className="w-full">
<LogOut className="mr-2" /> Wyloguj
</Button>
);
}
17 changes: 5 additions & 12 deletions src/components/group/group-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
import type { User } from 'lucia';
import { toast } from 'sonner';

import { FullScreenError } from '@/components/layout/error';
import { FullScreenLoading } from '@/components/layout/loading';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { api } from '@/trpc/react';
import type { GroupList } from '@/trpc/shared';

interface GroupSelectProps {
user: User;
groups: GroupList;
onSuccess?: () => void;
}

export function GroupSelect({ user }: GroupSelectProps) {
const { data: groups, status: groupsStatus } = api.group.list.useQuery();
export function GroupSelect({ user, groups, onSuccess }: GroupSelectProps) {
const { mutate: changeActiveGroup } = api.group.changeCurrent.useMutation();

const handleGroupSelect = (value: string) => {
Expand All @@ -23,19 +23,12 @@ export function GroupSelect({ user }: GroupSelectProps) {
{
onSuccess() {
toast.success('Pomyślnie wybrano grupę');
onSuccess && onSuccess();
},
},
);
};

if (groupsStatus === 'pending') {
return <FullScreenLoading />;
}

if (groupsStatus === 'error') {
return <FullScreenError />;
}

if (groups.length === 0) {
return (
<div className="text-center">
Expand Down
60 changes: 60 additions & 0 deletions src/components/layout/app-init.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import type { User } from 'lucia';
import { useRouter } from 'next/navigation';

import { CreateGroupForm } from '@/components/group/create-group-form';
import { GroupSelect } from '@/components/group/group-select';
import { MobileNav } from '@/components/layout/mobile-nav';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Separator } from '@/components/ui/separator';
import { GenderSelectForm } from '@/components/user/gender-select-form';
import type { GroupList } from '@/trpc/shared';

interface AppInitProps {
children: React.ReactNode;
user: User;
groups: GroupList;
}

export function AppInit({ children, user, groups }: AppInitProps) {
const router = useRouter();

if (!user.gender) {
return (
<div className="p-4">
<GenderSelectForm userId={user.id} />
</div>
);
}

if (!user.activeGroupId) {
return (
<div className="space-y-4 p-4">
<GroupSelect
user={user}
groups={groups}
onSuccess={() => {
router.refresh();
}}
/>
<Separator />
<Collapsible>
<CollapsibleTrigger className="w-full text-center text-muted-foreground">Stwórz grupę</CollapsibleTrigger>
<CollapsibleContent>
<CreateGroupForm />
</CollapsibleContent>
</Collapsible>
</div>
);
}

return (
<div>
<div className="min-h-dvh pb-20">{children}</div>
<div className="fixed bottom-0 z-40 h-20 w-full rounded-t-md bg-background shadow-[0px_4px_16px_rgba(17,17,26,0.1),_0px_8px_24px_rgba(17,17,26,0.1),_0px_16px_56px_rgba(17,17,26,0.1)]">
<MobileNav />
</div>
</div>
);
}
21 changes: 6 additions & 15 deletions src/components/settings/members-list.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
'use client';

import { AddUserToGroupForm } from '@/components/group/add-user-to-group-form';
import { FullScreenError } from '@/components/layout/error';
import { FullScreenLoading } from '@/components/layout/loading';
import { api } from '@/trpc/react';
import type { GroupCurrent, UserListNotInCurrentGroup } from '@/trpc/shared';

export function MembersList() {
const { data: group, status: groupStatus } = api.group.current.useQuery();
const { data: usersNotInCurrentGroup, status: usersNotInCurrentGroupStatus } =
api.user.listNotInCurrentGroup.useQuery();

if (groupStatus === 'pending' || usersNotInCurrentGroupStatus === 'pending') {
return <FullScreenLoading />;
}

if (groupStatus === 'error' || usersNotInCurrentGroupStatus === 'error') {
return <FullScreenError />;
}
interface MembersListProps {
group: GroupCurrent;
usersNotInCurrentGroup: UserListNotInCurrentGroup;
}

export function MembersList({ group, usersNotInCurrentGroup }: MembersListProps) {
return (
<div className="space-y-4">
<ol className="space-y-1">
Expand Down
11 changes: 7 additions & 4 deletions src/components/settings/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ interface SettingsProps {
}

export function Settings({ user }: SettingsProps) {
const { data: groups, status: groupsStatus } = api.group.list.useQuery();
const { data: group, status: groupStatus } = api.group.current.useQuery();
const { data: usersNotInCurrentGroup, status: usersNotInCurrentGroupStatus } =
api.user.listNotInCurrentGroup.useQuery();

if (groupStatus === 'pending') {
if (groupsStatus === 'pending' || groupStatus === 'pending' || usersNotInCurrentGroupStatus === 'pending') {
return <FullScreenLoading />;
}

if (groupStatus === 'error') {
if (groupsStatus === 'error' || groupStatus === 'error' || usersNotInCurrentGroupStatus === 'error') {
return <FullScreenError />;
}

Expand All @@ -40,13 +43,13 @@ export function Settings({ user }: SettingsProps) {

<div className="space-y-2 rounded-md bg-white p-4">
<Heading variant="h2">Aktywna grupa</Heading>
<GroupSelect user={user} />
<GroupSelect user={user} groups={groups} />
</div>

{user.id === group.adminId && (
<div className="rounded-md bg-white p-4">
<Heading variant="h2">Członkowie</Heading>
<MembersList />
<MembersList group={group} usersNotInCurrentGroup={usersNotInCurrentGroup} />
</div>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/server/api/root.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { authRouter } from '@/server/api/routers/auth';
import { expenseRouter } from '@/server/api/routers/expense';
import { groupRouter } from '@/server/api/routers/group';
import { userRouter } from '@/server/api/routers/user';
Expand All @@ -9,6 +10,7 @@ import { createCallerFactory, createTRPCRouter } from '@/server/api/trpc';
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
auth: authRouter,
expense: expenseRouter,
group: groupRouter,
user: userRouter,
Expand Down
13 changes: 13 additions & 0 deletions src/server/api/routers/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { cookies } from 'next/headers';

import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
import { lucia } from '@/server/auth';

export const authRouter = createTRPCRouter({
logout: protectedProcedure.mutation(async ({ ctx }) => {
await lucia.invalidateSession(ctx.session.id);

const sessionCookie = lucia.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}),
});
1 change: 1 addition & 0 deletions src/trpc/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export type ExpenseById = RouterOutputs['expense']['byId'];
export type ExpenseDebtSettlement = RouterOutputs['expense']['debt']['settlement'];
export type GroupList = RouterOutputs['group']['list'];
export type GroupCurrent = RouterOutputs['group']['current'];
export type UserListNotInCurrentGroup = RouterOutputs['user']['listNotInCurrentGroup'];
export type UserList = RouterOutputs['user']['list'];
export type UserById = RouterOutputs['user']['byId'];

0 comments on commit 79822d2

Please sign in to comment.