Skip to content
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

[FE] 구현한 컴포넌트를 바탕으로 약속 생성 페이지를 연결 #91

Merged
merged 15 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
36 changes: 0 additions & 36 deletions frontend/src/apis/getMeeting.ts

This file was deleted.

69 changes: 69 additions & 0 deletions frontend/src/apis/meetings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { API_URL } from '@constants/api';

export interface Schedules {
date: string;
times: string[];
}

export interface GetMeetingResponse {
meetingName: string;
startTime: string;
endTime: string;
availableDates: string[];
schedules: Schedules[];
}

export interface MeetingRequest {
hostName: string;
hostPassword: string;
meetingName: string;
meetingAvailableDates: string[];
meetingStartTime: string;
meetingEndTime: string;
}

export interface PostMeetingResponse {
uuid: string;
}

// uuid 기본값은 임시 설정된 uuid
const getMeeting = async (uuid: string): Promise<GetMeetingResponse> => {
const url = `${API_URL}/api/v1/meeting/${uuid}`;

const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

const data = await response.json();

return data.data;
};

export default getMeeting;

export const postMeeting = async (request: MeetingRequest): Promise<PostMeetingResponse> => {
const url = `${API_URL}/api/v1/meeting`;

const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
});

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

const data = await response.json();

return data.data;
};
2 changes: 1 addition & 1 deletion frontend/src/apis/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { API_URL } from '@constants/api';

import type { Schedules } from './getMeeting';
import type { Schedules } from './meetings';

