Skip to content

Commit fefaa6e

Browse files
committed
add: SignInGuard 및 상품 옵션 입력용 컴포넌트 추가
1 parent 5652173 commit fefaa6e

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React from 'react';
2+
import * as R from 'remeda';
3+
4+
import { FormControl, InputLabel, MenuItem, Select, TextField, Tooltip } from '@mui/material';
5+
6+
import ShopAPISchema from '@pyconkr-shop/schemas';
7+
import ShopAPIUtil from '@pyconkr-shop/utils';
8+
import { PriceDisplay } from './price_display';
9+
10+
type CommonOptionGroupType = {
11+
id: string;
12+
name: string;
13+
}
14+
type SelectableOptionGroupType = CommonOptionGroupType & {
15+
is_custom_response: false;
16+
custom_response_pattern: null;
17+
}
18+
type CustomResponseOptionGroupType = CommonOptionGroupType & {
19+
is_custom_response: true;
20+
custom_response_pattern: string;
21+
}
22+
type OptionGroupType = SelectableOptionGroupType | CustomResponseOptionGroupType;
23+
24+
type SimplifiedOption = Pick<ShopAPISchema.Option, ('id' | 'name' | 'additional_price' | 'leftover_stock')>;
25+
26+
const isFilledString = (str: unknown): str is string => R.isString(str) && !R.isEmpty(str);
27+
28+
const SelectableOptionGroupInput: React.FC<{
29+
optionGroup: SelectableOptionGroupType;
30+
options: SimplifiedOption[];
31+
defaultValue?: string;
32+
disabled?: boolean;
33+
disabledReason?: string;
34+
}> = ({ optionGroup, options, defaultValue, disabled, disabledReason }) => {
35+
const optionElements = options.map(
36+
(option) => {
37+
const isOptionOutOfStock = R.isNumber(option.leftover_stock) && option.leftover_stock <= 0;
38+
39+
return <MenuItem key={option.id} value={option.id} disabled={disabled || isOptionOutOfStock}>
40+
{option.name}
41+
{option.additional_price > 0 && (<> [ +<PriceDisplay price={option.additional_price} /> ]</>)}
42+
{isOptionOutOfStock && <> (품절)</>}
43+
</MenuItem>
44+
}
45+
)
46+
47+
const selectElement = <FormControl fullWidth>
48+
<InputLabel id={`${optionGroup.id}label`}>{optionGroup.name}</InputLabel>
49+
<Select label={`${optionGroup.id}label`} name={optionGroup.id} defaultValue={defaultValue} disabled={disabled} required>
50+
{optionElements}
51+
</Select>
52+
</FormControl>
53+
54+
return isFilledString(disabledReason) ? <Tooltip title={disabledReason}>{selectElement}</Tooltip> : selectElement
55+
}
56+
57+
const CustomResponseOptionGroupInput: React.FC<{
58+
optionGroup: CustomResponseOptionGroupType;
59+
defaultValue?: string;
60+
disabled?: boolean;
61+
disabledReason?: string;
62+
}> = ({ optionGroup, defaultValue, disabled, disabledReason }) => {
63+
const pattern = ShopAPIUtil.getCustomResponsePattern(optionGroup)?.source;
64+
65+
const textFieldElement = <TextField
66+
label={optionGroup.name}
67+
name={optionGroup.id}
68+
required
69+
defaultValue={defaultValue}
70+
disabled={disabled}
71+
slotProps={{ htmlInput: { pattern } }} />
72+
73+
return isFilledString(disabledReason) ? <Tooltip title={disabledReason}>{textFieldElement}</Tooltip> : textFieldElement
74+
}
75+
76+
export const OptionGroupInput: React.FC<{
77+
optionGroup: OptionGroupType;
78+
options: SimplifiedOption[];
79+
80+
defaultValue?: string;
81+
disabled?: boolean;
82+
disabledReason?: string;
83+
}> = ({ optionGroup, options, defaultValue, disabled, disabledReason }) => optionGroup.is_custom_response
84+
? <CustomResponseOptionGroupInput optionGroup={optionGroup} defaultValue={defaultValue} disabled={disabled} disabledReason={disabledReason} />
85+
: <SelectableOptionGroupInput optionGroup={optionGroup} options={options} defaultValue={defaultValue} disabled={disabled} disabledReason={disabledReason} />
86+
87+
88+
export const OrderProductRelationOptionInput: React.FC<{
89+
optionRel: ShopAPISchema.OrderProductItem["options"][number];
90+
disabled?: boolean;
91+
disabledReason?: string;
92+
}> = ({ optionRel, disabled, disabledReason }) => {
93+
let defaultValue: string | null = null;
94+
let guessedDisabledReason: string | undefined = undefined;
95+
let dummyOptions: { id: string; name: string; additional_price: number; leftover_stock: number | null }[] = [];
96+
97+
// type hinting을 위해 if문을 사용함
98+
if (optionRel.product_option_group.is_custom_response === false && R.isNonNull(optionRel.product_option)) {
99+
defaultValue = optionRel.product_option.id;
100+
guessedDisabledReason = '추가 비용이 발생하는 옵션은 수정할 수 없어요.';
101+
dummyOptions = [
102+
{
103+
id: optionRel.product_option.id,
104+
name: optionRel.product_option.name,
105+
additional_price: optionRel.product_option.additional_price || 0,
106+
leftover_stock: null,
107+
}
108+
]
109+
} else {
110+
defaultValue = optionRel.custom_response;
111+
}
112+
113+
return <OptionGroupInput
114+
key={optionRel.product_option_group.id}
115+
optionGroup={optionRel.product_option_group}
116+
options={dummyOptions}
117+
defaultValue={defaultValue || undefined}
118+
disabled={disabled || !ShopAPIUtil.isOrderProductOptionModifiable(optionRel)}
119+
disabledReason={disabledReason || guessedDisabledReason}
120+
/>
121+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
import { Typography } from '@mui/material';
4+
5+
import ShopAPIHook from '@pyconkr-shop/hooks';
6+
7+
export const ShopSignInGuard: React.FC<{ children: React.ReactNode, fallback?: React.ReactNode }> = ({ children, fallback }) => {
8+
const { data } = ShopAPIHook.useUserStatus();
9+
const renderedFallback = fallback || <Typography variant="h6" gutterBottom>로그인 후 이용해주세요.</Typography>;
10+
return (data && data.meta.is_authenticated === true) ? children : renderedFallback;
11+
};

0 commit comments

Comments
 (0)