# 퀴즈

퀴즈는 사용자가 간단한 질문에 답하고 포인트를 받을 수 있는 기능입니다. 오퍼월과 함께 사용하면 참여율이 높아집니다.

<figure><img src="/files/CoDtzAmQSUpbJq2C7Wcq" alt=""><figcaption></figcaption></figure>

## 시작하기

### 퀴즈 완료 이벤트 리스너 설정 (권장)

퀴즈 완료 이벤트를 받으려면 리스너를 설정합니다.

```typescript
import AdchainSdk, { addQuizCompletedListener } from '@1selfworld/adchain-sdk-core-react-native';

const QUIZ_UNIT_ID = 'quiz_unit_001'; // 대시보드에서 생성한 Unit ID

useEffect(() => {
  const subscription = addQuizCompletedListener((event) => {
    if (event.unitId === QUIZ_UNIT_ID) {
      loadQuizList();
    }
  });

  return () => subscription.remove();
}, []);
```

## 퀴즈 목록 조회

현재 진행 중인 퀴즈 목록을 가져옵니다.

```typescript
try {
  const quizResponse = await AdchainSdk.loadQuizList(QUIZ_UNIT_ID);
  const titleText = quizResponse.titleText; // 섹션 제목
  const quizzes = quizResponse.events;      // 퀴즈 목록

  quizzes.forEach(quiz => {
    console.log(`퀴즈: ${quiz.title}`);
    console.log(`보상: ${quiz.reward}`);
  });

  setQuizList(quizzes);
  setTitleText(titleText);
} catch (error) {
  console.error('퀴즈 조회 실패:', error);
}
```

{% hint style="info" %}
**중요:** 모든 Quiz API는 `unitId` 파라미터가 필수입니다. AdChain 대시보드에서 Quiz Unit을 생성하고 해당 ID를 사용하세요.
{% endhint %}

## 응답 데이터 구조

### QuizResponse

`loadQuizList()`가 반환하는 전체 응답 구조입니다.

| 필드                     | 타입        | 설명                        |
| ---------------------- | --------- | ------------------------- |
| `success`              | `boolean` | 요청 성공 여부                  |
| `titleText`            | `string?` | 퀴즈 섹션 제목 (예: "데일리 1분 퀴즈") |
| `completedImageUrl`    | `string?` | 모든 퀴즈 완료 시 표시할 배너 이미지 URL |
| `completedImageWidth`  | `number?` | 완료 배너 이미지 너비 (픽셀)         |
| `completedImageHeight` | `number?` | 완료 배너 이미지 높이 (픽셀)         |
| `events`               | `Quiz[]`  | 퀴즈 목록                     |
| `message`              | `string?` | 에러 메시지 (실패 시)             |

### Quiz

개별 퀴즈 객체의 구조입니다.

| 필드            | 타입        | 설명          |
| ------------- | --------- | ----------- |
| `id`          | `string`  | 퀴즈 고유 ID    |
| `title`       | `string`  | 퀴즈 제목       |
| `description` | `string`  | 퀴즈 설명       |
| `imageUrl`    | `string`  | 썸네일 이미지 URL |
| `reward`      | `number`  | 보상 포인트      |
| `isCompleted` | `boolean` | 완료 여부       |

**예시:**

```json
{
  "success": true,
  "titleText": "데일리 1분 퀴즈",
  "completedImageUrl": "https://example.com/completed.png",
  "completedImageWidth": 800,
  "completedImageHeight": 200,
  "events": [
    {
      "id": "quiz_001",
      "title": "오늘의 상식 퀴즈",
      "description": "간단한 상식 문제 5개",
      "imageUrl": "https://example.com/quiz1.jpg",
      "reward": 100,
      "isCompleted": false
    }
  ]
}
```

## 퀴즈 클릭 처리

사용자가 퀴즈를 클릭하면 `clickQuiz()` 메서드를 호출합니다. SDK가 자동으로 WebView를 열고 퀴즈를 진행합니다.

