@remit/web-client 0.0.75 → 0.0.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/mail/LabelApplyTrigger.tsx +61 -0
- package/src/components/mail/MessageList.tsx +1 -0
- package/src/components/mail/MessageListItem.test.ts +43 -0
- package/src/components/mail/MessageListItem.tsx +1 -0
- package/src/components/mail/MoveToTrigger.tsx +2 -2
- package/src/components/mail/SelectionToolbar.render.test.ts +51 -1
- package/src/components/mail/SelectionToolbar.stories.tsx +5 -0
- package/src/components/mail/SelectionToolbar.tsx +21 -0
- package/src/components/mail/organize/OrganizeRuleEditor.tsx +20 -0
- package/src/components/settings/DeleteFolderDialog.tsx +2 -1
- package/src/components/settings/FilterEditor.render.test.ts +1 -0
- package/src/components/settings/FilterEditor.tsx +18 -0
- package/src/components/settings/FilterEditorSurface.tsx +9 -1
- package/src/components/settings/FiltersList.render.test.ts +16 -0
- package/src/components/settings/FiltersList.tsx +32 -8
- package/src/components/settings/LabelsList.tsx +112 -0
- package/src/components/settings/settings-filter.stories.tsx +10 -1
- package/src/hooks/useApplyLabel.ts +68 -0
- package/src/hooks/useCreateMailbox.render.test.ts +109 -9
- package/src/hooks/useCreateMailbox.ts +64 -23
- package/src/hooks/useLabels.ts +124 -0
- package/src/hooks/useRuleEditorState.ts +8 -0
- package/src/lib/mailbox-sync-wait.test.ts +186 -0
- package/src/lib/mailbox-sync-wait.ts +98 -0
- package/src/lib/organize/filter-edit-model.test.ts +38 -0
- package/src/lib/organize/filter-edit-model.ts +15 -10
- package/src/lib/organize/label-delete-copy.test.ts +21 -0
- package/src/lib/organize/label-delete-copy.ts +22 -0
- package/src/lib/organize/organize-model.test.ts +35 -7
- package/src/lib/organize/organize-model.ts +17 -12
- package/src/lib/organize/rule-model.test.ts +5 -0
- package/src/lib/organize/rule-model.ts +1 -0
- package/src/routeTree.gen.ts +21 -0
- package/src/routes/settings/filters.tsx +19 -1
- package/src/routes/settings/labels.tsx +242 -0
- package/src/routes/settings.tsx +7 -1
package/src/routeTree.gen.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { Route as SettingsAdvancedRouteImport } from './routes/settings/advanced
|
|
|
23
23
|
import { Route as SettingsAppearanceRouteImport } from './routes/settings/appearance'
|
|
24
24
|
import { Route as SettingsFiltersRouteImport } from './routes/settings/filters'
|
|
25
25
|
import { Route as SettingsFoldersRouteImport } from './routes/settings/folders'
|
|
26
|
+
import { Route as SettingsLabelsRouteImport } from './routes/settings/labels'
|
|
26
27
|
import { Route as SettingsSendersRouteImport } from './routes/settings/senders'
|
|
27
28
|
import { Route as SettingsSuggestedVipsRouteImport } from './routes/settings/suggested-vips'
|
|
28
29
|
|
|
@@ -96,6 +97,11 @@ const SettingsFoldersRoute = SettingsFoldersRouteImport.update({
|
|
|
96
97
|
path: '/folders',
|
|
97
98
|
getParentRoute: () => SettingsRoute,
|
|
98
99
|
} as any)
|
|
100
|
+
const SettingsLabelsRoute = SettingsLabelsRouteImport.update({
|
|
101
|
+
id: '/labels',
|
|
102
|
+
path: '/labels',
|
|
103
|
+
getParentRoute: () => SettingsRoute,
|
|
104
|
+
} as any)
|
|
99
105
|
const SettingsSendersRoute = SettingsSendersRouteImport.update({
|
|
100
106
|
id: '/senders',
|
|
101
107
|
path: '/senders',
|
|
@@ -120,6 +126,7 @@ export interface FileRoutesByFullPath {
|
|
|
120
126
|
'/settings/appearance': typeof SettingsAppearanceRoute
|
|
121
127
|
'/settings/filters': typeof SettingsFiltersRoute
|
|
122
128
|
'/settings/folders': typeof SettingsFoldersRoute
|
|
129
|
+
'/settings/labels': typeof SettingsLabelsRoute
|
|
123
130
|
'/settings/senders': typeof SettingsSendersRoute
|
|
124
131
|
'/settings/suggested-vips': typeof SettingsSuggestedVipsRoute
|
|
125
132
|
'/mail/': typeof MailIndexRoute
|
|
@@ -136,6 +143,7 @@ export interface FileRoutesByTo {
|
|
|
136
143
|
'/settings/appearance': typeof SettingsAppearanceRoute
|
|
137
144
|
'/settings/filters': typeof SettingsFiltersRoute
|
|
138
145
|
'/settings/folders': typeof SettingsFoldersRoute
|
|
146
|
+
'/settings/labels': typeof SettingsLabelsRoute
|
|
139
147
|
'/settings/senders': typeof SettingsSendersRoute
|
|
140
148
|
'/settings/suggested-vips': typeof SettingsSuggestedVipsRoute
|
|
141
149
|
'/mail': typeof MailIndexRoute
|
|
@@ -155,6 +163,7 @@ export interface FileRoutesById {
|
|
|
155
163
|
'/settings/appearance': typeof SettingsAppearanceRoute
|
|
156
164
|
'/settings/filters': typeof SettingsFiltersRoute
|
|
157
165
|
'/settings/folders': typeof SettingsFoldersRoute
|
|
166
|
+
'/settings/labels': typeof SettingsLabelsRoute
|
|
158
167
|
'/settings/senders': typeof SettingsSendersRoute
|
|
159
168
|
'/settings/suggested-vips': typeof SettingsSuggestedVipsRoute
|
|
160
169
|
'/mail/': typeof MailIndexRoute
|
|
@@ -175,6 +184,7 @@ export interface FileRouteTypes {
|
|
|
175
184
|
| '/settings/appearance'
|
|
176
185
|
| '/settings/filters'
|
|
177
186
|
| '/settings/folders'
|
|
187
|
+
| '/settings/labels'
|
|
178
188
|
| '/settings/senders'
|
|
179
189
|
| '/settings/suggested-vips'
|
|
180
190
|
| '/mail/'
|
|
@@ -191,6 +201,7 @@ export interface FileRouteTypes {
|
|
|
191
201
|
| '/settings/appearance'
|
|
192
202
|
| '/settings/filters'
|
|
193
203
|
| '/settings/folders'
|
|
204
|
+
| '/settings/labels'
|
|
194
205
|
| '/settings/senders'
|
|
195
206
|
| '/settings/suggested-vips'
|
|
196
207
|
| '/mail'
|
|
@@ -209,6 +220,7 @@ export interface FileRouteTypes {
|
|
|
209
220
|
| '/settings/appearance'
|
|
210
221
|
| '/settings/filters'
|
|
211
222
|
| '/settings/folders'
|
|
223
|
+
| '/settings/labels'
|
|
212
224
|
| '/settings/senders'
|
|
213
225
|
| '/settings/suggested-vips'
|
|
214
226
|
| '/mail/'
|
|
@@ -322,6 +334,13 @@ declare module '@tanstack/react-router' {
|
|
|
322
334
|
preLoaderRoute: typeof SettingsFoldersRouteImport
|
|
323
335
|
parentRoute: typeof SettingsRoute
|
|
324
336
|
}
|
|
337
|
+
'/settings/labels': {
|
|
338
|
+
id: '/settings/labels'
|
|
339
|
+
path: '/labels'
|
|
340
|
+
fullPath: '/settings/labels'
|
|
341
|
+
preLoaderRoute: typeof SettingsLabelsRouteImport
|
|
342
|
+
parentRoute: typeof SettingsRoute
|
|
343
|
+
}
|
|
325
344
|
'/settings/senders': {
|
|
326
345
|
id: '/settings/senders'
|
|
327
346
|
path: '/senders'
|
|
@@ -361,6 +380,7 @@ interface SettingsRouteChildren {
|
|
|
361
380
|
SettingsAppearanceRoute: typeof SettingsAppearanceRoute
|
|
362
381
|
SettingsFiltersRoute: typeof SettingsFiltersRoute
|
|
363
382
|
SettingsFoldersRoute: typeof SettingsFoldersRoute
|
|
383
|
+
SettingsLabelsRoute: typeof SettingsLabelsRoute
|
|
364
384
|
SettingsSendersRoute: typeof SettingsSendersRoute
|
|
365
385
|
SettingsSuggestedVipsRoute: typeof SettingsSuggestedVipsRoute
|
|
366
386
|
SettingsIndexRoute: typeof SettingsIndexRoute
|
|
@@ -372,6 +392,7 @@ const SettingsRouteChildren: SettingsRouteChildren = {
|
|
|
372
392
|
SettingsAppearanceRoute: SettingsAppearanceRoute,
|
|
373
393
|
SettingsFiltersRoute: SettingsFiltersRoute,
|
|
374
394
|
SettingsFoldersRoute: SettingsFoldersRoute,
|
|
395
|
+
SettingsLabelsRoute: SettingsLabelsRoute,
|
|
375
396
|
SettingsSendersRoute: SettingsSendersRoute,
|
|
376
397
|
SettingsSuggestedVipsRoute: SettingsSuggestedVipsRoute,
|
|
377
398
|
SettingsIndexRoute: SettingsIndexRoute,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
mailboxOperationsListMailboxesOptions,
|
|
4
4
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
5
5
|
import type { RemitImapAccountResponse } from "@remit/api-http-client/types.gen.ts";
|
|
6
|
-
import { SettingsShell } from "@remit/ui";
|
|
6
|
+
import { type LabelOption, SettingsShell } from "@remit/ui";
|
|
7
7
|
import { useQuery } from "@tanstack/react-query";
|
|
8
8
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
9
9
|
import { useCallback, useMemo, useState } from "react";
|
|
@@ -11,6 +11,7 @@ import { FilterEditorSurface } from "@/components/settings/FilterEditorSurface";
|
|
|
11
11
|
import { FiltersList } from "@/components/settings/FiltersList";
|
|
12
12
|
import { ErrorState } from "@/components/ui/ErrorState";
|
|
13
13
|
import { useDeleteFilter, useFilterList } from "@/hooks/useFilters";
|
|
14
|
+
import { useLabelList } from "@/hooks/useLabels";
|
|
14
15
|
import { getMailboxDisplayName } from "@/lib/folder-roles";
|
|
15
16
|
import { buildMoveTargets } from "@/lib/move-targets";
|
|
16
17
|
import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
|
|
@@ -64,6 +65,21 @@ function AccountFilters({ account }: { account: RemitImapAccountResponse }) {
|
|
|
64
65
|
[mailboxesData?.items],
|
|
65
66
|
);
|
|
66
67
|
|
|
68
|
+
const { labels: labelItems } = useLabelList(accountId);
|
|
69
|
+
const labels: LabelOption[] = useMemo(
|
|
70
|
+
() =>
|
|
71
|
+
labelItems.map((label) => ({
|
|
72
|
+
id: label.labelId,
|
|
73
|
+
name: label.name,
|
|
74
|
+
color: label.color,
|
|
75
|
+
})),
|
|
76
|
+
[labelItems],
|
|
77
|
+
);
|
|
78
|
+
const labelById = useMemo(
|
|
79
|
+
() => new Map(labels.map((label) => [label.id, label])),
|
|
80
|
+
[labels],
|
|
81
|
+
);
|
|
82
|
+
|
|
67
83
|
const editingFilter = filters.find(
|
|
68
84
|
(filter) => filter.filterId === editingFilterId,
|
|
69
85
|
);
|
|
@@ -76,6 +92,7 @@ function AccountFilters({ account }: { account: RemitImapAccountResponse }) {
|
|
|
76
92
|
accountId={accountId}
|
|
77
93
|
filter={editingFilter}
|
|
78
94
|
folders={folders}
|
|
95
|
+
labels={labels}
|
|
79
96
|
onClose={() => setEditingFilterId(undefined)}
|
|
80
97
|
/>
|
|
81
98
|
)}
|
|
@@ -99,6 +116,7 @@ function AccountFilters({ account }: { account: RemitImapAccountResponse }) {
|
|
|
99
116
|
<FiltersList
|
|
100
117
|
filters={filters}
|
|
101
118
|
mailboxName={mailboxName}
|
|
119
|
+
labelById={labelById}
|
|
102
120
|
onEdit={setEditingFilterId}
|
|
103
121
|
onDelete={deleteFilter}
|
|
104
122
|
deletingFilterId={deletingFilterId}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { configOperationsGetConfigOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
2
|
+
import type {
|
|
3
|
+
RemitImapAccountResponse,
|
|
4
|
+
RemitImapLabelColor,
|
|
5
|
+
RemitImapLabelResponse,
|
|
6
|
+
} from "@remit/api-http-client/types.gen.ts";
|
|
7
|
+
import {
|
|
8
|
+
Banner,
|
|
9
|
+
Button,
|
|
10
|
+
Input,
|
|
11
|
+
labelColorOptions,
|
|
12
|
+
Select,
|
|
13
|
+
SettingsShell,
|
|
14
|
+
} from "@remit/ui";
|
|
15
|
+
import { useQuery } from "@tanstack/react-query";
|
|
16
|
+
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { LabelsList } from "@/components/settings/LabelsList";
|
|
19
|
+
import { ConfirmDialog } from "@/components/ui/ConfirmDialog";
|
|
20
|
+
import { ErrorState } from "@/components/ui/ErrorState";
|
|
21
|
+
import {
|
|
22
|
+
useCreateLabel,
|
|
23
|
+
useDeleteLabel,
|
|
24
|
+
useLabelList,
|
|
25
|
+
useUpdateLabel,
|
|
26
|
+
} from "@/hooks/useLabels";
|
|
27
|
+
import { deleteLabelConfirmCopy } from "@/lib/organize/label-delete-copy";
|
|
28
|
+
import { SETTINGS_ID_TO_PATH, SETTINGS_NAV_ITEMS } from "@/routes/settings";
|
|
29
|
+
|
|
30
|
+
export const Route = createFileRoute("/settings/labels")({
|
|
31
|
+
component: LabelsSettings,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const labelsHelp = (
|
|
35
|
+
<div className="space-y-3">
|
|
36
|
+
<p>
|
|
37
|
+
Labels mark mail without moving it — a message can carry any number of
|
|
38
|
+
them alongside whichever folder it lives in.
|
|
39
|
+
</p>
|
|
40
|
+
<p>
|
|
41
|
+
Use a label in a filter's action to have new (and existing) mail label
|
|
42
|
+
itself automatically, or apply one to a selection directly from the
|
|
43
|
+
mailbox.
|
|
44
|
+
</p>
|
|
45
|
+
<p>
|
|
46
|
+
Deleting a label removes it from every message, and deletes every filter
|
|
47
|
+
that applies it — the confirmation names how many.
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
/** Create a label for one account. A name, a color, and a create button. */
|
|
53
|
+
function NewLabel({ accountId }: { accountId: string }) {
|
|
54
|
+
const { createLabel, isPending, isError } = useCreateLabel(accountId);
|
|
55
|
+
const [name, setName] = useState("");
|
|
56
|
+
const [color, setColor] = useState<RemitImapLabelColor>("Default");
|
|
57
|
+
|
|
58
|
+
const handleCreate = () => {
|
|
59
|
+
const trimmed = name.trim();
|
|
60
|
+
if (trimmed === "") return;
|
|
61
|
+
createLabel(trimmed, color).then(() => {
|
|
62
|
+
setName("");
|
|
63
|
+
setColor("Default");
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="space-y-2 rounded-sm border border-line bg-surface p-3">
|
|
69
|
+
<p className="text-sm font-medium text-fg">New label</p>
|
|
70
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-end">
|
|
71
|
+
<div className="flex-1 space-y-1">
|
|
72
|
+
<span className="text-xs text-fg-muted">Name</span>
|
|
73
|
+
<Input
|
|
74
|
+
value={name}
|
|
75
|
+
onChange={(event) => setName(event.target.value)}
|
|
76
|
+
placeholder="e.g. Receipts"
|
|
77
|
+
aria-label="Label name"
|
|
78
|
+
onKeyDown={(event) => {
|
|
79
|
+
if (event.key === "Enter") {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
handleCreate();
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="space-y-1">
|
|
87
|
+
<span className="text-xs text-fg-muted">Color</span>
|
|
88
|
+
<Select
|
|
89
|
+
value={color}
|
|
90
|
+
onChange={(event) =>
|
|
91
|
+
setColor(event.target.value as RemitImapLabelColor)
|
|
92
|
+
}
|
|
93
|
+
aria-label="Label color"
|
|
94
|
+
className="w-28"
|
|
95
|
+
>
|
|
96
|
+
{labelColorOptions.map((option) => (
|
|
97
|
+
<option key={option} value={option}>
|
|
98
|
+
{option}
|
|
99
|
+
</option>
|
|
100
|
+
))}
|
|
101
|
+
</Select>
|
|
102
|
+
</div>
|
|
103
|
+
<Button
|
|
104
|
+
variant="primary"
|
|
105
|
+
onClick={handleCreate}
|
|
106
|
+
disabled={isPending || name.trim() === ""}
|
|
107
|
+
>
|
|
108
|
+
{isPending ? "Creating…" : "Create label"}
|
|
109
|
+
</Button>
|
|
110
|
+
</div>
|
|
111
|
+
{isError && (
|
|
112
|
+
<Banner tone="danger" variant="soft">
|
|
113
|
+
Couldn't create that label. Please try again.
|
|
114
|
+
</Banner>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function AccountLabels({ account }: { account: RemitImapAccountResponse }) {
|
|
121
|
+
const accountId = account.accountId;
|
|
122
|
+
const { labels, isPending, isError, error, refetch } =
|
|
123
|
+
useLabelList(accountId);
|
|
124
|
+
const { updateLabel } = useUpdateLabel(accountId);
|
|
125
|
+
const { deleteLabel, deletingLabelId } = useDeleteLabel(accountId);
|
|
126
|
+
const [pendingDelete, setPendingDelete] = useState<RemitImapLabelResponse>();
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<section className="space-y-2">
|
|
130
|
+
<h2 className="text-sm font-semibold text-fg">{account.email}</h2>
|
|
131
|
+
{isPending ? (
|
|
132
|
+
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: aria-label on loading skeleton provides useful context for assistive tech
|
|
133
|
+
<div
|
|
134
|
+
className="h-16 animate-pulse rounded-md border border-line bg-surface"
|
|
135
|
+
aria-busy="true"
|
|
136
|
+
aria-label={`Loading labels for ${account.email}`}
|
|
137
|
+
/>
|
|
138
|
+
) : isError ? (
|
|
139
|
+
<ErrorState
|
|
140
|
+
variant="inline"
|
|
141
|
+
title={`Couldn't load labels for ${account.email}`}
|
|
142
|
+
error={error}
|
|
143
|
+
onRetry={() => {
|
|
144
|
+
refetch();
|
|
145
|
+
}}
|
|
146
|
+
/>
|
|
147
|
+
) : (
|
|
148
|
+
<LabelsList
|
|
149
|
+
labels={labels}
|
|
150
|
+
onRename={(labelId, name) => updateLabel(labelId, { name })}
|
|
151
|
+
onRecolor={(labelId, color) =>
|
|
152
|
+
updateLabel(labelId, {
|
|
153
|
+
color: color as RemitImapLabelResponse["color"],
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
onDelete={setPendingDelete}
|
|
157
|
+
deletingLabelId={deletingLabelId}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
<NewLabel accountId={accountId} />
|
|
161
|
+
{pendingDelete &&
|
|
162
|
+
(() => {
|
|
163
|
+
const copy = deleteLabelConfirmCopy(
|
|
164
|
+
pendingDelete.name,
|
|
165
|
+
pendingDelete.filterCount,
|
|
166
|
+
);
|
|
167
|
+
return (
|
|
168
|
+
<ConfirmDialog
|
|
169
|
+
isOpen
|
|
170
|
+
title={copy.title}
|
|
171
|
+
description={copy.description}
|
|
172
|
+
confirmLabel="Delete label"
|
|
173
|
+
destructive
|
|
174
|
+
isBusy={deletingLabelId === pendingDelete.labelId}
|
|
175
|
+
onConfirm={() => {
|
|
176
|
+
deleteLabel(pendingDelete.labelId);
|
|
177
|
+
setPendingDelete(undefined);
|
|
178
|
+
}}
|
|
179
|
+
onCancel={() => setPendingDelete(undefined)}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
})()}
|
|
183
|
+
</section>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function LabelsSettings() {
|
|
188
|
+
const navigate = useNavigate();
|
|
189
|
+
const [helpOpen, setHelpOpen] = useState(true);
|
|
190
|
+
|
|
191
|
+
const {
|
|
192
|
+
data: config,
|
|
193
|
+
isPending,
|
|
194
|
+
isError,
|
|
195
|
+
error,
|
|
196
|
+
refetch,
|
|
197
|
+
} = useQuery(configOperationsGetConfigOptions());
|
|
198
|
+
|
|
199
|
+
const handleSelectNav = (id: string) => {
|
|
200
|
+
const path = SETTINGS_ID_TO_PATH[id];
|
|
201
|
+
if (path) void navigate({ to: path });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<SettingsShell
|
|
206
|
+
items={SETTINGS_NAV_ITEMS}
|
|
207
|
+
activeId="labels"
|
|
208
|
+
title="Labels"
|
|
209
|
+
description="Mark mail without moving it, per account."
|
|
210
|
+
help={labelsHelp}
|
|
211
|
+
helpOpen={helpOpen}
|
|
212
|
+
onToggleHelp={() => setHelpOpen((v) => !v)}
|
|
213
|
+
onSelect={handleSelectNav}
|
|
214
|
+
onBackToMail={() => void navigate({ to: "/mail" })}
|
|
215
|
+
>
|
|
216
|
+
{isPending ? (
|
|
217
|
+
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: aria-label on loading skeleton provides useful context for assistive tech
|
|
218
|
+
<div
|
|
219
|
+
className="h-24 animate-pulse rounded-sm border border-line bg-surface"
|
|
220
|
+
aria-busy="true"
|
|
221
|
+
aria-label="Loading accounts"
|
|
222
|
+
/>
|
|
223
|
+
) : isError ? (
|
|
224
|
+
<ErrorState
|
|
225
|
+
title="Couldn't load accounts"
|
|
226
|
+
error={error}
|
|
227
|
+
onRetry={() => {
|
|
228
|
+
refetch();
|
|
229
|
+
}}
|
|
230
|
+
/>
|
|
231
|
+
) : config.accounts.length === 0 ? (
|
|
232
|
+
<p className="py-12 text-sm text-fg-muted">No accounts configured.</p>
|
|
233
|
+
) : (
|
|
234
|
+
<div className="space-y-8">
|
|
235
|
+
{config.accounts.map((account) => (
|
|
236
|
+
<AccountLabels key={account.accountId} account={account} />
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
</SettingsShell>
|
|
241
|
+
);
|
|
242
|
+
}
|
package/src/routes/settings.tsx
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { SettingsNavItem } from "@remit/ui";
|
|
12
12
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
|
13
|
-
import { Filter, FolderTree, Inbox, Palette, Users } from "lucide-react";
|
|
13
|
+
import { Filter, FolderTree, Inbox, Palette, Tag, Users } from "lucide-react";
|
|
14
14
|
import { AdvancedNavIcon } from "@/components/settings/AdvancedNavIcon";
|
|
15
15
|
|
|
16
16
|
/* ------------------------------------------------------------------ */
|
|
@@ -34,6 +34,11 @@ export const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [
|
|
|
34
34
|
label: "Filters",
|
|
35
35
|
icon: <Filter className="size-4" />,
|
|
36
36
|
},
|
|
37
|
+
{
|
|
38
|
+
id: "labels",
|
|
39
|
+
label: "Labels",
|
|
40
|
+
icon: <Tag className="size-4" />,
|
|
41
|
+
},
|
|
37
42
|
{
|
|
38
43
|
id: "appearance",
|
|
39
44
|
label: "Appearance",
|
|
@@ -47,6 +52,7 @@ export const SETTINGS_ID_TO_PATH: Record<string, string> = {
|
|
|
47
52
|
senders: "/settings/senders",
|
|
48
53
|
folders: "/settings/folders",
|
|
49
54
|
filters: "/settings/filters",
|
|
55
|
+
labels: "/settings/labels",
|
|
50
56
|
appearance: "/settings/appearance",
|
|
51
57
|
advanced: "/settings/advanced",
|
|
52
58
|
};
|