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

[Feat] 주문서, 로딩, 결과 페이지 구현하기 #7

Merged
merged 14 commits into from
Mar 6, 2025

Conversation

elfffffy
Copy link
Collaborator

@elfffffy elfffffy commented Feb 13, 2025

Summary

  • Step 페이지 구현했습니다. (1~3)
  • Loading 페이지 구현했습니다.
  • Result 페이지 구현했습니다.
  • 기타사항

💡0227 추가 변경사항💡

  1. 폴더 정리 및 파일명 수정
  2. 전반적인 css 수정
  3. 함수명, 변수명 수정
  4. 익명함수 전환 + 함수 수정
  5. 수정된 내용에 따라 pr 내용 수정
  6. session storage 활용 => 💡로 표시
  7. PrevBtn.tsx 파일 생성 및 이전 버튼 활성화

Description

1. Order 페이지를 구현했습니다.

1) Order 페이지에 공통적으로 들어가는 '주문서를 작성해세요' 타이틀과 '이전/다음' 버튼을 컴포넌트로 만들었습니다.

OrderTitle.tsx

export default function OrderTitle() {
  return (
    <div className="mb-25 ml-30 pt-80">
      <div className="flex items-center gap-[8px]">
        <div className="h-25 w-25 bg-[url(../src/assets/img/icon/light.png)] bg-cover"></div>
        <h3 className="text-28 font-bold">주문서를 작성해주세요</h3>
      </div>
    </div>
  );
}

Order1.tsx

<PaginationBtn currentOrder="order1" next="다음" prev="이전" />

PaginationBtn.tsx

import NextBtn from "./NextBtn";
import PrevBtn from "./PrevBtn";

interface PaginationBtnProps {
  currentOrder: string;
  next: string;
  prev: string;
}

export default function PaginationBtn(props: PaginationBtnProps) {
  return (
    <div className="mt-50 grid w-full grid-cols-4 gap-14">
      <PrevBtn orderNum={props.currentOrder} word={props.prev} />
      <NextBtn orderNum={props.currentOrder} word={props.next} />
    </div>
  );
}
  • 이전 버튼과 다음 버튼도 컴포넌트로 만들었습니다.
  • props의 type을 지정했습니다. orderNum은 현재 페이지를 받고, word는 각각 '이전'과 '다음'을 받습니다.

NextBtn.tsx

export default function NextBtn(props: NextBtnProps) {
  const navigate = useNavigate();

  const clickPageNext = (currentOrder: string) => {
    if (currentOrder === "order1") {
      navigate("/order2");
    } else if (currentOrder === "order2") {
      navigate("/order3");
    } else if (currentOrder === "order3") {
      navigate("/loading");
    }
  };

  return (
    <button
      className="col-span-3 h-55 w-full rounded-20 bg-button text-[16px] font-medium text-white"
      onClick={() => {
        clickPageNext(props.orderNum);
      }}
    >
      {props.word}
    </button>
  );
}
  • '다음' 버튼을 클릭했을 때 함수가 실행이 되고, 조건문에 따라 이동될 페이지를 지정했습니다.
  • PrevBtn.tsx도 유사한 형태입니다.

2) 선택지들은 똑같은 형식으로 이루어져 있습니다. 형식은 다음과 같습니다.

Order1.tsx

const TEMP_MAP = [
    { name: "뜨겁게", index: "1-1" },
    { name: "따뜻하게", index: "1-2" },
    { name: "차갑게", index: "1-3" },
  ];
  • name, index, url 등 버튼 생성에 필요한 속성들을 담은 객체 배열을 만듭니다.
const [slectedTemp, setSelectedTemp] = useState<string | null>(null);
const [storageTemp, setStorageTemp] = useState(
    sessionStorage.getItem("selectedTemp"),
  );
const onClickTemp = (index: string) => setSelectedTemp(index);

