Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
0e5a22c
feat: 상품 구매 페이지 라우팅 연결, 금액 투입 기능 구현
usageness Mar 31, 2022
fca128f
refactor: valueAsNumber 적용
usageness Mar 31, 2022
0fce0d5
feat: PurchasableProductItemList 컴포넌트 추가
usageness Mar 31, 2022
52fd6f9
feat: 잔돈 반환 기능 구현
usageness Mar 31, 2022
4b7e16f
docs: step2 요구 사항 정리
usageness Apr 1, 2022
a9abc17
test: 자판기에 투입된 총 금액이 10,00원을 넘으면 오류를 발생시킨다
usageness Apr 1, 2022
1a1fff1
feat: 자판기에 투입된 총 금액이 10,000원을 넘으면, 오류를 발생시킨다.
usageness Apr 1, 2022
0d6f10d
test: 자판기 투입 금액 유효성 검사 추가
usageness Apr 1, 2022
8cad147
fix: 사용자가 금액을 투입할때 기존 자판기 금액을 참조하던 오류 수정
usageness Apr 1, 2022
634e67b
test: 잔돈을 반환할때 자판기에 충전된 잔돈이 유저가 투입한 금액에 비해 부족하면, 오류를 발생시킨다.
usageness Apr 1, 2022
2149520
feat: 잔돈을 반환할때 자판기에 충전된 잔돈이 유저가 투입한 금액에 비해 부족하면, 오류를 발생시킨다.
usageness Apr 1, 2022
a5a7563
test: 상품을 구매할때 가격보다 투입한 금액이 적으면, 오류를 발생시킨다.
usageness Apr 1, 2022
b326e6f
feat: 상품을 구매할때 가격보다 투입한 금액이 적으면, 오류를 발생시키는 기능 구현
usageness Apr 1, 2022
6d5f676
feat: 상품 구매 기능 ui와 연결
usageness Apr 1, 2022
14e3def
feat: 스낵바 구현 및 기존 alert 스낵바로 대체
usageness Apr 4, 2022
2908f8a
refactor: throwableFunctionHandler 추가
usageness Apr 4, 2022
eb890c7
fix: 상품 정보 수정시 사용자 권한의 아이템으로 보이던 오류 수정
usageness Apr 4, 2022
3d53c77
refactor: snackbar css 수정
usageness Apr 4, 2022
75159ad
chore: json server auth 설치 및 세팅
usageness Apr 4, 2022
e39d1db
chore: 로그인 버튼 마크업 작성
usageness Apr 4, 2022
f043681
feat: 로그인 페이지 마크업 및 연결
usageness Apr 4, 2022
82a8eb0
fix: 로그인 페이지 중복 로드 오류 수정에 따른 라우터 구조 변경
usageness Apr 5, 2022
90945af
feat: 회원가입 페이지 마크업 작성 및 라우터 연결
usageness Apr 5, 2022
b67bdfd
feat: 회원가입 api 연결 및 기본 에러 처리
usageness Apr 5, 2022
d9fa193
refactor: 회원가입 오류 메시지 및 기존 에러메시지 상수화
usageness Apr 5, 2022
9a83778
feat: 회원가입 유효성 확인 로직 구현
usageness Apr 5, 2022
6c792a4
feat: 로그인 api 연결
usageness Apr 5, 2022
ca21c06
chore: jwt-decode 설치
usageness Apr 5, 2022
6a229e5
feat: 로그인 확인 기능 구현
usageness Apr 5, 2022
afd29f6
feat: 유효하지 않은 토큰에 대한 에러 핸들링 추가
usageness Apr 6, 2022
6a3db7f
feat: 유저 메뉴 마크업 작성
usageness Apr 6, 2022
6a52c4c
feat: 회원 정보 수정 기능/로그아웃 기능 구현
usageness Apr 6, 2022
1e670fc
fix: 회원정보 수정 시 스토리지에 잘못된 값이 저장되던 오류 수정
usageness Apr 6, 2022
f12a9b1
feat: 관리자 권환 확인 기능 구현
usageness Apr 6, 2022
32b3991
chore: 에러 핸들링 객체 스펙 변경에 따른 호출부 변경
usageness Apr 6, 2022
dcd83c3
feat: 이름 길이 제한 추가
usageness Apr 6, 2022
40d76b7
docs: 요구 사항 체크
usageness Apr 6, 2022
21aae94
fix: 잘못된 이름 유효성 조건 수정
usageness Apr 6, 2022
bc2ed5f
test: 잘못된 이메일 주소로 계정 생성을 시도하면, 오류를 발생시킨다.
usageness Apr 6, 2022
df3720b
test: 이름의 길이가 2~6자 사이가 아니면, 오류를 발생시킨다.
usageness Apr 6, 2022
a667a90
chore: 비밀번호 구성 조건 변경 (특수문자 필수)
usageness Apr 6, 2022
a7a2b4e
test: 비밀번호가 6~20자 사이의 영문과 숫자, 특수문자 조합이 아니면 오류를 발생시킨다.
usageness Apr 6, 2022
bd98f37
test: 비밀번호와 비밀번호 확인이 같지 않으면 오류를 발생시킨다.
usageness Apr 6, 2022
3f2b41b
chore: 라우터 기본 연결값 추가
usageness Apr 6, 2022
5714262
chore: cypress 설치 및 기본 세팅
usageness Apr 6, 2022
15f8fa7
test: 회원가입/로그인 e2e 테스트 추가
usageness Apr 6, 2022
8a8163f
test: 관리자 기능 e2e 테스트 작성
usageness Apr 6, 2022
f1239ab
chore: 개발자 모드 상수 DEV_MODE 추가 및 적용
usageness Apr 6, 2022
e54f6e6
test: 사용자 기능 테스트 추가
usageness Apr 6, 2022
51941d1
fix: 잔액과 상관없이 상품이 결제되던 오류 수정
usageness Apr 6, 2022
27d64d6
chore: 요구 사항 문서 체크
usageness Apr 6, 2022
aae4175
chore: db file 정리
usageness Apr 6, 2022
43ef0fa
refactor: server 주소 상수화
usageness Apr 6, 2022
d0d5d52
chore: heroku 배포 주소 적용
usageness Apr 6, 2022
5418214
chore: npm start 명령어 수정
usageness Apr 6, 2022
d7c4eea
chore: 배포 서버 환경에 맞게 연결
usageness Apr 7, 2022
85e1ece
chore: 배포 주소 수정 http -> https
usageness Apr 7, 2022
4c40db3
chore: 사용되지 않는 import 제거
usageness Apr 11, 2022
794895d
chore: css 색상 변수화
usageness Apr 11, 2022
de49f5f
refactor: apiWrapper 클래스 추가
usageness Apr 12, 2022
f133f91
refactor: api 요청 반환값 타입 추가
usageness Apr 12, 2022
1905b31
refactor: api 메서드 반환 정보 수정
usageness Apr 12, 2022
2ab1f43
chore: 컴포넌트 내 미사용 메서드(refreshComponent) 삭제
usageness Apr 12, 2022
f7c7c59
chore: 컴포넌트 내 template 메서드 private 적용
usageness Apr 12, 2022
83e8849
refactor: 함수형 컴포넌트 class형으로 변환
usageness Apr 12, 2022
7ec926c
chore: 미사용 선택자, css 제거
usageness Apr 12, 2022
cfcf941
refactor: Component에 상속 적용
usageness Apr 13, 2022
c5c9fa8
refactor: test 전용 클래스 VendingMachine_Test 생성
usageness Apr 13, 2022
f79875c
chore: gitignore 테스트 스크린샷 파일 추가
usageness Apr 13, 2022
342d8f9
refactor: vendingMachine에서 coin 로직 분리
usageness Apr 13, 2022
2498cdb
fix: snackbar 오류 수정
usageness Apr 13, 2022
309aa54
chore: store 임시 적용
usageness Apr 13, 2022
696957b
fix: conflict fixed
usageness Apr 14, 2022
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ db.json

