# 🛒 올리브영 재입고 알림 Bottom Sheet

[![npm version](https://badge.fury.io/js/oliveyoung-restock-notification-popup.svg)](https://www.npmjs.com/package/oliveyoung-restock-notification-popup)
[![GitHub](https://img.shields.io/github/license/oy-daegyun/oliveyoung-mall-restock-noti-pop)](https://github.com/oy-daegyun/oliveyoung-mall-restock-noti-pop)

**모바일 중심의 재입고 알림 신청 Bottom Sheet 웹컴포넌트**

프레임워크에 독립적이며, JSP, Next.js v12/v14 등 모든 환경에서 사용 가능한 네이티브 웹컴포넌트입니다.

## ✨ **주요 특징**

- 📱 **모바일 우선 설계**: Bottom Sheet 형태로 하단에서 슬라이드업
- 💻 **반응형 지원**: PC에서는 중앙 모달로 자동 전환
- 🎭 **부드러운 애니메이션**: 슬라이드 업/다운 효과
- 👆 **스와이프 지원**: 모바일에서 드래그로 닫기
- ✅ **완전한 폼 기능**: 휴대폰/이메일 입력, 유효성 검사, 자동 포맷팅
- 🔄 **상태 관리**: 로딩/성공/에러 상태 처리
- 🎨 **스타일 격리**: Shadow DOM으로 CSS 충돌 방지
- 🌐 **프레임워크 독립적**: Vanilla JS, React, Vue, Angular 모두 지원

## 📦 **설치**

```bash
npm install oliveyoung-restock-notification-popup
```

## 🚀 **빠른 시작**

### 1. HTML에서 직접 사용

```html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/oliveyoung-restock-notification-popup"></script>
  </head>
  <body>
    <restock-notification-popup
      product-id="12345"
      product-name="올리브영 베스트 크림"
      product-price="29,000원"
      api-endpoint="/api/restock-notifications"
    >
    </restock-notification-popup>

    <button onclick="showPopup()">재입고 알림 신청</button>

    <script>
      function showPopup() {
        document.querySelector("restock-notification-popup").show();
      }

      // 성공 이벤트 리스너
      document.addEventListener("restock-notification-success", (e) => {
        console.log("신청 완료:", e.detail);
        alert("재입고 알림이 등록되었습니다!");
      });
    </script>
  </body>
</html>
```

### 2. React/Next.js에서 사용

**⚠️ Next.js에서 올바른 사용법:**

```jsx
import { useEffect, useRef, useState } from "react";

export default function ProductPage() {
  const popupRef = useRef(null);
  const [isComponentLoaded, setIsComponentLoaded] = useState(false);

  useEffect(() => {
    // 웹컴포넌트 동적 로드 (Next.js SSR 고려)
    const loadComponent = async () => {
      if (typeof window !== "undefined") {
        await import("oliveyoung-restock-notification-popup");
        setIsComponentLoaded(true);
      }
    };

    loadComponent();
  }, []);

  const showPopup = () => {
    if (isComponentLoaded && popupRef.current) {
      popupRef.current.show();
    }
  };

  const handleSuccess = (e) => {
    console.log("성공:", e.detail);
    // 성공 처리 로직
  };

  useEffect(() => {
    if (isComponentLoaded && popupRef.current) {
      // 이벤트 리스너 등록
      const popup = popupRef.current;
      popup.addEventListener("restock-notification-success", handleSuccess);

      return () => {
        popup.removeEventListener(
          "restock-notification-success",
          handleSuccess
        );
      };
    }
  }, [isComponentLoaded]);

  return (
    <div>
      <button onClick={showPopup}>재입고 알림 신청</button>

      {/* 컴포넌트 로드 후에만 렌더링 */}
      {isComponentLoaded && (
        <restock-notification-popup
          ref={popupRef}
          product-id="12345"
          product-name="올리브영 베스트 크림"
          product-price="29,000원"
          api-endpoint="/api/restock-notifications"
        />
      )}
    </div>
  );
}
```

**또는 커스텀 훅 사용 (권장):**

```jsx
// hooks/useRestockPopup.js
import { useEffect, useRef, useState } from "react";

export function useRestockPopup() {
  const popupRef = useRef(null);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    const loadComponent = async () => {
      if (typeof window !== "undefined") {
        await import("oliveyoung-restock-notification-popup");
        setIsLoaded(true);
      }
    };
    loadComponent();
  }, []);

  const showPopup = () => {
    if (isLoaded && popupRef.current) {
      popupRef.current.show();
    }
  };

  const hidePopup = () => {
    if (isLoaded && popupRef.current) {
      popupRef.current.hide();
    }
  };

  return { popupRef, isLoaded, showPopup, hidePopup };
}

// ProductPage.js에서 사용
import { useRestockPopup } from "../hooks/useRestockPopup";

export default function ProductPage() {
  const { popupRef, isLoaded, showPopup } = useRestockPopup();

  return (
    <div>
      <button onClick={showPopup}>재입고 알림 신청</button>
      {isLoaded && (
        <restock-notification-popup
          ref={popupRef}
          product-id="12345"
          product-name="올리브영 베스트 크림"
          product-price="29,000원"
          api-endpoint="/api/restock-notifications"
        />
      )}
    </div>
  );
}
```

### 3. JSP 환경에서 사용

```jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <script src="/node_modules/oliveyoung-restock-notification-popup/dist/restock-notification-popup.js"></script>
</head>
<body>
    <div class="product-detail">
        <h1>${product.name}</h1>
        <p class="price">${product.price}원</p>

        <c:if test="${product.soldOut}">
            <button onclick="showRestockPopup()">재입고 알림 신청</button>
        </c:if>
    </div>

    <restock-notification-popup
        product-id="${product.id}"
        product-name="${product.name}"
        product-price="${product.price}원"
        product-image="${product.imageUrl}"
        api-endpoint="/api/restock-notifications">
    </restock-notification-popup>

    <script>
        function showRestockPopup() {
            document.querySelector('restock-notification-popup').show();
        }

        document.addEventListener('restock-notification-success', function(e) {
            alert('재입고 알림 신청이 완료되었습니다!');
        });
    </script>
</body>
</html>
```

## 📋 **API 문서**

### **속성 (Attributes)**

| 속성            | 타입   | 필수 | 기본값 | 설명                       |
| --------------- | ------ | ---- | ------ | -------------------------- |
| `product-id`    | string | ✅   | -      | 상품 ID                    |
| `product-name`  | string | ✅   | -      | 상품명                     |
| `product-price` | string | ✅   | -      | 상품 가격                  |
| `product-image` | string | ❌   | -      | 상품 이미지 URL            |
| `api-endpoint`  | string | ✅   | -      | 재입고 알림 API 엔드포인트 |

### **메서드 (Methods)**

| 메서드   | 설명              |
| -------- | ----------------- |
| `show()` | 팝업을 표시합니다 |
| `hide()` | 팝업을 숨깁니다   |

```javascript
const popup = document.querySelector("restock-notification-popup");
popup.show(); // 팝업 열기
popup.hide(); // 팝업 닫기
```

### **이벤트 (Events)**

| 이벤트                         | 설명             | 데이터                               |
| ------------------------------ | ---------------- | ------------------------------------ |
| `restock-notification-show`    | 팝업이 표시될 때 | -                                    |
| `restock-notification-hide`    | 팝업이 숨겨질 때 | -                                    |
| `restock-notification-success` | 신청 성공시      | `{ productId, phone, email, agree }` |
| `restock-notification-error`   | 신청 실패시      | `{ error, data }`                    |

```javascript
document.addEventListener("restock-notification-success", (e) => {
  console.log("신청 데이터:", e.detail);
  // { productId: '12345', phone: '010-1234-5678', email: 'user@example.com', agree: true }
});

document.addEventListener("restock-notification-error", (e) => {
  console.error("에러:", e.detail.error);
});
```

## 📱 **반응형 디자인**

### **모바일 (768px 미만)**

- 🔥 Bottom Sheet 스타일
- 📱 화면 하단에서 슬라이드업
- 👆 스와이프 다운으로 닫기
- 🎯 큰 터치 버튼

### **PC/태블릿 (768px 이상)**

- 💻 중앙 모달 스타일
- ⚡ 페이드인/아웃 애니메이션
- 🖱️ 기존 데스크톱 UX

## 🔌 **API 연동**

서버에서 다음과 같은 형태의 API를 제공해야 합니다:

### **요청 (Request)**

```http
POST /api/restock-notifications
Content-Type: application/json

{
  "productId": "12345",
  "phone": "010-1234-5678",
  "email": "user@example.com",
  "agree": true
}
```

### **응답 (Response)**

**성공시:**

```json
{
  "success": true,
  "message": "재입고 알림이 등록되었습니다.",
  "notificationId": "abc123"
}
```

**실패시:**

```json
{
  "success": false,
  "message": "이미 등록된 상품입니다."
}
```

## 🎨 **커스터마이징**

CSS 변수를 사용하여 스타일을 커스터마이징할 수 있습니다:

```css
restock-notification-popup {
  --primary-color: #28a745;
  --border-radius: 12px;
  --overlay-color: rgba(0, 0, 0, 0.4);
}
```

## 🌐 **브라우저 지원**

- ✅ Chrome 60+
- ✅ Firefox 63+
- ✅ Safari 13+
- ✅ Edge 79+
- ❌ IE (웹컴포넌트 미지원)

## 📖 **다양한 환경에서의 설치 방법**

### **CDN 사용**

```html
<script src="https://unpkg.com/oliveyoung-restock-notification-popup"></script>
```

### **ES6 모듈**

```javascript
import "oliveyoung-restock-notification-popup";
```

### **CommonJS**

```javascript
require("oliveyoung-restock-notification-popup");
```

### **직접 파일 포함**

```bash
# dist 폴더를 프로젝트에 복사
cp -r node_modules/oliveyoung-restock-notification-popup/dist ./public/libs/
```

```html
<script src="./public/libs/restock-notification-popup.js"></script>
```

## 🧪 **데모**

- 📱 **테스트 페이지**: [GitHub Pages 데모](https://oy-daegyun.github.io/oliveyoung-mall-restock-noti-pop)
- 🔗 **NPM 패키지**: [npmjs.com](https://www.npmjs.com/package/oliveyoung-restock-notification-popup)
- 💾 **GitHub 저장소**: [GitHub](https://github.com/oy-daegyun/oliveyoung-mall-restock-noti-pop)

## 🤝 **기여하기**

1. 저장소를 Fork 합니다
2. 기능 브랜치를 만듭니다 (`git checkout -b feature/amazing-feature`)
3. 변경 사항을 커밋합니다 (`git commit -m 'Add amazing feature'`)
4. 브랜치에 푸시합니다 (`git push origin feature/amazing-feature`)
5. Pull Request를 생성합니다

## 📄 **라이선스**

MIT © [oy-daegyun](https://github.com/oy-daegyun)

## 📞 **문의**

- GitHub Issues: [Issues](https://github.com/oy-daegyun/oliveyoung-mall-restock-noti-pop/issues)
- NPM: [oliveyoung-restock-notification-popup](https://www.npmjs.com/package/oliveyoung-restock-notification-popup)
