Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4f6098a
feat: 다가오는 일정에 대한 api 통신 인터페이스 작성
dioo1461 Feb 17, 2025
f597cd8
feat: api 스키마 및 request 정의
dioo1461 Feb 17, 2025
2bc20c9
feat: response body 데이터 검증 추가
dioo1461 Feb 17, 2025
7df11d4
refactor: query options를 별개 객체로 분리하여 관리
dioo1461 Feb 17, 2025
cfbc877
feat: UpcomingSchedules에 api 연결, homePage에 prefetch 추가
dioo1461 Feb 17, 2025
33012c2
feat: SegmentControl이 label, value를 따로 받도록 구현 변경
dioo1461 Feb 17, 2025
3c4493f
feat: Refactor ongoing schedules and update related components
dioo1461 Feb 17, 2025
dd9c47c
chore: Expired Schedule -> Finished Schedule로 명칭 변경
dioo1461 Feb 17, 2025
c8c2984
feat: usePagination 훅 구현
dioo1461 Feb 18, 2025
1d64b5c
feat: update SegmentControl to accept className prop and refactor ong…
dioo1461 Feb 18, 2025
761b069
feat: FinishedSchedule api 연결
dioo1461 Feb 18, 2025
667fa6d
feat: ongoingSchedule api 연결
dioo1461 Feb 18, 2025
4d1a06d
feat: set fixed height for ongoing schedule list and list items
dioo1461 Feb 18, 2025
d5f1189
feat: refactor ongoing schedules components and add recommended sched…
dioo1461 Feb 18, 2025
8c4205d
feat: OngoingFallback 구현, home route에서 prefetch 적용
dioo1461 Feb 18, 2025
84b5c19
feat: 지난 일정에 Fallback 추가
dioo1461 Feb 18, 2025
e64c52b
chore: 빌드 에러 수정
dioo1461 Feb 18, 2025
fa4eae2
chore: 자잘한 휴먼에러 수정
dioo1461 Feb 18, 2025
d4998d2
feat: staletime을 기본 infinity로 설정
dioo1461 Feb 19, 2025
32beb3c
feat: pagination hover 시 prefetch 구현
dioo1461 Feb 19, 2025
282dd36
feat: Segment Control에도 button hover 시 prefetch 넘길 수 있게 구현
dioo1461 Feb 19, 2025
fa953a8
fix: page 바꿔도 content가 바뀌지 않던 문제 수정
dioo1461 Feb 19, 2025
dea6c75
feat: update fallback components to use full width and improve layout…
dioo1461 Feb 19, 2025
a570fc1
feat: 확정되지 않은 일정 선택 기능 구현
dioo1461 Feb 19, 2025
797402a
chore: 빌드 에러 해결
dioo1461 Feb 19, 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
65 changes: 65 additions & 0 deletions frontend/src/components/Icon/component/CheckGraphic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@

import type { IconProps } from '../Icon.d.ts';

export const CheckGraphic = ({ clickable = false, className, width = 24, height = 24 , stroke = "white", fill = "white", ...rest }: IconProps) => {
return (
<svg width={width} height={height || width} viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg" aria-label="check-graphic icon" fill="none" className={className} style={{ cursor: clickable ? "pointer": "default", ...rest.style }} {...rest}>
<g clipPath="url(#clip0_894_193)">
<mask id="mask0_894_193" style={{ maskType: 'alpha' }} maskUnits="userSpaceOnUse" x="0" y="0" width="180" height="180">
<rect width="180" height="180" fill="white"/>
</mask>
<g mask="url(#mask0_894_193)">
<rect width="180" height="180" fill="#F3F6FC"/>
<g filter="url(#filter0_f_894_193)">
<circle cx="115.2" cy="88.2" r="27" fill="#90C2FF"/>
</g>
<g filter="url(#filter1_f_894_193)">
<ellipse cx="46.35" cy="132.3" rx="14.85" ry="27" fill="#76E4B8"/>
</g>
<g filter="url(#filter2_d_894_193)">
<rect x="77.8491" y="65.25" width="72" height="72" rx="16" transform="rotate(10 77.8491 65.25)" fill="white"/>
<rect x="78.2547" y="65.8292" width="71" height="71" rx="15.5" transform="rotate(10 78.2547 65.8292)" stroke="#E5E8EB"/>
</g>
<mask id="mask1_894_193" mask-type='luminance' maskUnits="userSpaceOnUse" x="78" y="78" width="58" height="58">
<rect x="87.3398" y="78.8045" width="48.6" height="48.6" transform="rotate(10 87.3398 78.8045)" fill="white"/>
</mask>
<g mask="url(#mask1_894_193)">
<path d="M100.976 115.754L93.4197 104.963C92.5984 103.79 92.8446 102.394 94.0175 101.573C95.1904 100.751 96.5864 100.998 97.4077 102.17L103.485 110.85L118.968 100.009C120.141 99.1877 121.537 99.4339 122.358 100.607C123.18 101.78 122.933 103.176 121.76 103.997L104.401 116.152C103.662 116.844 103.029 116.938 102.43 116.833C101.832 116.727 101.269 116.422 100.976 115.754Z" fill="url(#paint0_linear_894_193)"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_f_894_193" x="11.8" y="38.8" width="254" height="254" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="50" result="effect1_foregroundBlur_894_193"/>
</filter>
<filter id="filter1_f_894_193" x="38.5" y="35.3" width="169.7" height="194" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="35" result="effect1_foregroundBlur_894_193"/>
</filter>
<filter id="filter2_d_894_193" x="47.8791" y="47.7826" width="118.344" height="118.344" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="10"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_894_193"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_894_193" result="shape"/>
</filter>
<linearGradient id="paint0_linear_894_193" x1="108.83" y1="97.4502" x2="105.322" y2="117.343" gradientUnits="userSpaceOnUse">
<stop stopColor="#64A8FF"/>
<stop offset="1" stopColor="#3182F6"/>
</linearGradient>
<clipPath id="clip0_894_193">
<rect width="180" height="180" rx="90" fill="white"/>
</clipPath>
</defs>
</svg>

);
};