# test file
cypress/fixtures/
cypress/videos/
cypress/videos/
cypress/screenshots/
3 changes: 3 additions & 0 deletions src/css/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@
--button-hover-color: rgba(0, 188, 212, 0.16);
--primary-text-color: rgba(0, 0, 0, 0.87);
--list-border-color: #dcdcdc;
--snackbar-bg-color: #333;
--snackbar-text-color: #fff;
--snackbar-error-bg-color: rgb(177, 28, 28);
}
33 changes: 14 additions & 19 deletions src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ a {
text-decoration: none;
}

li {
list-style-type: none;
text-align: center;
font-style: normal;
display: flex;
justify-content: center;
border-bottom: 1px solid var(--list-border-color);
}

#app {
width: 40%;
min-width: 465px;
Expand Down Expand Up @@ -165,10 +174,6 @@ input:focus {
height: 35px;
}

.single-input {
width: 300px;
}

.single-input-container {
margin: auto;
}
Expand Down Expand Up @@ -206,7 +211,9 @@ h4 {
align-items: center;
width: 100%;
}
#product-list-wrapper ul {

#product-list-wrapper ul,
#change-list-wrapper ul {
padding: 0;
}

Expand All @@ -217,19 +224,6 @@ h4 {
width: 100%;
}

#change-list-wrapper ul {
padding: 0;
}

