Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
e441cd0
feat: ์นด๋“œ ์ˆ˜์ • ๋ชจ๋‹ฌ์ฐฝ
jjangminii Sep 7, 2025
35c7804
Merge branch 'develop' of https://github.com/Pinback-Team/pinback-cliโ€ฆ
jjangminii Sep 8, 2025
1ca3a1a
feat: ๋ฐ์ดํŠธ ํ”ผ์ปค ์—ฐ๊ฒฐ
jjangminii Sep 8, 2025
55af71e
Merge branch 'develop' of https://github.com/Pinback-Team/pinback-cliโ€ฆ
jjangminii Sep 9, 2025
875a08a
Merge branch 'develop' of https://github.com/Pinback-Team/pinback-cliโ€ฆ
jjangminii Sep 9, 2025
5eb52bc
feat: popup ์˜คํ”ˆ
jjangminii Sep 9, 2025
8b82ef2
feat: onClose ์—ฐ๊ฒฐ
jjangminii Sep 9, 2025
eb3fcb3
feat: ์˜ค๋ฒ„๋ ˆ์ด
jjangminii Sep 9, 2025
fe47f5a
feat: ํ† ์ŠคํŠธ ์—ฐ๊ฒฐ
jjangminii Sep 9, 2025
746175a
Merge branch 'develop' of https://github.com/Pinback-Team/pinback-cliโ€ฆ
jjangminii Sep 10, 2025
62bd47d
feat: ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์—ฐ๊ฒฐ , ํ† ์ŠคํŠธ ์œ„์น˜ ์ •๋ฆฌ
jjangminii Sep 10, 2025
446b9e9
feat: ์นด๋“œ ํŽธ์ง‘ ๋ชจ๋‹ฌ ์ƒํƒœ
jjangminii Sep 10, 2025
98caeb1
chore: ์•ˆ์“ฐ๋Š” ์ฝ”๋“œ ์ •๋ฆฌ
jjangminii Sep 10, 2025
883ae6e
feat: ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ฐ˜์˜
jjangminii Sep 11, 2025
617d7fd
Merge branch 'develop' of https://github.com/Pinback-Team/pinback-cliโ€ฆ
jjangminii Sep 11, 2025
1cff62d
chore: ์•ˆ์“ฐ๋Š” ์ฝ”๋“œ ์ •๋ฆฌ
jjangminii Sep 11, 2025
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
8 changes: 3 additions & 5 deletions apps/client/src/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import { Sidebar } from '../shared/components/sidebar/Sidebar';
const Layout = () => {
return (
<>
<div className="flex h-screen">
<Sidebar />
<main className="flex-1 overflow-y-auto">
<div className="flex h-screen">
<Sidebar />
<Outlet />
</main>
</div>
</div>
</>
);
};
Expand Down
169 changes: 169 additions & 0 deletions apps/client/src/shared/components/cardEditModal/CardEditModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { Icon } from '@pinback/design-system/icons';
import {
AutoDismissToast,
Button,
DateTime,
Dropdown,
InfoBox,
PopupContainer,
Switch,
Textarea,
Toast,
validateDate,
validateTime,
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

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

์š”๊ฒŒ UI์•ˆ์ชฝ์œผ๋กœ ๋“ค์–ด๊ฐ€์žˆ๋‚˜์š”??

} from '@pinback/design-system/ui';
import { cn } from '@pinback/design-system/utils';
import { useState } from 'react';
Comment on lines +1 to +16
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

์œ ํšจ์„ฑ ํ•จ์ˆ˜ import ๊ฒฝ๋กœ ์˜ค๋ฅ˜ ๊ฐ€๋Šฅ์„ฑ

validateDate, validateTime๋ฅผ Design System์—์„œ importํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ด๋ฒˆ PR์—์„œ ์ถ”๊ฐ€๋œ ํŒŒ์ผ์€ apps/client/src/shared/utils/ValidateData.ts์ž…๋‹ˆ๋‹ค. ์ค‘๋ณต ์ •์˜๋‚˜ ๋นŒ๋“œ ์‹คํŒจ์˜ ์›์ธ์ด ๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์‹ค์ œ ์†Œ์Šค์—์„œ ๊ฐ€์ ธ์˜ค๋„๋ก ๊ฒฝ๋กœ๋ฅผ ์ •์ •ํ•˜์„ธ์š”.

 import {
   AutoDismissToast,
   Button,
   DateTime,
   Dropdown,
   InfoBox,
   PopupContainer,
   Switch,
   Textarea,
   Toast,
-  validateDate,
-  validateTime,
 } from '@pinback/design-system/ui';
 import { cn } from '@pinback/design-system/utils';
 import { useState } from 'react';
