-
Notifications
You must be signed in to change notification settings - Fork 2
Update after demo: 추가 최적화 및 기타 수정 #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
23dbe48
698ed91
b6cb35d
55f4a75
2763cf3
cf2157a
4ec8eaf
2b3894e
d1ca615
0a0ea18
eda3e43
86acdfe
978b3aa
a16fd87
ee6afc7
70ebaa3
bcc1cd5
7d5250b
02b17e6
7041351
5bf678b
5104f2a
df656bb
a5446c8
fabde4e
68ce4ea
21316d4
272432d
2e05c03
583c613
d29eeff
7a7a68a
3136c68
8801660
5c7a59d
3dc406c
b2629d4
dfac342
f065d94
525b888
8bf61b6
f08aa2a
08b4560
ee7a20e
1eb042b
396aab4
9387475
ab5fa12
8ee477b
d294635
55a220a
f07a016
e1b6af9
891c5e5
c2f7bec
08de64e
3f3ad2d
3d64154
99ac5b6
1589563
6a2f808
25a404b
23bc5da
af4bcf6
7c97603
d494c02
d313a9c
059e897
15c7ec4
2e7f446
b2f3216
5f81d91
c4a0e97
b7c9b3a
074ebf4
bc22043
010a449
a5e0ba9
be8d56d
4b3ae63
3ac8db2
3544b0a
3df8115
f290846
6d9f881
e8f1572
5e429ea
96c1443
81f8a50
2880789
2b186c7
0fa30b6
48d36d9
7a294f4
f996952
fc1fc8c
0843420
e551646
8084437
4c51f90
7335ef3
398f6c5
d4527a1
80b7d35
43e0d7b
e5eebd3
742c292
fc3f8b0
6b0e02b
b05e3a5
efee88a
71b6498
b78646d
8543b55
e750173
7cd8527
78d9b87
4d28b75
075108f
92fce12
96cb9c9
5aa099c
c07c9be
c6b066d
e3d324e
53f6b09
48e5a1f
f4aee9e
4766a1f
4a699eb
560ac3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { Api, HttpClient } from "./generated/motimo/Api"; | ||
| import { Api, HttpClient, HttpResponse } from "./generated/motimo/Api"; | ||
| import useAuthStore from "../stores/useAuthStore"; | ||
| import useToastStore from "@/stores/useToastStore"; | ||
|
|
||
|
|
@@ -18,10 +18,12 @@ const httpClient = new HttpClient({ | |
| } | ||
|
|
||
| const isGuest = useAuthStore.getState().isGuest; | ||
| if (isGuest) { | ||
| // 게스트거나 토큰 만료의 경우 | ||
| if (isGuest || !token) { | ||
| return { format: "json" }; | ||
| } | ||
|
|
||
| // 임의의 경우를 위해 남겨둠. | ||
| return {}; | ||
| }, | ||
| }); | ||
|
|
@@ -33,19 +35,23 @@ const showToast = (content: string, createdAt: Date) => { | |
|
|
||
| // Debouncer 감싸도 될 것 같은데? | ||
| const debounceer = <T, E>(apiRequest: typeof httpClient.request<T, E>) => { | ||
| const timeLimit = 1000; | ||
| let timer: number; | ||
| const timeLimit = 300; | ||
| const timerDictionary: { [apiFullUrl: string]: number } = {}; | ||
| let rejectTimer: (reason?: any) => void; | ||
| return ( | ||
| requestParams: Parameters<typeof httpClient.request<T, E>>[0], | ||
| ): ReturnType<typeof httpClient.request<T>> => { | ||
| const apiFullUrl = `${requestParams.path}?${requestParams.query}`; | ||
| const timer = timerDictionary[apiFullUrl]; | ||
|
|
||
|
Comment on lines
+44
to
46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Serialize
The diff above already replaces lines 44-46 with a safe 🤖 Prompt for AI Agents |
||
| if (timer) { | ||
| clearTimeout(timer); | ||
| rejectTimer("debouncing"); | ||
| } | ||
|
Comment on lines
37
to
50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Debouncer has race/incorrect rejection issues; make timers and rejectors per-URL and reject with a cancel sentinel
Apply: -const debounceer = <T, E>(apiRequest: typeof httpClient.request<T, E>) => {
- const timeLimit = 300;
- const timerDictionary: { [apiFullUrl: string]: number } = {};
- let rejectTimer: (reason?: any) => void;
+const debounceer = <T, E>(apiRequest: typeof httpClient.request<T, E>) => {
+ const timeLimit = 300;
+ const timerDictionary: Record<string, ReturnType<typeof setTimeout>> = {};
+ const rejectDictionary: Record<string, (reason?: any) => void> = {};
return (
- requestParams: Parameters<typeof httpClient.request<T, E>>[0],
- ): ReturnType<typeof httpClient.request<T>> => {
- const apiFullUrl = `${requestParams.path}?${requestParams.query}`;
- const timer = timerDictionary[apiFullUrl];
+ requestParams: Parameters<typeof httpClient.request<T, E>>[0],
+ ): ReturnType<typeof httpClient.request<T, E>> => {
+ const query =
+ typeof requestParams.query === "string"
+ ? requestParams.query
+ : requestParams.query
+ ? new URLSearchParams(requestParams.query as Record<string, any>).toString()
+ : "";
+ const apiFullUrl = `${requestParams.path}${query ? `?${query}` : ""}`;
+ const timer = timerDictionary[apiFullUrl];
if (timer) {
clearTimeout(timer);
- rejectTimer("debouncing");
+ rejectDictionary[apiFullUrl]?.(
+ Object.assign(new Error("request debounced"), {
+ name: "AbortError",
+ code: "ERR_CANCELED",
+ canceled: true,
+ })
+ );
}
- const apiRes: Promise<T> = new Promise((resolve, reject) => {
- rejectTimer = reject;
- timerDictionary[apiFullUrl] = Number(
- // timer = Number(
- setTimeout(async () => {
- try {
- const res = apiRequest(requestParams);
- resolve(res);
- } catch (error) {
- console.error(error);
- showToast(`API ERROR`, new Date());
- }
- }, timeLimit),
- );
+ const apiRes: Promise<T> = new Promise((resolve, reject) => {
+ rejectDictionary[apiFullUrl] = reject;
+ timerDictionary[apiFullUrl] = setTimeout(async () => {
+ try {
+ // see token/401 handling below
+ const res = await apiRequest(requestParams);
+ resolve(res);
+ } catch (error) {
+ // rejection handled below (401 retry) or propagated
+ reject(error);
+ } finally {
+ delete timerDictionary[apiFullUrl];
+ delete rejectDictionary[apiFullUrl];
+ }
+ }, timeLimit);
});Also applies to: 53-65 🤖 Prompt for AI Agents |
||
| const apiRes: Promise<T> = new Promise((resolve, reject) => { | ||
| rejectTimer = reject; | ||
| timer = Number( | ||
| timerDictionary[apiFullUrl] = Number( | ||
| // timer = Number( | ||
| setTimeout(async () => { | ||
| try { | ||
| const res = apiRequest(requestParams); | ||
|
|
@@ -57,10 +63,41 @@ const debounceer = <T, E>(apiRequest: typeof httpClient.request<T, E>) => { | |
| }, timeLimit), | ||
| ); | ||
| }); | ||
|
|
||
| // 토큰 재발급 처리 | ||
| tokenHandler(apiRes); | ||
|
|
||
|
Comment on lines
+67
to
69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 401 handling doesn’t retry the original request; Current approach calls Apply: - // 토큰 재발급 처리
- tokenHandler(apiRes);
-
return apiRes;
};
};
-// 토큰 처리
-const tokenHandler = async <T, E>(
- apiRes: ReturnType<typeof httpClient.request<T, E>>,
-) => {
- return apiRes.catch(async (e) => {
- if (e.status === 401) {
- const token = useAuthStore.getState().refreshToken;
-
- if (!token) {
- api.authController.logout();
- throw new Error("no refresh token");
- }
- const tokenRes = await api.authController.reissue({
- refreshToken: token,
- });
-
- if (!tokenRes?.accessToken || !tokenRes?.refreshToken) {
- throw new Error("token reissue error");
- }
-
- useAuthStore.setState((states) => ({
- ...states,
- accessToken: tokenRes.accessToken,
- refreshToken: tokenRes.refreshToken,
- }));
- }
- });
-};
+// Refresh tokens helper
+const refreshTokens = async () => {
+ const token = useAuthStore.getState().refreshToken;
+ if (!token) {
+ api.authController.logout();
+ throw new Error("no refresh token");
+ }
+ const tokenRes = await api.authController.reissue({ refreshToken: token });
+ if (!tokenRes?.accessToken || !tokenRes?.refreshToken) {
+ throw new Error("token reissue error");
+ }
+ useAuthStore.setState((s) => ({
+ ...s,
+ accessToken: tokenRes.accessToken,
+ refreshToken: tokenRes.refreshToken,
+ }));
+};And augment the debounced execution to retry on 401 (within the earlier diff block): - try {
- // see token/401 handling below
- const res = await apiRequest(requestParams);
- resolve(res);
- } catch (error) {
- // rejection handled below (401 retry) or propagated
- reject(error);
- } finally {
+ try {
+ try {
+ const res = await apiRequest(requestParams);
+ resolve(res);
+ } catch (error: any) {
+ if ((error as any)?.status === 401) {
+ await refreshTokens();
+ const retried = await apiRequest(requestParams);
+ resolve(retried);
+ } else {
+ throw error;
+ }
+ }
+ } catch (finalErr) {
+ showToast(`API ERROR`, new Date());
+ reject(finalErr);
+ } finally {Also applies to: 74-100, 55-63 |
||
| return apiRes; | ||
| }; | ||
| }; | ||
| // 토큰 처리 | ||
| const tokenHandler = async <T, E>( | ||
| apiRes: ReturnType<typeof httpClient.request<T, E>>, | ||
| ) => { | ||
| return apiRes.catch(async (e) => { | ||
| if (e.status === 401) { | ||
| const token = useAuthStore.getState().refreshToken; | ||
|
|
||
| if (!token) { | ||
| api.authController.logout(); | ||
| throw new Error("no refresh token"); | ||
| } | ||
| const tokenRes = await api.authController.reissue({ | ||
| refreshToken: token, | ||
| }); | ||
|
|
||
| if (!tokenRes?.accessToken || !tokenRes?.refreshToken) { | ||
| throw new Error("token reissue error"); | ||
| } | ||
|
|
||
| useAuthStore.setState((states) => ({ | ||
| ...states, | ||
| accessToken: tokenRes.accessToken, | ||
| refreshToken: tokenRes.refreshToken, | ||
| })); | ||
| } | ||
| }); | ||
| }; | ||
| httpClient.request = debounceer(httpClient.request); | ||
|
|
||
| // API 클라이언트 인스턴스 생성 | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ import { TodoResultRqEmotionEnum } from "@/api/generated/motimo/Api"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { subGoalApi, todoApi } from "@/api/service"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { postTodoResult } from "@/lib/fetching/postTodoResult"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| makeSubgoalInfiniteOptimisticData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useObservingExist, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // useObservingInfiniteOffset, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useSubGoalTodosAllInfinite, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -27,6 +28,7 @@ import { date2StringWithSpliter } from "@/utils/date2String"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import useBottomSheet from "@/hooks/useBottomSheet"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import useGoalWithSubGoalTodo from "@/hooks/queries/useGoalWithSubGoalTodo"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { AnimatePresence, motion } from "motion/react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface ListCardProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subGoalInfo: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -54,14 +56,14 @@ const ListCard = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 이 안에서도 subGoal에대한 todod들 가져오는 fetch있어야 함. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const observingRef = useRef<HTMLDivElement | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: fetchedTodoItemsInfo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mutate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isLoading, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isReachedLast, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSize, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isValidating, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = useSubGoalTodosAllInfinite(subGoalInfo.id ?? ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const existObserver = useObservingExist( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -152,7 +154,9 @@ const ListCard = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <main className="w-full flex-1 p-4 inline-flex flex-col justify-start items-center gap-5 overflow-hidden"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={`w-full flex-1 p-4 inline-flex flex-col justify-start items-center gap-5 overflow-hidden ${isLoading ? "opacity-50" : ""}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* <section className="flex items-center gap-2 w-full justify-between"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => onLeft()} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -289,32 +293,61 @@ const ListCard = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <section className="flex-1 w-full h-full gap-2 flex flex-col"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {(!checkedMore ? todoItemsInfo.slice(0, 5) : todoItemsInfo).map( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (todoInfo) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TodoItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChecked={async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onTodoCheck && onTodoCheck(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // const res = await todoApi.toggleTodoCompletion(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // if (res) mutate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title={todoInfo.title} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checked={todoInfo.checked} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={todoInfo.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onReportedClick={async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 일단 모달을 띄워야 함. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // postTodoResult(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setOpenBottomSheet(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTodoIdForResult(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reported={todoInfo.reported} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetDate={todoInfo.targetDate} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {!checkedMore && todoItemsInfo.length > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <section | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={`flex-1 w-full h-full gap-2 flex flex-col transition-opacity duration-300 ${isValidating ? "opacity-50" : ""}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <AnimatePresence> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {(!checkedMore ? todoItemsInfo.slice(0, 5) : todoItemsInfo).map( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (todoInfo) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const optimisticDataCallback = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| makeSubgoalInfiniteOptimisticData(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={todoInfo.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.3 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initial={{ opacity: 0, scale: 0.8 }} // 나타날 때 시작 상태 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| animate={{ opacity: 1, scale: 1 }} // 나타날 때 최종 상태 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit={{ opacity: 0, scale: 0.8 }} // 사라질 때 최종 상태 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TodoItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChecked={async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await mutate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| optimisticDataCallback, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // optimisticData: optimisticDataCallback, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| populateCache: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| revalidate: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rollbackOnError: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onTodoCheck && onTodoCheck(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // const res = await todoApi.toggleTodoCompletion(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // if (res) mutate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title={todoInfo.title} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checked={todoInfo.checked} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={todoInfo.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onReportedClick={async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 일단 모달을 띄워야 함. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // postTodoResult(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setOpenBottomSheet(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTodoIdForResult(todoInfo.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reported={todoInfo.reported} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetDate={todoInfo.targetDate} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </AnimatePresence> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+300
to
+349
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimistic update misconfigured: cache isn’t updated; request isn’t awaited.
Fix by using SWR’s optimisticData + in-mutate request (auto rollback on rejection) and let SWR revalidate. Apply: - <TodoItem
- onChecked={async () => {
- await mutate(
- optimisticDataCallback,
- {
- // optimisticData: optimisticDataCallback,
- populateCache: false,
- revalidate: false,
- rollbackOnError: true,
- },
- );
-
- onTodoCheck && onTodoCheck(todoInfo.id);
- }}
+ <TodoItem
+ onChecked={async () => {
+ await mutate(
+ async (current) => {
+ // perform server toggle; throw to trigger SWR rollback
+ await onTodoCheck?.(todoInfo.id);
+ return current; // keep optimistic cache until revalidate finishes
+ },
+ {
+ optimisticData: optimisticDataCallback,
+ populateCache: true,
+ revalidate: true,
+ rollbackOnError: true,
+ },
+ );
+ }}
title={todoInfo.title}
checked={todoInfo.checked}
- key={todoInfo.id}
+ /* key on motion.div is sufficient */Optional animation polish: - <AnimatePresence>
+ <AnimatePresence initial={false}>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {!checkedMore && todoItemsInfo.length > 5 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="w-full h-8 px-2 py-1 flex justify-center items-center" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,15 +9,20 @@ const Checkbox = ({ ...props }: CheckboxProps) => { | |||||||||||||
| <input | ||||||||||||||
| className={` | ||||||||||||||
| shrink-0 | ||||||||||||||
|
Comment on lines
10
to
11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add a visible keyboard focus style Currently focus can be invisible (appearance-none and outline-0 when checked). Ensure accessible focus indication. Add focus-visible styles in the base block: - shrink-0
+ shrink-0
+ focus-visible:outline-2 focus-visible:outline-Color-primary-70📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| ${`appearance-none w-4 h-4 relative bg-background-alternative rounded outline-[1.50px] outline-offset-[-1.50px] outline-Color-gray-20 overflow-hidden | ||||||||||||||
| ${`appearance-none w-4 h-4 relative rounded overflow-hidden | ||||||||||||||
| hover:outline-Color-gray-40 | ||||||||||||||
| checked:bg-center | ||||||||||||||
| checked:bg-background-strong | ||||||||||||||
| checked:outline-0 | ||||||||||||||
| checked:bg-[url("data:image/svg+xml,%3Csvg%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2014%2014%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M3.58325%207.27735L6.08343%209.77734L11.0833%204.77734%22%20stroke%3D%22white%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E")] | ||||||||||||||
| checked:hover:bg-Color-primary-70 | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| ${props.className ?? ""}`} | ||||||||||||||
| ${ | ||||||||||||||
| props.checked | ||||||||||||||
| ? `bg-center | ||||||||||||||
| bg-background-strong | ||||||||||||||
| bg-[url("data:image/svg+xml,%3Csvg%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2014%2014%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M3.58325%207.27735L6.08343%209.77734L11.0833%204.77734%22%20stroke%3D%22white%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E")] | ||||||||||||||
| outline-0 | ||||||||||||||
| hover:bg-Color-primary-70` | ||||||||||||||
| : `bg-background-alternative outline-[1.50px] outline-offset-[-1.50px] outline-Color-gray-20` | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+18
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Visual state tied to props.checked breaks uncontrolled checkboxes Relying on props.checked for styling desynchronizes UI when the input is used uncontrolled (e.g., with defaultChecked). Use Tailwind’s checked: variant so the visual state follows the actual input state. Apply this diff to remove the runtime conditional and style via pseudo-class: - ${
- props.checked
- ? `bg-center
- bg-background-strong
- bg-[url("data:image/svg+xml,%3Csvg%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2014%2014%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M3.58325%207.27735L6.08343%209.77734L11.0833%204.77734%22%20stroke%3D%22white%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E")]
- outline-0
- hover:bg-Color-primary-70`
- : `bg-background-alternative outline-[1.50px] outline-offset-[-1.50px] outline-Color-gray-20`
- }
+ bg-background-alternative
+ checked:bg-center
+ checked:bg-background-strong
+ checked:bg-[url("data:image/svg+xml,%3Csvg%20width%3D%2214%22%20height%3D%2214%22%20viewBox%3D%220%200%2014%2014%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M3.58325%207.27735L6.08343%209.77734L11.0833%204.77734%22%20stroke%3D%22white%22%20stroke-width%3D%221.5%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E")]
+ checked:outline-0
+ checked:hover:bg-Color-primary-70
🤖 Prompt for AI Agents |
||||||||||||||
| `} | ||||||||||||||
| type="checkbox" | ||||||||||||||
| {...props} | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix return type generic
Missing
EinReturnType<typeof httpClient.request<T, E>>causes typing drift.The diff above adjusts the signature at this line.
🤖 Prompt for AI Agents