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

Feature/#883 #2

Merged
merged 12 commits into from
Dec 28, 2024
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
61 changes: 61 additions & 0 deletions .github/workflows/frontend-dev-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: frontend-dev-deploy

on:
push:
branches: ["fe-dev"]
paths:
- "client/**"

jobs:
deploy:
runs-on: ubuntu-latest

defaults:
run:
shell: bash
working-directory: ./client

steps:
# 1. Git 리포지토리 체크아웃
- name: Checkout code
uses: actions/checkout@v4

# 2. Node.js 20.15.1 version으로 셋팅
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20.15.1"

# 3. 의존성 설치
- name: Install Dependencies
run: npm install

# 4. Dev 환경으로 빌드
- name: Build for Dev environment
run: npm run build-dev
env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
AMPLITUDE_KEY: ${{ secrets.AMPLITUDE_KEY }}
KAKAO_JAVASCRIPT_KEY: ${{ secrets.KAKAO_JAVASCRIPT_KEY }}
IMAGE_URL: ${{ secrets.IMAGE_URL }}
KAKAO_REDIRECT_URI: ${{ secrets.KAKAO_REDIRECT_URI }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

# 5. AWS 인증 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

# 6. S3에 빌드 결과 업로드
- name: Upload build results to S3
run: aws s3 sync ./dist s3://${{ secrets.S3_BUCKET_NAME }}/dev/ --delete

# 7. CloudFront 캐시 무효화
- name: Invalidate CloudFront Cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.DEV_CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
61 changes: 61 additions & 0 deletions .github/workflows/frontend-prod-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: frontend-prod-deploy

on:
push:
branches: ["main"]
paths:
- "client/**"

jobs:
deploy:
runs-on: ubuntu-latest

defaults:
run:
shell: bash
working-directory: ./client

steps:
# 1. Git 리포지토리 체크아웃
- name: Checkout code
uses: actions/checkout@v4

# 2. Node.js 20.15.1 version으로 셋팅
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20.15.1"

# 3. 의존성 설치
- name: Install Dependencies
run: npm install

# 4. Prod 환경으로 빌드
- name: Build for Prod environment
run: npm run build
env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
AMPLITUDE_KEY: ${{ secrets.AMPLITUDE_KEY }}
KAKAO_JAVASCRIPT_KEY: ${{ secrets.KAKAO_JAVASCRIPT_KEY }}
IMAGE_URL: ${{ secrets.IMAGE_URL }}
KAKAO_REDIRECT_URI: ${{ secrets.KAKAO_REDIRECT_URI }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

# 5. AWS 인증 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

# 6. S3에 빌드 결과 업로드
- name: Upload build results to S3
run: aws s3 sync ./dist s3://${{ secrets.S3_BUCKET_NAME }}/prod/ --delete

# 7. CloudFront 캐시 무효화
- name: Invalidate CloudFront Cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.PROD_CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
2 changes: 1 addition & 1 deletion client/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Cypress.Commands.add('interceptAPI', ({type, delay = 0, statusCode = 200}: Inter
});

Cypress.Commands.add('createEventName', (eventName: string) => {
cy.visit(ROUTER_URLS.createMemberEvent);
cy.visit(ROUTER_URLS.createUserEvent);
cy.get('input').type(eventName);
cy.get('button').contains('다음').click();
});
Expand Down
18 changes: 14 additions & 4 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {Outlet} from 'react-router-dom';
import {Global} from '@emotion/react';
import {ReactQueryDevtools} from '@tanstack/react-query-devtools';

import AmplitudeInitializer from '@components/AmplitudeInitializer/AmplitudeInitializer';
import ErrorCatcher from '@components/AppErrorBoundary/ErrorCatcher';
import QueryClientBoundary from '@components/QueryClientBoundary/QueryClientBoundary';
import ToastContainer from '@components/Toast/ToastContainer';

import {HDesignProvider} from '@HDesign/index';

Expand All @@ -15,10 +19,16 @@ const App: React.FC = () => {
<HDesignProvider>
<UnPredictableErrorBoundary>
<Global styles={GlobalStyle} />
<NetworkStateCatcher />
<AmplitudeInitializer>
<Outlet />
</AmplitudeInitializer>
<ErrorCatcher>
<QueryClientBoundary>
<ReactQueryDevtools initialIsOpen={false} />
<NetworkStateCatcher />
<ToastContainer />
<AmplitudeInitializer>
<Outlet />
</AmplitudeInitializer>
</QueryClientBoundary>
</ErrorCatcher>
</UnPredictableErrorBoundary>
</HDesignProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion client/src/UnPredictableErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ErrorBoundary} from 'react-error-boundary';

import {StrictPropsWithChildren} from '@type/strictPropsWithChildren';
import ErrorPage from '@pages/ErrorPage/ErrorPage';
import ErrorPage from '@pages/fallback/ErrorPage';

const UnPredictableErrorBoundary = ({children}: StrictPropsWithChildren) => {
return <ErrorBoundary fallback={<ErrorPage />}>{children}</ErrorBoundary>;
Expand Down
1 change: 1 addition & 0 deletions client/src/apis/endpointPrefix.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// TODO: (@weadie) 반복되서 쓰이는 이 api/events가 추후 수정 가능성이 있어서 일단 편집하기 편하게 이 변수를 재사용하도록 했습니다.
export const USER_API_PREFIX = '/api/events';
export const ADMIN_API_PREFIX = '/api/admin/events';
export const MEMBER_API_PREFIX = '/api/users';
12 changes: 11 additions & 1 deletion client/src/apis/request/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {UserInfo} from 'types/serviceType';

import {BASE_URL} from '@apis/baseUrl';
import {ADMIN_API_PREFIX, USER_API_PREFIX} from '@apis/endpointPrefix';
import {ADMIN_API_PREFIX, MEMBER_API_PREFIX, USER_API_PREFIX} from '@apis/endpointPrefix';
import {requestGet, requestGetWithoutResponse, requestPostWithoutResponse} from '@apis/fetcher';
import {WithEventId} from '@apis/withId.type';

Expand Down Expand Up @@ -42,3 +44,11 @@ export const requestGetKakaoLogin = async (code: string) => {

return null;
};

export const requestGetUserInfo = async () => {
return await requestGet<UserInfo>({
baseUrl: BASE_URL.HD,
endpoint: `${MEMBER_API_PREFIX}/mine`,
errorHandlingStrategy: 'unsubscribe',
});
};
15 changes: 11 additions & 4 deletions client/src/apis/request/event.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Event, EventCreationData, EventId, EventName, User} from 'types/serviceType';
import {CreatedEvents, Event, EventCreationData, EventId, EventName, User} from 'types/serviceType';
import {WithErrorHandlingStrategy} from '@errors/RequestGetError';

import {ADMIN_API_PREFIX, USER_API_PREFIX} from '@apis/endpointPrefix';
import {ADMIN_API_PREFIX, MEMBER_API_PREFIX, USER_API_PREFIX} from '@apis/endpointPrefix';
import {requestGet, requestPatch, requestPostWithResponse} from '@apis/fetcher';
import {WithEventId} from '@apis/withId.type';

Expand All @@ -14,7 +14,7 @@ export const requestPostGuestEvent = async (postEventArgs: EventCreationData) =>
});
};