```typescript
const handleQuizClick = async (quiz) => {
  try {
    await AdchainSdk.clickQuiz(QUIZ_UNIT_ID, quiz.id);
    // 퀴즈 WebView가 자동으로 열립니다
  } catch (error) {
    console.error('퀴즈 클릭 실패:', error);
  }
};

<TouchableOpacity onPress={() => handleQuizClick(quiz)}>
  <Text>{quiz.title}</Text>
</TouchableOpacity>
```

### clickQuiz 동작 흐름

`clickQuiz()` 호출 시 SDK는 자동으로:

1. `quiz_clicked` 이벤트 추적
2. 퀴즈 WebView를 전체화면으로 열기
3. 사용자가 퀴즈 완료 시:
   * `quiz_completed` 이벤트 추적
   * `addQuizCompletedListener` 콜백 호출 (설정된 경우)
   * 포인트 자동 지급 (서버)

## 이벤트 리스너 상세

### onQuizCompleted

사용자가 퀴즈를 완료했을 때 호출됩니다. **이 시점에서 UI를 새로고침해야 합니다.**

```typescript
const subscription = addQuizCompletedListener((event) => {
  // event.quizId: 완료된 퀴즈의 ID
  // event.unitId: 완료된 퀴즈의 Unit ID

  if (event.unitId === QUIZ_UNIT_ID) {
    loadQuizList();
    Alert.alert('퀴즈 완료!', '포인트가 지급되었습니다.');
  }
});
```

## 자동 추적 이벤트

SDK는 다음 이벤트를 자동으로 추적하여 AdChain 대시보드에 전송합니다.

| 이벤트명             | 발생 시점               | 페이로드                                                                            |
| ---------------- | ------------------- | ------------------------------------------------------------------------------- |
| `quiz_impressed` | 퀴즈 목록 로드 시 (각 퀴즈마다) | `quizId`, `quizTitle`, `impressionOrder`, `placementId`, `userId`               |
| `quiz_clicked`   | 퀴즈 클릭 시             | `quizId`, `quizTitle`, `landingUrl`, `impressionOrder`, `placementId`, `userId` |
| `quiz_completed` | 퀴즈 완료 시             | `quizId`, `quizTitle`, `impressionOrder`, `placementId`, `userId`               |

이 이벤트들은 별도로 구현할 필요가 없으며, AdChain 대시보드의 통계에 자동으로 반영됩니다.

## UI 구현 예시

`FlatList`로 퀴즈 목록을 구현하는 전체 예시:

```typescript
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, Image, StyleSheet } from 'react-native';
import AdchainSdk, { addQuizCompletedListener } from '@1selfworld/adchain-sdk-core-react-native';

const QUIZ_UNIT_ID = 'quiz_unit_001';

const QuizList = () => {
  const [quizzes, setQuizzes] = useState([]);
  const [titleText, setTitleText] = useState('');

  const loadQuizList = async () => {
    try {
      const response = await AdchainSdk.loadQuizList(QUIZ_UNIT_ID);
      setQuizzes(response.events);
      setTitleText(response.titleText || '데일리 퀴즈');
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    loadQuizList();

    const subscription = addQuizCompletedListener((event) => {
      if (event.unitId === QUIZ_UNIT_ID) {
        loadQuizList();
      }
    });

    return () => subscription.remove();
  }, []);

  const handleQuizClick = async (quiz) => {
    if (quiz.isCompleted) return;
    try {
      await AdchainSdk.clickQuiz(QUIZ_UNIT_ID, quiz.id);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <View>
      <Text style={styles.title}>{titleText}</Text>
      <FlatList
        data={quizzes}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <TouchableOpacity
            onPress={() => handleQuizClick(item)}
            disabled={item.isCompleted}
            style={[styles.quizItem, item.isCompleted && styles.completedItem]}
          >
            <Image source={{ uri: item.imageUrl }} style={styles.image} />
            <View style={styles.content}>
              <Text style={styles.quizTitle}>{item.title}</Text>
              <Text style={styles.description}>{item.description}</Text>
              <Text style={styles.point}>{item.reward}P</Text>
            </View>
            {item.isCompleted && (
              <View style={styles.completedBadge}>
                <Text>완료</Text>
              </View>
            )}
          </TouchableOpacity>
        )}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  title: { fontSize: 18, fontWeight: 'bold', padding: 16 },
  quizItem: { flexDirection: 'row', padding: 16 },
  completedItem: { opacity: 0.5 },
  image: { width: 64, height: 64 },
  content: { flex: 1, marginLeft: 12 },
  quizTitle: { fontSize: 16, fontWeight: '600' },
  description: { fontSize: 14, color: '#666', marginTop: 4 },
  point: { fontSize: 14, color: '#FF6B6B', marginTop: 4 },
  completedBadge: { padding: 4 },
});
```