const tempList = TEMP_MAP.map((btn) => (
    <button
      type="button"
      key={btn.index}
      className={`h-55 w-full rounded-full text-18 ${
        storageTemp != null
          ? storageTemp === btn.index
            ? "bg-green font-semibold text-white"
            : "bg-ccc25 text-888"
          : "bg-ccc25 text-888"
      }`}
      onClick={() => {
        onClickTemp(btn.index);
      }}
    >
      {btn.name}
    </button>

useEffect(() => {
    if (selectedTemp != null) {
      sessionStorage.setItem("selectedTemp", selectedTemp);
      setStorageTemp(selectedTemp);
    }
  }, [selectedTemp]);
));
  • map 함수를 통해 배열에 담긴 속성을 바탕으로 버튼을 생성합니다. 이때, tempList는 배열입니다.
  • 버튼이 기본 상태일 때와 클릭 되었을 때의 css가 다르기 때문에 삼항 연산자를 이용했습니다.
  • 💡 storageTemp가 null이 아니라면, 사용자가 이미 값을 넣어 놓은 상태이므로 새로고침을 했을 때, 선택 css가 발동되도록 했습니다.
  • 클릭했을 때, onClickTemp 함수가 실행이 되고, 이때 선택한 버튼의 index를 함께 전달됩니다.
  • 전달받은 indexuseState()를 활용하여 selectedTemp의 값으로 바꿔줍니다.
  • 💡useEffect를 통해 selectedTemp 값이 생기거나 변경될 때, storageTemp을 변경합니다.
export default function Order1() {
  return (
    ...
    {tempList}
    ...
  );
}
  • 온도 선택 버튼이 렌더링됩니다.

3) 재료 선택 선택지는 다릅니다. (최대 3개 선택)

Order2.tsx

const INGREDIENT_MAP = [
    { name: "행복", index: "4-1", img: "../src/assets/img/ingredient/1.png" },
    { name: "건강", index: "4-2", img: "../src/assets/img/ingredient/2.png" },
    { name: "성취", index: "4-3", img: "../src/assets/img/ingredient/3.png" },
    { name: "금전", index: "4-4", img: "../src/assets/img/ingredient/4.png" },
    { name: "행운", index: "4-5", img: "../src/assets/img/ingredient/5.png" },
    { name: "사랑", index: "4-6", img: "../src/assets/img/ingredient/6.png" },
    { name: "학업", index: "4-7", img: "../src/assets/img/ingredient/7.png" },
    { name: "온정", index: "4-8", img: "../src/assets/img/ingredient/8.png" },
];
  • 기본적으로 객체 배열을 만듭니다.

3-1) 버튼 이벤트에 관한 설명

const [selectedIngredient, setSelectedIngredient] = useState<string[]>(
    JSON.parse(sessionStorage.getItem("selectedIngredient") || "[]"),
);
const [isChecked, setIsChecked] = useState(false);
const [alarm, setAlarm] = useState(false);
  • 💡sessionStorage에 값이 있을 때는 selectedIngredient의 값을 가져옵니다. 빈 배열일 수도 있습니다.
  • isChecked는 체크여부를 파악하는 상태입니다.
  • alarm은 4개 이상 선택 시 발생하는 알림창에 대한 boolean 값입니다.
const ingredientList = INGREDIENT_MAP.map((btn) => {
    return (
      <li>
        <input
         ...
          checked={selectedIngredient?.includes(btn.index)}
          onChange={(e) => {
            clickIngredient(e, btn.index);
          }}
        />
        ...
      </li>
    );
  });
  • map 함수를 이용해서 버튼을 생성합니다.
  • 💡selectedIngredient에 선택한 값이 있다면 체크가 취소되고, 없다면 체크가 되도록 만들었습니다.
  • 버튼을 클릭하면 clickIngredient 함수를 실행하고, 이때 e(event)btn.index도 함께 넘어갑니다.
const clickIngredient = (
    e: React.ChangeEvent<HTMLInputElement>,
    value: string,
  ) => {
    setIsChecked(!isChecked);
    checkedIngredient(value, e.target.checked);
  };
  • 각 변수들의 type을 지정해줍니다. btn.indexvalue입니다.
  • false였던 isChecked의 값을 true로 변환합니다.
  • 클릭을 했을 때, checkedIngredient 함수가 실행되고, value와 이벤트의 체크 여부를 나타내는 e.target.checked도 넘어갑니다.