export const requestPostMemberEvent = async (eventName: EventName) => {
export const requestPostUserEvent = async (eventName: EventName) => {
return await requestPostWithResponse<EventId>({
endpoint: USER_API_PREFIX,
body: {
Expand Down Expand Up @@ -45,11 +45,18 @@ export const requestPatchEventName = async ({eventId, eventName}: RequestPatchEv

export type RequestPatchUser = Partial<User>;

// TODO: (@soha) 해당 요청은 user.ts 파일로 이동하는 건 어떨지?
export const requestPatchUser = async (args: RequestPatchUser) => {
return requestPatch({
endpoint: `/api/users`,
endpoint: MEMBER_API_PREFIX,
body: {
...args,
},
});
};

export const requestGetCreatedEvents = async () => {
return await requestGet<CreatedEvents>({
endpoint: `${USER_API_PREFIX}/mine`,
});
};
20 changes: 20 additions & 0 deletions client/src/apis/request/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {User} from 'types/serviceType';

import {BASE_URL} from '@apis/baseUrl';
import {MEMBER_API_PREFIX} from '@apis/endpointPrefix';
import {requestDelete} from '@apis/fetcher';
import {requestGet} from '@apis/fetcher';

export const requestDeleteUser = async () => {
await requestDelete({
baseUrl: BASE_URL.HD,
endpoint: `${MEMBER_API_PREFIX}`,
});
};

export const requestGetUserInfo = async () => {
return await requestGet<User>({
baseUrl: BASE_URL.HD,
endpoint: `/api/users/mine`,
});
};
4 changes: 2 additions & 2 deletions client/src/assets/image/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/** @jsxImportSource @emotion/react */
import type {Meta, StoryObj} from '@storybook/react';

import {useEffect, useState} from 'react';

import Checkbox from './Checkbox';

const meta = {
title: 'Components/Checkbox',
component: Checkbox,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
argTypes: {
labelText: {
description: '',
control: {type: 'text'},
},
isChecked: {
description: '',
control: {type: 'boolean'},
},
onChange: {
description: '',
control: {type: 'object'},
},
},
} satisfies Meta<typeof Checkbox>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Playground: Story = {
args: {
isChecked: false,
onChange: () => {},
labelText: '체크박스',
},
render: ({isChecked, onChange, labelText, ...args}) => {
const [isCheckedState, setIsCheckedState] = useState(isChecked);
const [labelTextState, setLabelTextState] = useState(labelText);

useEffect(() => {
setIsCheckedState(isChecked);
setLabelTextState(labelText);
}, [isChecked, labelText]);

const handleToggle = () => {
setIsCheckedState(!isCheckedState);
onChange();
};

return <Checkbox {...args} isChecked={isCheckedState} onChange={handleToggle} labelText={labelTextState} />;
},
};
38 changes: 38 additions & 0 deletions client/src/components/Design/components/Checkbox/Checkbox.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {css} from '@emotion/react';

import {WithTheme} from '@components/Design/type/withTheme';

interface CheckboxStyleProps {
isChecked: boolean;
}

export const checkboxStyle = () =>
css({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '0.75rem',
cursor: 'pointer',
});

export const inputGroupStyle = ({theme, isChecked}: WithTheme<CheckboxStyleProps>) =>
css({
position: 'relative',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',

'.check-icon': {
position: 'absolute',
},

'.checkbox-input': {
width: '1.375rem',
height: '1.375rem',
border: '1px solid',
borderRadius: '0.5rem',
borderColor: isChecked ? theme.colors.primary : theme.colors.tertiary,
backgroundColor: isChecked ? theme.colors.primary : theme.colors.white,
},
});
28 changes: 28 additions & 0 deletions client/src/components/Design/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/** @jsxImportSource @emotion/react */
import {useTheme} from '@components/Design/theme/HDesignProvider';

import Text from '../Text/Text';
import Icon from '../Icon/Icon';

import {checkboxStyle, inputGroupStyle} from './Checkbox.style';

interface Props {
labelText: string;
isChecked: boolean;
onChange: () => void;
}

const Checkbox = ({labelText, isChecked = false, onChange}: Props) => {
const {theme} = useTheme();
return (
<label css={checkboxStyle}>
<div css={inputGroupStyle({theme, isChecked})}>
{isChecked ? <Icon iconType="check" iconColor="onPrimary" className="check-icon" /> : null}
<input type="checkbox" checked={isChecked} onChange={onChange} className="checkbox-input" />
</div>
<Text size="bodyBold">{labelText}</Text>
</label>
);
};

export default Checkbox;
Loading
Loading