li {
list-style-type: none;
text-align: center;
font-style: normal;
display: flex;
justify-content: center;
border-bottom: 1px solid var(--list-border-color);
}

#product-list {
width: 100%;
}
Expand Down Expand Up @@ -279,7 +273,8 @@ li {
margin-top: -8px;
}

#change-list li {
#change-list li,
.single-input {
width: 300px;
}

Expand Down
6 changes: 3 additions & 3 deletions src/css/snackbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
visibility: hidden;
min-width: 250px;
max-width: 300px;
background-color: #333;
color: #fff;
background-color: var(--snackbar-bg-color);
color: var(--snackbar-text-color);
text-align: center;
border-radius: 2px;
padding: 16px;
Expand All @@ -22,7 +22,7 @@
}

.error {
background-color: rgb(177, 28, 28);
background-color: var(--snackbar-error-bg-color);
}

.show {
Expand Down
2 changes: 1 addition & 1 deletion src/js/__test__/vendingMachine.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ERROR_MESSAGE } from '../constants';
import vendingMachine from '../model/VendingMachine';
import vendingMachine from '../model/vendingMachine_Test';

describe('자판기 기본 기능 테스트', () => {
describe('자판기 상품 추가 기능 테스트', () => {
Expand Down
23 changes: 9 additions & 14 deletions src/js/api/requestLogin.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { ALERT_MESSAGE, ERROR_MESSAGE, SERVER_URL } from '../constants';
import { ERROR_MESSAGE } from '../constants';
import { LoginSuccess } from '../interfaces/apiStatus.interface';
import ApiWrapper from '../utils/ApiWrapper';

const requestLogin = async (accountData: Object) => {
const response = await fetch(SERVER_URL + '/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(accountData),
});
const apiWrapper = new ApiWrapper();

const dataResult = await response.json();
const requestLogin = async (accountData: Object) => {
const response = await apiWrapper.post('/login', accountData);
const dataResult: LoginSuccess | string = await response.json();

if (!response.ok) {
if (typeof dataResult === 'string') {
switch (dataResult) {
case 'Cannot find user':
throw new Error(ERROR_MESSAGE.USER_IS_NOT_EXIST);
Expand All @@ -21,9 +18,7 @@ const requestLogin = async (accountData: Object) => {
throw new Error(dataResult);
}

localStorage.setItem('accessToken', dataResult.accessToken);
localStorage.setItem('user', JSON.stringify(dataResult.user));
return ALERT_MESSAGE.LOGIN_SUCCESS(dataResult.user.name);
return dataResult;
};

export default requestLogin;
27 changes: 8 additions & 19 deletions src/js/api/requestModifyUserData.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
import { ALERT_MESSAGE, ERROR_MESSAGE, SERVER_URL } from '../constants';
import { ERROR_MESSAGE } from '../constants';
import { User } from '../interfaces/UserData.interface';
import ApiWrapper from '../utils/ApiWrapper';

const requestModifyUserData = async (userData: User) => {
const response = await fetch(SERVER_URL + `/users/${userData.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
const apiWrapper = new ApiWrapper();

const dataResult = await response.json();
const requestModifyUserData = async (userData: User) => {
const response = await apiWrapper.put(`/users/${userData.id}`, userData);
const dataResult: User | string = await response.json();

if (!response.ok) {
if (typeof dataResult === 'string') {
switch (dataResult) {
case 'Password is too short':
throw new Error(ERROR_MESSAGE.PASSWORD_IS_TOO_SHORT);
}
throw new Error(dataResult);
}

const updatedInfo = {
email: dataResult.email,
name: dataResult.name,
id: dataResult.id,
};

localStorage.setItem('user', JSON.stringify(updatedInfo));
return ALERT_MESSAGE.USER_INFO_MODIFY_SUCCESS;
return dataResult;
};

export default requestModifyUserData;
15 changes: 6 additions & 9 deletions src/js/api/requestRegister.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { ALERT_MESSAGE, ERROR_MESSAGE, SERVER_URL } from '../constants';
import { ERROR_MESSAGE } from '../constants';
import { User } from '../interfaces/UserData.interface';
import ApiWrapper from '../utils/ApiWrapper';

const apiWrapper = new ApiWrapper();

const requestRegister = async (userData: User) => {
const response = await fetch(SERVER_URL + '/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
const response = await apiWrapper.post('/users', userData);

if (!response.ok) {
const errorMessage = await response.json();
Expand All @@ -21,7 +18,7 @@ const requestRegister = async (userData: User) => {
throw new Error(errorMessage);
}

return ALERT_MESSAGE.REGISTER_SUCCESS;
return true;
};

export default requestRegister;
13 changes: 8 additions & 5 deletions src/js/components/AddChangeComponent.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import coinModel from '../model/CoinModel';
import vendingMachine from '../model/VendingMachine';
import throwableFunctionHandler from '../utils/throwableFunctionHandler';
import * as Component from './abstractComponents/Component';

class AddChangeComponent {
class AddChangeComponent extends Component.DynamicComponent {
$changeAddForm: HTMLElement;
$totalChange: HTMLElement;
noticeStateChanged: Function;
parentElement: HTMLElement;

constructor(parentElement: HTMLElement, noticeStateChanged: Function) {
super();
this.parentElement = parentElement;
this.noticeStateChanged = noticeStateChanged;
}

private bindEventAndElement = () => {
protected bindEventAndElement = () => {
this.$totalChange = document.querySelector('#total-change');
this.$changeAddForm = document.querySelector('#change-add-form');
this.$changeAddForm.addEventListener('submit', this.onSubmitChangeAdd);
Expand All @@ -28,16 +31,16 @@ class AddChangeComponent {
};

refreshChange = () => {
this.$totalChange.textContent = vendingMachine.getTotalMoney().toString();
this.$totalChange.textContent = coinModel.getCoinsValue(vendingMachine.getVendingMachineMoney()).toString();
};

render = () => {
this.parentElement.insertAdjacentHTML('beforeend', this.template());
this.bindEventAndElement();
};

private template = () => `
<div id="change-add-container" class="single-input-container">
protected template = () => `
<div id="change-add-container">
<p>자판기가 보유할 금액을 입력해주세요</p>
<form id="change-add-form">
<input
Expand Down
10 changes: 5 additions & 5 deletions src/js/components/AddProductComponent.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import vendingMachine from '../model/VendingMachine';
import { Product } from '../interfaces/VendingMachine.interface';
import throwableFunctionHandler from '../utils/throwableFunctionHandler';
import * as Component from './abstractComponents/Component';

class AddProductComponent {
class AddProductComponent extends Component.StaticComponent {
parentElement: HTMLElement;
noticeStateChanged: Function;
$productAddForm: HTMLElement;
$productList: HTMLElement;

constructor(parentElement: HTMLElement, noticeStateChanged: Function) {
super();
this.parentElement = parentElement;
this.noticeStateChanged = noticeStateChanged;
}

private bindEventAndElement = () => {
protected bindEventAndElement = () => {
this.$productAddForm = this.parentElement.querySelector('#product-add-form');
this.$productList = this.parentElement.querySelector('#product-list');

Expand All @@ -34,14 +36,12 @@ class AddProductComponent {
}
};

refreshComponent = () => {};

render = () => {
this.parentElement.insertAdjacentHTML('beforeend', this.template());
this.bindEventAndElement();
};

private template = () => `
protected template = () => `
<div id="product-manage-container">
<p>추가할 상품 정보를 입력해주세요.</p>
<form id="product-add-form">
Expand Down
12 changes: 7 additions & 5 deletions src/js/components/ChangeListComponent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import vendingMachine from '../model/VendingMachine';
import * as Component from './abstractComponents/Component';

class ChangeListComponent {
class ChangeListComponent extends Component.DynamicComponent {
$changeList: HTMLElement;
$amountCoin500: HTMLElement;
$amountCoin100: HTMLElement;
Expand All @@ -9,10 +10,11 @@ class ChangeListComponent {
parentElement: HTMLElement;

constructor(parentElement: HTMLElement) {
super();
this.parentElement = parentElement;
}

private bindElement = () => {
protected bindEventAndElement = () => {
this.$changeList = document.querySelector('#change-list');
this.$amountCoin500 = document.querySelector('#amount-coin-500');
this.$amountCoin100 = document.querySelector('#amount-coin-100');
Expand All @@ -21,7 +23,7 @@ class ChangeListComponent {
};

refreshChange = () => {
const { coin10, coin50, coin100, coin500 } = vendingMachine.getChanges();
const { coin10, coin50, coin100, coin500 } = vendingMachine.getVendingMachineMoney();

this.$amountCoin500.textContent = `${coin500}개`;
this.$amountCoin100.textContent = `${coin100}개`;
Expand All @@ -31,10 +33,10 @@ class ChangeListComponent {

render = () => {
this.parentElement.insertAdjacentHTML('beforeend', this.template());
this.bindElement();
this.bindEventAndElement();
};

private template = () => `
protected template = () => `
<div id="change-list-wrapper">
<h4>자판기가 보유한 동전</h4>
<ul id="change-list">
Expand Down
10 changes: 6 additions & 4 deletions src/js/components/InputMoneyComponent.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import vendingMachine from '../model/VendingMachine';
import throwableFunctionHandler from '../utils/throwableFunctionHandler';
import * as Component from './abstractComponents/Component';

class InputMoneyComponent {
class InputMoneyComponent extends Component.DynamicComponent {
$inputMoneyForm: HTMLElement;
$totalMoney: HTMLElement;
noticeStateChanged: Function;
parentElement: HTMLElement;

constructor(parentElement: HTMLElement, noticeStateChanged: Function) {
super();
this.parentElement = parentElement;
this.noticeStateChanged = noticeStateChanged;
}

private bindEventAndElement = () => {
protected bindEventAndElement = () => {
this.$totalMoney = document.querySelector('#total-money');
this.$inputMoneyForm = document.querySelector('#input-money-form');
this.$inputMoneyForm.addEventListener('submit', this.onSubmitInputMoney);
Expand All @@ -28,15 +30,15 @@ class InputMoneyComponent {
};

refreshChange = () => {
this.$totalMoney.textContent = vendingMachine.getUserMoney().toString();
this.$totalMoney.textContent = vendingMachine.getUserInputMoney().toString();
};

render = () => {
this.parentElement.insertAdjacentHTML('beforeend', this.template());
this.bindEventAndElement();
};

private template = () => `
protected template = () => `
<div id="input-money-container">
<p>상품을 구매할 금액을 투입해주세요</p>
<form id="input-money-form">
Expand Down
Loading