const checkedIngredient = (value: string, isChecked: boolean) => {
    setSelectedIngredient((prev) => {
      const updateSelectedIngredient = isChecked
        ? [...(prev || []), value]
        : selectedIngredient.filter((item) => item !== value);
      sessionStorage.setItem(
        "selectedIngredient",
        JSON.stringify(updateSelectedIngredient),
      );
      return updateSelectedIngredient;
    });
  };
  • 💡selectedIngredient 값을 변경합니다.
  • 💡체크 되었다면, 이전 배열을 복사하고, 클릭한 값을 넣어 새로운 배열을 만듭니다.
  • 💡체크를 취소했다면, 그 선택 취소한 item을 제외한 나머지들을 모아 새로운 배열을 만듭니다.
  • 💡그리고 ssession storage에 새로운 값을 넣습니다.
  • 💡이후 새로운 값 updateSelectedIngredient 을 반환합니다.
  • 💡하지만, selectedIngredient가 3을 초과할 수 없습니다. 이때, useEffect()를 사용해서 해결합니다.
  useEffect(() => {
    if (selectedIngredient != null) {
      if (selectedIngredient.length > 3) {
        setToggleAlarm(!toggleAlarm);
        setAlarm(true);
        setSelectedIngredient(selectedIngredient.slice(0, 3));
        sessionStorage.setItem(
          "selectedIngredient",
          JSON.stringify(selectedIngredient.slice(0, 3)),
        );
      }
    }
  }, [selectedIngredient, toggleAlarm]);
  • setSelectedIngredient(selectedIngredient.slice(0, 3)); 배열에 3개까지만 담길 수 있도록 설정합니다. 그 이상을 담길 경우, 삭제됩니다.
  • 💡session storage을 업데이트 합니다.
  • setAlarm(true);을 통해 3개를 초과하면 경고 팝업창이 나타나도록 합니다.

3-2) 버튼 css에 관한 설명

<input
          type="checkbox"
          id={btn.index}
          className="peer hidden"
          checked={selectedIngredient?.includes(btn.index)}
          onChange={(e) => {
            clickIngredient(e, btn.index);
          }}
/>
<label
          htmlFor={btn.index}
          className="flex h-100 w-full flex-col items-center justify-center rounded-10 border-[2px] border-solid border-ccc80 text-18 text-888 peer-checked:border-green peer-checked:bg-green10 peer-checked:font-medium peer-checked:text-333"
>
  • input의 className에 peer를 설정하고, label의 className에 peer를 설정하게 되면 형제관계가 됩니다. 즉, input의 상태가 변함에 따라 label의 스타일을 바꿔줄 수 있습니다.

2. Loading 페이지를 구현했습니다.

const [startLoading, setStartLoading] = useState(false);

  const delay = () => setStartLoading(true);

  const toGoNext = () => navigate("/result");

  useEffect(() => {
    setTimeout(delay, 750);
    setTimeout(toGoNext, 6000);
  });
  • setTimeout 함수를 사용해 지정한 시간 후에 코드가 수행될 수 있도록 했습니다.
  • 0.75초 후에 delay 함수가 실행됩니다.
  • 6초 후에 자동으로 Result.tsx 페이지로 넘어갑니다.
  • delay함수가 실행되면, false값을 가지던 startLoading를 true로 바꿉니다.
<div
        className={`absolute h-10 rounded-full bg-green ${startLoading ? "left-0 w-full transition-discrete duration-[5s]" : "-left-200 w-1/2"}`}
></div>
  • start 값의 변화에 따라 진행 바 애니메이션이 나타납니다.
  • 로딩 애니메이션 시간은 5초입니다.

3. Result 페이지를 구현했습니다.

  • 이 페이지는 대부분 앞 단계에서 사용자가 선택한 데이터를 활용해서 보여지게 됩니다.
  • 음료 이미지, 음료 제작자, 음료 이름, 음료 온도, 음료 재료 등이 보여집니다.

4. 기타사항

1) Order3.tsx AI

  • AI로 음료 이름 짓기 => how?

@falconlee236
Copy link
Owner

@elfffffy 지금 lint가 안맞는거 같은데 그거 수정해서 다시 올려줄래?

@falconlee236
Copy link
Owner

지금 충돌이 난다고 나와있을텐데,
충돌 해결방법은

  1. main branch로 이동 -> git checkout main
  2. main branch에서 변경사항 최신화 -> git pull
  3. 너의 작업 브렌치로 이동 git checkout dev/Steps
  4. main 내용을 작업 브랜치에 병합 시도 -> git merge main
  5. 이러면 충돌이 날것임, 로컬 브랜치에서 merge conflict를 해결하기
  6. 해결하면 git add, git commit, git push 하면 레포지토리에도 충돌 해결

