|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import { Article } from "@/backend/models/domain-models"; |
| 3 | +import { Article, Tag } from "@/backend/models/domain-models"; |
4 | 4 | import * as articleActions from "@/backend/services/article.actions"; |
| 5 | +import * as tagActions from "@/backend/services/tag.action"; |
5 | 6 | import { ArticleRepositoryInput } from "@/backend/services/inputs/article.input"; |
6 | 7 | import MultipleSelector from "@/components/ui/multi-select"; |
7 | 8 | import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; |
8 | 9 | import { useTranslation } from "@/i18n/use-translation"; |
9 | 10 | import { useSession } from "@/store/session.atom"; |
10 | 11 | import { zodResolver } from "@hookform/resolvers/zod"; |
11 | | -import { useMutation } from "@tanstack/react-query"; |
| 12 | +import { useMutation, useQuery } from "@tanstack/react-query"; |
12 | 13 | import { LinkIcon, Loader } from "lucide-react"; |
13 | 14 | import { useRouter } from "next/navigation"; |
14 | | -import React from "react"; |
| 15 | +import React, { useState } from "react"; |
15 | 16 | import { SubmitHandler, useForm } from "react-hook-form"; |
16 | 17 | import { z } from "zod"; |
17 | 18 | import { Button } from "../ui/button"; |
@@ -52,6 +53,8 @@ const ArticleEditorDrawer: React.FC<Props> = ({ article, open, onClose }) => { |
52 | 53 | }, |
53 | 54 | }); |
54 | 55 |
|
| 56 | + const [selectedTags, setSelectedTags] = useState<Tag[]>(article.tags ?? []); |
| 57 | + |
55 | 58 | const setDebounceHandler = useDebouncedCallback(async (slug: string) => { |
56 | 59 | const handle = await articleActions.getUniqueArticleHandle(slug); |
57 | 60 | form.setValue("handle", handle); |
@@ -108,49 +111,104 @@ const ArticleEditorDrawer: React.FC<Props> = ({ article, open, onClose }) => { |
108 | 111 | onSubmit={form.handleSubmit(handleOnSubmit)} |
109 | 112 | className="flex flex-col gap-2" |
110 | 113 | > |
111 | | - {/* {JSON.stringify(form.formState.errors)} */} |
112 | | - <pre>{JSON.stringify(article, null, 2)}</pre> |
113 | | - <FormField |
114 | | - control={form.control} |
115 | | - name="handle" |
116 | | - render={({ field }) => ( |
117 | | - <FormItem> |
118 | | - <FormLabel>{_t("Handle")}</FormLabel> |
119 | | - <FormControl> |
120 | | - <Input |
121 | | - placeholder="Your handle" |
122 | | - Prefix={ |
123 | | - <LinkIcon className="size-3 text-muted-foreground" /> |
124 | | - } |
125 | | - {...field} |
126 | | - onChange={(e) => { |
127 | | - setDebounceHandler(e.target.value); |
128 | | - form.setValue("handle", e.target.value); |
129 | | - }} |
130 | | - /> |
131 | | - </FormControl> |
132 | | - <FormDescription className="-mt-1"> |
133 | | - https://www.techdiary.dev/@{session?.user?.username}/ |
134 | | - {form.watch("handle")} |
135 | | - </FormDescription> |
136 | | - <FormMessage /> |
137 | | - </FormItem> |
138 | | - )} |
139 | | - /> |
140 | | - {/* */} |
141 | | - <FormField |
142 | | - control={form.control} |
143 | | - name="excerpt" |
144 | | - render={({ field }) => ( |
145 | | - <FormItem> |
146 | | - <FormLabel>{_t("Excerpt")}</FormLabel> |
147 | | - <FormControl> |
148 | | - <Textarea {...field} /> |
149 | | - </FormControl> |
150 | | - <FormMessage /> |
151 | | - </FormItem> |
152 | | - )} |
153 | | - /> |
| 114 | + {JSON.stringify({ |
| 115 | + errors: form.formState.errors, |
| 116 | + values: form.watch("tag_ids"), |
| 117 | + })} |
| 118 | + {/* <pre>{JSON.stringify(article, null, 2)}</pre> */} |
| 119 | + <div className="flex flex-col gap-6"> |
| 120 | + <FormField |
| 121 | + control={form.control} |
| 122 | + name="handle" |
| 123 | + render={({ field }) => ( |
| 124 | + <FormItem> |
| 125 | + <FormLabel>{_t("Handle")}</FormLabel> |
| 126 | + <FormControl> |
| 127 | + <Input |
| 128 | + placeholder="Your handle" |
| 129 | + Prefix={ |
| 130 | + <LinkIcon className="size-3 text-muted-foreground" /> |
| 131 | + } |
| 132 | + {...field} |
| 133 | + onChange={(e) => { |
| 134 | + setDebounceHandler(e.target.value); |
| 135 | + form.setValue("handle", e.target.value); |
| 136 | + }} |
| 137 | + /> |
| 138 | + </FormControl> |
| 139 | + <FormDescription className="-mt-1"> |
| 140 | + https://www.techdiary.dev/@{session?.user?.username}/ |
| 141 | + {form.watch("handle")} |
| 142 | + </FormDescription> |
| 143 | + <FormMessage /> |
| 144 | + </FormItem> |
| 145 | + )} |
| 146 | + /> |
| 147 | + <FormField |
| 148 | + control={form.control} |
| 149 | + name="excerpt" |
| 150 | + render={({ field }) => ( |
| 151 | + <FormItem> |
| 152 | + <FormLabel>{_t("Excerpt")}</FormLabel> |
| 153 | + <FormControl> |
| 154 | + <Textarea {...field} /> |
| 155 | + </FormControl> |
| 156 | + <FormMessage /> |
| 157 | + </FormItem> |
| 158 | + )} |
| 159 | + /> |
| 160 | + <FormField |
| 161 | + control={form.control} |
| 162 | + name="tag_ids" |
| 163 | + render={({ field }) => ( |
| 164 | + <FormItem> |
| 165 | + <FormLabel>{_t("Tags")}</FormLabel> |
| 166 | + <FormDescription className="text-xs"> |
| 167 | + {_t("Select tags to help categorize your article.")} |
| 168 | + </FormDescription> |
| 169 | + <FormControl> |
| 170 | + <MultipleSelector |
| 171 | + maxSelected={10} |
| 172 | + onSearch={async (searchTerm) => { |
| 173 | + const res = await tagActions.getTags({ |
| 174 | + limit: -1, |
| 175 | + page: 1, |
| 176 | + search: searchTerm, |
| 177 | + }); |
| 178 | + |
| 179 | + const searchResult = res?.nodes ?? []; |
| 180 | + return searchResult?.map((tag) => ({ |
| 181 | + label: tag.name, |
| 182 | + value: tag.id, |
| 183 | + })); |
| 184 | + }} |
| 185 | + value={ |
| 186 | + selectedTags?.map((option) => ({ |
| 187 | + label: option.name, |
| 188 | + value: option.id, |
| 189 | + })) ?? [] |
| 190 | + } |
| 191 | + onChange={(e) => { |
| 192 | + setSelectedTags( |
| 193 | + e.map((option) => ({ |
| 194 | + id: option.value, |
| 195 | + name: option.label, |
| 196 | + created_at: new Date(), |
| 197 | + updated_at: new Date(), |
| 198 | + })) |
| 199 | + ); |
| 200 | + form.setValue( |
| 201 | + "tag_ids", |
| 202 | + e.map((option) => option.value) |
| 203 | + ); |
| 204 | + }} |
| 205 | + /> |
| 206 | + </FormControl> |
| 207 | + <FormMessage /> |
| 208 | + </FormItem> |
| 209 | + )} |
| 210 | + /> |
| 211 | + </div> |
154 | 212 |
|
155 | 213 | {/* Seo settings */} |
156 | 214 | <div className="flex flex-col gap-6 mt-10"> |
|
0 commit comments