Skip to content

feat : React 금액 충전 기능 구현 #121

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

Open
wants to merge 3 commits into
base: yejchoi-vanilla/react-main
Choose a base branch
from
Open
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
1,501 changes: 1,069 additions & 432 deletions packages/yejchoi-react/package-lock.json

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions packages/yejchoi-react/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "yejchoi-react",
"name": "yejchoi",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
Expand All @@ -21,17 +21,18 @@
"devDependencies": {
"@chromatic-com/storybook": "^3.2.6",
"@eslint/js": "^9.21.0",
"@storybook/addon-essentials": "^8.6.10",
"@storybook/addon-onboarding": "^8.6.10",
"@storybook/blocks": "^8.6.10",
"@storybook/experimental-addon-test": "^8.6.10",
"@storybook/react": "^8.6.10",
"@storybook/react-vite": "^8.6.10",
"@storybook/test": "^8.6.10",
"@storybook/addon-essentials": "^8.6.11",
"@storybook/addon-onboarding": "^8.6.11",
"@storybook/blocks": "^8.6.11",
"@storybook/experimental-addon-test": "^8.6.11",
"@storybook/react": "^8.6.11",
"@storybook/react-vite": "^8.6.11",
"@storybook/test": "^8.6.11",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vanilla-extract/vite-plugin": "^5.0.1",
"@vitejs/plugin-react-swc": "^3.8.0",
"@vanilla-extract/webpack-plugin": "^2.3.18",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/browser": "^3.0.9",
"@vitest/coverage-v8": "^3.0.9",
"eslint": "^9.21.0",
Expand All @@ -40,7 +41,7 @@
"eslint-plugin-storybook": "^0.12.0",
"globals": "^15.15.0",
"playwright": "^1.51.1",
"storybook": "^8.6.10",
"storybook": "^8.6.11",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/yejchoi-react/src/components/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ type ButtonProps = FlexVariants & {
style?: CSSProperties;
width?: string;
height?: string;
onClick?: () => void;
}

const Button = (props : ButtonProps) => {
const {label, style, width,height , ...variants} = props
const {label, style, width, onClick ,height , ...variants} = props


return <button className={button(variants)} style={{...style, width, height}}>{label}</button>
return <button className={button(variants)} onClick={onClick} style={{...style, width, height}}>{label}</button>

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Flex = (props : FlexProps) => {

return (
<div className={flex(variants)} style={{ width, height, gap, ...style}}>
children
{children}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export const flex = recipe({
},
defaultVariants: {
direction: 'row',
align: 'center',
justify: 'center',
align: 'start',
justify: 'start',
},
})

2 changes: 1 addition & 1 deletion packages/yejchoi-react/src/components/text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Text = (props: TextProps) => {

return (
<p className={text(textVariants)} style={style} onClick={onClick}>
children
{children}
</p>
);
};
Expand Down
3 changes: 3 additions & 0 deletions packages/yejchoi-react/src/components/text/text.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {recipe} from "@vanilla-extract/recipes";
export const text = recipe({
base: {
display: 'inline-block',
margin: 0
},
variants: {
weight: {
Expand All @@ -21,6 +22,7 @@ export const text = recipe({
'2xl': { fontSize: vars.fontSize['2xl'], lineHeight: '32px' },
'3xl': { fontSize: vars.fontSize['3xl'], lineHeight: '34px' },
},

color: {
primary: { color: vars.themeColor.color.primary },
title: { color: vars.themeColor.color.text.title },
Expand All @@ -29,6 +31,7 @@ export const text = recipe({
white: { color: vars.color.white },
red: { color: vars.color.red },
},

align: {
left: { textAlign: 'left' },
center: { textAlign: 'center' },
Expand Down
7 changes: 3 additions & 4 deletions packages/yejchoi-react/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createRoot } from 'react-dom/client';

import Vending from "./pages/Vending";
import { createRoot } from 'react-dom/client'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
<Vending />
<App />
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Flex from "../../../../components/layout/flex/Flex.tsx";
import Text from "../../../../components/text/Text.tsx";
import Button from "../../../../components/button/Button.tsx";
import Grid from "../../../../components/layout/gird/Grid.tsx";
import {useAtomValue} from "jotai/index";
import {totalAmountState} from "../../../../stores/commonState.ts";

export interface Product {
name : string;
price : number;
id : number;
isUsed? : boolean
}

const LeftSide = () => {

const totalAmount = useAtomValue(totalAmountState);

const productList: Product[] = [
{"id" : 1, "name": "쿨라", "price": 1500, isUsed : true },
{"id" : 2, "name": "속이사이다", "price": 1700 ,isUsed : true },
{"id" : 3, "name": "판타지판타", "price": 1500 ,isUsed : true },
{"id" : 4, "name": "오뎅국물", "price": 1800 ,isUsed : true },
{"id" : 5, "name": "부장라떼", "price": 800 ,isUsed : true },
{"id" : 6, "name": "하늘판타", "price": 1500 ,isUsed : true },
{"id" : 7, "name": "레드뿔", "price": 2500 ,isUsed : true },
{"id" : 8, "name": "핫세븐", "price": 1900 ,isUsed : true },
{"id" : 9, "name": "커피우유", "price": 1400 ,isUsed : true },
{"id" : 10, "name": '데자와', "price": 1400 ,isUsed : true },
{"id" : 11, "name": "렌덤", "price": 1400, isUsed : true },
{"id" : 12, "name": "", "price": 0 ,isUsed : false },
]

return (
<Flex
direction={'column'}
align={'center'}
gap={'20px'}
style={{
flex: 2,
padding: '20px',
background: '#f0f8ff',
}}
>
<Flex direction={'column'} gap={'20px'}>
<Flex grow={'wFull'} justify={'center'} style={{ border: '2px solid #000'}}>

<Text weight={'bold'} size={'2xl'}>
{totalAmount.toLocaleString()}
</Text>

</Flex>
<Grid gridColumns={3} gap={'15px'}>
{productList?.map((product) => (
<Button
key={product.id}
label={product.name}
disabled={!product.isUsed}
style={{
padding: '10px',
backgroundColor: '#bce0fd',
border: '2px solid #000',
}}
/>
))}
</Grid>
</Flex>
</Flex>
);
};

export default LeftSide
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import Flex from "../../../../components/layout/flex/Flex.tsx";
import Button from "../../../../components/button/Button.tsx";
import Text from "../../../../components/text/Text.tsx";
import {useAtom} from "jotai";
import { messageState, totalAmountState} from "../../../../stores/commonState.ts";
import {useState} from "react";


const RightSide = () => {

const [totalAmount, setTotalAmount] = useAtom(totalAmountState);
const [message, setMessage] = useAtom(messageState)
const [inputData, setInputData] = useState(0)

const handleChange = (event : React.ChangeEvent<HTMLInputElement>) => {

setInputData(Number(event?.target?.value))
}

const handleAddClick = () => {

if(isNaN(inputData)) {
setMessage((currVal ) => [
...currVal,
{ message : '숫자만 입력할 수 있습니다. ' , state: "error" }
])

return;
}

if(inputData <= 0) {

setMessage((currVal ) => [
...currVal,
{ message : '1원 이상 넣어주세요' , state: "error" }
])

return;
}

setTotalAmount((currVal) => currVal + inputData);
setInputData(0)

const message= `${inputData}원 을 충전하였습니다.`

setMessage((currVal ) => [
...currVal,
{ message , state: "info" }
])
}

const handleReturnClick = () => {
const message= `${totalAmount}원 을 반환하였습니다.`

if(totalAmount <= 0) {

setMessage((currVal ) => [
...currVal,
{ message : '반환할 잔액이 없습니다.' , state: "error" }
])
return;
}

setMessage((currVal ) => [
...currVal,
{ message , state: "info" }
])
setTotalAmount(0)
setInputData(0)
}

return (
<Flex
direction={'column'}
gap={'15px'}
grow={'full'}
style={{
padding: '20px',
}}
>
<div
style={{
border: '2px solid #000',
padding: '10px',
textAlign: 'center',
}}
>
<input
type="number"
defaultValue={0}
onChange={(event) => handleChange(event)}
value={inputData}

style={{
fontSize: '28px',
textAlign: 'center',
border: 'none',
padding: '10px',
}}
/>
</div>
<Flex justify={'between'} grow={'wFull'}>
<Button label={'투입'} onClick={handleAddClick} />
<Button label={'반환'} onClick={handleReturnClick} />
</Flex>
<Flex
grow={'wFull'}
direction={'column'}
height={'100px'}

style={{
border: '2px solid #000',
background: '#fefefe',
overflowY: 'scroll',
}}
>
{message.map((msg, index) => (
<Text key={index} color={msg.state === 'error' ? 'red' : 'main'} >{msg?.message}</Text>
))}

</Flex>
</Flex>
);
};

export default RightSide
17 changes: 16 additions & 1 deletion packages/yejchoi-react/src/pages/Vending/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import Flex from "../../components/layout/flex/Flex.tsx";
import LeftSide from "./container/LeftSide";
import RightSide from "./container/RightSide";


const Vending = () => {


return (
<>자판기</>
<Flex
direction="row"
width="1000px"
style={{
margin: '0 auto',
border: '2px solid #000',
}}
>
<LeftSide />
<RightSide />
</Flex>
)
}

Expand Down
7 changes: 7 additions & 0 deletions packages/yejchoi-react/src/stores/commonState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {atom} from "jotai";
import {Message} from "../types/commonType.ts";


export const totalAmountState = atom<number>(0)

export const messageState =atom<Message[]>([])
4 changes: 4 additions & 0 deletions packages/yejchoi-react/src/types/commonType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Message {
message : string,
state : 'error' | 'info'
}
5 changes: 2 additions & 3 deletions packages/yejchoi-react/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import react from '@vitejs/plugin-react'
import {vanillaExtractPlugin} from "@vanilla-extract/vite-plugin";

// https://vite.dev/config/
export default defineConfig({
plugins: [react(), vanillaExtractPlugin()],
plugins: [vanillaExtractPlugin(), react()],
})