Note

merge conflict는 반드시 로컬 브랜치에서 해결한다.

@elfffffy

@elfffffy elfffffy added the feat 🔑 add features label Feb 25, 2025
Copy link
Collaborator Author

@elfffffy elfffffy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolve

  1. 충돌 해결 완료 - 저번에 브랜치 삭제해서 복붙하고 pr 다시 열었었는데, 이런 방법도 있는지 몰랐네요..😥
  2. lint 오류 해결 완료

To Do

  1. 충돌 해결 노션에 정리하기
  2. 새로운 파일들 앞 pr에서 언급된 부분들(폰트 사이즈, 함수명, 폴더 정리 등) 참고해서 수정하고 리뷰 request하기

@falconlee236
Copy link
Owner

충돌 해결한거 확인했고
어떤 부분 수정했는지 간단히 적어서 알려줘 고생이 많습니다 ㅠㅠ @elfffffy

- 앞 pr 내용 포함해서 수정하기
  - 폴더 정리
  - font-size/gap/flex 등 css 수정
  - 함수명, 변수명 정리
  - 익명함수로 전환 및 함수 수정
- session storage 활용해서 뒤로가기 했을 때도 체크한 데이터 보여주기
- useEffect 변경
@elfffffy elfffffy self-assigned this Feb 27, 2025
@elfffffy elfffffy changed the title [3] 주문서, 로딩, 결과 페이지 구현하기 [Feat] 주문서, 로딩, 결과 페이지 구현하기 Feb 27, 2025
- 중복된 코드 정리
@elfffffy
Copy link
Collaborator Author

@falconlee236

PR에 💡0227 추가 변경사항💡내용 추가 및 내용 수정했습니다.

요약하면 다음과 같습니다.

  • 전반적인 폴더 및 파일명 수정
  • 전반적인 css 수정
  • 전반적인 함수 수정
  • session storage 활용(뒤로가기+새로고침 했을 때 유지를 위해) => 💡로 표시

Order2에서 다중 선택 구현 + session storage 저장 때문에 시간이 좀 걸렸습니다..
merge 후에 mainpage에서 음료 선택하면 상세 정보 볼 수 있는 코드 pr 올리겠습니다!

Copy link
Owner

@falconlee236 falconlee236 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 크게 바꿔야 할 것은

전역 상태 관리를 할 때 상태관리 프레임워크를 사용하는 것이 프론트엔드의 기본이라는 점잉랴

그걸 위주로 바꿔주고, 변수 이름, 파일 이름 지을 때 더 고민을 해줬으면 좋겠어

나머지 자바스크립트 관련해서는 내가 계속 피드백 해줄테니까 배우면서 성장해 나가면 좋을 것 같음

@falconlee236
Copy link
Owner

상태관리 라이브러리를 적용하는데 엄청 오래 걸릴거고 엄청 어려울거야 하지만 너는 지금까지 잘 해왔으니까 기대하고 있을게 @elfffffy

- 파일명 수정
- map 함수 index 추가
- redux 설치
- 재료 3개 초과해서 선택할 때 뜨는 알림창을 sweetalert 라이브러리를 이용했습니다.
- redux 설치
@elfffffy
Copy link
Collaborator Author

elfffffy commented Mar 2, 2025

💡0302 추가 변경사항💡

  • 위에 언급한 부분들 모두 수정하고 댓글 달았습니다! 참고해주세요!
  • Redux와 Redux Toolkit을 설치해서 상태관리에 활용했습니다.

Summary

  • Redux를 정리했습니다.
  • Redux를 적용해 코드를 수정했습니다.
  • Sweetalert를 이용해 알림창을 수정했습니다.

1. Redux란?

  • 전역 상태 관리를 위한 라이브러리입니다.
  • Redux Toolkit은 Redux 작업을 단순하게 만들어줍니다.

1) 설치

npm install @reduxjs/toolkit react-redux

2) 주요 개념

store

  • state가 저장되어 있는 저장소입니다.
  • configureStore 함수를 사용해 store를 생성합니다.

action

  • state를 변경시키기 위한 명령입니다.
  • 객체 형태입니다.
  • dispatch()에 의해 reducer에게 전달됩니다.