CheckGraphic.displayName = 'CheckGraphic';
28 changes: 28 additions & 0 deletions frontend/src/components/Icon/component/ClockGraphic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import type { IconProps } from '../Icon.d.ts';

export const ClockGraphic = ({ clickable = false, className, width = 24, height = 24 , stroke = "white", fill = "white", ...rest }: IconProps) => {
return (
<svg width={width} height={height || width} viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" aria-label="clock-graphic icon" fill="none" className={className} style={{ cursor: clickable ? "pointer": "default", ...rest.style }} {...rest}>
<circle cx="100" cy="100" r="100" fill="url(#paint0_linear_894_208)"/>
<circle cx="100" cy="100.5" r="75" fill="url(#paint1_radial_894_208)"/>
<rect x="104.158" y="98.5" width="8" height="66" rx="4" transform="rotate(120 104.158 98.5)" fill="#4E5968"/>
<rect x="99.6569" y="106.512" width="8" height="50" rx="4" transform="rotate(135 99.6569 106.512)" fill="#4E5968"/>
<rect x="98" y="100" width="4" height="60" rx="2" fill="#4593FC"/>
<circle cx="100" cy="100.5" r="10" fill="white" stroke="#E5E8EB" strokeWidth="4"/>
<defs>
<linearGradient id="paint0_linear_894_208" x1="1.8593e06" y1="10.0503" x2="192.95" y2="206.91" gradientUnits="userSpaceOnUse">
<stop stopColor="#D1D6DB"/>
<stop offset="1" stopColor="#F2F4F6"/>
</linearGradient>
<radialGradient id="paint1_radial_894_208" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(100 100.5) rotate(90) scale(75)">
<stop offset="0.709" stopColor="white"/>
<stop offset="1" stopColor="#F1F1F1"/>
</radialGradient>
</defs>
</svg>

);
};

ClockGraphic.displayName = 'ClockGraphic';
2 changes: 2 additions & 0 deletions frontend/src/components/Icon/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export * from "./component/ArrowLeft.tsx";
export * from "./component/CalendarCheck.tsx";
export * from "./component/Calendar.tsx";
export * from "./component/CheckGraphic.tsx";
export * from "./component/Check.tsx";
export * from "./component/ChevronDown.tsx";
export * from "./component/ChevronLeft.tsx";
export * from "./component/ChevronRight.tsx";
export * from "./component/CircleCheck.tsx";
export * from "./component/ClockGraphic.tsx";
export * from "./component/Clock.tsx";
export * from "./component/Close.tsx";
export * from "./component/GoogleCalendar.tsx";
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/components/Icon/svg/check-graphic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions frontend/src/components/Icon/svg/clock-graphic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion frontend/src/components/Pagination/PaginationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ interface PaginationItemProps {
item: PaginationItemType;
currentPage: number;
onPageChange: (page: number) => void;
onHover?: (page: number) => void;
}

