diff --git a/src/pages/account.tsx b/src/pages/account.tsx
index fa1bceb..7812093 100644
--- a/src/pages/account.tsx
+++ b/src/pages/account.tsx
@@ -3,7 +3,7 @@ import MainLayout from '~/components/Layout/MainLayout';
import { Button } from '~/components/ui/button';
import Link from 'next/link';
import { UserAvatar } from '~/components/ui/avatar';
-import { Bell, ChevronRight, Download, Github, Star } from 'lucide-react';
+import { Bell, ChevronRight, Download, FileDown, Github, Star } from 'lucide-react';
import { signOut } from 'next-auth/react';
import { AppDrawer } from '~/components/ui/drawer';
import { SubmitFeedback } from '~/components/Account/SubmitFeedback';
@@ -13,9 +13,27 @@ import { type NextPageWithUser } from '~/types';
import { toast } from 'sonner';
import { env } from '~/env';
import { SubscribeNotification } from '~/components/Account/SubscribeNotification';
+import { useState } from 'react';
+import { LoadingSpinner } from '~/components/ui/spinner';
const AccountPage: NextPageWithUser = ({ user }) => {
const userQuery = api.user.me.useQuery();
+ const downloadQuery = api.user.downloadData.useMutation();
+
+ const [downloading, setDownloading] = useState(false);
+
+ async function downloadData() {
+ setDownloading(true);
+ const data = await downloadQuery.mutateAsync();
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'splitpro_data.json';
+ link.click();
+ URL.revokeObjectURL(url);
+ setDownloading(false);
+ }
return (
<>
@@ -136,6 +154,22 @@ const AccountPage: NextPageWithUser = ({ user }) => {
+
diff --git a/src/server/api/routers/user.ts b/src/server/api/routers/user.ts
index 0d738f8..8d53629 100644
--- a/src/server/api/routers/user.ts
+++ b/src/server/api/routers/user.ts
@@ -1,14 +1,20 @@
-import { SplitType } from '@prisma/client';
+import { type Balance, SplitType } from '@prisma/client';
import { boolean, z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '~/server/api/trpc';
import { db } from '~/server/db';
-import { addUserExpense, deleteExpense } from '../services/splitService';
+import {
+ addUserExpense,
+ deleteExpense,
+ getCompleteFriendsDetails,
+ getCompleteGroupDetails,
+} from '../services/splitService';
import { TRPCError } from '@trpc/server';
import { randomUUID } from 'crypto';
import { getDocumentUploadUrl } from '~/server/storage';
import { FILE_SIZE_LIMIT } from '~/lib/constants';
import { sendFeedbackEmail } from '~/server/mailer';
import { pushNotification } from '~/server/notification';
+import { toFixedNumber, toUIString } from '~/utils/numbers';
export const userRouter = createTRPCRouter({
me: protectedProcedure.query(async ({ ctx }) => {
@@ -441,4 +447,13 @@ export const userRouter = createTRPCRouter({
},
});
}),
+
+ downloadData: protectedProcedure.mutation(async ({ ctx }) => {
+ const user = ctx.session.user;
+
+ const friends = await getCompleteFriendsDetails(user.id);
+ const groups = await getCompleteGroupDetails(user.id);
+
+ return { friends, groups };
+ }),
});
diff --git a/src/server/api/services/splitService.ts b/src/server/api/services/splitService.ts
index 6e240ac..31075bd 100644
--- a/src/server/api/services/splitService.ts
+++ b/src/server/api/services/splitService.ts
@@ -551,3 +551,67 @@ export async function sendExpensePushNotification(expenseId: string) {
await Promise.all(pushNotifications);
}
+
+export async function getCompleteFriendsDetails(userId: number) {
+ const balances = await db.balance.findMany({
+ where: {
+ userId,
+ },
+ include: {
+ friend: true,
+ },
+ });
+
+ const friends = balances.reduce(
+ (acc, balance) => {
+ const friendId = balance.friendId;
+ if (!acc[friendId]) {
+ acc[friendId] = {
+ balances: [],
+ id: balance.friendId,
+ email: balance.friend.email,
+ name: balance.friend.name,
+ };
+ }
+
+ if (balance.amount !== 0) {
+ acc[friendId]?.balances.push({
+ currency: balance.currency,
+ amount:
+ balance.amount > 0 ? toFixedNumber(balance.amount) : toFixedNumber(balance.amount),
+ });
+ }
+
+ return acc;
+ },
+ {} as Record<
+ number,
+ {
+ id: number;
+ email?: string | null;
+ name?: string | null;
+ balances: { currency: string; amount: number }[];
+ }
+ >,
+ );
+
+ return friends;
+}
+
+export async function getCompleteGroupDetails(userId: number) {
+ const groups = await db.group.findMany({
+ where: {
+ groupUsers: {
+ some: {
+ userId,
+ },
+ },
+ },
+ include: {
+ groupUsers: true,
+ groupBalances: true,
+ },
+ });
+
+ return groups;
+}