+import { validateDate, validateTime } from '../../utils/ValidateData';
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Icon } from '@pinback/design-system/icons';
import {
AutoDismissToast,
Button,
DateTime,
Dropdown,
InfoBox,
PopupContainer,
Switch,
Textarea,
Toast,
validateDate,
validateTime,
} from '@pinback/design-system/ui';
import { cn } from '@pinback/design-system/utils';
import { useState } from 'react';
import { Icon } from '@pinback/design-system/icons';
import {
AutoDismissToast,
Button,
DateTime,
Dropdown,
InfoBox,
PopupContainer,
Switch,
Textarea,
Toast,
} from '@pinback/design-system/ui';
import { cn } from '@pinback/design-system/utils';
import { useState } from 'react';
import { validateDate, validateTime } from '../../utils/ValidateData';
๐Ÿค– Prompt for AI Agents
In apps/client/src/shared/components/cardEditModal/CardEditModal.tsx around
lines 1-16, the file currently imports validateDate and validateTime from the
design system but the project defines these utilities in
apps/client/src/shared/utils/ValidateData.ts; update the import to use the local
module instead of '@pinback/design-system/ui' (remove validateDate and
validateTime from that import list and import them from the correct relative
path, e.g. '../../utils/ValidateData' adjusted to the correct relative path),
ensuring there are no duplicate definitions and the build uses the local
implementations.


export interface CardEditModalProps {
onClose: () => void;
}

