From 279406b9c7da94ff343411b05b4c26b0de4f9316 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 12 Feb 2023 20:25:19 +0100 Subject: [PATCH 01/21] fix: Adapt to new API --- src/apis/get-alias.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apis/get-alias.ts b/src/apis/get-alias.ts index 775af6e..00fdee9 100644 --- a/src/apis/get-alias.ts +++ b/src/apis/get-alias.ts @@ -1,8 +1,8 @@ import {client} from "~/constants/axios-client" import {Alias} from "~/server-types" -export default async function getAlias(address: string): Promise { - const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/alias/${address}`, { +export default async function getAlias(aliasID: string): Promise { + const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/alias/${aliasID}`, { withCredentials: true, }) From 9a7b868301555a54dd044923cb77672a33840e21 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 12 Feb 2023 20:37:35 +0100 Subject: [PATCH 02/21] fix: Change address to id field in url --- src/App.tsx | 2 +- src/route-widgets/AliasesRoute/AliasesListItem.tsx | 2 +- src/routes/AliasDetailRoute.tsx | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 75f1d89..097515f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -81,7 +81,7 @@ const router = createBrowserRouter([ element: , }, { - path: "/aliases/:addressInBase64", + path: "/aliases/:id", element: , }, { diff --git a/src/route-widgets/AliasesRoute/AliasesListItem.tsx b/src/route-widgets/AliasesRoute/AliasesListItem.tsx index a88fcb4..6693421 100644 --- a/src/route-widgets/AliasesRoute/AliasesListItem.tsx +++ b/src/route-widgets/AliasesRoute/AliasesListItem.tsx @@ -29,7 +29,7 @@ export default function AliasesListItem({ // @ts-ignore component={isInCopyAddressMode ? undefined : RouterLink} key={alias.id} - to={isInCopyAddressMode ? undefined : `/aliases/${btoa(address)}`} + to={isInCopyAddressMode ? undefined : `/aliases/${alias.id}`} onClick={(event: any) => { if (isInCopyAddressMode) { event.preventDefault() diff --git a/src/routes/AliasDetailRoute.tsx b/src/routes/AliasDetailRoute.tsx index d291426..696fa07 100644 --- a/src/routes/AliasDetailRoute.tsx +++ b/src/routes/AliasDetailRoute.tsx @@ -25,13 +25,12 @@ import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes" export default function AliasDetailRoute(): ReactElement { const {t} = useTranslation() - const params = useParams() - const address = atob(params.addressInBase64 as string) + const {id: aliasID} = useParams() const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) - const queryKey = ["get_alias", address, encryptionStatus] + const queryKey = ["get_alias", aliasID, encryptionStatus] const query = useQuery(queryKey, async () => { - const alias = await getAlias(address) + const alias = await getAlias(aliasID!) if (encryptionStatus === EncryptionStatus.Available) { ;(alias as any as DecryptedAlias).notes = decryptAliasNotes( @@ -60,7 +59,7 @@ export default function AliasDetailRoute(): ReactElement { - + Date: Sun, 12 Feb 2023 21:15:07 +0100 Subject: [PATCH 03/21] feat: Add allowAliasDeletion field to admin settings --- public/locales/en-US/translation.json | 4 +++ src/constants/admin-settings.ts | 1 + .../GlobalSettingsRoute/SettingsForm.tsx | 29 +++++++++++++++++++ src/server-types.ts | 1 + 4 files changed, 35 insertions(+) diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index f67df95..31836e9 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -383,6 +383,10 @@ "allowStatistics": { "label": "Allow statistics", "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." + }, + "allowAliasDeletion": { + "label": "Allow alias deletion", + "description": "If enabled, users will be able to delete their aliases." } } }, diff --git a/src/constants/admin-settings.ts b/src/constants/admin-settings.ts index 5d340a0..b85bf6b 100644 --- a/src/constants/admin-settings.ts +++ b/src/constants/admin-settings.ts @@ -11,4 +11,5 @@ export const DEFAULT_ADMIN_SETTINGS: AdminSettings = { imageProxyStorageLifeTimeInHours: 24, enableImageProxy: true, allowStatistics: true, + allowAliasDeletion: false, } diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx index 5207c2f..e38c6fc 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx @@ -488,6 +488,35 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { + + + + } + disabled={formik.isSubmitting} + label={t( + "routes.AdminRoute.forms.settings.allowAliasDeletion.label", + )} + /> + + {(formik.touched.allowAliasDeletion && + formik.errors.allowAliasDeletion) || + t( + "routes.AdminRoute.forms.settings.allowAliasDeletion.description", + )} + + + diff --git a/src/server-types.ts b/src/server-types.ts index 6aa8310..0b2a5df 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -210,4 +210,5 @@ export interface AdminSettings { userEmailEnableDisposableEmails: boolean userEmailEnableOtherRelays: boolean | null allowStatistics: boolean | null + allowAliasDeletion: boolean | null } From 785a712eb78fa2ca075964d05350a8a7f3560a39 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 12 Feb 2023 21:20:04 +0100 Subject: [PATCH 04/21] fix: Loading settings --- src/routes/GlobalSettingsRoute.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/GlobalSettingsRoute.tsx b/src/routes/GlobalSettingsRoute.tsx index 2b841db..db94e40 100644 --- a/src/routes/GlobalSettingsRoute.tsx +++ b/src/routes/GlobalSettingsRoute.tsx @@ -13,7 +13,7 @@ import SettingsForm from "~/route-widgets/GlobalSettingsRoute/SettingsForm" export default function GlobalSettingsRoute(): ReactElement { const queryKey = ["get_admin_settings"] const query = useQuery(queryKey, async () => { - const settings = getAdminSettings() + const settings = await getAdminSettings() return _.mergeWith({}, DEFAULT_ADMIN_SETTINGS, settings, (o, s) => _.isNull(s) ? o : s, From d786199111dcd72f12dfe759c544d9a07acbc3f3 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 14 Feb 2023 19:57:21 +0100 Subject: [PATCH 05/21] feat: Add delete-alias.ts API --- src/apis/delete-alias.ts | 10 ++++++++++ src/apis/index.ts | 2 ++ 2 files changed, 12 insertions(+) create mode 100644 src/apis/delete-alias.ts diff --git a/src/apis/delete-alias.ts b/src/apis/delete-alias.ts new file mode 100644 index 0000000..180c2b2 --- /dev/null +++ b/src/apis/delete-alias.ts @@ -0,0 +1,10 @@ +import {client} from "~/constants/axios-client" +import {SimpleDetailResponse} from "~/server-types" + +export default async function deleteAlias(id: string): Promise { + const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/report/${id}`, { + withCredentials: true, + }) + + return data +} diff --git a/src/apis/index.ts b/src/apis/index.ts index bfb40f1..5f10655 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -52,3 +52,5 @@ export * from "./get-admin-settings" export {default as getAdminSettings} from "./get-admin-settings" export * from "./update-admin-settings" export {default as updateAdminSettings} from "./update-admin-settings" +export * from "./delete-alias" +export {default as deleteAlias} from "./delete-alias" From 00b983aed2a15d4a485dd9df28b8d7ffb3476047 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 14 Feb 2023 20:18:04 +0100 Subject: [PATCH 06/21] refactor: Make DeleteButton.tsx generic --- public/locales/en-US/translation.json | 3 +- src/App.tsx | 1 + .../widgets/DeleteAPIButton.tsx} | 37 ++++++++++++------- src/components/widgets/index.ts | 2 + src/routes/AliasDetailRoute.tsx | 10 +++-- src/routes/ReportDetailRoute.tsx | 24 ++++++++++-- 6 files changed, 55 insertions(+), 22 deletions(-) rename src/{route-widgets/ReportDetailRoute/DeleteButton.tsx => components/widgets/DeleteAPIButton.tsx} (66%) diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 31836e9..38dd63a 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -16,7 +16,8 @@ "loading": "Loading...", "actionNotUndoable": "This action cannot be undone!", "copyError": "Copying to clipboard did not work. Please copy the text manually.", - "experimentalFeature": "This is an experimental feature." + "experimentalFeature": "This is an experimental feature.", + "deletedSuccessfully": "Deleted successfully!" }, "routes": { diff --git a/src/App.tsx b/src/App.tsx index 097515f..cf21964 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -82,6 +82,7 @@ const router = createBrowserRouter([ }, { path: "/aliases/:id", + loader: getServerSettings, element: , }, { diff --git a/src/route-widgets/ReportDetailRoute/DeleteButton.tsx b/src/components/widgets/DeleteAPIButton.tsx similarity index 66% rename from src/route-widgets/ReportDetailRoute/DeleteButton.tsx rename to src/components/widgets/DeleteAPIButton.tsx index 0cf44a2..9ea0038 100644 --- a/src/route-widgets/ReportDetailRoute/DeleteButton.tsx +++ b/src/components/widgets/DeleteAPIButton.tsx @@ -15,24 +15,35 @@ import { } from "@mui/material" import {useMutation} from "@tanstack/react-query" -import {deleteReport} from "~/apis" import {useErrorSuccessSnacks} from "~/hooks" -import {SimpleDetailResponse} from "~/server-types" -export interface DeleteButtonProps { - id: string +export interface DeleteAPIButtonProps { + onDelete: () => Promise + label: string + continueLabel?: string + + description?: string + successMessage?: string + navigateTo?: string } -export default function ReportDetailRoute({id}: DeleteButtonProps): ReactElement { +export default function DeleteAPIButton({ + onDelete, + successMessage, + label, + continueLabel, + description, + navigateTo = "/aliases", +}: DeleteAPIButtonProps): ReactElement { const {t} = useTranslation() const {showError, showSuccess} = useErrorSuccessSnacks() const navigate = useNavigate() - const {mutate} = useMutation(() => deleteReport(id), { + const {mutate} = useMutation(onDelete, { onError: showError, onSuccess: () => { - showSuccess(t("relations.report.mutations.success.reportDeleted")) - navigate("/reports") + showSuccess(successMessage || t("general.deletedSuccessfully")) + navigate(navigateTo) }, }) @@ -47,14 +58,12 @@ export default function ReportDetailRoute({id}: DeleteButtonProps): ReactElement startIcon={} onClick={() => setShowDeleteDialog(true)} > - {t("routes.ReportDetailRoute.actions.delete.label")} + {label} setShowDeleteDialog(false)}> - {t("routes.ReportDetailRoute.actions.delete.label")} + {label} - - {t("routes.ReportDetailRoute.actions.delete.description")} - + {description && {description}} {t("general.actionNotUndoable")} @@ -69,7 +78,7 @@ export default function ReportDetailRoute({id}: DeleteButtonProps): ReactElement color="error" onClick={() => mutate()} > - {t("routes.ReportDetailRoute.actions.delete.continueAction")} + {continueLabel} diff --git a/src/components/widgets/index.ts b/src/components/widgets/index.ts index d25c848..7ff371e 100644 --- a/src/components/widgets/index.ts +++ b/src/components/widgets/index.ts @@ -45,6 +45,8 @@ export {default as LoadingData} from "./LoadingData" export * from "./ExternalLinkIndication" export {default as ExternalLinkIndication} from "./ExternalLinkIndication" export {default as ExtensionSignalHandler} from "./ExtensionalSignalHandler" +export * from "./DeleteAPIButton" +export {default as DeleteButton} from "./DeleteAPIButton" export * from "./StringPoolField" export * as SimplePageBuilder from "./simple-page-builder" diff --git a/src/routes/AliasDetailRoute.tsx b/src/routes/AliasDetailRoute.tsx index 696fa07..11a50d3 100644 --- a/src/routes/AliasDetailRoute.tsx +++ b/src/routes/AliasDetailRoute.tsx @@ -1,5 +1,5 @@ import {ReactElement, useContext} from "react" -import {useParams} from "react-router-dom" +import {useLoaderData, useParams} from "react-router-dom" import {AxiosError} from "axios" import {useTranslation} from "react-i18next" @@ -7,7 +7,7 @@ import {useQuery} from "@tanstack/react-query" import {Grid} from "@mui/material" import {getAlias} from "~/apis" -import {Alias, DecryptedAlias} from "~/server-types" +import {Alias, DecryptedAlias, ServerSettings} from "~/server-types" import { AliasTypeIndicator, AuthContext, @@ -25,6 +25,7 @@ import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes" export default function AliasDetailRoute(): ReactElement { const {t} = useTranslation() + const serverSettings = useLoaderData() as ServerSettings const {id: aliasID} = useParams() const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) const queryKey = ["get_alias", aliasID, encryptionStatus] @@ -43,7 +44,10 @@ export default function AliasDetailRoute(): ReactElement { }) return ( - + } + > query={query}> {alias => ( diff --git a/src/routes/ReportDetailRoute.tsx b/src/routes/ReportDetailRoute.tsx index aec204d..7d4b36f 100644 --- a/src/routes/ReportDetailRoute.tsx +++ b/src/routes/ReportDetailRoute.tsx @@ -7,10 +7,15 @@ import {useQuery} from "@tanstack/react-query" import {List} from "@mui/material" import {DecryptedReportContent, Report} from "~/server-types" -import {getReport} from "~/apis" -import {DecryptReport, QueryResult, SimpleOverlayInformation, SimplePageBuilder} from "~/components" +import {deleteReport, getReport} from "~/apis" +import { + DecryptReport, + DeleteButton, + QueryResult, + SimpleOverlayInformation, + SimplePageBuilder, +} from "~/components" import {WithEncryptionRequired} from "~/hocs" -import DeleteButton from "~/route-widgets/ReportDetailRoute/DeleteButton" import ExpandedUrlsListItem from "~/route-widgets/ReportDetailRoute/ExpandedUrlsListItem" import ProxiedImagesListItem from "~/route-widgets/ReportDetailRoute/ProxiedImagesListItem" import SinglePixelImageTrackersListItem from "~/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem" @@ -26,7 +31,18 @@ function ReportDetailRoute(): ReactElement { return ( } + actions={ + query.data && ( + deleteReport(params.id!)} + label={t("routes.ReportDetailRoute.actions.delete.label")} + description={t("routes.ReportDetailRoute.actions.delete.description")} + continueLabel={t("routes.ReportDetailRoute.actions.delete.continueAction")} + navigateTo={"/reports"} + successMessage={t("relations.report.mutations.success.reportDeleted")} + /> + ) + } > query={query}> {encryptedReport => ( From 6490aad2ed93bf84d658df409b1b277409c29b66 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 14 Feb 2023 20:29:46 +0100 Subject: [PATCH 07/21] feat: Add delete alias --- public/locales/en-US/translation.json | 10 +++++++++- src/apis/delete-alias.ts | 2 +- src/routes/AliasDetailRoute.tsx | 16 ++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 38dd63a..2f4e4b0 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -203,6 +203,13 @@ } } } + }, + "actions": { + "delete": { + "label": "Delete Alias", + "description": "Are you sure you want to delete this alias?", + "continueAction": "Delete Alias" + } } }, "ReportsRoute": { @@ -506,7 +513,8 @@ "notesUpdated": "Updated & encrypted notes successfully!", "aliasChangedToEnabled": "Alias has been enabled", "aliasChangedToDisabled": "Alias has been disabled", - "addressCopiedToClipboard": "Address has been copied to your clipboard!" + "addressCopiedToClipboard": "Address has been copied to your clipboard!", + "aliasDeleted": "Alias has been deleted!" } }, "settings": { diff --git a/src/apis/delete-alias.ts b/src/apis/delete-alias.ts index 180c2b2..9d24d17 100644 --- a/src/apis/delete-alias.ts +++ b/src/apis/delete-alias.ts @@ -2,7 +2,7 @@ import {client} from "~/constants/axios-client" import {SimpleDetailResponse} from "~/server-types" export default async function deleteAlias(id: string): Promise { - const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/report/${id}`, { + const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/alias/${id}`, { withCredentials: true, }) diff --git a/src/routes/AliasDetailRoute.tsx b/src/routes/AliasDetailRoute.tsx index 11a50d3..9110cea 100644 --- a/src/routes/AliasDetailRoute.tsx +++ b/src/routes/AliasDetailRoute.tsx @@ -6,12 +6,13 @@ import {useTranslation} from "react-i18next" import {useQuery} from "@tanstack/react-query" import {Grid} from "@mui/material" -import {getAlias} from "~/apis" +import {deleteAlias, getAlias} from "~/apis" import {Alias, DecryptedAlias, ServerSettings} from "~/server-types" import { AliasTypeIndicator, AuthContext, DecryptionPasswordMissingAlert, + DeleteButton, EncryptionStatus, QueryResult, SimplePage, @@ -46,7 +47,18 @@ export default function AliasDetailRoute(): ReactElement { return ( } + actions={ + query.data && ( + deleteAlias(aliasID!)} + label={t("routes.AliasDetailRoute.actions.delete.label")} + description={t("routes.AliasDetailRoute.actions.delete.description")} + continueLabel={t("routes.AliasDetailRoute.actions.delete.continueAction")} + navigateTo={"/aliases"} + successMessage={t("relations.alias.mutations.success.aliasDeleted")} + /> + ) + } > query={query}> {alias => ( From f854ab8a1784eba23b88027ab16f033a5a267504 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 14 Feb 2023 21:04:42 +0100 Subject: [PATCH 08/21] fix: Add allowAliasDeletion to ServerSettings --- src/routes/AliasDetailRoute.tsx | 1 + src/server-types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/routes/AliasDetailRoute.tsx b/src/routes/AliasDetailRoute.tsx index 9110cea..ce85126 100644 --- a/src/routes/AliasDetailRoute.tsx +++ b/src/routes/AliasDetailRoute.tsx @@ -48,6 +48,7 @@ export default function AliasDetailRoute(): ReactElement { deleteAlias(aliasID!)} diff --git a/src/server-types.ts b/src/server-types.ts index 0b2a5df..f42683b 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -83,6 +83,7 @@ export interface ServerSettings { customAliasSuffixLength: number instanceSalt: string publicKey: string + allowAliasDeletion: boolean } export interface Alias { From 17673852dd468e516e3bc8c7ed1b22edefece20a Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:27:20 +0100 Subject: [PATCH 09/21] refactor!: Adapt api fetchers to upstream server --- src/apis/get-me.ts | 4 ++-- src/apis/refresh-token.ts | 7 +------ src/apis/update-account.ts | 11 +++-------- src/apis/verify-email.ts | 12 +++--------- src/apis/verify-login-with-email.ts | 9 +++------ 5 files changed, 12 insertions(+), 31 deletions(-) diff --git a/src/apis/get-me.ts b/src/apis/get-me.ts index c211f00..bee5c36 100644 --- a/src/apis/get-me.ts +++ b/src/apis/get-me.ts @@ -1,7 +1,7 @@ -import {AuthenticationDetails} from "~/server-types" +import {ServerUser} from "~/server-types" import {client} from "~/constants/axios-client" -export default async function getMe(): Promise { +export default async function getMe(): Promise { const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/account/me`, { withCredentials: true, }) diff --git a/src/apis/refresh-token.ts b/src/apis/refresh-token.ts index 8d05f55..55b5d09 100644 --- a/src/apis/refresh-token.ts +++ b/src/apis/refresh-token.ts @@ -1,14 +1,9 @@ import {ServerUser} from "~/server-types" import {client} from "~/constants/axios-client" -export interface RefreshTokenResult { - user: ServerUser - detail: string -} - export const REFRESH_TOKEN_URL = `${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/refresh` -export default async function refreshToken(): Promise { +export default async function refreshToken(): Promise { const {data} = await client.post( REFRESH_TOKEN_URL, {}, diff --git a/src/apis/update-account.ts b/src/apis/update-account.ts index 5961783..956c224 100644 --- a/src/apis/update-account.ts +++ b/src/apis/update-account.ts @@ -1,4 +1,4 @@ -import {AuthenticationDetails, Language} from "~/server-types" +import {Language, ServerUser} from "~/server-types" import {client} from "~/constants/axios-client" import parseUser from "~/apis/helpers/parse-user" @@ -9,9 +9,7 @@ export interface UpdateAccountData { language?: Language } -export default async function updateAccount( - updateData: UpdateAccountData, -): Promise { +export default async function updateAccount(updateData: UpdateAccountData): Promise { const {data} = await client.patch( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/account`, updateData, @@ -20,8 +18,5 @@ export default async function updateAccount( }, ) - return { - ...data, - user: parseUser(data.user), - } + return parseUser(data.user) } diff --git a/src/apis/verify-email.ts b/src/apis/verify-email.ts index 89ca3ad..e8b350a 100644 --- a/src/apis/verify-email.ts +++ b/src/apis/verify-email.ts @@ -1,4 +1,4 @@ -import {AuthenticationDetails} from "~/server-types" +import {ServerUser} from "~/server-types" import {client} from "~/constants/axios-client" import parseUser from "~/apis/helpers/parse-user" @@ -7,10 +7,7 @@ export interface VerifyEmailData { token: string } -export default async function verifyEmail({ - email, - token, -}: VerifyEmailData): Promise { +export default async function verifyEmail({email, token}: VerifyEmailData): Promise { const {data} = await client.post( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/verify-email`, { @@ -22,8 +19,5 @@ export default async function verifyEmail({ }, ) - return { - ...data, - user: parseUser(data.user), - } + return parseUser(data.user) } diff --git a/src/apis/verify-login-with-email.ts b/src/apis/verify-login-with-email.ts index 229204e..8388144 100644 --- a/src/apis/verify-login-with-email.ts +++ b/src/apis/verify-login-with-email.ts @@ -1,4 +1,4 @@ -import {AuthenticationDetails} from "~/server-types" +import {ServerUser} from "~/server-types" import {client} from "~/constants/axios-client" import parseUser from "~/apis/helpers/parse-user" @@ -12,7 +12,7 @@ export default async function verifyLoginWithEmail({ email, token, sameRequestToken, -}: VerifyLoginWithEmailData): Promise { +}: VerifyLoginWithEmailData): Promise { const {data} = await client.post( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/login/email-token/verify`, { @@ -25,8 +25,5 @@ export default async function verifyLoginWithEmail({ }, ) - return { - ...data, - user: parseUser(data.user), - } + return parseUser(data.user) } From 7120c2d55b06ab6b41e9ae18797a84f68b6c4cd4 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:37:27 +0100 Subject: [PATCH 10/21] fix: Adapt APIs to upstream APIs --- src/apis/update-account.ts | 4 ++-- src/apis/verify-email.ts | 4 ++-- src/apis/verify-login-with-email.ts | 4 ++-- src/components/AuthContext/use-user.ts | 10 +++++----- .../CompleteAccountRoute/PasswordForm.tsx | 8 +++----- .../ConfirmCodeForm/ConfirmCodeForm.tsx | 11 +++-------- .../LoginRoute/ConfirmFromDifferentDevice.tsx | 6 +++--- src/routes/VerifyEmailRoute.tsx | 15 ++++++--------- 8 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/apis/update-account.ts b/src/apis/update-account.ts index 956c224..efb5d2a 100644 --- a/src/apis/update-account.ts +++ b/src/apis/update-account.ts @@ -10,7 +10,7 @@ export interface UpdateAccountData { } export default async function updateAccount(updateData: UpdateAccountData): Promise { - const {data} = await client.patch( + const {data: user} = await client.patch( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/account`, updateData, { @@ -18,5 +18,5 @@ export default async function updateAccount(updateData: UpdateAccountData): Prom }, ) - return parseUser(data.user) + return parseUser(user) } diff --git a/src/apis/verify-email.ts b/src/apis/verify-email.ts index e8b350a..65e84a0 100644 --- a/src/apis/verify-email.ts +++ b/src/apis/verify-email.ts @@ -8,7 +8,7 @@ export interface VerifyEmailData { } export default async function verifyEmail({email, token}: VerifyEmailData): Promise { - const {data} = await client.post( + const {data: user} = await client.post( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/verify-email`, { email: email, @@ -19,5 +19,5 @@ export default async function verifyEmail({email, token}: VerifyEmailData): Prom }, ) - return parseUser(data.user) + return parseUser(user) } diff --git a/src/apis/verify-login-with-email.ts b/src/apis/verify-login-with-email.ts index 8388144..9ae6004 100644 --- a/src/apis/verify-login-with-email.ts +++ b/src/apis/verify-login-with-email.ts @@ -13,7 +13,7 @@ export default async function verifyLoginWithEmail({ token, sameRequestToken, }: VerifyLoginWithEmailData): Promise { - const {data} = await client.post( + const {data: user} = await client.post( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/login/email-token/verify`, { email, @@ -25,5 +25,5 @@ export default async function verifyLoginWithEmail({ }, ) - return parseUser(data.user) + return parseUser(user) } diff --git a/src/components/AuthContext/use-user.ts b/src/components/AuthContext/use-user.ts index b9e44c4..988cc82 100644 --- a/src/components/AuthContext/use-user.ts +++ b/src/components/AuthContext/use-user.ts @@ -3,8 +3,8 @@ import {AxiosError} from "axios" import {useMutation, useQuery} from "@tanstack/react-query" -import {REFRESH_TOKEN_URL, RefreshTokenResult, getMe, refreshToken} from "~/apis" -import {AuthenticationDetails, ServerUser, User} from "~/server-types" +import {REFRESH_TOKEN_URL, getMe, refreshToken} from "~/apis" +import {ServerUser, User} from "~/server-types" import {client} from "~/constants/axios-client" export interface UseAuthData { @@ -22,11 +22,11 @@ export default function useUser({ user, updateUser, }: UseAuthData) { - const {mutateAsync: refresh} = useMutation(refreshToken, { - onError: () => logout(), + const {mutateAsync: refresh} = useMutation(refreshToken, { + onError: logout, }) - useQuery(["get_me"], getMe, { + useQuery(["get_me"], getMe, { refetchOnWindowFocus: "always", refetchOnReconnect: "always", retry: 2, diff --git a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx index e8dcf11..6c90a8a 100644 --- a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx @@ -13,7 +13,7 @@ import {useMutation} from "@tanstack/react-query" import {AuthContext, PasswordField, SimpleForm} from "~/components" import {setupEncryptionForUser} from "~/utils" import {useExtensionHandler, useNavigateToNext, useSystemPreferredTheme, useUser} from "~/hooks" -import {AuthenticationDetails, ServerSettings} from "~/server-types" +import {ServerSettings, ServerUser} from "~/server-types" import {UpdateAccountData, updateAccount} from "~/apis" export interface PasswordFormProps { @@ -51,9 +51,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement const {_setEncryptionPassword, login} = useContext(AuthContext) - const {mutateAsync} = useMutation( - updateAccount, - ) + const {mutateAsync} = useMutation(updateAccount) const formik = useFormik
({ validationSchema: schema, initialValues: { @@ -83,7 +81,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement encryptedNotes, }, { - onSuccess: ({user: newUser}) => { + onSuccess: newUser => { login(newUser) _setEncryptionPassword(encryptionPassword) navigateToNext() diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx index 35aec05..cc89a1e 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx @@ -24,12 +24,7 @@ import { } from "@mui/material" import {LoadingButton} from "@mui/lab" -import { - AuthenticationDetails, - ServerSettings, - ServerUser, - SimpleDetailResponse, -} from "~/server-types" +import {ServerSettings, ServerUser, SimpleDetailResponse} from "~/server-types" import {VerifyLoginWithEmailData, verifyLoginWithEmail} from "~/apis" import {MultiStepFormElement} from "~/components" import {parseFastAPIError} from "~/utils" @@ -84,10 +79,10 @@ export default function ConfirmCodeForm({ .label(t("routes.LoginRoute.forms.confirmCode.form.code.label")), }) - const {mutateAsync} = useMutation( + const {mutateAsync} = useMutation( verifyLoginWithEmail, { - onSuccess: ({user}) => onConfirm(user), + onSuccess: onConfirm, }, ) const formik = useFormik({ diff --git a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx index 905171f..5f0181e 100644 --- a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx +++ b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx @@ -6,7 +6,7 @@ import {useTranslation} from "react-i18next" import {useMutation} from "@tanstack/react-query" import {Box, Grid, Paper, Typography} from "@mui/material" -import {AuthenticationDetails, ServerUser} from "~/server-types" +import {ServerUser} from "~/server-types" import {verifyLoginWithEmail} from "~/apis" import {LoadingData} from "~/components" @@ -23,14 +23,14 @@ export default function ConfirmFromDifferentDevice({ onConfirm, }: ConfirmFromDifferentDeviceProps): ReactElement { const {t} = useTranslation() - const {mutate, isLoading, isError} = useMutation( + const {mutate, isLoading, isError} = useMutation( () => verifyLoginWithEmail({ email, token, }), { - onSuccess: ({user}) => onConfirm(user), + onSuccess: onConfirm, }, ) diff --git a/src/routes/VerifyEmailRoute.tsx b/src/routes/VerifyEmailRoute.tsx index b5a8786..5641ecc 100644 --- a/src/routes/VerifyEmailRoute.tsx +++ b/src/routes/VerifyEmailRoute.tsx @@ -9,7 +9,7 @@ import React, {ReactElement, useContext} from "react" import {Grid, Paper, Typography, useTheme} from "@mui/material" import {useMutation} from "@tanstack/react-query" -import {AuthenticationDetails, ServerSettings} from "~/server-types" +import {ServerSettings, ServerUser} from "~/server-types" import {VerifyEmailData, verifyEmail} from "~/apis" import {useQueryParams} from "~/hooks" import {AuthContext} from "~/components" @@ -41,15 +41,12 @@ export default function VerifyEmailRoute(): ReactElement { return token.split("").every(char => chars.includes(char)) }) - const {mutateAsync} = useMutation( - verifyEmail, - { - onSuccess: ({user}) => { - login(user) - navigate("/auth/complete-account") - }, + const {mutateAsync} = useMutation(verifyEmail, { + onSuccess: user => { + login(user) + navigate("/auth/complete-account") }, - ) + }) const {loading} = useAsync(async () => { await emailSchema.validate(email) await tokenSchema.validate(token) From 0263b51cf8ba3a2cf52dff3f057d049e5d127927 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:37:37 +0100 Subject: [PATCH 11/21] fix: Use correct locale for en --- src/I18nHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/I18nHandler.tsx b/src/I18nHandler.tsx index 47058d2..8237ff3 100644 --- a/src/I18nHandler.tsx +++ b/src/I18nHandler.tsx @@ -2,7 +2,7 @@ import * as yup from "yup" import {useTranslation} from "react-i18next" import {useEffect} from "react" import {de} from "yup-locales" -import en from "yup/es/locale" +import en from "yup/lib/locale" const YUP_LOCALE_LANGUAGE_MAP: Record = { "en-US": en, From c5bedec8e9d49c444ebbb0370b53b9fb8720e37a Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 21:02:30 +0100 Subject: [PATCH 12/21] fix: Adapt API to upstream --- src/apis/get-admin-settings.ts | 9 ++++++++- src/apis/get-server-settings.ts | 9 ++++++++- src/apis/resend-email-login-code.ts | 7 +++++++ src/apis/update-admin-settings.ts | 7 +++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/apis/get-admin-settings.ts b/src/apis/get-admin-settings.ts index afa9427..6ce2f5e 100644 --- a/src/apis/get-admin-settings.ts +++ b/src/apis/get-admin-settings.ts @@ -1,7 +1,14 @@ import {client} from "~/constants/axios-client" import {AdminSettings} from "~/server-types" -export default async function getAdminSettings(): Promise> { +export type GetAdminSettingsResponse = + | Partial + | { + detail: string + code: "error:settings:global_settings_disabled" + } + +export default async function getAdminSettings(): Promise { const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`, { withCredentials: true, }) diff --git a/src/apis/get-server-settings.ts b/src/apis/get-server-settings.ts index b6bb6f1..55b8045 100644 --- a/src/apis/get-server-settings.ts +++ b/src/apis/get-server-settings.ts @@ -1,6 +1,13 @@ import {ServerSettings} from "~/server-types" import {client} from "~/constants/axios-client" -export default async function getServerSettings(): Promise { +export type GetServerSettingsResponse = + | ServerSettings + | { + detail: string + code: "error:settings:statistics_disabled" + } + +export default async function getServerSettings(): Promise { return (await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/server/settings`)).data } diff --git a/src/apis/resend-email-login-code.ts b/src/apis/resend-email-login-code.ts index 97381ff..efef26b 100644 --- a/src/apis/resend-email-login-code.ts +++ b/src/apis/resend-email-login-code.ts @@ -6,6 +6,13 @@ export interface ResendEmailLoginCodeData { sameRequestToken: string } +export type ResendEmailLoginCodeResponse = + | SimpleDetailResponse + | { + detail: string + code: "ok:email_already_verified" + } + export default async function resendEmailLoginCode({ email, sameRequestToken, diff --git a/src/apis/update-admin-settings.ts b/src/apis/update-admin-settings.ts index 5be9fd9..b7d2abe 100644 --- a/src/apis/update-admin-settings.ts +++ b/src/apis/update-admin-settings.ts @@ -1,6 +1,13 @@ import {client} from "~/constants/axios-client" import {AdminSettings} from "~/server-types" +export type UpdateAdminSettingsResponse = + | Partial + | { + detail: string + code: "error:settings:global_settings_disabled" + } + export default async function updateAdminSettings( settings: Partial, ): Promise { From 8a2de63b6a63b9bdc00ba663971a72b476fad003 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 21:03:20 +0100 Subject: [PATCH 13/21] fix: Remove wrong api adaption --- src/apis/get-server-settings.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/apis/get-server-settings.ts b/src/apis/get-server-settings.ts index 55b8045..b6bb6f1 100644 --- a/src/apis/get-server-settings.ts +++ b/src/apis/get-server-settings.ts @@ -1,13 +1,6 @@ import {ServerSettings} from "~/server-types" import {client} from "~/constants/axios-client" -export type GetServerSettingsResponse = - | ServerSettings - | { - detail: string - code: "error:settings:statistics_disabled" - } - -export default async function getServerSettings(): Promise { +export default async function getServerSettings(): Promise { return (await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/server/settings`)).data } From 646a646eb0a7a58e0ad695646ab91a1e01ea4564 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:21:04 +0100 Subject: [PATCH 14/21] fix!: Fix QueryResult.tsx to allow "null" as a query data --- src/components/widgets/QueryResult.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/widgets/QueryResult.tsx b/src/components/widgets/QueryResult.tsx index b58593f..402b064 100644 --- a/src/components/widgets/QueryResult.tsx +++ b/src/components/widgets/QueryResult.tsx @@ -17,7 +17,7 @@ export default function QueryResult({ query, children: render, }: QueryResultProps): ReactElement { - if (query.data) { + if (query.data !== undefined) { return render(query.data) } From a367e93c55207210dfbd6fab1fd86197f68ddc72 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:21:27 +0100 Subject: [PATCH 15/21] fix: Update API to upstream --- public/locales/en-US/translation.json | 6 ++- src/apis/get-admin-settings.ts | 3 +- src/apis/resend-email-login-code.ts | 9 +---- src/apis/resend-email-verification-code.ts | 8 +++- src/apis/update-admin-settings.ts | 5 +-- .../GlobalSettingsRoute/SettingsDisabled.tsx | 38 +++++++++++++++++++ .../GlobalSettingsRoute/SettingsForm.tsx | 16 ++++++-- .../ConfirmCodeForm/ResendMailButton.tsx | 6 +-- .../YouGotMail/ResendMailButton.tsx | 21 +++++++--- .../SignupRoute/YouGotMail/YouGotMail.tsx | 2 +- 10 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 2f4e4b0..a757df4 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -407,7 +407,11 @@ "helperText": "This is just a preview. Those are not real aliases." }, "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", - "resetLabel": "Reset to defaults" + "resetLabel": "Reset to defaults", + "disabled": { + "title": "Global settings are disabled", + "description": "Global settings have been disabled. You can enable them in the configuration file." + } } } }, diff --git a/src/apis/get-admin-settings.ts b/src/apis/get-admin-settings.ts index 6ce2f5e..8754f03 100644 --- a/src/apis/get-admin-settings.ts +++ b/src/apis/get-admin-settings.ts @@ -2,8 +2,7 @@ import {client} from "~/constants/axios-client" import {AdminSettings} from "~/server-types" export type GetAdminSettingsResponse = - | Partial - | { + | Partial & { detail: string code: "error:settings:global_settings_disabled" } diff --git a/src/apis/resend-email-login-code.ts b/src/apis/resend-email-login-code.ts index efef26b..28ff7f3 100644 --- a/src/apis/resend-email-login-code.ts +++ b/src/apis/resend-email-login-code.ts @@ -1,18 +1,11 @@ -import {SimpleDetailResponse} from "~/server-types" import {client} from "~/constants/axios-client" +import {SimpleDetailResponse} from "~/server-types" export interface ResendEmailLoginCodeData { email: string sameRequestToken: string } -export type ResendEmailLoginCodeResponse = - | SimpleDetailResponse - | { - detail: string - code: "ok:email_already_verified" - } - export default async function resendEmailLoginCode({ email, sameRequestToken, diff --git a/src/apis/resend-email-verification-code.ts b/src/apis/resend-email-verification-code.ts index 67f90ae..a46cc4d 100644 --- a/src/apis/resend-email-verification-code.ts +++ b/src/apis/resend-email-verification-code.ts @@ -1,9 +1,15 @@ import {SimpleDetailResponse} from "~/server-types" import {client} from "~/constants/axios-client" +export type ResendEmailVerificationCodeResponse = + | SimpleDetailResponse & { + detail: string + code: "ok:email_already_verified" + } + export default async function resendEmailVerificationCode( email: string, -): Promise { +): Promise { const {data} = await client.post( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/resend-email`, { diff --git a/src/apis/update-admin-settings.ts b/src/apis/update-admin-settings.ts index b7d2abe..e92d99d 100644 --- a/src/apis/update-admin-settings.ts +++ b/src/apis/update-admin-settings.ts @@ -2,15 +2,14 @@ import {client} from "~/constants/axios-client" import {AdminSettings} from "~/server-types" export type UpdateAdminSettingsResponse = - | Partial - | { + | Partial & { detail: string code: "error:settings:global_settings_disabled" } export default async function updateAdminSettings( settings: Partial, -): Promise { +): Promise { const {data} = await client.patch( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`, settings, diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx new file mode 100644 index 0000000..79a15e3 --- /dev/null +++ b/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx @@ -0,0 +1,38 @@ +import {RiAlertFill} from "react-icons/ri" +import {ReactElement} from "react" +import {useTranslation} from "react-i18next" + +import {Container, Grid, Typography} from "@mui/material" + +export default function SettingsDisabled(): ReactElement { + console.log("asdas") + const {t} = useTranslation() + + return ( + + + + + {t("routes.AdminRoute.settings.disabled.title")} + + + + + + + + {t("routes.AdminRoute.settings.disabled.description")} + + + + + ) +} diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx index e38c6fc..87b542f 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx @@ -21,7 +21,7 @@ import {useMutation} from "@tanstack/react-query" import {AdminSettings} from "~/server-types" import {StringPoolField, createPool} from "~/components" -import {updateAdminSettings} from "~/apis" +import {UpdateAdminSettingsResponse, updateAdminSettings} from "~/apis" import {useErrorSuccessSnacks} from "~/hooks" import {queryClient} from "~/constants/react-query" import {parseFastAPIError} from "~/utils" @@ -85,7 +85,11 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { .label(t("routes.AdminRoute.forms.settings.allowStatistics.label")), }) - const {mutateAsync} = useMutation>( + const {mutateAsync} = useMutation< + UpdateAdminSettingsResponse, + AxiosError, + Partial + >( async settings => { // Set values to `null` that are their defaults const strippedSettings = Object.fromEntries( @@ -102,10 +106,14 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { }, { onError: showError, - onSuccess: newSettings => { + onSuccess: ({code, detail, ...newSettings}) => { + if (code === "error:settings:global_settings_disabled") { + return + } + showSuccess(t("routes.AdminRoute.settings.successMessage")) - queryClient.setQueryData(queryKey, newSettings) + queryClient.setQueryData>(queryKey, newSettings) }, }, ) diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx index 3d52a46..06e3c13 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx @@ -6,9 +6,9 @@ import React, {ReactElement} from "react" import {useMutation} from "@tanstack/react-query" -import {resendEmailLoginCode} from "~/apis" +import {ResendEmailLoginCodeResponse, resendEmailLoginCode} from "~/apis" import {MutationStatusSnackbar, TimedButton} from "~/components" -import {ServerSettings, SimpleDetailResponse} from "~/server-types" +import {ServerSettings} from "~/server-types" export interface ResendMailButtonProps { email: string @@ -22,7 +22,7 @@ export default function ResendMailButton({ const settings = useLoaderData() as ServerSettings const {t} = useTranslation() - const mutation = useMutation(() => + const mutation = useMutation(() => resendEmailLoginCode({ email, sameRequestToken, diff --git a/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx b/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx index b049c9e..a6426dc 100644 --- a/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx +++ b/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx @@ -6,20 +6,31 @@ import React, {ReactElement} from "react" import {useMutation} from "@tanstack/react-query" -import {resendEmailVerificationCode} from "~/apis" +import {ResendEmailVerificationCodeResponse, resendEmailVerificationCode} from "~/apis" import {MutationStatusSnackbar, TimedButton} from "~/components" -import {ServerSettings, SimpleDetailResponse} from "~/server-types" +import {ServerSettings} from "~/server-types" export interface ResendMailButtonProps { email: string + onEmailAlreadyVerified: () => void } -export default function ResendMailButton({email}: ResendMailButtonProps): ReactElement { +export default function ResendMailButton({ + email, + onEmailAlreadyVerified, +}: ResendMailButtonProps): ReactElement { const {t} = useTranslation() const settings = useLoaderData() as ServerSettings - const mutation = useMutation(() => - resendEmailVerificationCode(email), + const mutation = useMutation( + () => resendEmailVerificationCode(email), + { + onSuccess: ({code}: any) => { + if (code === "ok:email_already_verified") { + onEmailAlreadyVerified() + } + }, + }, ) const {mutate} = mutation diff --git a/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx b/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx index 5fa50bd..c01de46 100644 --- a/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx +++ b/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx @@ -67,7 +67,7 @@ export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactEle - + From 9c85cc13ac60751d191642e463f226e481c2828e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:21:38 +0100 Subject: [PATCH 16/21] feat: Show message when global settings are disabled --- src/routes/GlobalSettingsRoute.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/routes/GlobalSettingsRoute.tsx b/src/routes/GlobalSettingsRoute.tsx index db94e40..59cfbbc 100644 --- a/src/routes/GlobalSettingsRoute.tsx +++ b/src/routes/GlobalSettingsRoute.tsx @@ -8,21 +8,32 @@ import {AdminSettings} from "~/server-types" import {getAdminSettings} from "~/apis" import {QueryResult} from "~/components" import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings" +import SettingsDisabled from "~/route-widgets/GlobalSettingsRoute/SettingsDisabled" import SettingsForm from "~/route-widgets/GlobalSettingsRoute/SettingsForm" export default function GlobalSettingsRoute(): ReactElement { const queryKey = ["get_admin_settings"] - const query = useQuery(queryKey, async () => { - const settings = await getAdminSettings() + const query = useQuery(queryKey, async () => { + const {code, detail, ...settings} = await getAdminSettings() - return _.mergeWith({}, DEFAULT_ADMIN_SETTINGS, settings, (o, s) => - _.isNull(s) ? o : s, - ) as AdminSettings + if (code === "error:settings:global_settings_disabled") { + return null + } else { + return _.mergeWith({}, DEFAULT_ADMIN_SETTINGS, settings, (o, s) => + _.isNull(s) ? o : s, + ) as AdminSettings + } }) return ( - query={query}> - {settings => } + query={query}> + {settings => + settings === null ? ( + + ) : ( + + ) + } ) } From 5e979f70c78a67002f605704df20513c61c2c1d0 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:39:20 +0100 Subject: [PATCH 17/21] feat: Show generic error when no custom error specified --- src/hooks/use-error-success-snacks.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/hooks/use-error-success-snacks.ts b/src/hooks/use-error-success-snacks.ts index 9640ed4..009b244 100644 --- a/src/hooks/use-error-success-snacks.ts +++ b/src/hooks/use-error-success-snacks.ts @@ -28,17 +28,18 @@ export default function useErrorSuccessSnacks(): UseErrorSuccessSnacksResult { }) } const showError = (error: Error) => { - const parsedError = parseFastAPIError(error as AxiosError) + let message - if ("detail" in parsedError) { - $errorSnackbarKey.current = enqueueSnackbar( - parsedError.detail || t("general.defaultError"), - { - variant: "error", - autoHideDuration: ERROR_SNACKBAR_SHOW_DURATION, - }, - ) - } + try { + const parsedError = parseFastAPIError(error as AxiosError) + + message = parsedError.detail + } catch (e) {} + + $errorSnackbarKey.current = enqueueSnackbar(message || t("general.defaultError"), { + variant: "error", + autoHideDuration: ERROR_SNACKBAR_SHOW_DURATION, + }) } return { From 6f870f730222d488ee47540dcb62ea3d1abc1f19 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Thu, 16 Feb 2023 22:40:10 +0100 Subject: [PATCH 18/21] feat: Add delete button to reserved aliases --- public/locales/en-US/translation.json | 9 +++++++++ src/apis/delete-reserved-alias.ts | 13 +++++++++++++ src/apis/index.ts | 2 ++ src/routes/ReservedAliasDetailRoute.tsx | 24 +++++++++++++++++++++--- 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/apis/delete-reserved-alias.ts diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index a757df4..6e65679 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -412,6 +412,15 @@ "title": "Global settings are disabled", "description": "Global settings have been disabled. You can enable them in the configuration file." } + }, + "reservedAlias": { + "actions": { + "delete": { + "label": "Delete Reserved Alias", + "description": "Are you sure you want to delete this reserved alias?", + "continueAction": "Delete Reserved Alias" + } + } } } }, diff --git a/src/apis/delete-reserved-alias.ts b/src/apis/delete-reserved-alias.ts new file mode 100644 index 0000000..f3b019b --- /dev/null +++ b/src/apis/delete-reserved-alias.ts @@ -0,0 +1,13 @@ +import {client} from "~/constants/axios-client" +import {SimpleDetailResponse} from "~/server-types" + +export default async function deleteReservedAlias(id: string): Promise { + const {data} = await client.delete( + `${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias/${id}`, + { + withCredentials: true, + }, + ) + + return data +} diff --git a/src/apis/index.ts b/src/apis/index.ts index 5f10655..d976efd 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -54,3 +54,5 @@ export * from "./update-admin-settings" export {default as updateAdminSettings} from "./update-admin-settings" export * from "./delete-alias" export {default as deleteAlias} from "./delete-alias" +export * from "./delete-reserved-alias" +export {default as deleteReservedAlias} from "./delete-reserved-alias" diff --git a/src/routes/ReservedAliasDetailRoute.tsx b/src/routes/ReservedAliasDetailRoute.tsx index b3c9eac..74cbdbb 100644 --- a/src/routes/ReservedAliasDetailRoute.tsx +++ b/src/routes/ReservedAliasDetailRoute.tsx @@ -6,9 +6,9 @@ import {AxiosError} from "axios" import {useQuery} from "@tanstack/react-query" import {Grid} from "@mui/material" -import {QueryResult, SimplePage, SimplePageBuilder} from "~/components" +import {DeleteButton, QueryResult, SimplePage, SimplePageBuilder} from "~/components" import {ReservedAlias} from "~/server-types" -import {getReservedAlias} from "~/apis" +import {deleteReservedAlias, getReservedAlias} from "~/apis" import AliasActivationSwitch from "~/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch" import AliasAddress from "~/route-widgets/AliasDetailRoute/AliasAddress" import AliasUsersList from "~/route-widgets/ReservedAliasDetailRoute/AliasUsersList" @@ -21,7 +21,25 @@ export default function ReservedAliasDetailRoute(): ReactElement { const query = useQuery(queryKey, () => getReservedAlias(params.id!)) return ( - + deleteReservedAlias(params.id!)} + label={t("routes.AdminRoute.reservedAlias.actions.delete.label")} + description={t( + "routes.adminRoute.reservedAlias.actions.delete.description", + )} + continueLabel={t( + "routes.AdminRoute.reservedAlias.actions.delete.continueAction", + )} + navigateTo="/admin/reserved-aliases" + successMessage={t("relations.alias.mutations.success.aliasDeleted")} + /> + ) + } + > query={query}> {alias => ( From 3b130d054f860dca06b09d1a4b8ada55e1eca186 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 18 Feb 2023 11:23:23 +0100 Subject: [PATCH 19/21] feat: Add get cron report --- src/apis/get-latest-cron-report.ts | 13 +++++++++++++ src/apis/index.ts | 2 ++ src/server-types.ts | 8 ++++++++ 3 files changed, 23 insertions(+) create mode 100644 src/apis/get-latest-cron-report.ts diff --git a/src/apis/get-latest-cron-report.ts b/src/apis/get-latest-cron-report.ts new file mode 100644 index 0000000..b3ad51e --- /dev/null +++ b/src/apis/get-latest-cron-report.ts @@ -0,0 +1,13 @@ +import {CronReport} from "~/server-types" +import {client} from "~/constants/axios-client" + +export default async function getLatestCronReport(): Promise { + const {data} = await client.get( + `${import.meta.env.VITE_SERVER_BASE_URL}/v1/cron-report/latest/`, + { + withCredentials: true, + }, + ) + + return data +} diff --git a/src/apis/index.ts b/src/apis/index.ts index d976efd..fbb1e32 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -56,3 +56,5 @@ export * from "./delete-alias" export {default as deleteAlias} from "./delete-alias" export * from "./delete-reserved-alias" export {default as deleteReservedAlias} from "./delete-reserved-alias" +export * from "./get-latest-cron-report" +export {default as getLatestCronReport} from "./get-latest-cron-report" diff --git a/src/server-types.ts b/src/server-types.ts index f42683b..e8128fe 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -213,3 +213,11 @@ export interface AdminSettings { allowStatistics: boolean | null allowAliasDeletion: boolean | null } + +export interface CronReport { + id: string + createdAt: Date + reportData: { + encryptedReport: string + } +} From e07eb3e75dec8bed83cf58eb0f78018292554cb8 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 18 Feb 2023 12:53:28 +0100 Subject: [PATCH 20/21] feat: Add cron report status to AdminRoute --- public/locales/en-US/translation.json | 5 ++ src/App.tsx | 1 + src/apis/get-latest-cron-report.ts | 6 +- src/apis/helpers/decrypt-cron-report-data.ts | 33 ++++++++ .../AdminPage/ReservedAliasesList.tsx | 32 -------- src/route-widgets/AdminRoute/ServerStatus.tsx | 77 +++++++++++++++++++ src/routes/AdminRoute.tsx | 4 +- src/server-types.ts | 18 ++++- 8 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 src/apis/helpers/decrypt-cron-report-data.ts delete mode 100644 src/route-widgets/AdminPage/ReservedAliasesList.tsx create mode 100644 src/route-widgets/AdminRoute/ServerStatus.tsx diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 6e65679..0f99c43 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -421,6 +421,11 @@ "continueAction": "Delete Reserved Alias" } } + }, + "serverStatus": { + "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", + "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", + "success": "Everything okay with your server! The last cleanup job was {{relativeDescription}}." } } }, diff --git a/src/App.tsx b/src/App.tsx index cf21964..83d9842 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -106,6 +106,7 @@ const router = createBrowserRouter([ }, { path: "/admin", + loader: getServerSettings, element: , }, { diff --git a/src/apis/get-latest-cron-report.ts b/src/apis/get-latest-cron-report.ts index b3ad51e..17e8786 100644 --- a/src/apis/get-latest-cron-report.ts +++ b/src/apis/get-latest-cron-report.ts @@ -1,9 +1,9 @@ -import {CronReport} from "~/server-types" +import {ServerCronReport} from "~/server-types" import {client} from "~/constants/axios-client" -export default async function getLatestCronReport(): Promise { +export default async function getLatestCronReport(): Promise { const {data} = await client.get( - `${import.meta.env.VITE_SERVER_BASE_URL}/v1/cron-report/latest/`, + `${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/cron-report/latest/`, { withCredentials: true, }, diff --git a/src/apis/helpers/decrypt-cron-report-data.ts b/src/apis/helpers/decrypt-cron-report-data.ts new file mode 100644 index 0000000..d03c0fc --- /dev/null +++ b/src/apis/helpers/decrypt-cron-report-data.ts @@ -0,0 +1,33 @@ +import camelcaseKeys from "camelcase-keys" +import update from "immutability-helper" + +import {AuthContextType} from "~/components" +import {CronReport} from "~/server-types" +import {extractCleartextFromSignedMessage} from "~/utils" + +export default async function decryptCronReportData( + signedMessage: string, + decryptContent: AuthContextType["_decryptUsingPrivateKey"], + publicKeyInPEM: string, +): Promise { + const encryptedMessage = await extractCleartextFromSignedMessage(signedMessage, publicKeyInPEM) + + return update( + camelcaseKeys( + JSON.parse(await decryptContent(encryptedMessage)) as CronReport["reportData"], + { + deep: true, + }, + ), + { + report: { + startedAt: { + $apply: startedAt => new Date(startedAt), + }, + finishedAt: { + $apply: finishedAt => new Date(finishedAt), + }, + }, + }, + ) +} diff --git a/src/route-widgets/AdminPage/ReservedAliasesList.tsx b/src/route-widgets/AdminPage/ReservedAliasesList.tsx deleted file mode 100644 index b856845..0000000 --- a/src/route-widgets/AdminPage/ReservedAliasesList.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import {ReactElement} from "react" -import {AxiosError} from "axios" - -import {useQuery} from "@tanstack/react-query" -import {List, ListItem, ListItemText} from "@mui/material" - -import {getReservedAliases} from "~/apis" -import {PaginationResult, ReservedAlias} from "~/server-types" -import {QueryResult} from "~/components" - -export interface ReservedAliasesListProps {} - -export default function ReservedAliasesList({}: ReservedAliasesListProps): ReactElement { - const query = useQuery, AxiosError>( - ["getReservedAliases"], - () => getReservedAliases(), - ) - - return ( - , AxiosError> query={query}> - {({items}) => ( - - {items.map(alias => ( - - - - ))} - - )} - - ) -} diff --git a/src/route-widgets/AdminRoute/ServerStatus.tsx b/src/route-widgets/AdminRoute/ServerStatus.tsx new file mode 100644 index 0000000..8ed9c7f --- /dev/null +++ b/src/route-widgets/AdminRoute/ServerStatus.tsx @@ -0,0 +1,77 @@ +import {ReactElement, useContext} from "react" +import {AxiosError} from "axios" +import {useLoaderData} from "react-router-dom" +import {useTranslation} from "react-i18next" +import format from "date-fns/format" +import subDays from "date-fns/subDays" + +import {useQuery} from "@tanstack/react-query" +import {Alert} from "@mui/material" + +import {CronReport, ServerSettings} from "~/server-types" +import {getLatestCronReport} from "~/apis" +import {AuthContext, QueryResult} from "~/components" +import decryptCronReportData from "~/apis/helpers/decrypt-cron-report-data" +import formatRelative from "date-fns/formatRelative" + +const MAX_REPORT_DAY_THRESHOLD = 5 + +export default function ServerStatus(): ReactElement { + const serverSettings = useLoaderData() as ServerSettings + const {t} = useTranslation() + const {_decryptUsingPrivateKey} = useContext(AuthContext) + + const query = useQuery(["get_latest_cron_report"], async () => { + const encryptedReport = await getLatestCronReport() + + ;(encryptedReport as any as CronReport).reportData = await decryptCronReportData( + encryptedReport.reportData.encryptedReport, + _decryptUsingPrivateKey, + serverSettings.publicKey, + ) + + return encryptedReport as any as CronReport + }) + + return ( + query={query}> + {report => { + const thresholdDate = subDays(new Date(), MAX_REPORT_DAY_THRESHOLD) + + if (report.createdAt < thresholdDate) { + return ( + + {t("routes.AdminRoute.serverStatus.noRecentReports", { + date: format(new Date(report.createdAt), "Pp"), + })} + + ) + } + + if (report.reportData.report.status === "error") { + return ( + + {t("routes.AdminRoute.serverStatus.error", { + relativeDescription: formatRelative( + new Date(report.createdAt), + new Date(), + ), + })} + + ) + } + + return ( + + {t("routes.AdminRoute.serverStatus.success", { + relativeDescription: formatRelative( + new Date(report.createdAt), + new Date(), + ), + })} + + ) + }} + + ) +} diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx index 37ce762..994dc15 100644 --- a/src/routes/AdminRoute.tsx +++ b/src/routes/AdminRoute.tsx @@ -8,14 +8,13 @@ import {List, ListItemButton, ListItemIcon, ListItemText} from "@mui/material" import {SimplePageBuilder} from "~/components" import {useNavigateToNext, useUser} from "~/hooks" +import ServerStatus from "~/route-widgets/AdminRoute/ServerStatus" export default function AdminRoute(): ReactElement { const {t} = useTranslation() const navigateToNext = useNavigateToNext() const user = useUser() - console.log(user) - useLayoutEffect(() => { if (!user.isAdmin) { navigateToNext() @@ -24,6 +23,7 @@ export default function AdminRoute(): ReactElement { return ( + diff --git a/src/server-types.ts b/src/server-types.ts index e8128fe..a51acbe 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -214,10 +214,26 @@ export interface AdminSettings { allowAliasDeletion: boolean | null } -export interface CronReport { +export interface ServerCronReport { id: string createdAt: Date reportData: { encryptedReport: string } } + +export interface CronReport { + id: string + createdAt: Date + reportData: { + version: "1.0" + id: string + report: { + startedAt: Date + finishedAt: Date + status: "success" | "error" + expiredImages: number + nonVerifiedUsers: number + } + } +} From f838d85470e2a163c31935cd279081bb7af06f90 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 18 Feb 2023 13:08:50 +0100 Subject: [PATCH 21/21] fix: Update server-types.ts --- src/server-types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server-types.ts b/src/server-types.ts index a51acbe..45192f4 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -234,6 +234,7 @@ export interface CronReport { status: "success" | "error" expiredImages: number nonVerifiedUsers: number + expiredReports: number } } }