Update labels to use react-query

This commit is contained in:
Jackson Harper 2024-07-31 12:33:26 +08:00
parent 11e336735f
commit 1fc73bd59e
17 changed files with 343 additions and 340 deletions

View file

@ -2,16 +2,17 @@ import AutosizeInput_, { AutosizeInputProps } from 'react-input-autosize'
import { Box, SpanBox } from './LayoutPrimitives'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Label } from '../../lib/networking/fragments/labelFragment'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { isTouchScreenDevice } from '../../lib/deviceType'
import { EditLabelChip } from './EditLabelChip'
import { LabelsDispatcher } from '../../lib/hooks/useSetPageLabels'
import { EditLabelChipStack } from './EditLabelChipStack'
import { useGetLabels } from '../../lib/networking/labels/useLabels'
// AutosizeInput is a Class component, but the types are broken in React 18.
// TODO: Maybe move away from this component, since it hasn't been updated for 3 years.
// https://github.com/JedWatson/react-input-autosize/issues
const AutosizeInput = AutosizeInput_ as unknown as React.FunctionComponent<AutosizeInputProps>
const AutosizeInput =
AutosizeInput_ as unknown as React.FunctionComponent<AutosizeInputProps>
const MaxUnstackedLabels = 7
@ -40,7 +41,7 @@ type LabelsPickerProps = {
export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
const inputRef = useRef<HTMLInputElement | null>()
const availableLabels = useGetLabelsQuery()
const { data: availableLabels } = useGetLabels()
const [isStackExpanded, setIsStackExpanded] = useState(false)
const {
focused,
@ -80,9 +81,10 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
setTabCount(_tabCount)
}
const matches = availableLabels.labels.filter((l) =>
l.name.toLowerCase().startsWith(_tabStartValue)
)
const matches =
availableLabels?.filter((l) =>
l.name.toLowerCase().startsWith(_tabStartValue)
) ?? []
if (_tabCount < matches.length) {
setInputValue(matches[_tabCount].name)

View file

@ -8,13 +8,15 @@ import {
ModalTitleBar,
} from '../../elements/ModalPrimitives'
import { SetLabelsControl } from './SetLabelsControl'
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
import { showSuccessToast } from '../../../lib/toastHelpers'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { v4 as uuidv4 } from 'uuid'
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
import { LabelAction } from '../../../lib/hooks/useSetPageLabels'
import { Button } from '../../elements/Button'
import {
useCreateLabel,
useGetLabels,
} from '../../../lib/networking/labels/useLabels'
type AddBulkLabelsModalProps = {
onOpenChange: (open: boolean) => void
@ -24,12 +26,14 @@ type AddBulkLabelsModalProps = {
export function AddBulkLabelsModal(
props: AddBulkLabelsModalProps
): JSX.Element {
const availableLabels = useGetLabelsQuery()
const { data: availableLabels } = useGetLabels()
const createLabel = useCreateLabel()
const [tabCount, setTabCount] = useState(-1)
const [inputValue, setInputValue] = useState('')
const [tabStartValue, setTabStartValue] = useState('')
const [errorMessage, setErrorMessage] =
useState<string | undefined>(undefined)
const [errorMessage, setErrorMessage] = useState<string | undefined>(
undefined
)
const errorTimeoutRef = useRef<NodeJS.Timeout | undefined>()
const [highlightLastLabel, setHighlightLastLabel] = useState(false)
const [isSaving, setIsSaving] = useState(false)
@ -97,10 +101,11 @@ export function AddBulkLabelsModal(
(newLabels: Label[], tempLabel: Label) => {
;(async () => {
const currentLabels = newLabels
const newLabel = await createLabelMutation(
tempLabel.name,
tempLabel.color
)
const newLabel = await createLabel.mutateAsync({
name: tempLabel.name,
color: tempLabel.color,
description: undefined,
})
const idx = currentLabels.findIndex((l) => l.id === tempLabel.id)
if (newLabel) {
showSuccessToast(`Created label ${newLabel.name}`, {
@ -132,7 +137,7 @@ export function AddBulkLabelsModal(
const trimmedValue = value.trim()
const current = selectedLabels.labels ?? []
const lowerCasedValue = trimmedValue.toLowerCase()
const existing = availableLabels.labels.find(
const existing = availableLabels?.find(
(l) => l.name.toLowerCase() == lowerCasedValue
)

View file

@ -4,14 +4,16 @@ import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { styled, theme } from '../../tokens/stitches.config'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { Check, Circle, Plus, WarningCircle } from '@phosphor-icons/react'
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
import { useRouter } from 'next/router'
import { LabelsPicker } from '../../elements/LabelsPicker'
import { LabelsDispatcher } from '../../../lib/hooks/useSetPageLabels'
import {
useCreateLabel,
useGetLabels,
} from '../../../lib/networking/labels/useLabels'
export interface LabelsProvider {
labels?: Label[]
@ -282,10 +284,10 @@ function Footer(props: FooterProps): JSX.Element {
}
export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const router = useRouter()
const { inputValue, setInputValue, selectedLabels, setHighlightLastLabel } =
props
const { labels, revalidate } = useGetLabelsQuery()
const { data: labels } = useGetLabels()
const createLabel = useCreateLabel()
// Move focus through the labels list on tab or arrow up/down keys
const [focusedIndex, setFocusedIndex] = useState<number | undefined>(0)
@ -321,9 +323,8 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
props.dispatchLabels({ type: 'SAVE', labels: newSelectedLabels })
props.clearInputState()
revalidate()
},
[isSelected, props, revalidate]
[isSelected, props]
)
const filteredLabels = useMemo(() => {
@ -342,11 +343,11 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const createLabelFromFilterText = useCallback(
async (text: string) => {
const trimmedLabelName = text.trim()
const label = await createLabelMutation(
trimmedLabelName,
randomLabelColorHex(),
''
)
const label = await createLabel.mutateAsync({
name: trimmedLabelName,
color: randomLabelColorHex(),
description: undefined,
})
if (label) {
showSuccessToast(`Created label ${label.name}`, {
position: 'bottom-right',
@ -425,7 +426,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
}, [inputValue, setInputValue, createLabelFromFilterText])
const selectEnteredLabel = useCallback(() => {
const label = labels.find(
const label = labels?.find(
(l: Label) => l.name.toLowerCase() == inputValue.toLowerCase()
)
if (!label) {
@ -509,7 +510,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
<Footer
filterText={inputValue}
selectedLabels={props.selectedLabels}
availableLabels={labels}
availableLabels={labels ?? []}
focused={focusedIndex === filteredLabels.length + 1}
createEnteredLabel={createEnteredLabel}
selectEnteredLabel={selectEnteredLabel}

View file

@ -8,13 +8,15 @@ import {
ModalTitleBar,
} from '../../elements/ModalPrimitives'
import { LabelsProvider, SetLabelsControl } from './SetLabelsControl'
import { createLabelMutation } from '../../../lib/networking/mutations/createLabelMutation'
import { showSuccessToast } from '../../../lib/toastHelpers'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { v4 as uuidv4 } from 'uuid'
import { randomLabelColorHex } from '../../../utils/settings-page/labels/labelColorObjects'
import { LabelsDispatcher } from '../../../lib/hooks/useSetPageLabels'
import * as Dialog from '@radix-ui/react-dialog'
import {
useCreateLabel,
useGetLabels,
} from '../../../lib/networking/labels/useLabels'
type SetLabelsModalProps = {
provider: LabelsProvider
@ -28,7 +30,7 @@ type SetLabelsModalProps = {
export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
const [inputValue, setInputValue] = useState('')
const { selectedLabels, dispatchLabels } = props
const availableLabels = useGetLabelsQuery()
const { data: availableLabels } = useGetLabels()
const [tabCount, setTabCount] = useState(-1)
const [tabStartValue, setTabStartValue] = useState('')
const [errorMessage, setErrorMessage] = useState<string | undefined>(
@ -37,6 +39,8 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
const errorTimeoutRef = useRef<NodeJS.Timeout | undefined>()
const [highlightLastLabel, setHighlightLastLabel] = useState(false)
const createLabel = useCreateLabel()
const showMessage = useCallback(
(msg: string, timeout?: number) => {
if (errorTimeoutRef.current) {
@ -82,10 +86,11 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
(newLabels: Label[], tempLabel: Label) => {
;(async () => {
const currentLabels = newLabels
const newLabel = await createLabelMutation(
tempLabel.name,
tempLabel.color
)
const newLabel = await createLabel.mutateAsync({
name: tempLabel.name,
color: tempLabel.color,
description: undefined,
})
const idx = currentLabels.findIndex((l) => l.id === tempLabel.id)
if (newLabel) {
showSuccessToast(`Created label ${newLabel.name}`, {
@ -116,7 +121,7 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
(value: string) => {
const current = selectedLabels ?? []
const lowerCasedValue = value.toLowerCase()
const existing = availableLabels.labels.find(
const existing = availableLabels?.find(
(l) => l.name.toLowerCase() == lowerCasedValue
)

View file

@ -1,6 +1,5 @@
import dayjs, { Dayjs } from 'dayjs'
import { useCallback, useState } from 'react'
import { updatePageMutation } from '../../../lib/networking/mutations/updatePageMutation'
import {
ArticleAttributes,
useUpdateItem,

View file

@ -8,7 +8,6 @@ import {
SubscriptionType,
useGetSubscriptionsQuery,
} from '../../../lib/networking/queries/useGetSubscriptionsQuery'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { theme } from '../../tokens/stitches.config'
import { useRegisterActions } from 'kbar'
@ -21,6 +20,7 @@ import Link from 'next/link'
import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon'
import { NavMenuFooter } from './Footer'
import { escapeQuotes } from '../../../utils/helper'
import { useGetLabels } from '../../../lib/networking/labels/useLabels'
export const LIBRARY_LEFT_MENU_WIDTH = '275px'
@ -50,7 +50,7 @@ export function LibraryLegacyMenu(props: LibraryFilterMenuProps): JSX.Element {
isSessionStorage: false,
initialValue: [],
})
const labelsResponse = useGetLabelsQuery()
const labelsResponse = useGetLabels()
const searchesResponse = useGetSavedSearchQuery()
const subscriptionsResponse = useGetSubscriptionsQuery()
@ -58,9 +58,9 @@ export function LibraryLegacyMenu(props: LibraryFilterMenuProps): JSX.Element {
if (
!labelsResponse.error &&
!labelsResponse.isLoading &&
labelsResponse.labels
labelsResponse.data
) {
setLabels(labelsResponse.labels)
setLabels(labelsResponse.data)
}
}, [setLabels, labelsResponse])

View file

@ -0,0 +1,71 @@
import { gql } from 'graphql-request'
import { labelFragment } from '../fragments/labelFragment'
export const GQL_GET_LABELS = gql`
query GetLabels {
labels {
... on LabelsSuccess {
labels {
...LabelFields
}
}
... on LabelsError {
errorCodes
}
}
}
${labelFragment}
`
export const GQL_CREATE_LABEL = gql`
mutation CreateLabel($input: CreateLabelInput!) {
createLabel(input: $input) {
... on CreateLabelSuccess {
label {
id
name
color
description
createdAt
}
}
... on CreateLabelError {
errorCodes
}
}
}
`
export const GQL_DELETE_LABEL = gql`
mutation DeleteLabel($id: ID!) {
deleteLabel(id: $id) {
... on DeleteLabelSuccess {
label {
id
}
}
... on DeleteLabelError {
errorCodes
}
}
}
`
export const GQL_UPDATE_LABEL = gql`
mutation UpdateLabel($input: UpdateLabelInput!) {
updateLabel(input: $input) {
... on UpdateLabelSuccess {
label {
id
name
color
description
createdAt
}
}
... on UpdateLabelError {
errorCodes
}
}
}
`

View file

@ -0,0 +1,160 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { gqlFetcher } from '../networkHelpers'
import {
GQL_CREATE_LABEL,
GQL_DELETE_LABEL,
GQL_GET_LABELS,
GQL_UPDATE_LABEL,
} from './gql'
import { Label } from '../fragments/labelFragment'
export function useGetLabels() {
return useQuery({
queryKey: ['labels'],
queryFn: async () => {
const response = (await gqlFetcher(GQL_GET_LABELS)) as LabelsData
if (response.labels?.errorCodes?.length) {
throw new Error(response.labels.errorCodes[0])
}
return response.labels?.labels
},
})
}
export const useCreateLabel = () => {
const queryClient = useQueryClient()
const createLabel = async (variables: {
name: string
color: string
description: string | undefined
}) => {
const result = (await gqlFetcher(GQL_CREATE_LABEL, {
input: {
name: variables.name,
color: variables.color,
description: variables.description,
},
})) as CreateLabelData
if (result.createLabel.errorCodes?.length) {
throw new Error(result.createLabel.errorCodes[0])
}
return result.createLabel.label
}
return useMutation({
mutationFn: createLabel,
onSuccess: (newLabel) => {
const keys = queryClient.getQueryCache().findAll({ queryKey: ['labels'] })
keys.forEach((query) => {
queryClient.setQueryData(query.queryKey, (data: Label[]) => {
return [...data, newLabel]
})
})
},
})
}
export const useDeleteLabel = () => {
const queryClient = useQueryClient()
const deleteLabel = async (variables: { labelId: string }) => {
const result = (await gqlFetcher(GQL_DELETE_LABEL, {
id: variables.labelId,
})) as DeleteLabelData
if (result.deleteLabel.errorCodes?.length) {
throw new Error(result.deleteLabel.errorCodes[0])
}
return result.deleteLabel?.label?.id
}
return useMutation({
mutationFn: deleteLabel,
onSuccess: (deletedId) => {
if (deletedId) {
const keys = queryClient
.getQueryCache()
.findAll({ queryKey: ['labels'] })
keys.forEach((query) => {
queryClient.setQueryData(query.queryKey, (data: Label[]) => {
return data.filter((label) => label.id !== deletedId)
})
})
}
},
})
}
export const useUpdateLabel = () => {
const queryClient = useQueryClient()
const updateLabel = async (variables: {
labelId: string
name: string
color: string
description: string
}) => {
const result = (await gqlFetcher(GQL_UPDATE_LABEL, {
input: {
labelId: variables.labelId,
name: variables.name,
color: variables.color,
description: variables.description,
},
})) as UpdateLabelData
if (result.updateLabel.errorCodes?.length) {
throw new Error(result.updateLabel.errorCodes[0])
}
return result.updateLabel?.label
}
return useMutation({
mutationFn: updateLabel,
onSuccess: (updatedLabel) => {
console.log('updated label: ', updatedLabel)
if (updatedLabel) {
const keys = queryClient
.getQueryCache()
.findAll({ queryKey: ['labels'] })
keys.forEach((query) => {
queryClient.setQueryData(query.queryKey, (data: Label[]) => {
return [
...data.filter((label) => label.id !== updatedLabel.id),
updatedLabel,
]
})
})
}
},
})
}
type LabelsResult = {
labels?: Label[]
errorCodes?: string[]
}
type LabelsData = {
labels?: LabelsResult
}
type CreateLabelResult = {
label?: Label
errorCodes?: string[]
}
type CreateLabelData = {
createLabel: CreateLabelResult
}
type DeleteLabelResult = {
label?: Label
errorCodes?: string[]
}
type DeleteLabelData = {
deleteLabel: DeleteLabelResult
}
type UpdateLabelResult = {
label?: Label
errorCodes?: string[]
}
type UpdateLabelData = {
updateLabel: UpdateLabelResult
}

View file

@ -1,51 +0,0 @@
import { gql } from 'graphql-request'
import { Label } from '../fragments/labelFragment'
import { gqlFetcher } from '../networkHelpers'
type CreateLabelResult = {
createLabel: CreateLabel
errorCodes?: unknown[]
}
type CreateLabel = {
label: Label
}
export async function createLabelMutation(
name: string,
color: string,
description?: string
): Promise<any | undefined> {
const mutation = gql`
mutation CreateLabel($input: CreateLabelInput!) {
createLabel(input: $input) {
... on CreateLabelSuccess {
label {
id
name
color
description
createdAt
}
}
... on CreateLabelError {
errorCodes
}
}
}
`
try {
const data = (await gqlFetcher(mutation, {
input: {
name,
color,
description,
},
})) as CreateLabelResult
return data.errorCodes ? undefined : data.createLabel.label
} catch (error) {
console.log('createLabelMutation error', error)
return undefined
}
}

View file

@ -1,45 +0,0 @@
import { gql } from 'graphql-request'
import { Label } from '../fragments/labelFragment'
import { gqlFetcher } from '../networkHelpers'
type DeleteLabelResult = {
deleteLabel: DeleteLabel
errorCodes?: unknown[]
}
type DeleteLabel = {
label: Label
}
export async function deleteLabelMutation(
labelId: string
): Promise<any | undefined> {
const mutation = gql`
mutation DeleteLabel($id: ID!) {
deleteLabel(id: $id) {
... on DeleteLabelSuccess {
label {
id
name
color
description
createdAt
}
}
... on DeleteLabelError {
errorCodes
}
}
}
`
try {
const data = (await gqlFetcher(mutation, {
id: labelId,
})) as DeleteLabelResult
return data.errorCodes ? undefined : data.deleteLabel.label.id
} catch (error) {
console.log('deleteLabelMutation error', error)
return undefined
}
}

View file

@ -1,42 +0,0 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
export type UpdateLabelInput = {
labelId: string
name: string
color: string
description?: string
}
export async function updateLabelMutation(
input: UpdateLabelInput
): Promise<string | undefined> {
const mutation = gql`
mutation UpdateLabel($input: UpdateLabelInput!) {
updateLabel(input: $input) {
... on UpdateLabelSuccess {
label {
id
name
color
description
createdAt
}
}
... on UpdateLabelError {
errorCodes
}
}
}
`
try {
const data = await gqlFetcher(mutation, {
input,
})
const output = data as any
return output?.updatedLabel
} catch (err) {
return undefined
}
}

View file

@ -1,50 +0,0 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { State } from '../fragments/articleFragment'
export type UpdatePageInput = {
pageId: string
title?: string
byline?: string | undefined
description?: string
savedAt?: string
publishedAt?: string
state?: State
}
export async function updatePageMutation(
input: UpdatePageInput
): Promise<string | undefined> {
const mutation = gql`
mutation UpdatePage($input: UpdatePageInput!) {
updatePage(input: $input) {
... on UpdatePageSuccess {
updatedPage {
id
title
url
createdAt
author
image
description
savedAt
publishedAt
}
}
... on UpdatePageError {
errorCodes
}
}
}
`
try {
const data = await gqlFetcher(mutation, {
input,
})
const output = data as any
return output.updatePage
} catch (err) {
return undefined
}
}

View file

@ -1,66 +0,0 @@
import { gql } from 'graphql-request'
import useSWR from 'swr'
import { Label, labelFragment } from '../fragments/labelFragment'
import { publicGqlFetcher } from '../networkHelpers'
type LabelsQueryResponse = {
error: any
isLoading: boolean
isValidating: boolean
labels: Label[]
revalidate: () => void
}
type LabelsResponseData = {
labels?: LabelsData
}
type LabelsData = {
labels?: unknown
}
export function useGetLabelsQuery(): LabelsQueryResponse {
const query = gql`
query GetLabels {
labels {
... on LabelsSuccess {
labels {
...LabelFields
}
}
... on LabelsError {
errorCodes
}
}
}
${labelFragment}
`
const { data, error, mutate, isValidating } = useSWR(query, publicGqlFetcher)
try {
if (data && !error) {
const result = data as LabelsResponseData
const labels = result.labels?.labels as Label[]
return {
error,
isLoading: !error && !data,
isValidating,
labels,
revalidate: () => {
mutate()
},
}
}
} catch (error) {
console.log('error', error)
}
return {
error,
isLoading: !error && !data,
isValidating: false,
labels: [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
revalidate: () => {},
}
}

View file

@ -9,10 +9,6 @@ import {
VStack,
} from '../../components/elements/LayoutPrimitives'
import { Toaster } from 'react-hot-toast'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { createLabelMutation } from '../../lib/networking/mutations/createLabelMutation'
import { updateLabelMutation } from '../../lib/networking/mutations/updateLabelMutation'
import { deleteLabelMutation } from '../../lib/networking/mutations/deleteLabelMutation'
import { applyStoredTheme, isDarkTheme } from '../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { Label, LabelColor } from '../../lib/networking/fragments/labelFragment'
@ -35,6 +31,12 @@ import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
import { InfoLink } from '../../components/elements/InfoLink'
import { usePersistedState } from '../../lib/hooks/usePersistedState'
import { FeatureHelpBox } from '../../components/elements/FeatureHelpBox'
import {
useCreateLabel,
useDeleteLabel,
useGetLabels,
useUpdateLabel,
} from '../../lib/networking/labels/useLabels'
const HeaderWrapper = styled(Box, {
width: '100%',
@ -143,7 +145,11 @@ const Input = styled('input', { ...inputStyles })
const TextArea = styled('textarea', { ...inputStyles })
export default function LabelsPage(): JSX.Element {
const { labels, revalidate } = useGetLabelsQuery()
const { data: labels, isLoading } = useGetLabels()
const createLabel = useCreateLabel()
const deleteLabel = useDeleteLabel()
const updateLabel = useUpdateLabel()
const [labelColorHex, setLabelColorHex] = useState('#000000')
const [editingLabelId, setEditingLabelId] = useState<string | null>(null)
const [nameInputText, setNameInputText] = useState<string>('')
@ -162,6 +168,9 @@ export default function LabelsPage(): JSX.Element {
applyStoredTheme()
const sortedLabels = useMemo(() => {
if (!labels) {
return []
}
return labels.sort((left: Label, right: Label) =>
left.name.localeCompare(right.name)
)
@ -186,29 +195,35 @@ export default function LabelsPage(): JSX.Element {
setLabelColorHex('#000000')
}
async function createLabel(): Promise<void> {
const res = await createLabelMutation(
nameInputText.trim(),
labelColorHex,
descriptionInputText
)
async function doCreateLabel(): Promise<void> {
const res = await createLabel.mutateAsync({
name: nameInputText.trim(),
color: labelColorHex,
description: descriptionInputText,
})
if (res) {
showSuccessToast('Label created', { position: 'bottom-right' })
resetLabelState()
revalidate()
} else {
showErrorToast('Failed to create label')
}
}
async function updateLabel(id: string): Promise<void> {
await updateLabelMutation({
labelId: id,
name: nameInputText,
color: labelColorHex,
description: descriptionInputText,
})
revalidate()
async function doUpdateLabel(id: string): Promise<void> {
try {
await updateLabel.mutateAsync({
labelId: id,
name: nameInputText,
color: labelColorHex,
description: descriptionInputText,
})
} catch (err) {
console.log('error updating label: ', err)
showErrorToast('Failed to update label')
return
}
showSuccessToast('Label updated', { position: 'bottom-right' })
resetLabelState()
}
const onEditPress = (label: Label | null) => {
@ -222,17 +237,16 @@ export default function LabelsPage(): JSX.Element {
}
}
async function onDeleteLabel(id: string): Promise<void> {
const result = await deleteLabelMutation(id)
async function onDeleteLabel(labelId: string): Promise<void> {
const result = await deleteLabel.mutateAsync({ labelId })
if (result) {
showSuccessToast('Label deleted', { position: 'bottom-right' })
} else {
showErrorToast('Failed to delete label', { position: 'bottom-right' })
}
revalidate()
}
async function deleteLabel(id: string): Promise<void> {
async function doDeleteLabel(id: string): Promise<void> {
setConfirmRemoveLabelId(id)
}
@ -349,14 +363,14 @@ export default function LabelsPage(): JSX.Element {
handleGenerateRandomColor={handleGenerateRandomColor}
setEditingLabelId={setEditingLabelId}
setLabelColorHex={setLabelColorHex}
deleteLabel={deleteLabel}
deleteLabel={doDeleteLabel}
nameInputText={nameInputText}
descriptionInputText={descriptionInputText}
setNameInputText={setNameInputText}
setDescriptionInputText={setDescriptionInputText}
setIsCreateMode={setIsCreateMode}
createLabel={createLabel}
updateLabel={updateLabel}
createLabel={doCreateLabel}
updateLabel={doUpdateLabel}
onEditPress={onEditPress}
resetState={resetLabelState}
/>
@ -369,15 +383,15 @@ export default function LabelsPage(): JSX.Element {
handleGenerateRandomColor={handleGenerateRandomColor}
setEditingLabelId={setEditingLabelId}
setLabelColorHex={setLabelColorHex}
deleteLabel={deleteLabel}
deleteLabel={doDeleteLabel}
nameInputText={nameInputText}
descriptionInputText={descriptionInputText}
setNameInputText={setNameInputText}
setDescriptionInputText={setDescriptionInputText}
setIsCreateMode={setIsCreateMode}
createLabel={createLabel}
createLabel={doCreateLabel}
resetState={resetLabelState}
updateLabel={updateLabel}
updateLabel={doUpdateLabel}
/>
)
) : null}
@ -396,15 +410,15 @@ export default function LabelsPage(): JSX.Element {
handleGenerateRandomColor: handleGenerateRandomColor,
setEditingLabelId: setEditingLabelId,
setLabelColorHex: setLabelColorHex,
deleteLabel: deleteLabel,
deleteLabel: doDeleteLabel,
nameInputText: nameInputText,
descriptionInputText: descriptionInputText,
setNameInputText: setNameInputText,
setDescriptionInputText: setDescriptionInputText,
setIsCreateMode: setIsCreateMode,
createLabel: createLabel,
createLabel: doCreateLabel,
resetState: resetLabelState,
updateLabel: updateLabel,
updateLabel: doUpdateLabel,
}
if (editingLabelId == label.id) {

View file

@ -15,13 +15,13 @@ import {
import { StyledText } from '../../components/elements/StyledText'
import { SettingsLayout } from '../../components/templates/SettingsLayout'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { useGetSavedSearchQuery } from '../../lib/networking/queries/useGetSavedSearchQuery'
import { Label } from '../../lib/networking/fragments/labelFragment'
import { CheckSquare, Circle, Square } from '@phosphor-icons/react'
import { SavedSearch } from '../../lib/networking/fragments/savedSearchFragment'
import { usePersistedState } from '../../lib/hooks/usePersistedState'
import { escapeQuotes } from '../../utils/helper'
import { useGetLabels } from '../../lib/networking/labels/useLabels'
export type PinnedSearch = {
type: 'saved-search' | 'label'
@ -34,7 +34,7 @@ const PINNED_SEARCHES_KEY = `--library-pinned-searches`
type ListAction = 'RESET' | 'ADD_ITEM' | 'REMOVE_ITEM'
export default function PinnedSearches(): JSX.Element {
const { labels } = useGetLabelsQuery()
const { data: labels } = useGetLabels()
const { savedSearches } = useGetSavedSearchQuery()
const [hidePinnedSearches, setHidePinnedSearches] = usePersistedState({
key: '--library-hide-pinned-searches',
@ -103,7 +103,7 @@ export default function PinnedSearches(): JSX.Element {
if (pinnedSearches.state == 'INITIAL') {
return { labelItems: [], savedSearchItems: [] }
}
const labelItems = labels.map((label) => {
const labelItems = labels?.map((label) => {
return {
label,
isSelected: !!pinnedSearches.items.find(
@ -244,7 +244,7 @@ export default function PinnedSearches(): JSX.Element {
<StyledText style="modalTitle" css={{ mt: '20px' }}>
Labels
</StyledText>
{items.labelItems.map((item) => {
{items.labelItems?.map((item) => {
return (
<LabelButton
label={item.label}

View file

@ -8,16 +8,16 @@ import { Label } from '../../lib/networking/fragments/labelFragment'
import { deleteRuleMutation } from '../../lib/networking/mutations/deleteRuleMutation'
import { setRuleMutation } from '../../lib/networking/mutations/setRuleMutation'
import { useGetIntegrationsQuery } from '../../lib/networking/queries/useGetIntegrationsQuery'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import {
Rule,
RuleAction,
RuleActionType,
RuleEventType,
useGetRulesQuery
useGetRulesQuery,
} from '../../lib/networking/queries/useGetRulesQuery'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { useGetLabels } from '../../lib/networking/labels/useLabels'
type CreateRuleModalProps = {
isModalOpen: boolean
@ -131,7 +131,7 @@ type CreateActionModalProps = {
const CreateActionModal = (props: CreateActionModalProps): JSX.Element => {
const [form] = Form.useForm()
const { labels } = useGetLabelsQuery()
const { data: labels } = useGetLabels()
const { integrations } = useGetIntegrationsQuery()
const integrationOptions = ['NOTION', 'READWISE']
@ -232,7 +232,7 @@ const CreateActionModal = (props: CreateActionModalProps): JSX.Element => {
]}
>
<Select mode="multiple">
{labels.map((label) => {
{labels?.map((label) => {
return (
<Select.Option key={label.id} value={label.id}>
{label.name}
@ -302,7 +302,7 @@ const CreateActionModal = (props: CreateActionModalProps): JSX.Element => {
export default function Rules(): JSX.Element {
const { rules, revalidate } = useGetRulesQuery()
const { labels } = useGetLabelsQuery()
const { data: labels } = useGetLabels()
const [isCreateRuleModalOpen, setIsCreateRuleModalOpen] = useState(false)
const [createActionRule, setCreateActionRule] = useState<Rule | undefined>(
undefined

View file

@ -8,7 +8,6 @@ import {
} from 'react'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { useGetSavedSearchQuery } from '../../lib/networking/queries/useGetSavedSearchQuery'
import { SettingsLayout } from '../../components/templates/SettingsLayout'
import { Toaster } from 'react-hot-toast'
@ -36,6 +35,7 @@ import { styled } from '@stitches/react'
import { SavedSearch } from '../../lib/networking/fragments/savedSearchFragment'
import { escapeQuotes } from '../../utils/helper'
import { Shortcut } from '../../components/templates/navMenu/NavigationMenu'
import { useGetLabels } from '../../lib/networking/labels/useLabels'
type ListAction = 'RESET' | 'ADD_ITEM' | 'REMOVE_ITEM'
const SHORTCUTS_KEY = 'library-shortcuts'
@ -232,7 +232,7 @@ type ListProps = {
}
const AvailableItems = (props: ListProps): JSX.Element => {
const { labels } = useGetLabelsQuery()
const { data: labels } = useGetLabels()
const { savedSearches } = useGetSavedSearchQuery()
const { subscriptions } = useGetSubscriptionsQuery()