export default function CardEditModal({ onClose }: CardEditModalProps) {
const [isRemindOn, setIsRemindOn] = useState(true);
const [selected, setSelected] = useState<string | null>(null);
const [isPopupOpen, setIsPopupOpen] = useState(false);

// ์ž…๋ ฅ ํ•„๋“œ ์ƒํƒœ: ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜ฌ ๋ฐ์ดํ„ฐ
const [title] = useState('');
const [source] = useState('');
const [memo, setMemo] = useState('');
const [categories] = useState<string[]>([]);
const [categoryTitle, setCategoryTitle] = useState('');
const [date, setDate] = useState('');
const [time, setTime] = useState('');
const [dateError, setDateError] = useState('');
const [timeError, setTimeError] = useState('');

const [toastIsOpen, setToastIsOpen] = useState(false);
const [isPopError, setIsPopError] = useState(false);
const [errorTxt, setErrorTxt] = useState('');

const saveCategory = () => {
if (categoryTitle.length > 20) {
setIsPopError(true);
setErrorTxt('20์ž ์ด๋‚ด๋กœ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”');
} else {
setIsPopupOpen(false);
}
};

const handleDateChange = (value: string) => {
setDate(value);
setDateError(validateDate(value));
};

const handleTimeChange = (value: string) => {
setTime(value);
setTimeError(validateTime(value));
};
Comment on lines +56 to +59
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

time ์ž…๋ ฅ์— ๋Œ€ํ•ด ๋ถ€๋ชจ onChange๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์•„ ๊ฒ€์ฆ/์ƒํƒœ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

Design System์˜ DateTime ๊ตฌํ˜„(์ œ๊ณต๋œ ์Šค๋‹ˆํŽซ ๊ธฐ์ค€)์—์„œ type="time"์€ ๋‚ด๋ถ€ ์ž…๋ ฅ ์ด๋ฒคํŠธ๋ฅผ ๋ชจ๋‘ onBeforeInput/onKeyDown์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ , props.onChange๋Š” ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ:

  • handleTimeChange๊ฐ€ ๊ฒฐ์ฝ” ํ˜ธ์ถœ๋˜์ง€ ์•Š์•„ time/timeError ์ƒํƒœ๊ฐ€ ๊ฐฑ์‹ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • state={timeError ? 'error' : ...}์™€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ Œ๋”๋ง์ด ๋ฌด์˜๋ฏธํ•ด์ง‘๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ(๊ถŒ์žฅ): DS์˜ DateTime(time)์—์„œ ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ๋ถ€๋ชจ๋กœ ํ‘œ์ค€ํ™”๋œ ๋ฌธ์ž์—ด(HH:MM 24h ๋“ฑ)์„ ์ „๋‹ฌํ•˜๋„๋ก ์ˆ˜์ •ํ•˜์„ธ์š”. ์ตœ์†Œ ์ˆ˜์ • ์˜ˆ์‹œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

// packages/design-system/src/components/dateTime/DateTime.tsx (์š”์ง€)
-      const next = (timeDigits + add).slice(0, 4);
+      const next = (timeDigits + add).slice(0, 4);
       setTimeDigits(next);
       setInput(formatTime12(next));
+      props.onChange?.(digitsToHHMM(next)); // NEW: ๋ถ€๋ชจ๋กœ 24h "HH:MM" ์ „๋‹ฌ
       nextCaretRef.current = mapCaretByDigitsPos(next.length, 'time');
       e.preventDefault();

... (delete/paste ์ฒ˜๋ฆฌ ์ง€์ ์—๋„ ๋™์ผํ•˜๊ฒŒ ํ˜ธ์ถœ ์ถ”๊ฐ€)

+function digitsToHHMM(digits: string) {
+  const h = digits.slice(0, 2).padStart(2, '0');
+  const m = digits.slice(2, 4).padStart(2, '0');
+  return `${h}:${m}`;
+}

DS ๋ฐ˜์˜ ์ „ ์ž„์‹œ ๋Œ€์‘์„ ์›ํ•˜์‹œ๋ฉด ๋ง์”€ ์ฃผ์„ธ์š”. UI์—์„œ time ๊ฒ€์ฆ/ํ‘œ์‹œ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฐ€๋“œ ์ ์šฉ์•ˆ๋„ ๋“œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 142-147


const handleSwitchChange = (checked: boolean) => {
setIsRemindOn(checked);
};

return (
<div className="flex flex-col">
<div
role="dialog"
aria-modal="true"
className={cn(
'w-[31.2rem] rounded-[1.2rem] bg-white px-[3.2rem] py-[2.4rem] shadow-xl',
'flex flex-col gap-[1.6rem]'
)}
>
{isPopupOpen && (
<PopupContainer
isOpen={isPopupOpen}
onClose={() => setIsPopupOpen(false)}
type="input"
title="์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ํ•˜๊ธฐ"
left="์ทจ์†Œ"
right="ํ™•์ธ"
inputValue={categoryTitle}
isError={isPopError}
errortext={errorTxt}
onInputChange={setCategoryTitle}
placeholder="์นดํ…Œ๊ณ ๋ฆฌ ์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"
onLeftClick={() => setIsPopupOpen(false)}
onRightClick={saveCategory}
/>
)}
<header className="flex items-center justify-between">
<Icon name="logo" width={72} height={20} />
<button
type="button"
aria-label="๋‹ซ๊ธฐ"
onClick={onClose}
className="rounded-[0.6rem] p-[0.4rem] hover:bg-gray-100"
>
<Icon name="ic_close" size={20} />
</button>
</header>

<InfoBox title={title} source={source} />

<section className="flex flex-col gap-[0.8rem]">
<p className="caption1-sb text-font-black-1">์นดํ…Œ๊ณ ๋ฆฌ</p>
<Dropdown
options={categories}
selectedValue={selected}
onChange={(value) => setSelected(value)}
placeholder="์„ ํƒํ•ด์ฃผ์„ธ์š”"
onAddItem={() => setIsPopupOpen(true)}
addItemLabel="์ถ”๊ฐ€ํ•˜๊ธฐ"
/>
</section>

<section className="flex flex-col gap-[0.8rem]">
<p className="caption1-sb text-font-black-1">๋ฉ”๋ชจ</p>
<Textarea
placeholder="๋‚˜์ค‘์— ๋‚ด๊ฐ€ ๊บผ๋‚ด์ค„ ์ˆ˜ ์žˆ๊ฒŒ ์‚ด์ง ์ ์–ด์ค˜!"
maxLength={500}
className="h-[12rem]"
value={memo}
onChange={(e) => setMemo(e.target.value)}
/>
</section>

<section className="flex flex-col gap-[1.2rem]">
<div className="flex items-center justify-between">
<p className="caption1-sb text-font-black-1">๋ฆฌ๋งˆ์ธ๋“œ</p>
<Switch checked={isRemindOn} onCheckedChange={handleSwitchChange} />
</div>

<div className="flex justify-between gap-[0.8rem]">
<DateTime
type="date"
state={dateError ? 'error' : isRemindOn ? 'default' : 'disabled'}
value={date}
onChange={handleDateChange}
/>
<DateTime
type="time"
state={timeError ? 'error' : isRemindOn ? 'default' : 'disabled'}
value={time}
onChange={handleTimeChange}
/>
</div>

{dateError && <p className="body3-r text-error">{dateError}</p>}
{timeError && <p className="body3-r text-error">{timeError}</p>}
</section>
{/* TODO: onClick ์ถ”ํ›„ ์ €์žฅ api ์—ฐ๊ฒฐํ›„ ์‹คํŒจ/์„ฑ๊ณต ์—ฐ๊ฒฐ */}
<Button onClick={() => setToastIsOpen(true)}>์ €์žฅํ•˜๊ธฐ</Button>
</div>
{toastIsOpen && (
<div className="absolute bottom-[2.4rem] left-1/2 -translate-x-1/2">
<AutoDismissToast
duration={1000}
fadeMs={1000}
onClose={() => setToastIsOpen(false)}
>
<Toast text={`์ €์žฅ์— ์‹คํŒจํ–ˆ์–ด์š”.\n๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”`} />
</AutoDismissToast>
</div>
)}
</div>
);
}
46 changes: 46 additions & 0 deletions apps/client/src/shared/utils/ValidateData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export const validateDate = (value: string): string => {
const regex = /^(\d{4})\.(\d{2})\.(\d{2})$/;
const match = value.match(regex);

if (!match) return '์œ ํšจํ•œ ๋‚ ์งœ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”';

const year = parseInt(match[1], 10);
const month = parseInt(match[2], 10);
const day = parseInt(match[3], 10);

if (month < 1 || month > 12) return '์œ ํšจํ•œ ๋‚ ์งœ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”';

const testDate = new Date(year, month - 1, day);
if (
testDate.getFullYear() !== year ||
testDate.getMonth() !== month - 1 ||
testDate.getDate() !== day
) {
return '์œ ํšจํ•œ ๋‚ ์งœ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”';
}

const today = new Date();
today.setHours(0, 0, 0, 0);
if (testDate < today) return 'ํ˜„์žฌ ์‹œ์  ์ดํ›„ ๋‚ ์งœ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”';

return '';
};