## 빈 상태 처리

모든 퀴즈가 완료되었을 때 `completedImageUrl`을 사용하여 완료 배너를 표시할 수 있습니다. 완료 배너는 단순 표시로 두지 말고 **탭 시 Offerwall을 열어 추가 리워드 동선으로 연결**하는 것을 권장합니다.

```typescript
import { Image, Text, TouchableOpacity, View } from 'react-native';
import AdchainSdk from '@1selfworld/adchain-sdk-core-react-native';

const CompletedBanner = ({ quizResponse }) => {
  const handleCompletedBannerPress = async () => {
    try {
      await AdchainSdk.openOfferwall('quiz_offerwall');
    } catch (e) {
      console.error('오퍼월 열기 실패', e);
    }
  };

  if (quizResponse?.events.length === 0 && quizResponse.completedImageUrl) {
    return (
      <View style={styles.completedContainer}>
        <TouchableOpacity onPress={handleCompletedBannerPress} activeOpacity={0.8}>
          <Image
            source={{ uri: quizResponse.completedImageUrl }}
            style={{
              width: '100%',
              aspectRatio:
                quizResponse.completedImageWidth! / quizResponse.completedImageHeight!,
            }}
            resizeMode="contain"
          />
        </TouchableOpacity>
        <Text style={styles.completedText}>
          모든 퀴즈를 완료했습니다! 배너를 탭해 추가 리워드를 받아보세요.
        </Text>
      </View>
    );
  }

  return null;
};
```

## 여러 Quiz Unit 운영

같은 앱에서 여러 Quiz Unit을 운영할 수 있습니다 (예: 메인 화면, 프로필 화면). 각 Unit별로 다른 퀴즈 세트를 관리합니다.

```typescript
const MAIN_QUIZ_UNIT = 'quiz_main_001';
const PROFILE_QUIZ_UNIT = 'quiz_profile_001';

// 메인 화면
const quizzes = await AdchainSdk.loadQuizList(MAIN_QUIZ_UNIT);
await AdchainSdk.clickQuiz(MAIN_QUIZ_UNIT, quizId);

// 프로필 화면
const profileQuizzes = await AdchainSdk.loadQuizList(PROFILE_QUIZ_UNIT);
await AdchainSdk.clickQuiz(PROFILE_QUIZ_UNIT, quizId);
```

이벤트 리스너에서는 `event.unitId`로 어느 Unit의 이벤트인지 판단합니다.

## 주의사항

* **로그인 필수**: 사용자가 로그인되어 있어야 퀴즈 조회 가능
* **네트워크 필요**: 오프라인 상태에서는 조회 불가
* **이벤트 리스너 우선 설정**: `loadQuizList()` 호출 전에 리스너를 등록하세요
* **`onQuizCompleted`에서 반드시 새로고침**: 그렇지 않으면 UI가 stale 상태로 남습니다

## 다음 단계

* [미션 사용하기](/undefined-2/mission.md)
* [오퍼월](/undefined-2/offerwall.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://adchain-doc.1self.world/undefined-2/quiz.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
