Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@ import { useTranslations } from "next-intl";
import { useState } from "react";

import { AnimatedToast } from "@/components/toast/AnimatedToast";
import { MAX_CUSTOM_TAG_COUNT } from "@/schemas/tag/tag-schema";

export const TagLimitToastContainer = () => {
interface TagLimitToastContainerProps {
count?: number;
onClose?: () => void;
}

export const TagLimitToastContainer = ({
count = MAX_CUSTOM_TAG_COUNT,
onClose,
}: TagLimitToastContainerProps) => {
const [isOpen, setIsOpen] = useState<boolean>(true);
const t = useTranslations("Toast");

const handleClose = () => {
setIsOpen(false);
onClose?.();
};

return (
<AnimatedToast
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onClose={handleClose}
message={
<p className="mb-0">
{t.rich("tagLimit", {
count,
blue: (chunks) => (
<span className="text-timo-blue-300">{chunks}</span>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { useTranslations } from "next-intl";
import { overlay } from "overlay-kit";
import { useState } from "react";

import { TagLimitToastContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/toast/TagLimitToastContainer";
import { SettingsProfileView } from "@/app/[locale]/(main)/settings/_components/account/SettingsProfileView";
import { useSettingsProfile } from "@/app/[locale]/(main)/settings/_hooks/account/use-settings-profile";
import { useSettingsProfileLabels } from "@/app/[locale]/(main)/settings/_hooks/account/use-settings-profile-labels";
import { CreateTagModalContainer } from "@/components/tag/CreateTagModalContainer";
import { AnimatedToast } from "@/components/toast/AnimatedToast";
import { MAX_CUSTOM_TAG_COUNT } from "@/schemas/tag/tag-schema";

export const SettingsProfileContainer = () => {
const tToast = useTranslations("Toast");
Expand All @@ -20,6 +22,7 @@ export const SettingsProfileContainer = () => {
profileState.calendarConnected,
);
const [isTagErrorToastOpen, setIsTagErrorToastOpen] = useState(false);
const [isTagLimitToastOpen, setIsTagLimitToastOpen] = useState(false);
const [isLanguageErrorToastOpen, setIsLanguageErrorToastOpen] =
useState(false);

Expand All @@ -37,6 +40,15 @@ export const SettingsProfileContainer = () => {
};

const handleAddTag = () => {
const customTagCount = profileState.tags.filter(
(tag) => !tag.isDefault,
).length;

if (customTagCount >= MAX_CUSTOM_TAG_COUNT) {
setIsTagLimitToastOpen(true);
return;
}

const existingLabels = profileState.tags.map((tag) => tag.label);

overlay.open(({ isOpen, close, unmount }) => (
Expand Down Expand Up @@ -78,6 +90,10 @@ export const SettingsProfileContainer = () => {
onLogout={profileActions.onLogout}
/>

{isTagLimitToastOpen && (
<TagLimitToastContainer onClose={() => setIsTagLimitToastOpen(false)} />
)}

Comment on lines +93 to +96

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

도메인 간 직접 임포트를 피하고 AnimatedToast를 직접 활용해 보세요!

home 도메인에 있는 TagLimitToastContainersettings 도메인에서 직접 가져와 사용하는 것은 도메인 간 강결합을 유발합니다. 아키텍처 규칙("도메인 간 직접 import 금지")에 위배되기도 해요.

대신 이 파일 하단에서 이미 사용 중인 AnimatedToast를 직접 활용해 토스트를 렌더링하는 것을 추천합니다. 이렇게 하면 상태(state)가 이중으로 관리되는 문제도 예방할 수 있어 코드가 훨씬 깔끔해진답니다. React 공식 문서의 컴포넌트 독립성 유지를 참고해 보세요! ✨

♻️ 도메인 의존성을 제거하는 수정 제안
-      {isTagLimitToastOpen && (
-        <TagLimitToastContainer
-          count={MAX_SETTING_CUSTOM_TAG_COUNT}
-          onClose={() => setIsTagLimitToastOpen(false)}
-        />
-      )}
+      <AnimatedToast
+        isOpen={isTagLimitToastOpen}
+        onClose={() => setIsTagLimitToastOpen(false)}
+        message={
+          <p className="mb-0">
+            {tToast.rich("tagLimit", {
+              count: MAX_SETTING_CUSTOM_TAG_COUNT,
+              blue: (chunks) => (
+                <span className="text-timo-blue-300">{chunks}</span>
+              ),
+            })}
+          </p>
+        }
+      />

파일 상단의 TagLimitToastContainer 임포트도 함께 제거해 주세요:

// 7번 라인의 다음 코드를 삭제합니다.
import { TagLimitToastContainer } from "`@/app/`[locale]/(main)/(with-time-sidebar)/home/_containers/toast/TagLimitToastContainer";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/account/SettingsProfileContainer.tsx
around lines 95 - 101, Remove the TagLimitToastContainer import and replace its
usage in SettingsProfileContainer with the already-used AnimatedToast,
preserving the isTagLimitToastOpen visibility state,
MAX_SETTING_CUSTOM_TAG_COUNT message/count, and close behavior via
setIsTagLimitToastOpen(false).

Source: Path instructions

<AnimatedToast
isOpen={isTagErrorToastOpen}
onClose={() => setIsTagErrorToastOpen(false)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { useMyProfileQuery } from "@/queries/auth/use-my-profile-query";
import { useCreateTagMutation } from "@/queries/tag/use-create-tag-mutation";
import { useDeleteTagMutation } from "@/queries/tag/use-delete-tag-mutation";
import { useTagsQuery } from "@/queries/tag/use-tags-query";
import { tagCreateDataSchema } from "@/schemas/tag/tag-schema";
import {
MAX_CUSTOM_TAG_COUNT,
tagCreateDataSchema,
} from "@/schemas/tag/tag-schema";
import { getDefaultTagLabelKey } from "@/utils/todo/tag-label";

const LANGUAGE_REQUEST_MAP: Record<
Expand Down Expand Up @@ -87,6 +90,13 @@ export const useSettingsProfile = () => {
};

const handleCreateTag = (label: string, handlers: CreateTagHandlers) => {
const customTagCount = tagItems.filter((tag) => !tag.isDefault).length;

if (customTagCount >= MAX_CUSTOM_TAG_COUNT) {
handlers.onError();
return;
}

createTag(
{ name: label },
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useController, useForm } from "react-hook-form";
import type { CreateTodoRequest } from "@/schemas/todo/todo-schema";
import type { PriorityLevel } from "@repo/timo-design-system/ui";

import { TagLimitToastContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/toast/TagLimitToastContainer";
import { OverlayModal } from "@/components/modal/OverlayModal";
import { AnimatedToast } from "@/components/toast/AnimatedToast";
import { TodoIconField } from "@/components/todo-modal/common/TodoIconField";
Expand Down Expand Up @@ -214,19 +215,9 @@ export const CreateTodoModalContent = ({
</div>
</OverlayModal>

<AnimatedToast
isOpen={tagField.isTagLimitToastOpen}
onClose={tagField.closeTagLimitToast}
message={
<p className="mb-0">
{tToast.rich("tagLimit", {
blue: (chunks) => (
<span className="text-timo-blue-300">{chunks}</span>
),
})}
</p>
}
/>
{tagField.isTagLimitToastOpen && (
<TagLimitToastContainer onClose={tagField.closeTagLimitToast} />
)}

<AnimatedToast
isOpen={timeField.isAiDurationErrorToastOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
} from "@/api/generated/models";
import type { UpdateTodoSubmitHandlers } from "@/hooks/todo-modal/detail/use-update-todo-submit";

import { TagLimitToastContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/toast/TagLimitToastContainer";
import { OverlayModal } from "@/components/modal/OverlayModal";
import { AnimatedToast } from "@/components/toast/AnimatedToast";
import { TodoIconField } from "@/components/todo-modal/common/TodoIconField";
Expand Down Expand Up @@ -292,19 +293,9 @@ export const DetailTodoModalContent = ({
</Modal.Panel>
</Modal>

<AnimatedToast
isOpen={detailTodoForm.isTagLimitToastOpen}
onClose={detailTodoForm.closeTagLimitToast}
message={
<p className="mb-0">
{tToast.rich("tagLimit", {
blue: (chunks) => (
<span className="text-timo-blue-300">{chunks}</span>
),
})}
</p>
}
/>
{detailTodoForm.isTagLimitToastOpen && (
<TagLimitToastContainer onClose={detailTodoForm.closeTagLimitToast} />
)}

<AnimatedToast
isOpen={detailTodoForm.isCreateTagErrorToastOpen}
Expand Down
12 changes: 8 additions & 4 deletions apps/timo-web/hooks/todo-modal/common/use-tag-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import type { Control, FieldValues, Path } from "react-hook-form";
import { CreateTagModalContainer } from "@/components/tag/CreateTagModalContainer";
import { useCreateTagMutation } from "@/queries/tag/use-create-tag-mutation";
import { useTagsQuery } from "@/queries/tag/use-tags-query";
import { tagCreateDataSchema } from "@/schemas/tag/tag-schema";
import {
MAX_CUSTOM_TAG_COUNT,
tagCreateDataSchema,
} from "@/schemas/tag/tag-schema";
import { getDefaultTagLabelKey } from "@/utils/todo/tag-label";

const MAX_TAG_COUNT = 8;

export interface UseTagFieldParams<TFieldValues extends FieldValues> {
control: Control<TFieldValues>;
name?: Path<TFieldValues>;
Expand Down Expand Up @@ -61,6 +62,9 @@ export const useTagField = <TFieldValues extends FieldValues>({
const selectedTagOption = tagOptions.find(
(option) => option.id === field.value,
);
const customTagCount = (tagsQuery.data?.tags ?? []).filter(
(tag) => !tag.isDefault,
).length;

const handleSelectTag = (label: string) => {
const option = tagOptions.find((item) => item.label === label);
Expand All @@ -74,7 +78,7 @@ export const useTagField = <TFieldValues extends FieldValues>({
};

const handleAddTagClick = () => {
if (tagOptions.length >= MAX_TAG_COUNT) {
if (customTagCount >= MAX_CUSTOM_TAG_COUNT) {
setIsTagLimitToastOpen(true);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/timo-web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
}
},
"Toast": {
"tagLimit": "You can create up to <blue>8 tags</blue>, and to add more, please delete existing tags.",
"tagLimit": "You can create up to <blue>{count} tags</blue>, and to add more, please delete existing tags.",
"todoLimitLine1": "You can add up to <blue>20 to-dos</blue> per day.",
"todoLimitLine2": "To add a new to-do, please delete an existing one.",
"onboardingSubmitFailed": "Failed to save onboarding. Please try again.",
Expand Down
2 changes: 1 addition & 1 deletion apps/timo-web/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
}
},
"Toast": {
"tagLimit": "태그는 <blue>최대 8개</blue>까지 만들 수 있으며, 추가하려면 기존 태그를 삭제해 주세요.",
"tagLimit": "태그는 <blue>최대 {count}개</blue>까지 만들 수 있으며, 추가하려면 기존 태그를 삭제해 주세요.",
"todoLimitLine1": "투두는 하루에 <blue>최대 20개</blue>까지 추가할 수 있어요.",
"todoLimitLine2": "새로운 투두를 추가하려면 기존 투두를 삭제해주세요.",
"onboardingSubmitFailed": "온보딩 저장에 실패했어요. 다시 시도해 주세요.",
Expand Down
2 changes: 2 additions & 0 deletions apps/timo-web/schemas/tag/tag-schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { z } from "zod";

export const MAX_CUSTOM_TAG_COUNT = 4;

export const tagSchema = z.object({
tagId: z.number(),
name: z.string(),
Expand Down
Loading