npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

luqta-web-sdk

v1.3.0

Published

Luqta SDK - Official JavaScript/TypeScript SDK for Luqta API integration. Easy-to-use client for React, Angular, Vue, Next.js, Svelte, and vanilla JavaScript. Supports CDN and npm installation with TypeScript support, authentication, user management, and

Readme

Luqta Web SDK

npm version License: MIT TypeScript

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

npm install luqta-sdk

Or use CDN:

<script src="https://unpkg.com/luqta-sdk@latest/dist/luqta-sdk.min.js"></script>

Step 2: Add a Container Element

<div id="luqta-container"></div>

Step 3: Initialize and Render

import { LuqtaClient } from 'luqta-sdk';

const client = new LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: '[email protected]' }
});

await client.render();

That's it! The SDK will display a complete contest UI with all features.


Table of Contents


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)

npm install luqta-sdk
import { LuqtaClient } from 'luqta-sdk';

Option 2: CDN

<!-- 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

<!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: '[email protected]' }
      });

      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:

// Step 1: Create client with credentials
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: '[email protected]' }
});

// 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

const client = new LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: '[email protected]' },

  // 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

import { LuqtaClient } from 'luqta-sdk';

const client = new LuqtaClient({
  mode: 'custom',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: '[email protected]' }
});

Step 2: Initialize User

// Required before any user-specific API calls
await client.initializeUser();

Step 3: Use API Methods

// 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

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: '[email protected]' },
        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

<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: '[email protected]' }
    });

    await this.client.render();
  }
};
</script>

Example 3: Next.js Integration

'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: '[email protected]' }
      });

      await client.render();
    };

    initLuqta();
  }, []);

  return <div id="luqta-container" style={{ minHeight: '600px' }} />;
}

Example 4: Vanilla JavaScript (Full Page)

<!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: '[email protected]' },
          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

const client = new LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: '[email protected]' },
  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:

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:

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: '[email protected]' });

// Sync user — server hashes everything, nothing readable stored in DB
await client.syncUser({
  email: '[email protected]',
  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:

const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: '[email protected]' },
  zeroStorage: true
});

await client.initializeSdk();

// Sync + initialize in one call (recommended for new users)
await client.syncAndInitializeUser({
  email: '[email protected]',
  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:

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:

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:

user: { email: '[email protected]' }
// 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: '[email protected]',
  phone_number: '+923001234567',
  username: 'john_doe',
  uuid: '550e8400-e29b-41d4-a716-446655440000'
}

Identifier priority (used for hashing and lookup): uuidusernameemailphone


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

const client = new LuqtaSDK.LuqtaClient({
  mode: 'preconfigured',
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  containerId: 'luqta-container',
  user: { email: '[email protected]' },
  showProfileIcon: true,   // <-- opt in
});

Toggle at runtime via configure()

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: "[email protected]" 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

const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: '[email protected]' },
  zeroStorage: true   // <-- enable zero storage
});

Zero Storage with Sync

// Sync user — only sdk_hash_user written to DB, no PII
await client.syncUser({
  email: '[email protected]',
  name: 'John Doe',
  country: 'PK'
});

// Initialize user — lookup uses hash, no plain-text comparison
await client.initializeUser();

Zero Storage with syncAndInitializeUser

const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  zeroStorage: true
});

await client.initializeSdk();
client.setUser({ email: '[email protected]' });

// Single call: syncs (hash-only) then initializes
await client.syncAndInitializeUser({
  email: '[email protected]',
  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

// 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

// 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

// 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

// 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

// 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

// Set user identification (before initialization)
// Accepts: email, phone_number, username, uuid (at least one required)
client.setUser({
  email: '[email protected]',
  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: '[email protected]',
  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: '[email protected]',
  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
function logout() {
  // Clear SDK user session first
  client.clearUserToken();

  // Then clear your app's auth state
  authService.logout();
}

Profile Methods

// 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

// 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

// 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

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:

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: '[email protected]' }
};

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 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:

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?

// 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?

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 section for framework-specific code.

How do I customize the colors?

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:

const client = new LuqtaClient({
  mode: 'custom',  // API only, no UI
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: '[email protected]' }
});

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).
// 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.

// Enable it at client creation
const client = new LuqtaSDK.LuqtaClient({
  apiKey: 'YOUR_API_KEY',
  appId: 'YOUR_APP_ID',
  user: { email: '[email protected]' },
  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 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:

try {
  await client.initializeUser();
} catch (error) {
  if (error.code === 'USER_NOT_SYNCED') {
    await client.syncAndInitializeUser({
      name: 'User Name',
      email: '[email protected]'
    });
  }
}

Can I configure the UI after creating the client?

Yes! Use the configure() method:

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


License

MIT License - see LICENSE for details.