forked from Archives/Athou_commafeed
react-async-hook library provides useAsyncCallback that does the same thing as useMutation
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
"react-hooks/exhaustive-deps": [
|
||||
"warn",
|
||||
{
|
||||
"additionalHooks": "useAsync"
|
||||
"additionalHooks": "(^useAsync$)"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -67,8 +67,9 @@
|
||||
"useState",
|
||||
"useAppSelector",
|
||||
"useAppDispatch",
|
||||
"useAsync",
|
||||
"useForm",
|
||||
"useMutation",
|
||||
"useAsyncCallback",
|
||||
"useCallback",
|
||||
"useEffect"
|
||||
]
|
||||
|
||||
42
commafeed-client/package-lock.json
generated
42
commafeed-client/package-lock.json
generated
@@ -32,8 +32,7 @@
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"tinycon": "^0.6.8",
|
||||
"use-mutation": "^2.2.1"
|
||||
"tinycon": "^0.6.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lingui/cli": "^3.14.0",
|
||||
@@ -8490,31 +8489,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-mutation": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/use-mutation/-/use-mutation-2.2.1.tgz",
|
||||
"integrity": "sha512-qGvtQpVgj2Ekcrrykiq4rOlOhUrlebaO/sVVGpXjrb0IEOBtZqNzq8hKJwrYGkyUWqu3pBPpOEyss8OHRhS3Ow==",
|
||||
"dependencies": {
|
||||
"use-safe-callback": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/use-safe-callback": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/use-safe-callback/-/use-safe-callback-1.0.2.tgz",
|
||||
"integrity": "sha512-RVeoyxgjJNfu9baNwulBM+HuPQGlcE1MJtrA1w50B5Hd0tyeKvrBIPdCfRx4Trrrxe34M6qRTys/bfpqsKRcyA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
@@ -14903,20 +14877,6 @@
|
||||
"use-isomorphic-layout-effect": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"use-mutation": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/use-mutation/-/use-mutation-2.2.1.tgz",
|
||||
"integrity": "sha512-qGvtQpVgj2Ekcrrykiq4rOlOhUrlebaO/sVVGpXjrb0IEOBtZqNzq8hKJwrYGkyUWqu3pBPpOEyss8OHRhS3Ow==",
|
||||
"requires": {
|
||||
"use-safe-callback": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"use-safe-callback": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/use-safe-callback/-/use-safe-callback-1.0.2.tgz",
|
||||
"integrity": "sha512-RVeoyxgjJNfu9baNwulBM+HuPQGlcE1MJtrA1w50B5Hd0tyeKvrBIPdCfRx4Trrrxe34M6qRTys/bfpqsKRcyA==",
|
||||
"requires": {}
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
"react-infinite-scroller": "^1.2.6",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"tinycon": "^0.6.8",
|
||||
"use-mutation": "^2.2.1"
|
||||
"tinycon": "^0.6.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lingui/cli": "^3.14.0",
|
||||
|
||||
@@ -101,5 +101,3 @@ export const errorToStrings = (err: any) => {
|
||||
|
||||
return strings
|
||||
}
|
||||
|
||||
export const errorsToStrings = (errors: any[]) => errors.map(errorToStrings).flat()
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useForm } from "@mantine/form"
|
||||
import { client, errorToStrings } from "app/client"
|
||||
import { UserModel } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { TbDeviceFloppy } from "react-icons/tb"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
interface UserEditProps {
|
||||
user?: UserModel
|
||||
@@ -17,18 +17,17 @@ export function UserEdit(props: UserEditProps) {
|
||||
const form = useForm<UserModel>({
|
||||
initialValues: props.user ?? ({ enabled: true } as UserModel),
|
||||
})
|
||||
const [saveUser, saveUserResult] = useMutation(client.admin.saveUser, { onSuccess: props.onSave })
|
||||
const errors = errorToStrings(saveUserResult.error)
|
||||
const saveUser = useAsyncCallback(client.admin.saveUser, { onSuccess: props.onSave })
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.length > 0 && (
|
||||
{saveUser.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(saveUser.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(saveUser)}>
|
||||
<form onSubmit={form.onSubmit(saveUser.execute)}>
|
||||
<Stack>
|
||||
<TextInput label={t`Name`} {...form.getInputProps("name")} required />
|
||||
<PasswordInput label={t`Password`} {...form.getInputProps("password")} required={!props.user} />
|
||||
@@ -40,7 +39,7 @@ export function UserEdit(props: UserEditProps) {
|
||||
<Button variant="default" onClick={props.onCancel}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveUserResult.status === "running"}>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveUser.loading}>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -7,8 +7,8 @@ import { reloadTree } from "app/slices/tree"
|
||||
import { useAppDispatch } from "app/store"
|
||||
import { AddCategoryRequest } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { TbFolderPlus } from "react-icons/tb"
|
||||
import useMutation from "use-mutation"
|
||||
import { CategorySelect } from "./CategorySelect"
|
||||
|
||||
export function AddCategory() {
|
||||
@@ -16,23 +16,22 @@ export function AddCategory() {
|
||||
|
||||
const form = useForm<AddCategoryRequest>()
|
||||
|
||||
const [addCategory, addCategoryResult] = useMutation(client.category.add, {
|
||||
const addCategory = useAsyncCallback(client.category.add, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToSelectedSource())
|
||||
},
|
||||
})
|
||||
const errors = errorToStrings(addCategoryResult.error)
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.length > 0 && (
|
||||
{addCategory.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(addCategory.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(addCategory)}>
|
||||
<form onSubmit={form.onSubmit(addCategory.execute)}>
|
||||
<Stack>
|
||||
<TextInput label={t`Category`} placeholder={t`Category`} {...form.getInputProps("name")} required />
|
||||
<CategorySelect label={t`Parent`} {...form.getInputProps("parentId")} clearable />
|
||||
@@ -40,7 +39,7 @@ export function AddCategory() {
|
||||
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" leftIcon={<TbFolderPlus size={16} />} loading={addCategoryResult.status === "running"}>
|
||||
<Button type="submit" leftIcon={<TbFolderPlus size={16} />} loading={addCategory.loading}>
|
||||
<Trans>Add</Trans>
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -6,8 +6,8 @@ import { redirectToSelectedSource } from "app/slices/redirect"
|
||||
import { reloadTree } from "app/slices/tree"
|
||||
import { useAppDispatch } from "app/store"
|
||||
import { Alert } from "components/Alert"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { TbFileImport } from "react-icons/tb"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
export function ImportOpml() {
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -18,23 +18,22 @@ export function ImportOpml() {
|
||||
},
|
||||
})
|
||||
|
||||
const [importOpml, importOpmlResult] = useMutation(client.feed.importOpml, {
|
||||
const importOpml = useAsyncCallback(client.feed.importOpml, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToSelectedSource())
|
||||
},
|
||||
})
|
||||
const errors = errorToStrings(importOpmlResult.error)
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.length > 0 && (
|
||||
{importOpml.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(importOpml.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(v => importOpml(v.file))}>
|
||||
<form onSubmit={form.onSubmit(v => importOpml.execute(v.file))}>
|
||||
<Stack>
|
||||
<FileInput
|
||||
label={t`OPML file`}
|
||||
@@ -48,7 +47,7 @@ export function ImportOpml() {
|
||||
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" leftIcon={<TbFileImport size={16} />} loading={importOpmlResult.status === "running"}>
|
||||
<Button type="submit" leftIcon={<TbFileImport size={16} />} loading={importOpml.loading}>
|
||||
<Trans>Import</Trans>
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t, Trans } from "@lingui/macro"
|
||||
import { Box, Button, Group, Stack, Stepper, TextInput } from "@mantine/core"
|
||||
import { useForm } from "@mantine/form"
|
||||
import { client, errorsToStrings, errorToStrings } from "app/client"
|
||||
import { client, errorToStrings } from "app/client"
|
||||
import { Constants } from "app/constants"
|
||||
import { redirectToFeed, redirectToSelectedSource } from "app/slices/redirect"
|
||||
import { reloadTree } from "app/slices/tree"
|
||||
@@ -9,8 +9,8 @@ import { useAppDispatch } from "app/store"
|
||||
import { FeedInfoRequest, SubscribeRequest } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { useState } from "react"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { TbRss } from "react-icons/tb"
|
||||
import useMutation from "use-mutation"
|
||||
import { CategorySelect } from "./CategorySelect"
|
||||
|
||||
export function Subscribe() {
|
||||
@@ -31,20 +31,19 @@ export function Subscribe() {
|
||||
},
|
||||
})
|
||||
|
||||
const [fetchFeed, fetchFeedResult] = useMutation(client.feed.fetchFeed, {
|
||||
const fetchFeed = useAsyncCallback(client.feed.fetchFeed, {
|
||||
onSuccess: ({ data }) => {
|
||||
step1Form.setFieldValue("url", data.data.url)
|
||||
step1Form.setFieldValue("title", data.data.title)
|
||||
step1Form.setFieldValue("url", data.url)
|
||||
step1Form.setFieldValue("title", data.title)
|
||||
setActiveStep(step => step + 1)
|
||||
},
|
||||
})
|
||||
const [subscribe, subscribeResult] = useMutation(client.feed.subscribe, {
|
||||
const subscribe = useAsyncCallback(client.feed.subscribe, {
|
||||
onSuccess: sub => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToFeed(sub.data.data.id))
|
||||
dispatch(redirectToFeed(sub.data.id))
|
||||
},
|
||||
})
|
||||
const errors = errorsToStrings([fetchFeedResult.error, errorToStrings(subscribeResult.error)])
|
||||
|
||||
const previousStep = () => {
|
||||
if (activeStep === 0) dispatch(redirectToSelectedSource())
|
||||
@@ -52,17 +51,23 @@ export function Subscribe() {
|
||||
}
|
||||
const nextStep = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
if (activeStep === 0) {
|
||||
step0Form.onSubmit(fetchFeed)(e)
|
||||
step0Form.onSubmit(fetchFeed.execute)(e)
|
||||
} else if (activeStep === 1) {
|
||||
step1Form.onSubmit(subscribe)(e)
|
||||
step1Form.onSubmit(subscribe.execute)(e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.length > 0 && (
|
||||
{fetchFeed.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(fetchFeed.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{subscribe.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errorToStrings(subscribe.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -96,16 +101,12 @@ export function Subscribe() {
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
{activeStep === 0 && (
|
||||
<Button type="submit" loading={fetchFeedResult.status === "running"}>
|
||||
<Button type="submit" loading={fetchFeed.loading}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{activeStep === 1 && (
|
||||
<Button
|
||||
type="submit"
|
||||
leftIcon={<TbRss size={16} />}
|
||||
loading={fetchFeedResult.status === "running" || subscribeResult.status === "running"}
|
||||
>
|
||||
<Button type="submit" leftIcon={<TbRss size={16} />} loading={fetchFeed.loading || subscribe.loading}>
|
||||
<Trans>Subscribe</Trans>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { t, Trans } from "@lingui/macro"
|
||||
import { Anchor, Box, Button, Checkbox, Divider, Group, Input, PasswordInput, Stack, Text, TextInput } from "@mantine/core"
|
||||
import { useForm } from "@mantine/form"
|
||||
import { openConfirmModal } from "@mantine/modals"
|
||||
import { client, errorsToStrings } from "app/client"
|
||||
import { client, errorToStrings } from "app/client"
|
||||
import { redirectToLogin, redirectToSelectedSource } from "app/slices/redirect"
|
||||
import { reloadProfile } from "app/slices/user"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { ProfileModificationRequest } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { useEffect } from "react"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
interface FormData extends ProfileModificationRequest {
|
||||
newPasswordConfirmation?: string
|
||||
@@ -27,18 +27,17 @@ export function ProfileSettings() {
|
||||
})
|
||||
const { setValues } = form
|
||||
|
||||
const [saveProfile, saveProfileResult] = useMutation(client.user.saveProfile, {
|
||||
const saveProfile = useAsyncCallback(client.user.saveProfile, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadProfile())
|
||||
dispatch(redirectToSelectedSource())
|
||||
},
|
||||
})
|
||||
const [deleteProfile, deleteProfileResult] = useMutation(client.user.deleteProfile, {
|
||||
const deleteProfile = useAsyncCallback(client.user.deleteProfile, {
|
||||
onSuccess: () => {
|
||||
dispatch(redirectToLogin())
|
||||
},
|
||||
})
|
||||
const errors = errorsToStrings([saveProfileResult.error, deleteProfileResult.error])
|
||||
|
||||
const openDeleteProfileModal = () =>
|
||||
openConfirmModal({
|
||||
@@ -50,7 +49,7 @@ export function ProfileSettings() {
|
||||
),
|
||||
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => deleteProfile({}),
|
||||
onConfirm: () => deleteProfile.execute(),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,13 +63,19 @@ export function ProfileSettings() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{errors.length > 0 && (
|
||||
{saveProfile.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(saveProfile.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(saveProfile)}>
|
||||
{deleteProfile.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errorToStrings(deleteProfile.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(saveProfile.execute)}>
|
||||
<Stack>
|
||||
<Input.Wrapper label={t`User name`}>
|
||||
<Box>{profile?.name}</Box>
|
||||
@@ -105,7 +110,7 @@ export function ProfileSettings() {
|
||||
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveProfileResult.status === "running"}>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={saveProfile.loading}>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
<Divider orientation="vertical" />
|
||||
@@ -113,7 +118,7 @@ export function ProfileSettings() {
|
||||
color="red"
|
||||
leftIcon={<TbTrash size={16} />}
|
||||
onClick={() => openDeleteProfileModal()}
|
||||
loading={deleteProfileResult.status === "running"}
|
||||
loading={deleteProfile.loading}
|
||||
>
|
||||
<Trans>Delete account</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -7,9 +7,8 @@ import { UserEdit } from "components/admin/UserEdit"
|
||||
import { Alert } from "components/Alert"
|
||||
import { Loader } from "components/Loader"
|
||||
import { RelativeDate } from "components/RelativeDate"
|
||||
import { useAsync } from "react-async-hook"
|
||||
import { useAsync, useAsyncCallback } from "react-async-hook"
|
||||
import { TbCheck, TbPencil, TbPlus, TbTrash, TbX } from "react-icons/tb"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
function BooleanIcon({ value }: { value: boolean }) {
|
||||
return value ? <TbCheck size={18} /> : <TbX size={18} />
|
||||
@@ -20,13 +19,12 @@ export function AdminUsersPage() {
|
||||
const query = useAsync(() => client.admin.getAllUsers(), [])
|
||||
const users = query.result?.data.sort((a, b) => a.id - b.id)
|
||||
|
||||
const [deleteUser, deleteUserResult] = useMutation(client.admin.deleteUser, {
|
||||
const deleteUser = useAsyncCallback(client.admin.deleteUser, {
|
||||
onSuccess: () => {
|
||||
query.execute()
|
||||
closeAllModals()
|
||||
},
|
||||
})
|
||||
const errors = errorToStrings(deleteUserResult.error)
|
||||
|
||||
const openUserEditModal = (title: string, user?: UserModel) => {
|
||||
openModal({
|
||||
@@ -57,7 +55,7 @@ export function AdminUsersPage() {
|
||||
),
|
||||
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => deleteUser({ id: user.id }),
|
||||
onConfirm: () => deleteUser.execute({ id: user.id }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,9 +71,9 @@ export function AdminUsersPage() {
|
||||
</Group>
|
||||
</Title>
|
||||
|
||||
{errors.length > 0 && (
|
||||
{deleteUser.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(deleteUser.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -134,7 +132,7 @@ export function AdminUsersPage() {
|
||||
<ActionIcon
|
||||
color={theme.primaryColor}
|
||||
onClick={() => openUserDeleteModal(u)}
|
||||
loading={deleteUserResult.status === "running"}
|
||||
loading={deleteUser.loading}
|
||||
>
|
||||
<TbTrash size={18} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -13,35 +13,34 @@ import { Alert } from "components/Alert"
|
||||
import { CategorySelect } from "components/content/add/CategorySelect"
|
||||
import { Loader } from "components/Loader"
|
||||
import { useEffect } from "react"
|
||||
import { useAsync } from "react-async-hook"
|
||||
import { useAsync, useAsyncCallback } from "react-async-hook"
|
||||
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
|
||||
import { useParams } from "react-router-dom"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
export function CategoryDetailsPage() {
|
||||
const { id = Constants.categoryIds.all } = useParams()
|
||||
|
||||
const apiKey = useAppSelector(state => state.user.profile?.apiKey)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const query = useAsync(() => client.category.getRoot(), [])
|
||||
const category = query.result && flattenCategoryTree(query.result.data).find(c => c.id === id)
|
||||
|
||||
const form = useForm<CategoryModificationRequest>()
|
||||
const { setValues } = form
|
||||
|
||||
const [modify, modifyResult] = useMutation(client.category.modify, {
|
||||
const modifyCategory = useAsyncCallback(client.category.modify, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToSelectedSource())
|
||||
},
|
||||
})
|
||||
const [deleteCategory, deleteCategoryResult] = useMutation(client.category.delete, {
|
||||
const deleteCategory = useAsyncCallback(client.category.delete, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToRootCategory())
|
||||
},
|
||||
})
|
||||
const errors = [...errorToStrings(modifyResult.error), ...errorToStrings(deleteCategoryResult.error)]
|
||||
|
||||
const openDeleteCategoryModal = () => {
|
||||
const categoryName = category?.name
|
||||
@@ -56,7 +55,7 @@ export function CategoryDetailsPage() {
|
||||
),
|
||||
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => deleteCategory({ id: +id }),
|
||||
onConfirm: () => deleteCategory.execute({ id: +id }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,13 +72,19 @@ export function CategoryDetailsPage() {
|
||||
if (!category) return <Loader />
|
||||
return (
|
||||
<Container>
|
||||
{errors.length > 0 && (
|
||||
{modifyCategory.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(modifyCategory.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(modify)}>
|
||||
{deleteCategory.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errorToStrings(deleteCategory.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(modifyCategory.execute)}>
|
||||
<Stack>
|
||||
<Title order={3}>{category.name}</Title>
|
||||
<Input.Wrapper label={t`Generated feed url`}>
|
||||
@@ -105,7 +110,7 @@ export function CategoryDetailsPage() {
|
||||
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyResult.status === "running"}>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyCategory.loading}>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
<Divider orientation="vertical" />
|
||||
@@ -113,7 +118,7 @@ export function CategoryDetailsPage() {
|
||||
color="red"
|
||||
leftIcon={<TbTrash size={16} />}
|
||||
onClick={() => openDeleteCategoryModal()}
|
||||
loading={deleteCategoryResult.status === "running"}
|
||||
loading={deleteCategory.loading}
|
||||
>
|
||||
<Trans>Delete</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { t, Trans } from "@lingui/macro"
|
||||
import { Anchor, Box, Button, Code, Container, Divider, Group, Input, NumberInput, Stack, Text, TextInput, Title } from "@mantine/core"
|
||||
import { useForm } from "@mantine/form"
|
||||
import { openConfirmModal } from "@mantine/modals"
|
||||
import { client, errorsToStrings } from "app/client"
|
||||
import { client, errorToStrings } from "app/client"
|
||||
import { redirectToRootCategory, redirectToSelectedSource } from "app/slices/redirect"
|
||||
import { reloadTree } from "app/slices/tree"
|
||||
import { useAppDispatch, useAppSelector } from "app/store"
|
||||
@@ -12,10 +12,9 @@ import { CategorySelect } from "components/content/add/CategorySelect"
|
||||
import { Loader } from "components/Loader"
|
||||
import { RelativeDate } from "components/RelativeDate"
|
||||
import { useEffect } from "react"
|
||||
import { useAsync } from "react-async-hook"
|
||||
import { useAsync, useAsyncCallback } from "react-async-hook"
|
||||
import { TbDeviceFloppy, TbTrash } from "react-icons/tb"
|
||||
import { useParams } from "react-router-dom"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
function FilteringExpressionDescription() {
|
||||
const example = <Code>url.contains('youtube') or (author eq 'athou' and title.contains('github')</Code>
|
||||
@@ -60,19 +59,18 @@ export function FeedDetailsPage() {
|
||||
const form = useForm<FeedModificationRequest>()
|
||||
const { setValues } = form
|
||||
|
||||
const [modify, modifyResult] = useMutation(client.feed.modify, {
|
||||
const modifyFeed = useAsyncCallback(client.feed.modify, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToSelectedSource())
|
||||
},
|
||||
})
|
||||
const [unsubscribe, unsubscribeResult] = useMutation(client.feed.unsubscribe, {
|
||||
const unsubscribe = useAsyncCallback(client.feed.unsubscribe, {
|
||||
onSuccess: () => {
|
||||
dispatch(reloadTree())
|
||||
dispatch(redirectToRootCategory())
|
||||
},
|
||||
})
|
||||
const errors = errorsToStrings([modifyResult.error, unsubscribeResult.error])
|
||||
|
||||
const openUnsubscribeModal = () => {
|
||||
const feedName = feed?.name
|
||||
@@ -87,7 +85,7 @@ export function FeedDetailsPage() {
|
||||
),
|
||||
labels: { confirm: t`Confirm`, cancel: t`Cancel` },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => unsubscribe({ id: +id }),
|
||||
onConfirm: () => unsubscribe.execute({ id: +id }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,13 +97,19 @@ export function FeedDetailsPage() {
|
||||
if (!feed) return <Loader />
|
||||
return (
|
||||
<Container>
|
||||
{errors.length > 0 && (
|
||||
{modifyFeed.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(modifyFeed.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(modify)}>
|
||||
{unsubscribe.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errorToStrings(unsubscribe.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(modifyFeed.execute)}>
|
||||
<Stack>
|
||||
<Title order={3}>{feed.name}</Title>
|
||||
<Input.Wrapper label={t`Feed URL`}>
|
||||
@@ -159,7 +163,7 @@ export function FeedDetailsPage() {
|
||||
<Button variant="default" onClick={() => dispatch(redirectToSelectedSource())}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyResult.status === "running"}>
|
||||
<Button type="submit" leftIcon={<TbDeviceFloppy size={16} />} loading={modifyFeed.loading}>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
<Divider orientation="vertical" />
|
||||
@@ -167,7 +171,7 @@ export function FeedDetailsPage() {
|
||||
color="red"
|
||||
leftIcon={<TbTrash size={16} />}
|
||||
onClick={() => openUnsubscribeModal()}
|
||||
loading={unsubscribeResult.status === "running"}
|
||||
loading={unsubscribe.loading}
|
||||
>
|
||||
<Trans>Unsubscribe</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -7,8 +7,8 @@ import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { LoginRequest } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { Logo } from "components/Logo"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { Link } from "react-router-dom"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
export function LoginPage() {
|
||||
const serverInfos = useAppSelector(state => state.server.serverInfos)
|
||||
@@ -21,12 +21,11 @@ export function LoginPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const [login, loginResult] = useMutation(client.user.login, {
|
||||
const login = useAsyncCallback(client.user.login, {
|
||||
onSuccess: () => {
|
||||
dispatch(redirectToRootCategory())
|
||||
},
|
||||
})
|
||||
const errors = errorToStrings(loginResult.error)
|
||||
|
||||
return (
|
||||
<Container size="xs">
|
||||
@@ -40,12 +39,12 @@ export function LoginPage() {
|
||||
<Title order={2} mb="md">
|
||||
<Trans>Log in</Trans>
|
||||
</Title>
|
||||
{errors.length > 0 && (
|
||||
{login.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(login.error)} />
|
||||
</Box>
|
||||
)}
|
||||
<form onSubmit={form.onSubmit(login)}>
|
||||
<form onSubmit={form.onSubmit(login.execute)}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={t`User Name or E-mail`}
|
||||
@@ -69,7 +68,7 @@ export function LoginPage() {
|
||||
</Anchor>
|
||||
)}
|
||||
|
||||
<Button type="submit" loading={loginResult.status === "running"}>
|
||||
<Button type="submit" loading={login.loading}>
|
||||
<Trans>Log in</Trans>
|
||||
</Button>
|
||||
{serverInfos?.allowRegistrations && (
|
||||
|
||||
@@ -6,8 +6,8 @@ import { PasswordResetRequest } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { Logo } from "components/Logo"
|
||||
import { useState } from "react"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { Link } from "react-router-dom"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
export function PasswordRecoveryPage() {
|
||||
const [message, setMessage] = useState("")
|
||||
@@ -18,15 +18,11 @@ export function PasswordRecoveryPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const [recoverPassword, recoverPasswordResult] = useMutation(client.user.passwordReset, {
|
||||
onMutate: () => {
|
||||
setMessage("")
|
||||
},
|
||||
const recoverPassword = useAsyncCallback(client.user.passwordReset, {
|
||||
onSuccess: () => {
|
||||
setMessage(t`An email has been sent if this address was registered. Check your inbox.`)
|
||||
},
|
||||
})
|
||||
const errors = errorToStrings(recoverPasswordResult.error)
|
||||
|
||||
return (
|
||||
<Container size="xs">
|
||||
@@ -40,17 +36,25 @@ export function PasswordRecoveryPage() {
|
||||
<Title order={2} mb="md">
|
||||
<Trans>Password Recovery</Trans>
|
||||
</Title>
|
||||
{errors.length > 0 && (
|
||||
|
||||
{recoverPassword.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(recoverPassword.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{message && (
|
||||
<Box mb="md">
|
||||
<Alert level="success" messages={[message]} />
|
||||
</Box>
|
||||
)}
|
||||
<form onSubmit={form.onSubmit(recoverPassword)}>
|
||||
|
||||
<form
|
||||
onSubmit={form.onSubmit(req => {
|
||||
setMessage("")
|
||||
recoverPassword.execute(req)
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
type="email"
|
||||
@@ -61,7 +65,7 @@ export function PasswordRecoveryPage() {
|
||||
required
|
||||
/>
|
||||
|
||||
<Button type="submit" loading={recoverPasswordResult.status === "running"}>
|
||||
<Button type="submit" loading={recoverPassword.loading}>
|
||||
<Trans>Recover password</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import { useAppDispatch, useAppSelector } from "app/store"
|
||||
import { RegistrationRequest } from "app/types"
|
||||
import { Alert } from "components/Alert"
|
||||
import { Logo } from "components/Logo"
|
||||
import { useAsyncCallback } from "react-async-hook"
|
||||
import { Link } from "react-router-dom"
|
||||
import useMutation from "use-mutation"
|
||||
|
||||
export function RegistrationPage() {
|
||||
const serverInfos = useAppSelector(state => state.server.serverInfos)
|
||||
@@ -22,12 +22,11 @@ export function RegistrationPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const [register, registerResult] = useMutation(client.user.register, {
|
||||
const register = useAsyncCallback(client.user.register, {
|
||||
onSuccess: () => {
|
||||
dispatch(redirectToRootCategory())
|
||||
},
|
||||
})
|
||||
const errors = errorToStrings(registerResult.error)
|
||||
|
||||
return (
|
||||
<Container size="xs">
|
||||
@@ -48,13 +47,13 @@ export function RegistrationPage() {
|
||||
)}
|
||||
{serverInfos?.allowRegistrations && (
|
||||
<>
|
||||
{errors.length > 0 && (
|
||||
{register.error && (
|
||||
<Box mb="md">
|
||||
<Alert messages={errors} />
|
||||
<Alert messages={errorToStrings(register.error)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<form onSubmit={form.onSubmit(register)}>
|
||||
<form onSubmit={form.onSubmit(register.execute)}>
|
||||
<Stack>
|
||||
<TextInput label="User Name" placeholder="User Name" {...form.getInputProps("name")} size="md" required />
|
||||
<TextInput
|
||||
@@ -72,7 +71,7 @@ export function RegistrationPage() {
|
||||
size="md"
|
||||
required
|
||||
/>
|
||||
<Button type="submit" loading={registerResult.status === "running"}>
|
||||
<Button type="submit" loading={register.loading}>
|
||||
<Trans>Sign up</Trans>
|
||||
</Button>
|
||||
<Center>
|
||||
|
||||
Reference in New Issue
Block a user