mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-22 17:20:29 +02:00
commit
63605bf5da
@ -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": {
|
||||
@ -202,6 +203,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"delete": {
|
||||
"label": "Delete Alias",
|
||||
"description": "Are you sure you want to delete this alias?",
|
||||
"continueAction": "Delete Alias"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ReportsRoute": {
|
||||
@ -383,6 +391,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."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -395,7 +407,25 @@
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"reservedAlias": {
|
||||
"actions": {
|
||||
"delete": {
|
||||
"label": "Delete Reserved Alias",
|
||||
"description": "Are you sure you want to delete this reserved alias?",
|
||||
"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}}."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -501,7 +531,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": {
|
||||
|
@ -81,7 +81,8 @@ const router = createBrowserRouter([
|
||||
element: <AliasesRoute />,
|
||||
},
|
||||
{
|
||||
path: "/aliases/:addressInBase64",
|
||||
path: "/aliases/:id",
|
||||
loader: getServerSettings,
|
||||
element: <AliasDetailRoute />,
|
||||
},
|
||||
{
|
||||
@ -105,6 +106,7 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
loader: getServerSettings,
|
||||
element: <AdminRoute />,
|
||||
},
|
||||
{
|
||||
|
@ -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<string, unknown> = {
|
||||
"en-US": en,
|
||||
|
10
src/apis/delete-alias.ts
Normal file
10
src/apis/delete-alias.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
|
||||
export default async function deleteAlias(id: string): Promise<SimpleDetailResponse> {
|
||||
const {data} = await client.delete(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/alias/${id}`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
13
src/apis/delete-reserved-alias.ts
Normal file
13
src/apis/delete-reserved-alias.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
|
||||
export default async function deleteReservedAlias(id: string): Promise<SimpleDetailResponse> {
|
||||
const {data} = await client.delete(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias/${id}`,
|
||||
{
|
||||
withCredentials: true,
|
||||
},
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {AdminSettings} from "~/server-types"
|
||||
|
||||
export default async function getAdminSettings(): Promise<Partial<AdminSettings>> {
|
||||
export type GetAdminSettingsResponse =
|
||||
| Partial<AdminSettings> & {
|
||||
detail: string
|
||||
code: "error:settings:global_settings_disabled"
|
||||
}
|
||||
|
||||
export default async function getAdminSettings(): Promise<GetAdminSettingsResponse> {
|
||||
const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {Alias} from "~/server-types"
|
||||
|
||||
export default async function getAlias(address: string): Promise<Alias> {
|
||||
const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/alias/${address}`, {
|
||||
export default async function getAlias(aliasID: string): Promise<Alias> {
|
||||
const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/alias/${aliasID}`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
|
13
src/apis/get-latest-cron-report.ts
Normal file
13
src/apis/get-latest-cron-report.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {ServerCronReport} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
|
||||
export default async function getLatestCronReport(): Promise<ServerCronReport> {
|
||||
const {data} = await client.get(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/cron-report/latest/`,
|
||||
{
|
||||
withCredentials: true,
|
||||
},
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
@ -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<AuthenticationDetails> {
|
||||
export default async function getMe(): Promise<ServerUser> {
|
||||
const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/account/me`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
|
33
src/apis/helpers/decrypt-cron-report-data.ts
Normal file
33
src/apis/helpers/decrypt-cron-report-data.ts
Normal file
@ -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<CronReport["reportData"]> {
|
||||
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),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
@ -52,3 +52,9 @@ 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"
|
||||
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"
|
||||
|
@ -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<RefreshTokenResult> {
|
||||
export default async function refreshToken(): Promise<ServerUser> {
|
||||
const {data} = await client.post(
|
||||
REFRESH_TOKEN_URL,
|
||||
{},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
|
||||
export interface ResendEmailLoginCodeData {
|
||||
email: string
|
||||
|
@ -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<SimpleDetailResponse> {
|
||||
): Promise<ResendEmailVerificationCodeResponse> {
|
||||
const {data} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/resend-email`,
|
||||
{
|
||||
|
@ -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,10 +9,8 @@ export interface UpdateAccountData {
|
||||
language?: Language
|
||||
}
|
||||
|
||||
export default async function updateAccount(
|
||||
updateData: UpdateAccountData,
|
||||
): Promise<AuthenticationDetails> {
|
||||
const {data} = await client.patch(
|
||||
export default async function updateAccount(updateData: UpdateAccountData): Promise<ServerUser> {
|
||||
const {data: user} = 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(user)
|
||||
}
|
||||
|
@ -1,9 +1,15 @@
|
||||
import {client} from "~/constants/axios-client"
|
||||
import {AdminSettings} from "~/server-types"
|
||||
|
||||
export type UpdateAdminSettingsResponse =
|
||||
| Partial<AdminSettings> & {
|
||||
detail: string
|
||||
code: "error:settings:global_settings_disabled"
|
||||
}
|
||||
|
||||
export default async function updateAdminSettings(
|
||||
settings: Partial<AdminSettings>,
|
||||
): Promise<AdminSettings> {
|
||||
): Promise<UpdateAdminSettingsResponse> {
|
||||
const {data} = await client.patch(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`,
|
||||
settings,
|
||||
|
@ -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,11 +7,8 @@ export interface VerifyEmailData {
|
||||
token: string
|
||||
}
|
||||
|
||||
export default async function verifyEmail({
|
||||
email,
|
||||
token,
|
||||
}: VerifyEmailData): Promise<AuthenticationDetails> {
|
||||
const {data} = await client.post(
|
||||
export default async function verifyEmail({email, token}: VerifyEmailData): Promise<ServerUser> {
|
||||
const {data: user} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/verify-email`,
|
||||
{
|
||||
email: email,
|
||||
@ -22,8 +19,5 @@ export default async function verifyEmail({
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
...data,
|
||||
user: parseUser(data.user),
|
||||
}
|
||||
return parseUser(user)
|
||||
}
|
||||
|
@ -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,8 +12,8 @@ export default async function verifyLoginWithEmail({
|
||||
email,
|
||||
token,
|
||||
sameRequestToken,
|
||||
}: VerifyLoginWithEmailData): Promise<AuthenticationDetails> {
|
||||
const {data} = await client.post(
|
||||
}: VerifyLoginWithEmailData): Promise<ServerUser> {
|
||||
const {data: user} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/auth/login/email-token/verify`,
|
||||
{
|
||||
email,
|
||||
@ -25,8 +25,5 @@ export default async function verifyLoginWithEmail({
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
...data,
|
||||
user: parseUser(data.user),
|
||||
}
|
||||
return parseUser(user)
|
||||
}
|
||||
|
@ -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<RefreshTokenResult, AxiosError, void>(refreshToken, {
|
||||
onError: () => logout(),
|
||||
const {mutateAsync: refresh} = useMutation<ServerUser, AxiosError, void>(refreshToken, {
|
||||
onError: logout,
|
||||
})
|
||||
|
||||
useQuery<AuthenticationDetails, AxiosError>(["get_me"], getMe, {
|
||||
useQuery<ServerUser, AxiosError>(["get_me"], getMe, {
|
||||
refetchOnWindowFocus: "always",
|
||||
refetchOnReconnect: "always",
|
||||
retry: 2,
|
||||
|
@ -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<any>
|
||||
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<SimpleDetailResponse, AxiosError, void>(() => deleteReport(id), {
|
||||
const {mutate} = useMutation<void, AxiosError, void>(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={<MdDelete />}
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
>
|
||||
{t("routes.ReportDetailRoute.actions.delete.label")}
|
||||
{label}
|
||||
</Button>
|
||||
<Dialog open={showDeleteDialog} onClose={() => setShowDeleteDialog(false)}>
|
||||
<DialogTitle>{t("routes.ReportDetailRoute.actions.delete.label")}</DialogTitle>
|
||||
<DialogTitle>{label}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{t("routes.ReportDetailRoute.actions.delete.description")}
|
||||
</DialogContentText>
|
||||
{description && <DialogContentText>{description}</DialogContentText>}
|
||||
<DialogContentText color="error">
|
||||
{t("general.actionNotUndoable")}
|
||||
</DialogContentText>
|
||||
@ -69,7 +78,7 @@ export default function ReportDetailRoute({id}: DeleteButtonProps): ReactElement
|
||||
color="error"
|
||||
onClick={() => mutate()}
|
||||
>
|
||||
{t("routes.ReportDetailRoute.actions.delete.continueAction")}
|
||||
{continueLabel}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
@ -17,7 +17,7 @@ export default function QueryResult<TQueryFnData, TError = AxiosError>({
|
||||
query,
|
||||
children: render,
|
||||
}: QueryResultProps<TQueryFnData, TError>): ReactElement {
|
||||
if (query.data) {
|
||||
if (query.data !== undefined) {
|
||||
return render(query.data)
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -11,4 +11,5 @@ export const DEFAULT_ADMIN_SETTINGS: AdminSettings = {
|
||||
imageProxyStorageLifeTimeInHours: 24,
|
||||
enableImageProxy: true,
|
||||
allowStatistics: true,
|
||||
allowAliasDeletion: false,
|
||||
}
|
||||
|
@ -28,17 +28,18 @@ export default function useErrorSuccessSnacks(): UseErrorSuccessSnacksResult {
|
||||
})
|
||||
}
|
||||
const showError = (error: Error) => {
|
||||
let message
|
||||
|
||||
try {
|
||||
const parsedError = parseFastAPIError(error as AxiosError)
|
||||
|
||||
if ("detail" in parsedError) {
|
||||
$errorSnackbarKey.current = enqueueSnackbar(
|
||||
parsedError.detail || t("general.defaultError"),
|
||||
{
|
||||
message = parsedError.detail
|
||||
} catch (e) {}
|
||||
|
||||
$errorSnackbarKey.current = enqueueSnackbar(message || t("general.defaultError"), {
|
||||
variant: "error",
|
||||
autoHideDuration: ERROR_SNACKBAR_SHOW_DURATION,
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -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<PaginationResult<ReservedAlias>, AxiosError>(
|
||||
["getReservedAliases"],
|
||||
() => getReservedAliases(),
|
||||
)
|
||||
|
||||
return (
|
||||
<QueryResult<PaginationResult<ReservedAlias>, AxiosError> query={query}>
|
||||
{({items}) => (
|
||||
<List>
|
||||
{items.map(alias => (
|
||||
<ListItem key={alias.id}>
|
||||
<ListItemText primary={`${alias.local}@${alias.domain}`} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</QueryResult>
|
||||
)
|
||||
}
|
77
src/route-widgets/AdminRoute/ServerStatus.tsx
Normal file
77
src/route-widgets/AdminRoute/ServerStatus.tsx
Normal file
@ -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<CronReport, AxiosError>(["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 (
|
||||
<QueryResult<CronReport> query={query}>
|
||||
{report => {
|
||||
const thresholdDate = subDays(new Date(), MAX_REPORT_DAY_THRESHOLD)
|
||||
|
||||
if (report.createdAt < thresholdDate) {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
{t("routes.AdminRoute.serverStatus.noRecentReports", {
|
||||
date: format(new Date(report.createdAt), "Pp"),
|
||||
})}
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
if (report.reportData.report.status === "error") {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
{t("routes.AdminRoute.serverStatus.error", {
|
||||
relativeDescription: formatRelative(
|
||||
new Date(report.createdAt),
|
||||
new Date(),
|
||||
),
|
||||
})}
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert severity="success">
|
||||
{t("routes.AdminRoute.serverStatus.success", {
|
||||
relativeDescription: formatRelative(
|
||||
new Date(report.createdAt),
|
||||
new Date(),
|
||||
),
|
||||
})}
|
||||
</Alert>
|
||||
)
|
||||
}}
|
||||
</QueryResult>
|
||||
)
|
||||
}
|
@ -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()
|
||||
|
@ -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<AuthenticationDetails, AxiosError, UpdateAccountData>(
|
||||
updateAccount,
|
||||
)
|
||||
const {mutateAsync} = useMutation<ServerUser, AxiosError, UpdateAccountData>(updateAccount)
|
||||
const formik = useFormik<Form>({
|
||||
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()
|
||||
|
38
src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx
Normal file
38
src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx
Normal file
@ -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 (
|
||||
<Container maxWidth="xs">
|
||||
<Grid
|
||||
container
|
||||
spacing={4}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
maxWidth="80%"
|
||||
alignSelf="center"
|
||||
marginX="auto"
|
||||
>
|
||||
<Grid item>
|
||||
<Typography variant="h6" component="h2">
|
||||
{t("routes.AdminRoute.settings.disabled.title")}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<RiAlertFill size={40} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="body1">
|
||||
{t("routes.AdminRoute.settings.disabled.description")}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
)
|
||||
}
|
@ -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<AdminSettings, AxiosError, Partial<AdminSettings>>(
|
||||
const {mutateAsync} = useMutation<
|
||||
UpdateAdminSettingsResponse,
|
||||
AxiosError,
|
||||
Partial<AdminSettings>
|
||||
>(
|
||||
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<AdminSettings>(queryKey, newSettings)
|
||||
queryClient.setQueryData<Partial<AdminSettings>>(queryKey, newSettings)
|
||||
},
|
||||
},
|
||||
)
|
||||
@ -488,6 +496,35 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormGroup key="allow_alias_deletion">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={formik.values.allowAliasDeletion!}
|
||||
onChange={formik.handleChange}
|
||||
name="allowAliasDeletion"
|
||||
/>
|
||||
}
|
||||
disabled={formik.isSubmitting}
|
||||
label={t(
|
||||
"routes.AdminRoute.forms.settings.allowAliasDeletion.label",
|
||||
)}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={
|
||||
formik.touched.allowAliasDeletion &&
|
||||
Boolean(formik.errors.allowAliasDeletion)
|
||||
}
|
||||
>
|
||||
{(formik.touched.allowAliasDeletion &&
|
||||
formik.errors.allowAliasDeletion) ||
|
||||
t(
|
||||
"routes.AdminRoute.forms.settings.allowAliasDeletion.description",
|
||||
)}
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
@ -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<AuthenticationDetails, AxiosError, VerifyLoginWithEmailData>(
|
||||
const {mutateAsync} = useMutation<ServerUser, AxiosError, VerifyLoginWithEmailData>(
|
||||
verifyLoginWithEmail,
|
||||
{
|
||||
onSuccess: ({user}) => onConfirm(user),
|
||||
onSuccess: onConfirm,
|
||||
},
|
||||
)
|
||||
const formik = useFormik<Form>({
|
||||
|
@ -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<SimpleDetailResponse, AxiosError, void>(() =>
|
||||
const mutation = useMutation<ResendEmailLoginCodeResponse, AxiosError, void>(() =>
|
||||
resendEmailLoginCode({
|
||||
email,
|
||||
sameRequestToken,
|
||||
|
@ -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<AuthenticationDetails, AxiosError, void>(
|
||||
const {mutate, isLoading, isError} = useMutation<ServerUser, AxiosError, void>(
|
||||
() =>
|
||||
verifyLoginWithEmail({
|
||||
email,
|
||||
token,
|
||||
}),
|
||||
{
|
||||
onSuccess: ({user}) => onConfirm(user),
|
||||
onSuccess: onConfirm,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -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<SimpleDetailResponse, AxiosError, void>(() =>
|
||||
resendEmailVerificationCode(email),
|
||||
const mutation = useMutation<ResendEmailVerificationCodeResponse, AxiosError, void>(
|
||||
() => resendEmailVerificationCode(email),
|
||||
{
|
||||
onSuccess: ({code}: any) => {
|
||||
if (code === "ok:email_already_verified") {
|
||||
onEmailAlreadyVerified()
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
const {mutate} = mutation
|
||||
|
||||
|
@ -67,7 +67,7 @@ export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactEle
|
||||
<OpenMailButton domain={domain} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ResendMailButton email={email} />
|
||||
<ResendMailButton email={email} onEmailAlreadyVerified={onGoBack} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MultiStepFormElement>
|
||||
|
@ -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 (
|
||||
<SimplePageBuilder.Page title={t("routes.AdminRoute.title")}>
|
||||
<ServerStatus />
|
||||
<List>
|
||||
<ListItemButton component={Link} to="/admin/reserved-aliases">
|
||||
<ListItemIcon>
|
||||
|
@ -1,17 +1,18 @@
|
||||
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"
|
||||
|
||||
import {useQuery} from "@tanstack/react-query"
|
||||
import {Grid} from "@mui/material"
|
||||
|
||||
import {getAlias} from "~/apis"
|
||||
import {Alias, DecryptedAlias} from "~/server-types"
|
||||
import {deleteAlias, getAlias} from "~/apis"
|
||||
import {Alias, DecryptedAlias, ServerSettings} from "~/server-types"
|
||||
import {
|
||||
AliasTypeIndicator,
|
||||
AuthContext,
|
||||
DecryptionPasswordMissingAlert,
|
||||
DeleteButton,
|
||||
EncryptionStatus,
|
||||
QueryResult,
|
||||
SimplePage,
|
||||
@ -25,13 +26,13 @@ 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 serverSettings = useLoaderData() as ServerSettings
|
||||
const {id: aliasID} = useParams()
|
||||
const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
|
||||
const queryKey = ["get_alias", address, encryptionStatus]
|
||||
const queryKey = ["get_alias", aliasID, encryptionStatus]
|
||||
|
||||
const query = useQuery<Alias | DecryptedAlias, AxiosError>(queryKey, async () => {
|
||||
const alias = await getAlias(address)
|
||||
const alias = await getAlias(aliasID!)
|
||||
|
||||
if (encryptionStatus === EncryptionStatus.Available) {
|
||||
;(alias as any as DecryptedAlias).notes = decryptAliasNotes(
|
||||
@ -44,7 +45,22 @@ export default function AliasDetailRoute(): ReactElement {
|
||||
})
|
||||
|
||||
return (
|
||||
<SimplePage title={t("routes.AliasDetailRoute.title")}>
|
||||
<SimplePage
|
||||
title={t("routes.AliasDetailRoute.title")}
|
||||
actions={
|
||||
serverSettings.allowAliasDeletion &&
|
||||
query.data && (
|
||||
<DeleteButton
|
||||
onDelete={() => 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")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<QueryResult<Alias | DecryptedAlias> query={query}>
|
||||
{alias => (
|
||||
<SimplePageBuilder.MultipleSections>
|
||||
@ -60,7 +76,7 @@ export default function AliasDetailRoute(): ReactElement {
|
||||
<AliasTypeIndicator type={alias.type} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<AliasAddress address={address} />
|
||||
<AliasAddress address={`${alias.local}@${alias.domain}`} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ChangeAliasActivationStatusSwitch
|
||||
|
@ -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<AdminSettings, AxiosError>(queryKey, async () => {
|
||||
const settings = getAdminSettings()
|
||||
const query = useQuery<AdminSettings | null, AxiosError>(queryKey, async () => {
|
||||
const {code, detail, ...settings} = await getAdminSettings()
|
||||
|
||||
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 (
|
||||
<QueryResult<AdminSettings> query={query}>
|
||||
{settings => <SettingsForm settings={settings} queryKey={queryKey} />}
|
||||
<QueryResult<AdminSettings | null> query={query}>
|
||||
{settings =>
|
||||
settings === null ? (
|
||||
<SettingsDisabled />
|
||||
) : (
|
||||
<SettingsForm settings={settings} queryKey={queryKey} />
|
||||
)
|
||||
}
|
||||
</QueryResult>
|
||||
)
|
||||
}
|
||||
|
@ -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 (
|
||||
<SimplePageBuilder.Page
|
||||
title="Report Details"
|
||||
actions={query.data && <DeleteButton id={query.data.id} />}
|
||||
actions={
|
||||
query.data && (
|
||||
<DeleteButton
|
||||
onDelete={() => 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")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<QueryResult<Report> query={query}>
|
||||
{encryptedReport => (
|
||||
|
@ -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<ReservedAlias, AxiosError>(queryKey, () => getReservedAlias(params.id!))
|
||||
|
||||
return (
|
||||
<SimplePage title={t("routes.ReservedAliasDetailRoute.title")}>
|
||||
<SimplePage
|
||||
title={t("routes.ReservedAliasDetailRoute.title")}
|
||||
actions={
|
||||
query.data && (
|
||||
<DeleteButton
|
||||
onDelete={() => 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")}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<QueryResult<ReservedAlias, AxiosError> query={query}>
|
||||
{alias => (
|
||||
<SimplePageBuilder.MultipleSections>
|
||||
|
@ -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<AuthenticationDetails, AxiosError, VerifyEmailData>(
|
||||
verifyEmail,
|
||||
{
|
||||
onSuccess: ({user}) => {
|
||||
const {mutateAsync} = useMutation<ServerUser, AxiosError, VerifyEmailData>(verifyEmail, {
|
||||
onSuccess: user => {
|
||||
login(user)
|
||||
navigate("/auth/complete-account")
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
const {loading} = useAsync(async () => {
|
||||
await emailSchema.validate(email)
|
||||
await tokenSchema.validate(token)
|
||||
|
@ -83,6 +83,7 @@ export interface ServerSettings {
|
||||
customAliasSuffixLength: number
|
||||
instanceSalt: string
|
||||
publicKey: string
|
||||
allowAliasDeletion: boolean
|
||||
}
|
||||
|
||||
export interface Alias {
|
||||
@ -210,4 +211,30 @@ export interface AdminSettings {
|
||||
userEmailEnableDisposableEmails: boolean
|
||||
userEmailEnableOtherRelays: boolean | null
|
||||
allowStatistics: boolean | null
|
||||
allowAliasDeletion: boolean | null
|
||||
}
|
||||
|
||||
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
|
||||
expiredReports: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user