Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/backend/src/api/routes/users.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Post,
Expand Down Expand Up @@ -280,4 +281,9 @@ export class UsersController {
track: uniqueId,
});
}

@Delete('/delete')
async deleteUser(@GetUserFromRequest() user: User) {
return this._userService.deleteUser(user.id);
}
}
8 changes: 8 additions & 0 deletions apps/frontend/src/components/layout/settings.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { SignaturesComponent } from '@gitroom/frontend/components/settings/signa
import { Autopost } from '@gitroom/frontend/components/autopost/autopost';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
import { SVGLine } from '@gitroom/frontend/components/launches/launches.component';
import { UserComponent } from '../settings/user.component';
export const SettingsPopup: FC<{
getRef?: Ref<any>;
}> = (props) => {
Expand Down Expand Up @@ -115,6 +116,7 @@ export const SettingsPopup: FC<{
if (user?.tier?.public_api && isGeneral && showLogout) {
arr.push({ tab: 'api', label: t('public_api', 'Public API') });
}
arr.push({ tab: 'user', label: t('user', 'User') });

return arr;
}, [user, isGeneral, showLogout, t]);
Expand Down Expand Up @@ -198,6 +200,12 @@ export const SettingsPopup: FC<{
</div>
)}

{tab === 'user' && user?.tier.current !== 'FREE' && (
<div>
<UserComponent />
</div>
)}

{tab === 'api' &&
!!user?.tier?.public_api &&
isGeneral &&
Expand Down
77 changes: 77 additions & 0 deletions apps/frontend/src/components/settings/user.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use client';

import React, { FC, useCallback } from 'react';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { Button } from '@gitroom/react/form/button';
import { useToaster } from '@gitroom/react/toaster/toaster';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';

export const UserComponent: FC = () => {
const fetch = useFetch();
const toast = useToaster();
const t = useT();

const deleteAccount = useCallback(async () => {
try {
const res = await fetch('/user/delete', {
method: 'DELETE',
});
if (!res.ok) throw new Error('Failed to delete account');

toast.show(
t('account_deleted', 'Account deleted successfully.'),
'success'
);
window.location.href = '/';
} catch (err) {
toast.show(
t(
'delete_account_failed',
'Failed to delete account. Please try again.'
),
'warning'
);
}
}, []);

const confirmDelete = useCallback(async () => {
const confirmed = await deleteDialog(
t(
'delete_account_confirmation',
'Are you sure you want to delete your account? This action cannot be undone.'
),
t('delete', 'Delete')
);

if (confirmed) deleteAccount();
}, [deleteAccount]);

return (
<div className="flex flex-col">
<h3 className="text-[20px]">{t('delete_account', 'Delete Account')}</h3>
<div className="text-customColor18 mt-[4px]">
{t(
'delete_account_description',
'Permanently delete your account and all associated data. This cannot be undone.'
)}
</div>
<div className="my-[16px] bg-sixth border-red-500 border rounded-[4px] p-[24px] flex items-center justify-between">
<div className="flex flex-col">
<div className="text-[16px] font-medium text-red-600">
{t('are_you_sure', 'Are you sure?')}
</div>
<div className="text-customColor18 text-sm">
{t(
'delete_warning',
'Deleting your account will remove all data permanently. This cannot be undone.'
)}
</div>
</div>
<Button onClick={confirmDelete}>
{t('delete_account_button', 'Delete Account')}
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,11 @@ export class AgenciesRepository {

return insertAgency;
}
deleteByUserId(userId: string) {
return this._socialMediaAgencies.model.socialMediaAgency.deleteMany({
where: {
userId,
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ export class ItemUserRepository {
},
});
}
deleteAllItemsByUser(userId: string) {
return this._itemUser.model.itemUser.deleteMany({
where: {
userId,
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -912,4 +912,10 @@ export class MessagesRepository {
},
});
}

async deletePayoutProblemsByUser(userId: string) {
await this._payoutProblems.model.payoutProblems.deleteMany({
where: { userId },
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,10 @@ export class OrganizationRepository {
},
});
}

async deleteUserOrganizations(userId: string) {
return this._userOrg.model.userOrganization.deleteMany({
where: { userId },
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -694,4 +694,12 @@ export class PostsRepository {
},
});
}

async deleteCommentsByUser(userId: string) {
return this._comments.model.comments.deleteMany({
where: {
userId,
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,10 @@ export class UsersRepository {
count,
};
}

deleteUser(userId: string) {
return this._user.model.user.delete({
where: { id: userId },
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import { Provider } from '@prisma/client';
import { ItemsDto } from '@gitroom/nestjs-libraries/dtos/marketplace/items.dto';
import { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto';
import { OrganizationRepository } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.repository';
import { ItemUserRepository } from '../marketplace/item.user.repository';
import { AgenciesRepository } from '../agencies/agencies.repository';
import { PostsRepository } from '../posts/posts.repository';
import { MessagesRepository } from '../marketplace/messages.repository';

@Injectable()
export class UsersService {
constructor(
private _usersRepository: UsersRepository,
private _organizationRepository: OrganizationRepository
private _organizationRepository: OrganizationRepository,
private _itemUserRepository: ItemUserRepository,
private _agenciesRepository: AgenciesRepository,
private _postsRepository: PostsRepository,
private _messagesRepository: MessagesRepository
) {}

getUserByEmail(email: string) {
Expand Down Expand Up @@ -55,4 +63,14 @@ export class UsersService {
changePersonal(userId: string, body: UserDetailDto) {
return this._usersRepository.changePersonal(userId, body);
}

async deleteUser(userId: string){
//Deleting all models the user has first
await this._organizationRepository.deleteUserOrganizations(userId);
await this._itemUserRepository.deleteAllItemsByUser(userId)
await this._agenciesRepository.deleteByUserId(userId)
await this._postsRepository.deleteCommentsByUser(userId)
await this._messagesRepository.deletePayoutProblemsByUser(userId)
return this._usersRepository.deleteUser(userId)
}
}