# Luqta Web SDK

[![npm version](https://img.shields.io/npm/v/luqta-sdk.svg)](https://www.npmjs.com/package/luqta-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)

> Official JavaScript/TypeScript SDK for integrating Luqta contest management, user engagement, and gamification features into web applications.

---

## Quick Start (5 Minutes)

### Step 1: Install the SDK

```bash
npm install luqta-sdk
```

Or use CDN:
```html
<script src="https://unpkg.com/luqta-sdk@latest/dist/luqta-sdk.min.js"></script>
```

### Step 2: Add a Container Element

```html
<div id="luqta-container"></div>
```

### Step 3: Initialize and Render

```javascript
import { LuqtaClient } from 'luqta-sdk';

const client = new LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: 'user@example.com' }
});

await client.render();
```

**That's it!** The SDK will display a complete contest UI with all features.

---

## Table of Contents

- [Quick Start](#quick-start-5-minutes)
- [Features](#features)
- [Installation](#installation)
- [Usage Modes](#usage-modes)
  - [Pre-configured UI Mode](#1-pre-configured-ui-mode-recommended)
  - [Custom UI Mode](#2-custom-ui-mode-api-only)
- [Complete Examples](#complete-examples)
- [Configuration Options](#configuration-options)
- [API Reference](#api-reference)
- [Level Types](#level-types)
- [Error Handling](#error-handling)
- [TypeScript Support](#typescript-support)
- [FAQ](#faq)

---

## Features

| Feature | Description |
|---------|-------------|
| **Pre-built UI** | Complete contest UI with zero design work |
| **Custom Branding** | Match your app's colors, fonts, and logo |
| **5 Level Types** | Text, QR Code, Link, Image Upload, Quiz |
| **QR Scanner** | Built-in camera scanner for QR codes |
| **Image Upload** | File picker with preview |
| **Quiz System** | Progressive quiz with instant answer feedback |
| **Bilingual** | English and Arabic with RTL support |
| **Zero Storage Mode** | Privacy-first: no PII stored, all identifiers hashed server-side |
| **TypeScript** | Full type definitions included |
| **Zero Dependencies** | Lightweight core library |

---

## Installation

### Option 1: npm (Recommended)

```bash
npm install luqta-sdk
```

```javascript
import { LuqtaClient } from 'luqta-sdk';
```

### Option 2: CDN

```html
<!-- Add before </body> -->
<script src="https://unpkg.com/luqta-sdk@latest/dist/luqta-sdk.min.js"></script>

<script>
  const client = new LuqtaSDK.LuqtaClient({
    // your config
  });
</script>
```

---

## Usage Modes

### 1. Pre-configured UI Mode (Recommended)

**Best for:** Quick integration with minimal code. The SDK handles all UI, screens, and user flows.

#### Option A: All-in-One Initialization

```html
<!DOCTYPE html>
<html>
<head>
  <title>My App with Luqta</title>
</head>
<body>
  <!-- Container where SDK UI will appear -->
  <div id="luqta-container" style="width: 100%; max-width: 800px; margin: 0 auto;"></div>

  <script src="https://unpkg.com/luqta-sdk@latest/dist/luqta-sdk.min.js"></script>
  <script>
    async function initLuqta() {
      const client = new LuqtaSDK.LuqtaClient({
        mode: 'preconfigured',
        apiKey: 'YOUR_API_KEY',
        appId: 'YOUR_APP_ID',
        containerId: 'luqta-container',
        user: { email: 'user@example.com' }
      });

      await client.render();
    }

    initLuqta();
  </script>
</body>
</html>
```

#### Option B: Step-by-Step Initialization (More Control)

For more control over the initialization process, use separate steps:

```javascript
// Step 1: Create client with credentials
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: 'user@example.com' }
});

// Step 2: Initialize SDK (validates credentials, fetches initial data)
const result = await client.initializeSdk();
console.log('Found contests:', result.contests.data?.items?.length);

// Step 3: Configure UI settings (optional - call anytime before render)
client.configure({
  containerId: 'luqta-container',
  branding: {
    primaryColor: '#5304fb',
    secondaryColor: '#8f67fd',
    appName: 'My App'
  },
  locale: 'en',
  rtl: false,
  onAction: (action) => console.log('Action:', action),
  onError: (error) => console.error('Error:', error)
});

// Step 4: Render the UI
await client.render();
```

#### With Custom Branding

```javascript
const client = new LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: 'user@example.com' },

  // Customize to match your brand
  branding: {
    primaryColor: '#5304fb',      // Main buttons and links
    secondaryColor: '#8f67fd',    // Gradients
    backgroundColor: '#ffffff',   // Page background
    textColor: '#111827',         // Text color
    logoUrl: 'https://yoursite.com/logo.png',
    appName: 'My App',
    borderRadius: 12,             // Rounded corners (px)
    fontFamily: 'Inter, sans-serif'
  },

  // Language settings
  locale: 'en',  // 'en' or 'ar'
  rtl: false,    // true for Arabic

  // Track user actions
  onAction: (action) => {
    console.log('User action:', action.type, action.data);
  },

  // Handle errors
  onError: (error) => {
    console.error('SDK error:', error.message);
  }
});

await client.render();
```

#### What the Pre-configured UI Includes

1. **Contest List** - Grid of available contests with banners and status
2. **Contest Detail** - Full contest info with levels, prizes, and progress
3. **Level Modals** - Different UI for each level type:
   - Text: Input field
   - QR: Camera scanner with "Scan Now" button
   - Link: Opens link in new tab
   - Image: Upload area with preview
   - Quiz: Progressive question flow with "Answer (X of Y)" button, instant correct/incorrect feedback
4. **Level Date Validation** - Levels with future start dates or past end dates are blocked with info messages
5. **Congratulation Screens** - Level and contest completion celebrations
6. **Progress Tracking** - Visual progress bars and completed badges
7. **Powered by Luqta** - Subtle watermark on preconfigured UI

---

### 2. Custom UI Mode (API Only)

**Best for:** Building your own UI design while using SDK for API calls.

#### Step 1: Create Client

```javascript
import { LuqtaClient } from 'luqta-sdk';

const client = new LuqtaClient({
  mode: 'custom',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: 'user@example.com' }
});
```

#### Step 2: Initialize User

```javascript
// Required before any user-specific API calls
await client.initializeUser();
```

#### Step 3: Use API Methods

```javascript
// Get all contests
const contests = await client.contests.getAll({ page: 1, per_page: 10 });
console.log(contests.data.items);

// Participate in a contest
await client.contests.participate(123);

// Get contest progress with levels
const progress = await client.contests.getProgress(123);
console.log(progress.data.levels);

// Complete a text level
await client.levels.complete(456, { textContent: 'my answer' });

// Complete a QR level
await client.levels.complete(456, { qrCode: 'scanned_data' });

// Complete an image level
await client.levels.completeWithImage(456, 'https://example.com/photo.jpg');

// Start a quiz (pass quizId and levelId)
const quiz = await client.quiz.start(789, 456);
// Progressive flow: submit answer, check if correct, advance or retry
const answerResult = await client.quiz.submitAnswer(
  quiz.attempt.id, quiz.current_question.id, selectedOptionId, 456
);
if (answerResult.answer.is_correct && !answerResult.navigation.has_next_question) {
  console.log('Quiz complete!');
}
```

---

## Complete Examples

### Example 1: React Integration

```jsx
import { useEffect, useState } from 'react';
import { LuqtaClient } from 'luqta-sdk';

function LuqtaContests() {
  const [client, setClient] = useState(null);

  useEffect(() => {
    const initClient = async () => {
      const luqtaClient = new LuqtaClient({
        mode: 'preconfigured',
        apiKey: process.env.REACT_APP_LUQTA_API_KEY,
        appId: process.env.REACT_APP_LUQTA_APP_ID,
        containerId: 'luqta-container',
        user: { email: 'user@example.com' },
        branding: {
          primaryColor: '#5304fb'
        },
        onAction: (action) => {
          if (action.type === 'level_completed') {
            console.log('Points earned:', action.data.points_earned);
          }
        }
      });

      await luqtaClient.render();
      setClient(luqtaClient);
    };

    initClient();
  }, []);

  return <div id="luqta-container" style={{ minHeight: '600px' }} />;
}

export default LuqtaContests;
```

### Example 2: Vue.js Integration

```vue
<template>
  <div id="luqta-container" style="min-height: 600px;"></div>
</template>

<script>
import { LuqtaClient } from 'luqta-sdk';

export default {
  name: 'LuqtaContests',
  data() {
    return {
      client: null
    };
  },
  async mounted() {
    this.client = new LuqtaClient({
      mode: 'preconfigured',
      apiKey: import.meta.env.VITE_LUQTA_API_KEY,
      appId: import.meta.env.VITE_LUQTA_APP_ID,
      containerId: 'luqta-container',
      user: { email: 'user@example.com' }
    });

    await this.client.render();
  }
};
</script>
```

### Example 3: Next.js Integration

```jsx
'use client';
import { useEffect } from 'react';

export default function LuqtaPage() {
  useEffect(() => {
    const initLuqta = async () => {
      // Dynamic import for client-side only
      const { LuqtaClient } = await import('luqta-sdk');

      const client = new LuqtaClient({
        mode: 'preconfigured',
        apiKey: process.env.NEXT_PUBLIC_LUQTA_API_KEY,
        appId: process.env.NEXT_PUBLIC_LUQTA_APP_ID,
        containerId: 'luqta-container',
        user: { email: 'user@example.com' }
      });

      await client.render();
    };

    initLuqta();
  }, []);

  return <div id="luqta-container" style={{ minHeight: '600px' }} />;
}
```

### Example 4: Vanilla JavaScript (Full Page)

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Luqta Contests</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; background: #f5f5f5; }
    #luqta-container {
      max-width: 900px;
      margin: 20px auto;
      background: white;
      border-radius: 16px;
      min-height: 80vh;
    }
  </style>
</head>
<body>
  <div id="luqta-container"></div>

  <script src="https://unpkg.com/luqta-sdk@latest/dist/luqta-sdk.min.js"></script>
  <script>
    (async function() {
      try {
        const client = new LuqtaSDK.LuqtaClient({
          mode: 'preconfigured',
          apiKey: 'YOUR_API_KEY',
          appId: 'YOUR_APP_ID',
          containerId: 'luqta-container',
          user: { email: 'user@example.com' },
          branding: {
            primaryColor: '#5304fb',
            appName: 'My Contests'
          },
          locale: 'en',
          onAction: function(action) {
            console.log('Action:', action.type);

            if (action.type === 'contest_joined') {
              console.log('Joined contest:', action.data.contest_name);
            }

            if (action.type === 'level_completed') {
              console.log('Earned points:', action.data.points_earned);
            }
          }
        });

        await client.render();
        console.log('Luqta SDK loaded successfully!');

      } catch (error) {
        console.error('Failed to load Luqta SDK:', error);
      }
    })();
  </script>
</body>
</html>
```

### Example 5: Arabic RTL Layout

```javascript
const client = new LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: 'user@example.com' },
  locale: 'ar',  // Arabic language
  rtl: true      // Right-to-left layout
});

await client.render();
```

### Example 6: Step-by-Step with User Sync (New Users)

When integrating with your app where users might not exist in Luqta yet:

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID'
});

// Step 1: Initialize SDK (no user needed)
await client.initializeSdk();
console.log('SDK ready!');

// Step 2: Set user from your app's auth
client.setUser({
  email: currentUser.email,
  phone_number: currentUser.phone
});

// Step 3: Try to initialize user
try {
  await client.initializeUser();
} catch (error) {
  if (error.code === 'USER_NOT_SYNCED') {
    // User doesn't exist in Luqta - sync them first
    await client.syncAndInitializeUser({
      name: currentUser.name,
      email: currentUser.email,
      phone_number: currentUser.phone,
      gender: currentUser.gender,
      country: currentUser.country
    });
  } else {
    throw error;
  }
}

// Step 4: Configure and render UI
client.configure({
  containerId: 'luqta-container',
  branding: { primaryColor: '#5304fb' }
});

await client.render();
```

### Example 7: Zero Storage Mode (Privacy-First)

For apps with strict data privacy requirements — no PII is stored in the database:

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  zeroStorage: true   // all data hashed server-side, no PII stored
});

await client.initializeSdk();

// Set the user identifier
client.setUser({ email: 'user@example.com' });

// Sync user — server hashes everything, nothing readable stored in DB
await client.syncUser({
  email: 'user@example.com',
  name: 'John Doe',
  country: 'PK'
});

// Initialize — lookup uses hash comparison, not plain-text
await client.initializeUser();

// Render UI as normal
client.configure({ containerId: 'luqta-container' });
await client.render();
```

Or use the single convenience call:

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: 'user@example.com' },
  zeroStorage: true
});

await client.initializeSdk();

// Sync + initialize in one call (recommended for new users)
await client.syncAndInitializeUser({
  email: 'user@example.com',
  name: 'John Doe'
});

client.configure({ containerId: 'luqta-container' });
await client.render();
```

### Example 8: UUID-based Identification

Use your own internal user IDs instead of email/phone:

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { uuid: currentUser.id }  // your internal UUID
});

await client.initializeSdk();

await client.syncAndInitializeUser({
  uuid: currentUser.id,
  name: currentUser.displayName
});

client.configure({ containerId: 'luqta-container' });
await client.render();
```

Combine UUID with zero storage for maximum privacy:

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { uuid: currentUser.id },
  zeroStorage: true  // even the UUID is hashed before storage
});
```

---

## Configuration Options

### Required Options

| Option | Type | Description |
|--------|------|-------------|
| `apiKey` | string | Your Luqta API key |
| `appId` | string | Your Luqta application ID |

### Pre-configured Mode Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `mode` | string | - | Must be `'preconfigured'` |
| `containerId` | string | - | ID of the HTML container element |
| `user` | object | - | User identification (email, phone, username, or uuid) |
| `zeroStorage` | boolean | `false` | Enable zero storage mode — no PII stored, all hashed server-side |
| `branding` | object | - | Custom branding options (see below) |
| `locale` | string | `'en'` | Language: `'en'` or `'ar'` |
| `rtl` | boolean | `false` | Enable right-to-left layout |
| `showProfileIcon` | boolean | `false` | Show a draggable floating profile icon when the user is initialized. Tapping it opens a bottom sheet with name, total points, and contest statistics. |
| `onAction` | function | - | Callback for user actions |
| `onError` | function | - | Callback for errors |

### Branding Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `primaryColor` | string | `'#5304fb'` | Main action color |
| `secondaryColor` | string | `'#8f67fd'` | Secondary/gradient color |
| `backgroundColor` | string | `'#ffffff'` | Background color |
| `textColor` | string | `'#111827'` | Main text color |
| `logoUrl` | string | - | URL to your logo image |
| `appName` | string | - | App name displayed in header |
| `borderRadius` | number | `8` | Border radius in pixels |
| `fontFamily` | string | `'system-ui'` | Font family |

### User Identification

Provide at least one identifier:

```javascript
user: { email: 'user@example.com' }
// OR
user: { phone_number: '+923001234567' }  // E.164 format
// OR
user: { username: 'john_doe' }
// OR
user: { uuid: '550e8400-e29b-41d4-a716-446655440000' }
// OR combine any
user: {
  email: 'user@example.com',
  phone_number: '+923001234567',
  username: 'john_doe',
  uuid: '550e8400-e29b-41d4-a716-446655440000'
}
```

**Identifier priority** (used for hashing and lookup): `uuid` → `username` → `email` → `phone`

---

## Profile Icon

When `showProfileIcon: true` and the user is initialized, a draggable circular button is rendered (bottom-right of the container). Tapping it opens a bottom sheet that shows the user's name, contact, total points, and contest statistics — all pulled from `/sdk/app/user/profile`.

The icon is only rendered in **preconfigured mode**. In custom mode, call `client.profile.get()` and build your own profile UI.

### Enable the profile icon

```javascript
const client = new LuqtaSDK.LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: 'user@example.com' },
  showProfileIcon: true,   // <-- opt in
});
```

### Toggle at runtime via configure()

```javascript
client.configure({
  containerId: 'luqta-container',
  showProfileIcon: true,
});
await client.render();
```

The button is hidden automatically when no user token is present, so you can safely leave it enabled during the logged-out state.

---

## Zero Storage Mode

Zero Storage is a privacy-first option that ensures **no readable user data is ever stored** in the database. All user identifiers and profile fields are hashed server-side using SHA-256 before storage.

### How It Works

| Without Zero Storage | With Zero Storage |
|---------------------|-------------------|
| `email: "user@example.com"` stored in DB | `email` column is `null` |
| `name: "John Doe"` stored in DB | `name` column is `null` |
| `phone_number: "+923001234567"` stored in DB | `phone_number` column is `null` |
| All PII visible in database | Only `sdk_hash_user` object stored (all values hashed) |

- The client sends plain data + `zero_storage: true`
- The **server** computes all hashes — the client never hashes anything
- The `sdk_hash_user` object stores a SHA-256 hash for each field
- Lookup and identity matching use the hash, not the original value
- Even after sync, the original identifier values are never persisted

### Enable Zero Storage

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: 'user@example.com' },
  zeroStorage: true   // <-- enable zero storage
});
```

### Zero Storage with Sync

```javascript
// Sync user — only sdk_hash_user written to DB, no PII
await client.syncUser({
  email: 'user@example.com',
  name: 'John Doe',
  country: 'PK'
});

// Initialize user — lookup uses hash, no plain-text comparison
await client.initializeUser();
```

### Zero Storage with syncAndInitializeUser

```javascript
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  zeroStorage: true
});

await client.initializeSdk();
client.setUser({ email: 'user@example.com' });

// Single call: syncs (hash-only) then initializes
await client.syncAndInitializeUser({
  email: 'user@example.com',
  name: 'John Doe'
});
```

### Configuration Option

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `zeroStorage` | boolean | `false` | Enable zero storage mode. When `true`, no PII is written to any column — only hashed data in `sdk_hash_user`. |

### What Gets Stored in Zero Storage Mode

| Field | Stored? |
|-------|---------|
| `email` | No (null) |
| `phone_number` | No (null) |
| `name` | No (null) |
| `uuid` | No (null) |
| `sdk_username` | No (null) |
| `sdk_user_id` | No (null) |
| `country` | No (null) |
| `gender` | No (null) |
| `dob` | No (null) |
| `sdk_hash_user._id` | Yes (lookup hash) |
| `sdk_hash_user.email` | Yes (SHA-256) |
| `sdk_hash_user.name` | Yes (SHA-256) |
| `sdk_hash_user.phone` | Yes (SHA-256) |
| `Status`, `type`, `total_points` | Yes (non-PII operational fields) |

### Switching from Normal to Zero Storage

If a user was previously synced without zero storage and you re-sync with `zeroStorage: true`, all existing readable columns are **automatically nulled out** in the same update — so no stale PII remains.

---

## API Reference

### SDK Initialization Methods

```javascript
// Initialize SDK (validates API key and App ID, fetches initial data)
const result = await client.initializeSdk();
// Returns: { success: true, contests: PaginatedResponse<Contest> }

// Check if SDK is ready
const sdkReady = client.isSdkReady();

// Fetch contests (after SDK initialization)
const contests = await client.fetchContests({ page: 1, per_page: 10 });
```

### UI Configuration Methods

```javascript
// Configure UI settings (for step-by-step initialization)
client.configure({
  containerId: 'luqta-container',
  branding: {
    primaryColor: '#5304fb',
    secondaryColor: '#8f67fd',
    backgroundColor: '#ffffff',
    textColor: '#111827',
    logoUrl: 'https://example.com/logo.png',
    appName: 'My App',
    borderRadius: 12,
    fontFamily: 'Inter, sans-serif'
  },
  locale: 'en',
  rtl: false,
  screens: ['contests', 'quizzes', 'rewards', 'profile'],
  onAction: (action) => console.log(action),
  onError: (error) => console.error(error)
});

// Update branding after initialization
client.setBranding({
  primaryColor: '#ff5722',
  logoUrl: 'https://example.com/new-logo.png'
});

// Get current branding
const branding = client.getBranding();

// Set language
client.setLocale('ar', true); // Arabic with RTL

// Get current locale
const locale = client.getLocale();

// Render UI (for preconfigured mode)
await client.render();

// Refresh UI content
await client.refreshUI();
```

### Contest Methods

```javascript
// Get all contests (paginated)
const contests = await client.contests.getAll({ page: 1, per_page: 10 });

// Get trending contests
const trending = await client.contests.getTrending();

// Get premium contests
const premium = await client.contests.getPremium();

// Get recent contests
const recent = await client.contests.getRecent();

// Participate in a contest
await client.contests.participate(contestId);

// Participate with access code (for private contests)
await client.contests.participate(contestId, 'ACCESS_CODE');

// Get contest progress with levels
const progress = await client.contests.getProgress(contestId);

// Get contest history
const history = await client.contests.getHistory();
```

### Level Methods

```javascript
// Complete text level
await client.levels.complete(levelId, { textContent: 'answer' });

// Complete QR level
await client.levels.complete(levelId, { qrCode: 'scanned_data' });

// Complete link level
await client.levels.complete(levelId, { link: 'https://...' });

// Complete image level
await client.levels.completeWithImage(levelId, 'https://image-url.com/photo.jpg');
// OR with base64
await client.levels.completeWithImage(levelId, 'data:image/jpeg;base64,...');

// Get congratulation data after completing a level
const congrats = await client.levels.getCongratulation(levelId, contestId);
```

### Quiz Methods

```javascript
// Start a quiz (requires quizId and levelId)
const quiz = await client.quiz.start(quizId, levelId);
// GET /sdk/app/quiz/start?quiz_id=X&level_id=Y
// Returns: { attempt, quiz, progress, current_question, full_quiz, instructions }

// Submit an answer (progressive flow — one question at a time)
const result = await client.quiz.submitAnswer(attemptId, questionId, optionId, levelId);
// POST /sdk/app/quiz/attempt/answer
// Body: { current_attempt_id, current_question_id, answer_id, level_id }
// Returns: { answer: { is_correct }, navigation: { has_next_question, can_proceed },
//           current_question, progress, message }
//
// If is_correct == false → show message, retry same question (unlimited attempts)
// If is_correct == true && has_next_question → use current_question for next question
// If is_correct == true && !has_next_question && can_proceed → quiz complete

// Submit/complete the quiz
const results = await client.quiz.submit(attemptId);
// Returns: score, grade, statistics
```

#### Quiz Flow (Progressive)

The quiz uses a progressive flow where each question must be answered correctly before advancing:

1. Call `quiz.start(quizId, levelId)` → receive first `current_question`
2. User selects an option → clicks "Answer (X of Y)" button
3. Call `quiz.submitAnswer(attemptId, questionId, optionId, levelId)`
4. **Incorrect** → toast with API message, user retries the same question
5. **Correct + has next** → show `current_question` from response
6. **Correct + no next + can proceed** → congratulation screen

### User Management Methods

```javascript
// Set user identification (before initialization)
// Accepts: email, phone_number, username, uuid (at least one required)
client.setUser({
  email: 'user@example.com',
  phone_number: '+923001234567',
  username: 'john_doe',
  uuid: '550e8400-e29b-41d4-a716-446655440000'
});

// Initialize user session (required for user-specific APIs)
// Throws USER_NOT_SYNCED if user hasn't been synced yet
await client.initializeUser();

// Sync user profile data to Luqta
// When zeroStorage: true, all fields are hashed server-side before storage
await client.syncUser({
  name: 'John Doe',
  email: 'john@example.com',
  phone_number: '+923001234567',
  username: 'john_doe',
  uuid: '550e8400-...',
  gender: 'male',
  country: 'PK',
  dob: '1990-01-01'
});

// Sync AND initialize in one call (recommended for new users)
await client.syncAndInitializeUser({
  name: 'John Doe',
  email: 'john@example.com',
  phone_number: '+923001234567'
});

// Check if user is initialized
const isReady = client.isInitialized();

// Clear user token (call on logout)
// Removes user session while keeping SDK initialized
client.clearUserToken();
```

#### Handling Logout

When a user logs out from your app, call `clearUserToken()` to remove the user session from the SDK. This ensures:
- No progression data is displayed for the logged-out user
- The SDK remains initialized (no need to re-call `initializeSdk()`)
- A new user can be set and initialized without restarting the SDK

```javascript
function logout() {
  // Clear SDK user session first
  client.clearUserToken();

  // Then clear your app's auth state
  authService.logout();
}
```

### Profile Methods

```javascript
// Get user profile
const profile = await client.profile.get();

// Get user activities
const activities = await client.profile.getActivities();

// Get user progress
const progress = await client.profile.getProgress();

// Submit app feedback
await client.profile.submitFeedback(5, 'Great app!');
```

### Reward Methods

```javascript
// Get rewards list (no user required)
const rewards = await client.rewards.getList();

// Get user earnings (requires user initialization)
const earnings = await client.rewards.getEarnings();

// Redeem a reward
await client.rewards.redeem(rewardId, points);

// Get reward history
const rewardHistory = await client.rewards.getHistory();

// Get prize history
const prizeHistory = await client.rewards.getPrizeHistory();
```

### Notification Methods

```javascript
// Get all notifications
const notifications = await client.notifications.getAll();

// Mark notifications as read
await client.notifications.markAsRead([notificationId1, notificationId2]);

// Update notification settings
await client.notifications.updateSettings({
  push_enabled: true,
  email_enabled: false
});
```

---

## Level Types

The SDK supports 5 level types:

| Type | Description | Completion Method |
|------|-------------|-------------------|
| `text` | User enters a text answer | `complete(id, { textContent: '...' })` |
| `qr` | User scans a QR code | `complete(id, { qrCode: '...' })` |
| `link` | User visits a URL | `complete(id, { link: '...' })` |
| `image` | User uploads a photo | `completeWithImage(id, 'url')` |
| `quiz` | User answers questions | `quiz.start()`, `quiz.submit()` |

### Pre-configured UI for Each Type

**Text Level:**
- Simple input field
- Submit button

**QR Level:**
- QR icon with animated rings
- "Scan Now" button
- Opens camera for scanning
- Fallback manual input

**Link Level:**
- Opens URL in new tab
- Auto-completes on visit

**Image Level:**
- Dashed upload area
- Click to select file
- Image preview before submit
- "Upload photo" button

**Quiz Level:**
- Question with options
- Progress indicator
- Answer feedback
- Results screen with grade

---

## Error Handling

```javascript
import { LuqtaClient, LuqtaError } from 'luqta-sdk';

try {
  await client.contests.participate(123);
} catch (error) {
  if (error instanceof LuqtaError) {
    console.error('Error Code:', error.code);
    console.error('Message:', error.message);

    // Handle specific errors
    switch (error.code) {
      case 'USER_NOT_INITIALIZED':
        await client.initializeUser();
        break;
      case 'NETWORK_ERROR':
        alert('Please check your internet connection');
        break;
      default:
        alert(error.message);
    }
  }
}
```

### Common Error Codes

| Code | Meaning | Solution |
|------|---------|----------|
| `MISSING_API_KEY` | API key not provided | Add apiKey to config |
| `MISSING_APP_ID` | App ID not provided | Add appId to config |
| `SDK_NOT_INITIALIZED` | SDK not initialized | Call `initializeSdk()` first |
| `USER_NOT_INITIALIZED` | User session not started | Call `initializeUser()` first |
| `USER_NOT_SYNCED` | User needs to be synced first | Call `syncUser()` or `syncAndInitializeUser()` |
| `MISSING_USER_CONFIG` | No user email/phone provided | Set user in config or call `setUser()` |
| `INVALID_EMAIL_FORMAT` | Email format invalid | Use valid email |
| `INVALID_PHONE_FORMAT` | Phone format invalid | Use E.164 format (+123...) |
| `NETWORK_ERROR` | No internet connection | Check connection |
| `TIMEOUT` | Request took too long | Try again |

---

## TypeScript Support

The SDK includes full TypeScript definitions:

```typescript
import {
  LuqtaClient,
  LuqtaError,
  Contest,
  Level,
  Quiz,
  ApiResponse,
  PaginatedResponse,
  BrandingConfig,
  UIConfig,
  UIAction,
  UserProfile,
  UserIdentification
} from 'luqta-sdk';

// Type-safe configuration
const config: PreconfiguredModeConfig = {
  mode: 'preconfigured',
  apiKey: 'key',
  appId: 'app',
  containerId: 'container',
  user: { email: 'user@example.com' }
};

const client = new LuqtaClient(config);

// Type-safe responses
const response: PaginatedResponse<Contest> = await client.contests.getAll();
const contests: Contest[] = response.data.items;
```

---

## FAQ

### How do I get API keys?

Contact Luqta support or sign up at [luqta.com](https://luqta.com) to get your API key and App ID.

### Can I use this without user authentication?

Yes! For public contest listings, you don't need user authentication:

```javascript
const client = new LuqtaClient({
  mode: 'custom',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID'
  // No user needed
});

await client.initializeSdk();
const contests = await client.contests.getAll();
```

### How do I change the language?

```javascript
// Option 1: During initialization
const client = new LuqtaClient({
  // ...
  locale: 'ar',
  rtl: true
});

// Option 2: After initialization
client.setLocale('ar', true);
await client.refreshUI();
```

### How do I track user actions?

```javascript
const client = new LuqtaClient({
  // ...
  onAction: (action) => {
    switch (action.type) {
      case 'contest_joined':
        analytics.track('Contest Joined', action.data);
        break;
      case 'level_completed':
        analytics.track('Level Completed', action.data);
        break;
      case 'quiz_completed':
        analytics.track('Quiz Completed', action.data);
        break;
    }
  }
});
```

### Does it work with React/Vue/Angular?

Yes! The SDK is framework-agnostic. See the [Complete Examples](#complete-examples) section for framework-specific code.

### How do I customize the colors?

```javascript
const client = new LuqtaClient({
  // ...
  branding: {
    primaryColor: '#FF5722',    // Orange
    secondaryColor: '#FF9800',  // Light orange
    backgroundColor: '#FAFAFA', // Light gray
    textColor: '#212121'        // Dark gray
  }
});
```

### Can I use my own UI?

Yes! Use Custom UI Mode and build your own interface:

```javascript
const client = new LuqtaClient({
  mode: 'custom',  // API only, no UI
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: 'user@example.com' }
});

await client.initializeUser();
const contests = await client.contests.getAll();
// Now build your own UI with the data
```

### What's the difference between `initializeSdk()` and `initializeUser()`?

- **`initializeSdk()`**: Validates your API key and App ID. No user info needed. Required before fetching contests.
- **`initializeUser()`**: Creates a user session for user-specific actions (participate, complete levels, etc.). Requires at least one identifier (email, phone, username, or uuid).

```javascript
// SDK init (app-level) - always do this first
await client.initializeSdk();

// User init (optional) - only if you need user-specific features
await client.initializeUser();
```

### What is Zero Storage mode?

Zero Storage prevents any readable/plain-text user data from being stored in the database. When enabled (`zeroStorage: true`), the client sends normal data to the server, but the server hashes every field using SHA-256 before storage. Only the hashed values are persisted.

```javascript
// Enable it at client creation
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: 'user@example.com' },
  zeroStorage: true
});
```

Key points:
- No email, phone, name, uuid, or any identifier is stored in readable form
- Hashing is done **server-side** — the client just sends plain data + `zero_storage: true`
- Lookup on subsequent calls uses the same hash formula, so identity is preserved
- If a user was previously synced without zero storage, re-syncing with it enabled automatically clears all old PII columns

See the [Zero Storage Mode](#zero-storage-mode) section for full details.

### What if I get "USER_NOT_SYNCED" error?

This means the user doesn't exist in Luqta yet. Sync them first:

```javascript
try {
  await client.initializeUser();
} catch (error) {
  if (error.code === 'USER_NOT_SYNCED') {
    await client.syncAndInitializeUser({
      name: 'User Name',
      email: 'user@example.com'
    });
  }
}
```

### Can I configure the UI after creating the client?

Yes! Use the `configure()` method:

```javascript
const client = new LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID'
});

await client.initializeSdk();

// Configure UI later
client.configure({
  containerId: 'luqta-container',
  branding: { primaryColor: '#5304fb' }
});

await client.render();
```

---

## Support

- **Documentation**: [https://luqta.com/docs](https://luqta.com/docs)
- **GitHub Issues**: [https://github.com/luqta/luqta-sdk/issues](https://github.com/luqta/luqta-sdk/issues)
- **Email**: support@luqta.com

---

## License

MIT License - see [LICENSE](LICENSE) for details.

---

<p align="center">
  <strong>Made with care by the Luqta Team</strong><br>
  <sub>2024-2025 Luqta. All rights reserved.</sub>
</p>