export const postSchedule = async (requestData: Schedules[]) => {
const url = `${API_URL}/api/v1/schedule`;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Time/Picker/TimePicker.util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { GetMeetingResponse } from '@apis/getMeeting';
import type { GetMeetingResponse } from '@apis/meetings';

type TimeSlot = Record<string, number>;

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Time/Picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import TimeSlot from '@components/_common/TimeSlot';

import useTimePick from '@hooks/useTimePick/useTimePick';

import type { GetMeetingResponse } from '@apis/getMeeting';
import type { GetMeetingResponse } from '@apis/meetings';

import { usePostScheduleMutation } from '@stores/servers/meeting/mutations';
import { usePostScheduleMutation } from '@stores/servers/schedule/mutations';

import { s_buttonContainer, s_cell, s_table, s_th } from '../Time.styles';
import { convertToSchedule, generateScheduleMatrix } from './TimePicker.util';
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Time/Viewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStatePro
import Button from '@components/_common/Button';
import TimeSlot from '@components/_common/TimeSlot';

import type { GetMeetingResponse } from '@apis/getMeeting';
import type { GetMeetingResponse } from '@apis/meetings';

import { generateScheduleMatrix } from '../Picker/TimePicker.util';
import { s_buttonContainer, s_cell, s_table, s_th } from '../Time.styles';
Expand Down
8 changes: 0 additions & 8 deletions frontend/src/components/_common/Field/Field.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,29 @@ export const Default: Story = {
args: {
labelText: '사용자 이름',
description: '사용자 이름을 입력하세요.',
type: 'text',
id: 'userName',
placeholder: '사용자 이름을 입력하세요.',
},
};

export const WithDescription: Story = {
args: {
labelText: '이메일',
description: '이메일 주소를 입력해 주세요.',
type: 'email',
id: 'email',
placeholder: '이메일을 입력하세요.',
},
};

export const WithLongDescription: Story = {
args: {
labelText: '비밀번호',
description: '비밀번호는 최소 8자 이상이어야 하며, 문자와 숫자를 혼합하여 입력해 주세요.',
type: 'password',
id: 'password',
placeholder: '비밀번호를 입력하세요.',
},
};

export const WithoutDescription: Story = {
args: {
labelText: '검색',
type: 'text',
id: 'search',
placeholder: '검색어를 입력하세요.',
},
};
9 changes: 5 additions & 4 deletions frontend/src/components/_common/Field/Field.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import { css } from '@emotion/react';
export const s_field = css`
display: flex;
flex-direction: column;
gap: 0.75rem;
height: 6rem;
gap: 0.8rem;
`;

export const s_label = css`
font-size: 2.4rem;
font-weight: 700;
`;

export const s_description = css`
font-size: 0.75rem;
font-weight: 200;
font-size: 1.2rem;
font-weight: 400;
color: #8c8989;
`;
16 changes: 11 additions & 5 deletions frontend/src/components/_common/Field/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import type { InputHTMLAttributes } from 'react';
import type { LabelHTMLAttributes, PropsWithChildren } from 'react';

import Input from '../Input';
import { s_description, s_field, s_label } from './Field.styles';

interface FieldProps extends InputHTMLAttributes<HTMLInputElement> {
interface FieldProps extends LabelHTMLAttributes<HTMLLabelElement> {
id: string;
labelText: string;
description?: string;
flexDirection?: string;
}

export default function Field({ labelText, id, description = '', ...props }: FieldProps) {
export default function Field({
children,
labelText,
id,
description = '',
}: PropsWithChildren<FieldProps>) {
return (
<div css={s_field}>
<label css={s_label} htmlFor={id}>
{labelText}
</label>
{description && <div css={s_description}>{description}</div>}
<Input id={id} {...props} />
{children}
</div>
);
}
4 changes: 2 additions & 2 deletions frontend/src/components/_common/Input/Input.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { css } from '@emotion/react';

export const s_input = css`
width: 100%;
height: 2.5rem;
padding: 0 0.5rem;
height: 4.4rem;
padding: 0.8rem;
`;
6 changes: 4 additions & 2 deletions frontend/src/hooks/useCalendarInfo/useCalendarInfo.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ export function getDayInfo({
}) {
const date = index - firstDayIndex + 1;
const isDate = index >= firstDayIndex;
// TODO: 추후에 padStart 사용하는 곳이 많다 보니, util 함수로 분리 필요 (@낙타)
const formattedMonth = String(month).padStart(2, '0');

const dateString = `${year}-${month}-${date}`;
const todayString = `${year}-${month}-${currentDate.getDate()}`;
const dateString = `${year}-${formattedMonth}-${String(date).padStart(2, '0')}`;
const todayString = `${year}-${formattedMonth}-${String(currentDate.getDate()).padStart(2, '0')}`;
const isToday = dateString === todayString;
// TODO : 일단은 일요일만 isHolday로 설정되도록 구현, 추후에 진짜 공휴일도 포함할 것인지 논의 필요, 찾아보니 라이브러리가 있더라구요.(@해리)
// TODO : 매직넘버 의미있는 상수화 필요(@해리)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useTimeRangeDropdown/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const INITIAL_START_TIME = '0:00';
export const INITIAL_START_TIME = '00:00';
export const INITIAL_END_TIME = '24:00';

export const MINIMUM_TIME = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ describe('useTimeRangeDropdown', () => {
});

it('선택한 시작 시간(startTime)이 끝 시간(endTime)보다 느리다면 선택한 시간값으로 변경되지 않는다.', () => {
const CHANGE_TIME = '1:00';
const CHANGE_TIME = '01:00';
const { result } = renderHook(() => useTimeRangeDropdown());

act(() => {
result.current.onEndTimeChange('0:00');
result.current.onEndTimeChange('00:00');
});

act(() => {
Expand All @@ -28,7 +28,7 @@ describe('useTimeRangeDropdown', () => {
});

it('선택한 시작 시간(startTime)이 끝 시간(endTime)보다 빠르다면 값이 선택한 시간값으로 변경된다.', () => {
const CHANGE_TIME = '1:00';
const CHANGE_TIME = '01:00';
const { result } = renderHook(() => useTimeRangeDropdown());

act(() => {
Expand All @@ -43,7 +43,7 @@ describe('useTimeRangeDropdown', () => {
});

it('선택한 끝 시간(endTime)이 시작 시간(startTime)보다 빠르다면 선택한 시간값으로 변경되지 않는다.', () => {
const CHANGE_TIME = '1:00';
const CHANGE_TIME = '01:00';
const { result } = renderHook(() => useTimeRangeDropdown());

act(() => {
Expand All @@ -62,7 +62,7 @@ describe('useTimeRangeDropdown', () => {
const { result } = renderHook(() => useTimeRangeDropdown());

act(() => {
result.current.onStartTimeChange('0:00');
result.current.onStartTimeChange('00:00');
});

act(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function generateStartTimeOptions(endTime: string) {
for (let i = MINIMUM_TIME; i < endHours; i++) {
const label = formatHours(i);

times.push({ value: `${i}:00`, label: label + ':00' });
times.push({ value: `${String(i).padStart(2, '0')}:00`, label: label + ':00' });
}

return times;
Expand All @@ -30,18 +30,18 @@ export function generateEndTimeOptions(startTime: string) {
const startHours = Number(startTime.split(':')[0]);

for (let i = startHours + 1; i <= MAXIMUM_TIME; i++) {
const label = formatHours(i);
const label = formatHours(i).padStart(2, '0');

times.push({ value: `${i}:00`, label: label + ':00' });
times.push({ value: `${String(i).padStart(2, '0')}:00`, label: label + ':00' });
}

return times;
}

// 만약 시작 시간보다 끝 시간이 빠르다면 false를 반환하는 함수(@낙타)
export function isTimeSelectable(startTime: string, endTime: string) {
const [startHours, startMinutes] = startTime.split(':');
const [endHours, endMinutes] = endTime.split(':');
const [startHours, startMinutes] = startTime.split(':').map(Number);
const [endHours, endMinutes] = endTime.split(':').map(Number);

if (endHours < startHours) return false;
if (endHours === startHours && endMinutes < startMinutes) return false;
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/layouts/GlobalLayout.styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { css } from '@emotion/react';

export const s_globalContainer = css`
display: flex;
flex-direction: column;
gap: 2.4rem;

min-width: 39.3rem;
max-width: 60rem;
height: 100%;
Expand All @@ -10,5 +14,7 @@ export const s_globalContainer = css`
`;

export const s_content = css`
overflow-y: scroll;
height: calc(100vh - 8.4rem);
padding: 0 1.6rem;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { css } from '@emotion/react';

import theme from '@styles/theme';

export const s_formContainer = css`
display: flex;
flex-direction: column;
gap: 2.4rem;
`;

export const s_dropdownContainer = css`
display: flex;
gap: 1.2rem;
justify-content: flex-start;
`;

export const s_confirmContainer = css`
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 1.6rem;
`;

export const s_confirm = css`
width: 20rem;
height: 4.8rem;

font-size: 2rem;
font-weight: 800;
color: #fff;

background: ${theme.color.primary};
border: none;
border-radius: 8px;

&:hover {
color: ${theme.color.primary};
background: #fff;
border: 1px solid ${theme.color.primary};
}
`;
Loading