reducer

  • 상태를 변경하는 일을 담당하는 함수입니다.
  • dispatch로부터 전달받은 action을 참고해서 store 상태를 업데이트 합니다.

dispatch

  • action을 가지고 reducer를 찾아가는 함수입니다.

2. Redux 적용 및 코드 수정

main.tsx

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
);
  • Provider를 통해 store를 전체에서 사용할 수 있도록 설정했습니다.

store/store.ts

export const store = configureStore({
  reducer: {
    temperature: temperatureReducer,
    ...
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
  • store.ts 파일을 생성하고, configureStore()로 store를 생성합니다.
  • store의 state와 dispatch를 외부에서 원활하게 사용하기 위해 타입을 지정해서 export합니다.

store/hook.ts

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
  • useAppDispatch()를 호출할 때, dispatch에 타입이 지정되도록 설정했습니다.
  • useDispatch는 Action 객체를 Reducer로 전달해줍니다.
  • useAppSelector()를 호출할 때, selector에 타입이 지정되도록 설정했습니다.
  • useSelector는 store에 저장된 state에 접근하여 읽을 수 있습니다.

store/features/orders/temperatureSlice.ts

const initialTemperatureState: TemperatureState = { temperature: "" };

export const temperatureSlice = createSlice({
  name: "temperature",
  initialState: initialTemperatureState,
  reducers: {
    updateTemperature: (state, action: PayloadAction<string>) => {
      state.temperature = action.payload;
    },
  },
});
  • createSlice()는 한 번에 reducer, action 생성자를 만들 수 있습니다.
  • name : slice이름으로 prefix로 사용됩니다. => nameReducer
  • initialState : 초기 상태 값입니다.
  • reducers 객체 안에 상태를 변경하는 updateTemperature 함수를 정의했습니다.
  • 이때, updateTemperature 액션 생성자도 자동으로 만들어집니다.
  • 현재 state.temperature 값을 action.payload 값으로 변경하고, payload값은 string이도록 설정했습니다.
export const selectTemperature = (state: RootState) => state.temperature;
export const { updateTemperature } = temperatureSlice.actions;
export default temperatureSlice.reducer;
  • state에서 값을 가져올 때 쉽게 가져오기 위해 selectTemperature export했습니다.
  • 액션 생성자인 updateTemperature를 export 했습니다.
  • store에 등록할 reducer를 export 했습니다.

OrderTemperatureCup.tsx

const dispatch = useAppDispatch();
const currentTemperature = useAppSelector(selectTemperature);
const clickTemperature = (index: string) => dispatch(updateTemperature(index));
const TemperatureList = TEMPERATURE_MAP.map((btn, index) => (
    <button
      className={`h-55 w-full rounded-full text-18 ${
        currentTemperature != null
          ? currentTemperature.temperature === `temperature-${index}`
            ? "bg-green font-semibold text-white"
            : "bg-ccc25 text-888"
          : "bg-ccc25 text-888"
      }`}
      onClick={() => {
        clickTemperature(`temperature-${index}`);
      }}
>
  ));
  • 'useAppSelector' 현재 상태를 가져옵니다.
  • currentTemperature이 null이 아니라면, 사용자가 온도를 선택했다는 뜻입니다.
  • 이때, currentTemperature.temperature === temperature-${index}를 만족하면, 클릭된 css가 적용되도록 했습니다.
  • 클릭할 때, clickTemperature 함수를 실행하고, action을 dispatch합니다.
  • 이때, reducer는 선택한 온도의 index를 받아서 상태를 변경하게 됩니다.

흐름 파악하기

image

3. Sweetalert

npm install sweetalert2

ingredientSlice.tsx

import Swal from "sweetalert2";
export const ingredientSlice = createSlice({
  ...
  reducers: {
    updateIngredient: (state, action: PayloadAction<string>) => {
      if (state.ingredient?.includes(action.payload)) {
        state.ingredient = state.ingredient.filter(
          (item) => item !== action.payload,
        );
      } else {
        if (state.ingredient.length < 3) {
          state.ingredient.push(action.payload);
        } else {
          Swal.fire({
            text: "재료는 최대 3개까지 선택 가능합니다.",
            icon: "warning",
          });
        }
      }
    },
  },
});
  • 이미 선택한 재료를 한 번 더 클릭한다면 취소가 되도록 했습니다.
  • 이미 선택되지 않은 상태에서 state.ingredient.length < 3을 만족하면 재료가 추가되도록 했습니다.
  • 3개를 초과하면 Swal.fire()을 활용하여 경고창이 뜨도록 설정했습니다.

@elfffffy elfffffy requested a review from falconlee236 March 2, 2025 07:36
Copy link
Owner

@falconlee236 falconlee236 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리덕스 엄청 훌륭하게 썼는데? 리덕스가 react의 flux 패턴을 사용하는거라 이것도 한번 알아두면 좋을듯 https://velog.io/@bluecoolgod80/FLUX-%ED%8C%A8%ED%84%B4%EA%B3%BC-Redux

몇가지 물어볼게

  1. 윈도우에서 테스트 하는데 모든음료 보기 버튼 누르면 다음과 같이 나오는게 정상 작동인거지?
    image
  2. 이거 테스트는 어떻게 한거야? 실제로 작동 되는 코드가 맞는지 궁금하네

그리고 이제 어떤 작업할지 이슈를 생성해주고 여기에 댓글 남겨줄래

@elfffffy
참고로 ai 이름 추천은 간단하게 붙일 수 있으니까 그건 후순위로 미뤄두자

@elfffffy
Copy link
Collaborator Author

elfffffy commented Mar 4, 2025

  • 우선 화면이 저렇게 뜨는 거 맞습니다!
  • 모든 음료 보기 버튼이 필요성에 대한 생각이 들었습니다. mainpage, sharedpage에서 슬라이드 하면 이때까지 받은 음료를 볼 수 있고, 클릭만 한다면 자세한 디테일을 볼 수 있기 때문이죠.
  • 오히려.. 선물 받은 음료의 데이터를 분석해서 차트로 보여주는게 더 낫지 않을까..하는 생각이 들었답니다..
  • 예를 들면, 행복이라는 재료는 5번이나 선택되었고, 건강은 10번.. 그리고 아이스는 5잔, 핫은 2잔 + 가능하다면 편지 내용에 많이 언급된 키워드 보여주기.. 이런 것들..?

그래서 정리하자면

  1. 모든 음료 보기 버튼 유지하기
  2. 모든 음료 보기 버튼 삭제하기
  3. 모든 음료 보기 버튼 삭제 + 데이터 분석 페이지 넣기

이렇게 되겠습니다. 뭐가 괜찮을까요?

#13 이슈 작성했습니다!

@elfffffy elfffffy requested a review from falconlee236 March 4, 2025 06:25
@falconlee236
Copy link
Owner

falconlee236 commented Mar 4, 2025

  • 우선 화면이 저렇게 뜨는 거 맞습니다!
  • 모든 음료 보기 버튼이 필요성에 대한 생각이 들었습니다. mainpage, sharedpage에서 슬라이드 하면 이때까지 받은 음료를 볼 수 있고, 클릭만 한다면 자세한 디테일을 볼 수 있기 때문이죠.
  • 오히려.. 선물 받은 음료의 데이터를 분석해서 차트로 보여주는게 더 낫지 않을까..하는 생각이 들었답니다..
  • 예를 들면, 행복이라는 재료는 5번이나 선택되었고, 건강은 10번.. 그리고 아이스는 5잔, 핫은 2잔 + 가능하다면 편지 내용에 많이 언급된 키워드 보여주기.. 이런 것들..?

그래서 정리하자면

  1. 모든 음료 보기 버튼 유지하기
  2. 모든 음료 보기 버튼 삭제하기
  3. 모든 음료 보기 버튼 삭제 + 데이터 분석 페이지 넣기

이렇게 되겠습니다. 뭐가 괜찮을까요?

#13 이슈 작성했습니다!

3번방안이 제일 좋을듯?
모든음료 버튼 삭제 하고 데이터분석 페이지 넣기 이슈도 만들자

@elfffffy 리뷰는 퇴근하고.. 할게..

Copy link
Owner

@falconlee236 falconlee236 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 리뷰 다 했고, 버튼 삭제관련해서는 너가 이슈 따로 팠으니까 머지 할게

@falconlee236 falconlee236 merged commit c32a636 into main Mar 6, 2025
2 checks passed
@falconlee236 falconlee236 deleted the dev/Steps branch March 6, 2025 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat 🔑 add features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants