Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,64 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useDeleteUserAccountMutation } from '~/generated/graphql';
|
||||
|
||||
export const DeleteAccount = () => {
|
||||
const [isDeleteAccountModalOpen, setIsDeleteAccountModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const [deleteUserAccount] = useDeleteUserAccountMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const userEmail = currentUser?.email;
|
||||
const { signOut } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
signOut();
|
||||
navigate(AppPath.SignIn);
|
||||
}, [signOut, navigate]);
|
||||
|
||||
const deleteAccount = async () => {
|
||||
await deleteUserAccount();
|
||||
handleLogout();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<H2Title
|
||||
title="Danger zone"
|
||||
description="Delete account and all the associated data"
|
||||
/>
|
||||
|
||||
<Button
|
||||
accent="danger"
|
||||
onClick={() => setIsDeleteAccountModalOpen(true)}
|
||||
variant="secondary"
|
||||
title="Delete account"
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
confirmationValue={userEmail}
|
||||
confirmationPlaceholder={userEmail ?? ''}
|
||||
isOpen={isDeleteAccountModalOpen}
|
||||
setIsOpen={setIsDeleteAccountModalOpen}
|
||||
title="Account Deletion"
|
||||
subtitle={
|
||||
<>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
entire account. <br /> Please type in your email to confirm.
|
||||
</>
|
||||
}
|
||||
onConfirmClick={deleteAccount}
|
||||
deleteButtonText="Delete account"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,61 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import {
|
||||
ConfirmationModal,
|
||||
StyledConfirmationButton,
|
||||
} from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useDeleteCurrentWorkspaceMutation } from '~/generated/graphql';
|
||||
|
||||
export const DeleteWorkspace = () => {
|
||||
const [isDeleteWorkSpaceModalOpen, setIsDeleteWorkSpaceModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const [deleteCurrentWorkspace] = useDeleteCurrentWorkspaceMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const userEmail = currentUser?.email;
|
||||
const { signOut } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = useCallback(() => {
|
||||
signOut();
|
||||
navigate(AppPath.SignIn);
|
||||
}, [signOut, navigate]);
|
||||
|
||||
const deleteWorkspace = async () => {
|
||||
await deleteCurrentWorkspace();
|
||||
handleLogout();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<H2Title title="Danger zone" description="Delete your whole workspace" />
|
||||
<StyledConfirmationButton
|
||||
onClick={() => setIsDeleteWorkSpaceModalOpen(true)}
|
||||
variant="secondary"
|
||||
title="Delete workspace"
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
confirmationPlaceholder={userEmail}
|
||||
confirmationValue={userEmail}
|
||||
isOpen={isDeleteWorkSpaceModalOpen}
|
||||
setIsOpen={setIsDeleteWorkSpaceModalOpen}
|
||||
title="Workspace Deletion"
|
||||
subtitle={
|
||||
<>
|
||||
This action cannot be undone. This will permanently delete your
|
||||
entire workspace. <br /> Please type in your email to confirm.
|
||||
</>
|
||||
}
|
||||
onConfirmClick={deleteWorkspace}
|
||||
deleteButtonText="Delete workspace"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
|
||||
export const EmailField = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
value={currentUser?.email}
|
||||
disabled
|
||||
fullWidth
|
||||
key={'email-' + currentUser?.id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,126 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
const StyledComboInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
> * + * {
|
||||
margin-left: ${({ theme }) => theme.spacing(4)};
|
||||
}
|
||||
`;
|
||||
|
||||
type NameFieldsProps = {
|
||||
autoSave?: boolean;
|
||||
onFirstNameUpdate?: (firstName: string) => void;
|
||||
onLastNameUpdate?: (lastName: string) => void;
|
||||
};
|
||||
|
||||
export const NameFields = ({
|
||||
autoSave = true,
|
||||
onFirstNameUpdate,
|
||||
onLastNameUpdate,
|
||||
}: NameFieldsProps) => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
|
||||
const [firstName, setFirstName] = useState(
|
||||
currentWorkspaceMember?.name?.firstName ?? '',
|
||||
);
|
||||
const [lastName, setLastName] = useState(
|
||||
currentWorkspaceMember?.name?.lastName ?? '',
|
||||
);
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: 'workspaceMember',
|
||||
});
|
||||
|
||||
// TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com)
|
||||
const debouncedUpdate = debounce(async () => {
|
||||
if (onFirstNameUpdate) {
|
||||
onFirstNameUpdate(firstName);
|
||||
}
|
||||
if (onLastNameUpdate) {
|
||||
onLastNameUpdate(lastName);
|
||||
}
|
||||
try {
|
||||
if (!currentWorkspaceMember?.id) {
|
||||
throw new Error('User is not logged in');
|
||||
}
|
||||
|
||||
if (autoSave) {
|
||||
await updateOneRecord({
|
||||
idToUpdate: currentWorkspaceMember?.id,
|
||||
input: {
|
||||
name: {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setCurrentWorkspaceMember({
|
||||
...currentWorkspaceMember,
|
||||
name: {
|
||||
firstName,
|
||||
lastName,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentWorkspaceMember) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
currentWorkspaceMember.name?.firstName !== firstName ||
|
||||
currentWorkspaceMember.name?.lastName !== lastName
|
||||
) {
|
||||
debouncedUpdate();
|
||||
}
|
||||
|
||||
return () => {
|
||||
debouncedUpdate.cancel();
|
||||
};
|
||||
}, [
|
||||
firstName,
|
||||
lastName,
|
||||
currentUser,
|
||||
debouncedUpdate,
|
||||
autoSave,
|
||||
currentWorkspaceMember,
|
||||
]);
|
||||
|
||||
return (
|
||||
<StyledComboInputContainer>
|
||||
<TextInput
|
||||
label="First Name"
|
||||
value={firstName}
|
||||
onChange={setFirstName}
|
||||
placeholder="Tim"
|
||||
fullWidth
|
||||
/>
|
||||
<TextInput
|
||||
label="Last Name"
|
||||
value={lastName}
|
||||
onChange={setLastName}
|
||||
placeholder="Cook"
|
||||
fullWidth
|
||||
/>
|
||||
</StyledComboInputContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,105 @@
|
||||
import { useState } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { ImageInput } from '@/ui/input/components/ImageInput';
|
||||
import { getImageAbsoluteURIOrBase64 } from '@/users/utils/getProfilePictureAbsoluteURI';
|
||||
import { useUploadProfilePictureMutation } from '~/generated/graphql';
|
||||
|
||||
export const ProfilePictureUploader = () => {
|
||||
const [uploadPicture, { loading: isUploading }] =
|
||||
useUploadProfilePictureMutation();
|
||||
|
||||
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
|
||||
const [uploadController, setUploadController] =
|
||||
useState<AbortController | null>(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: 'workspaceMember',
|
||||
});
|
||||
|
||||
const handleUpload = async (file: File) => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
setUploadController(controller);
|
||||
|
||||
try {
|
||||
if (!currentWorkspaceMember?.id) {
|
||||
throw new Error('User is not logged in');
|
||||
}
|
||||
const result = await uploadPicture({
|
||||
variables: {
|
||||
file,
|
||||
},
|
||||
context: {
|
||||
fetchOptions: {
|
||||
signal: controller.signal,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setUploadController(null);
|
||||
setErrorMessage(null);
|
||||
|
||||
const avatarUrl = result?.data?.uploadProfilePicture;
|
||||
|
||||
if (!avatarUrl) {
|
||||
throw new Error('Avatar URL not found');
|
||||
}
|
||||
|
||||
await updateOneRecord({
|
||||
idToUpdate: currentWorkspaceMember?.id,
|
||||
input: {
|
||||
avatarUrl,
|
||||
},
|
||||
});
|
||||
|
||||
setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl });
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
setErrorMessage('An error occured while uploading the picture.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAbort = async () => {
|
||||
if (uploadController) {
|
||||
uploadController.abort();
|
||||
setUploadController(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = async () => {
|
||||
if (!currentWorkspaceMember?.id) {
|
||||
throw new Error('User is not logged in');
|
||||
}
|
||||
|
||||
await updateOneRecord({
|
||||
idToUpdate: currentWorkspaceMember?.id,
|
||||
input: {
|
||||
avatarUrl: null,
|
||||
},
|
||||
});
|
||||
|
||||
setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl: null });
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageInput
|
||||
picture={getImageAbsoluteURIOrBase64(currentWorkspaceMember?.avatarUrl)}
|
||||
onUpload={handleUpload}
|
||||
onRemove={handleRemove}
|
||||
onAbort={handleAbort}
|
||||
isUploading={isUploading}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user