Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit 24b7dea

Browse files
committed
Refactor topic and webhook forms to use modals
- Replaced TopicFormDialog with TopicFormModal for better modal handling. - Updated TopicDetail to use presentAddNotesToTopic for adding notes. - Removed WebhookEditorDialog and implemented WebhookEditorModal for webhook editing. - Adjusted TopicsRouteViewContent and WebhooksRouteViewContent to utilize new modal components. - Enhanced MasterDetailLayout to support pixel-based resizing for list components. - Cleaned up unused code and improved overall structure for better maintainability. Signed-off-by: Innei <tukon479@gmail.com>
1 parent adcfc03 commit 24b7dea

46 files changed

Lines changed: 1996 additions & 2281 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/admin/src/features/ai/components/AiGroupedResourceSurface.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,6 @@ export function AiGroupedResourceSurface<
180180

181181
return (
182182
<MasterDetailLayout
183-
defaultSize={0.36}
184-
maxSize={0.48}
185-
minSize={0.28}
186183
showDetailOnMobile={showDetailOnMobile}
187184
list={
188185
<section className="flex h-full min-h-0 flex-col bg-white dark:bg-neutral-950">

apps/admin/src/features/ai/components/AiTasksSurface.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,6 @@ export function AiTasksSurface() {
218218

219219
return (
220220
<MasterDetailLayout
221-
defaultSize={0.4}
222-
maxSize={0.5}
223-
minSize={0.3}
224221
showDetailOnMobile={showDetailOnMobile}
225222
list={
226223
<section className="flex h-full min-h-0 flex-col border-b border-neutral-200 lg:border-b-0 lg:border-r dark:border-neutral-800">

apps/admin/src/features/backup/components/BackupRouteViewContent.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,6 @@ export function BackupRouteViewContent() {
191191

192192
return (
193193
<MasterDetailLayout
194-
defaultSize={0.38}
195-
maxSize={0.45}
196-
minSize={0.25}
197194
showDetailOnMobile={showDetailOnMobile}
198195
list={
199196
<section className="flex h-full min-h-0 flex-col">

apps/admin/src/features/categories/components/CategoriesRouteViewContent.tsx

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { cn } from '~/utils/cn'
1414

1515
import { getErrorMessage } from '../utils/errors'
1616
import { CategoryDetail } from './CategoryDetail'
17-
import { CategoryFormDialog } from './CategoryFormDialog'
17+
import { presentCategoryForm } from './CategoryFormModal'
1818
import { CategoryRow } from './CategoryRow'
1919
import { DetailEmpty } from './DetailEmpty'
2020
import { EmptyList } from './EmptyList'
@@ -26,7 +26,6 @@ export function CategoriesRouteViewContent() {
2626
const { t } = useI18n()
2727
const queryClient = useQueryClient()
2828
const [selectedItem, setSelectedItem] = useState<SelectedItem | null>(null)
29-
const [formMode, setFormMode] = useState<CategoryFormMode | null>(null)
3029
const [showDetailOnMobile, setShowDetailOnMobile] = useState(false)
3130

3231
const categoriesQuery = useQuery({
@@ -91,11 +90,17 @@ export function CategoriesRouteViewContent() {
9190
setShowDetailOnMobile(true)
9291
}
9392

93+
const openForm = async (mode: CategoryFormMode) => {
94+
const category = await presentCategoryForm(mode)
95+
if (category) {
96+
setSelectedItem({ id: category.id, kind: 'category' })
97+
setShowDetailOnMobile(true)
98+
await invalidateCategories()
99+
}
100+
}
101+
94102
return (
95103
<MasterDetailLayout
96-
defaultSize={0.34}
97-
maxSize={0.44}
98-
minSize={0.25}
99104
showDetailOnMobile={showDetailOnMobile}
100105
list={
101106
<section className="flex h-full min-h-0 flex-col border-b border-neutral-200 lg:border-b-0 lg:border-r dark:border-neutral-800">
@@ -115,7 +120,7 @@ export function CategoriesRouteViewContent() {
115120
{categories.length} / {tags.length}
116121
</span>
117122
<Button
118-
onClick={() => setFormMode({ kind: 'create' })}
123+
onClick={() => void openForm({ kind: 'create' })}
119124
type="button"
120125
variant="subtle"
121126
>
@@ -135,7 +140,7 @@ export function CategoriesRouteViewContent() {
135140
action={
136141
<Button
137142
className="mt-3"
138-
onClick={() => setFormMode({ kind: 'create' })}
143+
onClick={() => void openForm({ kind: 'create' })}
139144
type="button"
140145
>
141146
{t('categories.list.create')}
@@ -200,7 +205,7 @@ export function CategoriesRouteViewContent() {
200205
deleteMutation.mutate(category.id)
201206
}
202207
}}
203-
onEdit={(category) => setFormMode({ category, kind: 'edit' })}
208+
onEdit={(category) => void openForm({ category, kind: 'edit' })}
204209
/>
205210
) : selectedTag ? (
206211
<TagDetail
@@ -212,19 +217,6 @@ export function CategoriesRouteViewContent() {
212217
)}
213218
</section>
214219
}
215-
>
216-
{formMode ? (
217-
<CategoryFormDialog
218-
mode={formMode}
219-
onClose={() => setFormMode(null)}
220-
onSaved={async (category) => {
221-
setFormMode(null)
222-
setSelectedItem({ id: category.id, kind: 'category' })
223-
setShowDetailOnMobile(true)
224-
await invalidateCategories()
225-
}}
226-
/>
227-
) : null}
228-
</MasterDetailLayout>
220+
/>
229221
)
230222
}

apps/admin/src/features/categories/components/CategoryFormDialog.tsx

Lines changed: 0 additions & 117 deletions
This file was deleted.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { useMutation } from '@tanstack/react-query'
2+
import { Loader2 } from 'lucide-react'
3+
import { FormEvent, useState } from 'react'
4+
import { toast } from 'sonner'
5+
import type { CreateCategoryData } from '~/api/categories'
6+
import type { CategoryModel } from '~/models/category'
7+
import type { CategoryFormMode } from '../types/categories'
8+
9+
import { createCategory, updateCategory } from '~/api/categories'
10+
import { useI18n } from '~/i18n'
11+
import { ModalHeader } from '~/ui/feedback/modal'
12+
import { present, useModal } from '~/ui/feedback/modal-imperative'
13+
import { Button } from '~/ui/primitives/button'
14+
import { TextInput } from '~/ui/primitives/text-field'
15+
16+
import { getErrorMessage } from '../utils/errors'
17+
18+
interface CategoryFormModalProps {
19+
mode: CategoryFormMode
20+
}
21+
22+
function CategoryFormModal(props: CategoryFormModalProps) {
23+
const { t } = useI18n()
24+
const modal = useModal<CategoryModel>()
25+
const [name, setName] = useState(
26+
props.mode.kind === 'edit' ? props.mode.category.name : '',
27+
)
28+
const [slug, setSlug] = useState(
29+
props.mode.kind === 'edit' ? props.mode.category.slug : '',
30+
)
31+
const title =
32+
props.mode.kind === 'edit'
33+
? t('categories.form.editTitle')
34+
: t('categories.form.createTitle')
35+
36+
const mutation = useMutation({
37+
mutationFn: (data: CreateCategoryData) =>
38+
props.mode.kind === 'edit'
39+
? updateCategory(props.mode.category.id, { ...data, type: 0 })
40+
: createCategory(data),
41+
onError: (error: unknown) =>
42+
toast.error(getErrorMessage(error, t('categories.form.saveFailed'))),
43+
onSuccess: (category) => {
44+
toast.success(
45+
props.mode.kind === 'edit'
46+
? t('categories.form.updated')
47+
: t('categories.form.created'),
48+
)
49+
modal.close(category)
50+
},
51+
})
52+
53+
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
54+
event.preventDefault()
55+
const payload = {
56+
name: name.trim(),
57+
slug: slug.trim(),
58+
}
59+
60+
if (!payload.name || !payload.slug) {
61+
toast.error(t('categories.form.validateRequired'))
62+
return
63+
}
64+
65+
mutation.mutate(payload)
66+
}
67+
68+
return (
69+
<form className="flex w-full flex-col" onSubmit={onSubmit}>
70+
<ModalHeader subtitle={t('categories.form.subtitle')} title={title} />
71+
<div className="grid gap-4 p-5">
72+
<TextInput
73+
autoFocus
74+
label={t('categories.form.name')}
75+
labelClassName="text-xs text-neutral-500 dark:text-neutral-400"
76+
onChange={setName}
77+
value={name}
78+
/>
79+
<TextInput
80+
controlClassName="font-mono"
81+
label={t('categories.form.slug')}
82+
labelClassName="text-xs text-neutral-500 dark:text-neutral-400"
83+
onChange={setSlug}
84+
value={slug}
85+
/>
86+
</div>
87+
<div className="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
88+
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
89+
{t('common.cancel')}
90+
</Button>
91+
<Button disabled={mutation.isPending} type="submit">
92+
{mutation.isPending ? (
93+
<Loader2 aria-hidden="true" className="size-4 animate-spin" />
94+
) : null}
95+
{t('common.save')}
96+
</Button>
97+
</div>
98+
</form>
99+
)
100+
}
101+
102+
/**
103+
* Open the category form modal. Resolves with the saved category on success.
104+
*/
105+
export async function presentCategoryForm(
106+
mode: CategoryFormMode,
107+
): Promise<CategoryModel | undefined> {
108+
const handle = present<CategoryFormModalProps, CategoryModel>(
109+
CategoryFormModal,
110+
{ mode },
111+
{
112+
modalProps: { popupStyle: { width: 'min(92vw, 28rem)' } },
113+
},
114+
)
115+
return await handle
116+
}

apps/admin/src/features/comments/components/CommentsRouteViewContent.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,6 @@ export function CommentsRouteViewContent() {
256256

257257
return (
258258
<MasterDetailLayout
259-
defaultSize={0.42}
260-
maxSize={0.5}
261-
minSize={0.25}
262259
showDetailOnMobile={showDetailOnMobile}
263260
list={
264261
<FocusScope

apps/admin/src/features/cron/components/CronRouteViewContent.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ export function CronRouteViewContent() {
162162

163163
return (
164164
<MasterDetailLayout
165-
defaultSize={0.42}
166165
detail={
167166
<section className="min-h-0">
168167
{selectedTask ? (
@@ -307,8 +306,6 @@ export function CronRouteViewContent() {
307306
</Scroll>
308307
</section>
309308
}
310-
maxSize={0.5}
311-
minSize={0.3}
312309
showDetailOnMobile={showDetailOnMobile}
313310
/>
314311
)

0 commit comments

Comments
 (0)