Skip to content

Commit

Permalink
feat: add user profile
Browse files Browse the repository at this point in the history
  • Loading branch information
kdurek committed Aug 19, 2023
1 parent c8a4d57 commit 9be4d3b
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface ExpenseDetailsPageProps {

export default async function ExpenseDetailsPage({ params }: ExpenseDetailsPageProps) {
const session = await getServerAuthSession();

if (!session) {
return redirect('/logowanie');
}
Expand All @@ -22,7 +23,7 @@ export default async function ExpenseDetailsPage({ params }: ExpenseDetailsPageP
userId: session.user.id,
});

if (!paramUser) {
if (!paramUser || !currentUser) {
redirect('/');
}

Expand Down
11 changes: 10 additions & 1 deletion src/app/(app)/(settings)/ustawienia/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { AddUserToGroupForm } from 'components/forms/add-user-to-group-form';
import { GroupSelect } from 'components/group-select';
import { LogoutButton } from 'components/logout-button';
import { Section } from 'components/section';
import { buttonVariants } from 'components/ui/button';
import { Separator } from 'components/ui/separator';
import { cn } from 'lib/utils';
import { User2 } from 'lucide-react';
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { createTrpcCaller } from 'server/api/caller';
import { getServerAuthSession } from 'server/auth';
Expand All @@ -22,7 +26,12 @@ export default async function SettingsPage() {
return (
<Section title="Ustawienia">
<div className="flex flex-col gap-6">
<LogoutButton />
<div className="flex flex-col gap-4">
<Link href={'/ustawienia/profil'} className={cn(buttonVariants({ variant: 'outline' }))}>
<User2 className="mr-2" /> Profil
</Link>
<LogoutButton />
</div>
<Separator />
<div className="space-y-2">
<h2 className="text-xl font-bold">Aktywna grupa</h2>
Expand Down
28 changes: 28 additions & 0 deletions src/app/(app)/(settings)/ustawienia/profil/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { UserForm } from 'components/forms/user-form';
import { Section } from 'components/section';
import { redirect } from 'next/navigation';
import { createTrpcCaller } from 'server/api/caller';
import { getServerAuthSession } from 'server/auth';

export default async function ProfilePage() {
const session = await getServerAuthSession();

if (!session) {
redirect('/logowanie');
}

const caller = await createTrpcCaller();
const user = await caller.user.getById({
userId: session.user.id,
});

if (!user) {
redirect('/');
}

return (
<Section title="Profil">
<UserForm user={user} />
</Section>
);
}
4 changes: 2 additions & 2 deletions src/components/expense-feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from 'components/ui/dialog';
import { format } from 'date-fns';
import { useInfiniteExpenses } from 'hooks/use-infinite-expenses';
import { cn } from 'lib/utils';
import { cn, getFirstName } from 'lib/utils';
import { CircleDollarSign } from 'lucide-react';
import Link from 'next/link';
import { useSession } from 'next-auth/react';
Expand All @@ -31,7 +31,7 @@ export function ExpenseListItem({ expense }: ExpenseCardProps) {
const { data: session } = useSession();
const descriptionParts = expense.description?.split('\n');
const hasDescription = descriptionParts?.length;
const [payerFirstName] = expense.payer.name?.split(' ') ?? '';
const payerFirstName = getFirstName(expense.payer.name);
const formattedDate = format(expense.createdAt, 'EEEEEE, d MMMM');

const getSettledStateIcon = () => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/expense-payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { NumberInput } from 'components/ui/number-input';
import { Separator } from 'components/ui/separator';
import { useDisclosure } from 'hooks/use-disclosure';
import { useUpdateExpenseDebt } from 'hooks/use-update-expense-debt';
import { cn } from 'lib/utils';
import { cn, getFirstName } from 'lib/utils';
import { Loader2, Square, XSquare } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { useForm } from 'react-hook-form';
Expand Down Expand Up @@ -39,7 +39,7 @@ export function ExpensePayment({ debt }: ExpenseCardPaymentProps) {
resolver: zodResolver(expenseCardPaymentFormSchema),
});

const [debtorFirstName] = debt.debtor.name?.split(' ') ?? '';
const debtorFirstName = getFirstName(debt.debtor.name);

const notHavePermission = session?.user?.id !== debt.debtorId && session?.user?.id !== debt.expense.payerId;

Expand Down
69 changes: 69 additions & 0 deletions src/components/forms/user-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from 'components/ui/button';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from 'components/ui/form';
import { Input } from 'components/ui/input';
import { useUpdateUser } from 'hooks/use-update-user';
import { useForm } from 'react-hook-form';
import { GetUserById } from 'utils/api';
import { z } from 'zod';

const updateUserFormSchema = z.object({
name: z
.string({ required_error: 'Musisz podać imię i nazwisko' })
.min(3, 'Minimalna długość to 3 znaki')
.refine((value) => value.split(' ').length === 2, { message: 'Musisz podać imię i nazwisko' }),
});

type UpdateUserFormSchema = z.infer<typeof updateUserFormSchema>;

interface UserFormProps {
user: GetUserById;
}

export function UserForm({ user }: UserFormProps) {
const defaultValues = user
? {
name: user.name || '',
}
: {
name: '',
};

const form = useForm<UpdateUserFormSchema>({
resolver: zodResolver(updateUserFormSchema),
defaultValues,
});

const { mutate: updateUser } = useUpdateUser();

const handleUpdateUser = async (values: UpdateUserFormSchema) => {
if (user) {
updateUser({ userId: user.id, name: values.name });
}
};

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleUpdateUser)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Imię i nazwisko</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mt-6 flex justify-end">
<Button>Zapisz</Button>
</div>
</form>
</Form>
);
}
14 changes: 14 additions & 0 deletions src/hooks/use-update-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useRouter } from 'next/navigation';
import { api } from 'utils/api';

export function useUpdateUser() {
const router = useRouter();
const utils = api.useContext();

return api.user.update.useMutation({
async onSuccess(_, variables) {
await utils.user.getById.invalidate({ userId: variables.userId });
router.refresh();
},
});
}
5 changes: 5 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

export function getFirstName(name: string | null) {
const [firstName] = name?.split(' ') ?? '';
return firstName;
}

export function getInitials(name: string | null) {
const words = name?.split(' ');
const initials = words?.map((word) => word.charAt(0).toUpperCase()).join('');
Expand Down
2 changes: 1 addition & 1 deletion src/server/api/routers/expense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const expenseRouter = createTRPCRouter({
}),

getById: protectedProcedure.input(z.object({ id: z.string().cuid2() })).query(({ input, ctx }) => {
return ctx.prisma.expense.findFirst({
return ctx.prisma.expense.findUnique({
where: {
id: input.id,
},
Expand Down
20 changes: 19 additions & 1 deletion src/server/api/routers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const userRouter = createTRPCRouter({
}),

getById: publicProcedure.input(z.object({ userId: z.string().cuid2() })).query(({ input, ctx }) => {
return ctx.prisma.user.findFirst({
return ctx.prisma.user.findUnique({
where: {
id: input.userId,
},
Expand Down Expand Up @@ -143,4 +143,22 @@ export const userRouter = createTRPCRouter({
},
});
}),

update: protectedProcedure
.input(
z.object({
userId: z.string().cuid2(),
name: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
return ctx.prisma.user.update({
where: {
id: input.userId,
},
data: {
name: input.name,
},
});
}),
});

0 comments on commit 9be4d3b

Please sign in to comment.