Skip to content

Commit ab3125b

Browse files
authored
Add download splitpro data (#35)
1 parent 79041b2 commit ab3125b

File tree

3 files changed

+116
-3
lines changed

3 files changed

+116
-3
lines changed

src/pages/account.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import MainLayout from '~/components/Layout/MainLayout';
33
import { Button } from '~/components/ui/button';
44
import Link from 'next/link';
55
import { UserAvatar } from '~/components/ui/avatar';
6-
import { Bell, ChevronRight, Download, Github, Star } from 'lucide-react';
6+
import { Bell, ChevronRight, Download, FileDown, Github, Star } from 'lucide-react';
77
import { signOut } from 'next-auth/react';
88
import { AppDrawer } from '~/components/ui/drawer';
99
import { SubmitFeedback } from '~/components/Account/SubmitFeedback';
@@ -13,9 +13,27 @@ import { type NextPageWithUser } from '~/types';
1313
import { toast } from 'sonner';
1414
import { env } from '~/env';
1515
import { SubscribeNotification } from '~/components/Account/SubscribeNotification';
16+
import { useState } from 'react';
17+
import { LoadingSpinner } from '~/components/ui/spinner';
1618

1719
const AccountPage: NextPageWithUser = ({ user }) => {
1820
const userQuery = api.user.me.useQuery();
21+
const downloadQuery = api.user.downloadData.useMutation();
22+
23+
const [downloading, setDownloading] = useState(false);
24+
25+
async function downloadData() {
26+
setDownloading(true);
27+
const data = await downloadQuery.mutateAsync();
28+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
29+
const url = URL.createObjectURL(blob);
30+
const link = document.createElement('a');
31+
link.href = url;
32+
link.download = 'splitpro_data.json';
33+
link.click();
34+
URL.revokeObjectURL(url);
35+
setDownloading(false);
36+
}
1937

2038
return (
2139
<>
@@ -136,6 +154,22 @@ const AccountPage: NextPageWithUser = ({ user }) => {
136154
</p>
137155
</div>
138156
</AppDrawer>
157+
<Button
158+
variant="ghost"
159+
className="text-md w-full justify-between px-0 hover:text-foreground/80"
160+
onClick={downloadData}
161+
disabled={downloading}
162+
>
163+
<div className="flex items-center gap-4">
164+
<FileDown className="h-5 w-5 text-teal-500" />
165+
Download splitpro data
166+
</div>
167+
{downloading ? (
168+
<LoadingSpinner />
169+
) : (
170+
<ChevronRight className="h-6 w-6 text-gray-500" />
171+
)}
172+
</Button>
139173
</div>
140174

141175
<div className="mt-20 flex justify-center">

src/server/api/routers/user.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
import { SplitType } from '@prisma/client';
1+
import { type Balance, SplitType } from '@prisma/client';
22
import { boolean, z } from 'zod';
33
import { createTRPCRouter, protectedProcedure } from '~/server/api/trpc';
44
import { db } from '~/server/db';
5-
import { addUserExpense, deleteExpense } from '../services/splitService';
5+
import {
6+
addUserExpense,
7+
deleteExpense,
8+
getCompleteFriendsDetails,
9+
getCompleteGroupDetails,
10+
} from '../services/splitService';
611
import { TRPCError } from '@trpc/server';
712
import { randomUUID } from 'crypto';
813
import { getDocumentUploadUrl } from '~/server/storage';
914
import { FILE_SIZE_LIMIT } from '~/lib/constants';
1015
import { sendFeedbackEmail } from '~/server/mailer';
1116
import { pushNotification } from '~/server/notification';
17+
import { toFixedNumber, toUIString } from '~/utils/numbers';
1218

1319
export const userRouter = createTRPCRouter({
1420
me: protectedProcedure.query(async ({ ctx }) => {
@@ -441,4 +447,13 @@ export const userRouter = createTRPCRouter({
441447
},
442448
});
443449
}),
450+
451+
downloadData: protectedProcedure.mutation(async ({ ctx }) => {
452+
const user = ctx.session.user;
453+
454+
const friends = await getCompleteFriendsDetails(user.id);
455+
const groups = await getCompleteGroupDetails(user.id);
456+
457+
return { friends, groups };
458+
}),
444459
});

src/server/api/services/splitService.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,3 +551,67 @@ export async function sendExpensePushNotification(expenseId: string) {
551551

552552
await Promise.all(pushNotifications);
553553
}
554+
555+
export async function getCompleteFriendsDetails(userId: number) {
556+
const balances = await db.balance.findMany({
557+
where: {
558+
userId,
559+
},
560+
include: {
561+
friend: true,
562+
},
563+
});
564+
565+
const friends = balances.reduce(
566+
(acc, balance) => {
567+
const friendId = balance.friendId;
568+
if (!acc[friendId]) {
569+
acc[friendId] = {
570+
balances: [],
571+
id: balance.friendId,
572+
email: balance.friend.email,
573+
name: balance.friend.name,
574+
};
575+
}
576+
577+
if (balance.amount !== 0) {
578+
acc[friendId]?.balances.push({
579+
currency: balance.currency,
580+
amount:
581+
balance.amount > 0 ? toFixedNumber(balance.amount) : toFixedNumber(balance.amount),
582+
});
583+
}
584+
585+
return acc;
586+
},
587+
{} as Record<
588+
number,
589+
{
590+
id: number;
591+
email?: string | null;
592+
name?: string | null;
593+
balances: { currency: string; amount: number }[];
594+
}
595+
>,
596+
);
597+
598+
return friends;
599+
}
600+
601+
export async function getCompleteGroupDetails(userId: number) {
602+
const groups = await db.group.findMany({
603+
where: {
604+
groupUsers: {
605+
some: {
606+
userId,
607+
},
608+
},
609+
},
610+
include: {
611+
groupUsers: true,
612+
groupBalances: true,
613+
},
614+
});
615+
616+
return groups;
617+
}

0 commit comments

Comments
 (0)