const PaginationItem = ({ item, currentPage, onPageChange }: PaginationItemProps) => {
const PaginationItem = ({
item,
currentPage,
onPageChange,
onHover,
}: PaginationItemProps) => {
// 구분자 렌더링
if (item === SEPARATOR) {
return (
Expand All @@ -30,6 +36,7 @@ const PaginationItem = ({ item, currentPage, onPageChange }: PaginationItemProps
className={paginationItemStyle({ selected: isSelected })}
key={`page-${item}`}
onClick={() => onPageChange(item)}
onMouseEnter={() => onHover?.(item)}
>
<Text typo={isSelected ? 't2' : 'b2M'}>{item}</Text>
</button>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/Pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ interface PaginationProps {
totalPages: number;
onPageChange: (page: number) => void;
className?: string;
onPageButtonHover?: (page: number) => void;
}

const Pagination = ({
currentPage,
totalPages,
onPageChange,
onPageButtonHover,
className,
}: PaginationProps) => {
const pages = getPaginationItems(currentPage, totalPages);
Expand All @@ -30,6 +32,7 @@ const Pagination = ({
currentPage={currentPage}
item={item}
key={index}
onHover={onPageButtonHover}
onPageChange={onPageChange}
/>,
)}
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/components/SegmentControl/ControlButton.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import { useSafeContext } from '@/hooks/useSafeContext';

import Button from '../Button';
import type { SegmentControlProps } from '.';
import type { SegmentControlProps, SegmentOption } from '.';
import { SegmentControlContext } from './SegmentControlContext';

interface ControlButtonProps {
value: string;
segmentOption: SegmentOption;
segmentControlStyle: SegmentControlProps['style'];
onButtonHover?: (value: string) => void;
}

const ControlButton = ({
value,
segmentOption,
segmentControlStyle,
onButtonHover,
}: ControlButtonProps ) => {
const { selectedValue, handleSelect } = useSafeContext(SegmentControlContext);

const { label, value } = segmentOption;
return (
<Button
as='li'
onClick={() => handleSelect(value)}
onMouseEnter={() => onButtonHover?.(value)}
radius='max'
size='lg'
style={getButtonStyle(selectedValue === value, segmentControlStyle)}
variant='secondary'
>
{value}
{label}
</Button>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,26 @@ export default meta;

export const Default: StoryObj<typeof SegmentControl> = {
args: {
values: ['라벨1', '라벨2', '라벨3'],
defaultValue: '라벨1',
segmentOptions: [
{ label: '라벨1', value: 'value1' },
{ label: '라벨2', value: 'value2' },
{ label: '라벨3', value: 'value3' },
],
defaultValue: 'value1',
},
};

export const WithContent = () => (
<SegmentControl defaultValue='라벨1' values={['라벨1', '라벨2', '라벨3']}>
<SegmentControl.Content value='라벨1'>컨텐츠1</SegmentControl.Content>
<SegmentControl.Content value='라벨2'>컨텐츠2</SegmentControl.Content>
<SegmentControl.Content value='라벨3'>컨텐츠3</SegmentControl.Content>
<SegmentControl
defaultValue='value1'
segmentOptions={[
{ label: '라벨1', value: 'value1' },
{ label: '라벨2', value: 'value2' },
{ label: '라벨3', value: 'value3' },
]}
>
<SegmentControl.Content value='value1'>컨텐츠1</SegmentControl.Content>
<SegmentControl.Content value='value2'>컨텐츠2</SegmentControl.Content>
<SegmentControl.Content value='value3'>컨텐츠3</SegmentControl.Content>
</SegmentControl>
);
22 changes: 14 additions & 8 deletions frontend/src/components/SegmentControl/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,31 @@ import ControlButton from './ControlButton';
import { controlButtonContainerStyle } from './index.css';
import { SegmentControlContext } from './SegmentControlContext';

export interface SegmentOption {
label: string;
value: string;
}
export interface SegmentControlProps extends PropsWithChildren {
values: string[];
segmentOptions: SegmentOption[];
style?: 'weak' | 'filled';
shadow?: boolean;
defaultValue?: string;
onValueChange?: (value: string) => void;
className?: string;
onButtonHover?: (value: string) => void;
};

const SegmentControl = ({
values,
segmentOptions,
style = 'filled',
shadow = true,
defaultValue = values[0] ?? '',
defaultValue = segmentOptions[0]?.value ?? '',
onValueChange,
children,
className,
onButtonHover,
}: SegmentControlProps) => {
const [selectedValue, setSelectedValue] = useState(defaultValue);

const handleSelect = (value: string) => {
setSelectedValue(value);
onValueChange?.(value);
Expand All @@ -40,11 +45,12 @@ const SegmentControl = ({
className={controlButtonContainerStyle({ style, shadow })}
direction='row'
>
{values.map((value, idx) => (
<ControlButton
key={`${value}-${idx}`}
{segmentOptions.map((segmentOption, idx) => (
<ControlButton
key={`${segmentOption.value}-${idx}`}
onButtonHover={onButtonHover}
segmentControlStyle={style}
value={value}
segmentOption={segmentOption}
/>
))}
</Flex>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/features/discussion/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ export const useDiscussionParticipantsQuery = (discussionId: string) => {
);

return { participants, isLoading };
};
};
28 changes: 28 additions & 0 deletions frontend/src/features/discussion/model/invitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { z } from 'zod';

import { zCoerceToDate, zCoerceToTime } from '@/utils/zod';

export const InvitationResponseSchema = z.object({
host: z.string(),
title: z.string(),
dateRangeStart: zCoerceToDate,
dateRangeEnd: zCoerceToDate,
timeRangeStart: zCoerceToTime,
timeRangeEnd: zCoerceToTime,
duration: z.number(),
isFull: z.boolean(),
requirePassword: z.boolean(),
});

export const InvitationJoinRequestSchema = z.object({
password: z.string().regex(/^\d{4,6}$/, 'Password must be a 4 to 6 digit number'),
});

export const InvitationJoinResponseSchema = z.object({
isSuccess: z.boolean(),
failedCount: z.number(),
});

export type InviteResponse = z.infer<typeof InvitationResponseSchema>;
export type InviteJoinRequest = z.infer<typeof InvitationJoinRequestSchema>;
export type InviteJoinResponse = z.infer<typeof InvitationJoinResponseSchema>;
Loading