// ์‹œ๊ฐ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
export const validateTime = (value: string | undefined): string => {
if (!value) return '์‹œ๊ฐ„์„ ์ž…๋ ฅํ•˜์„ธ์š”';

const clean = value.replace(/[^0-9:]/g, '');
const regex = /^(\d{1,2}):(\d{1,2})$/;
const match = clean.match(regex);

if (!match) return '์œ ํšจํ•œ ์‹œ๊ฐ„์„ ์ž‘์„ฑํ•˜์„ธ์š”';
const hour = parseInt(match[1], 10);
const minute = parseInt(match[2], 10);

if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
return '์œ ํšจํ•œ ์‹œ๊ฐ„์„ ์ž‘์„ฑํ•˜์„ธ์š”';
}

return '';
};
2 changes: 1 addition & 1 deletion packages/design-system/src/components/infobox/InfoBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface InfoBoxProps {
}
const InfoBox = ({ title, source, imgUrl }: InfoBoxProps) => {
return (
<div className="border-main400 flex h-[6.8rem] w-[24.8rem] items-center justify-between gap-[0.8rem] rounded-[4px] border bg-white px-[0.8rem] py-[1.2rem]">
<div className="border-main400 flex h-[6.8rem] w-[full] items-center justify-between gap-[0.8rem] rounded-[4px] border bg-white px-[0.8rem] py-[1.2rem]">
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue

Tailwind ํด๋ž˜์Šค ์˜คํƒ€: w-[full]๋Š” ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

w-[full]๋Š” ์ž„์˜ ๊ฐ’ ๊ตฌ๋ฌธ์ด์ง€๋งŒ full์€ ๊ธธ์ด ๊ฐ’์ด ์•„๋‹ˆ๋ฏ€๋กœ ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜๋„๋Š” w-full๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

-    <div className="border-main400 flex h-[6.8rem] w-[full] items-center justify-between gap-[0.8rem] rounded-[4px] border bg-white px-[0.8rem] py-[1.2rem]">
+    <div className="border-main400 flex h-[6.8rem] w-full items-center justify-between gap-[0.8rem] rounded-[4px] border bg-white px-[0.8rem] py-[1.2rem]">
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="border-main400 flex h-[6.8rem] w-[full] items-center justify-between gap-[0.8rem] rounded-[4px] border bg-white px-[0.8rem] py-[1.2rem]">
<div className="border-main400 flex h-[6.8rem] w-full items-center justify-between gap-[0.8rem] rounded-[4px] border bg-white px-[0.8rem] py-[1.2rem]">
๐Ÿค– Prompt for AI Agents
In packages/design-system/src/components/infobox/InfoBox.tsx around line 8, the
Tailwind class contains a typo: `w-[full]` is an invalid arbitrary value usage
for width; replace it with the standard `w-full` class to restore the intended
full-width styling.

<img className="h-[4.4rem] w-[4.4rem] rounded-[0.4rem]" src={imgUrl} />
<div className="items-left flex flex-col justify-center gap-[0.2rem] text-left">
<p className="sub3-sb w-[18rem] truncate">{title}</p>
Expand Down
Loading