questro
Version:
A lightweight, modular gamification library for React with unique visual components. Features combo meters, daily challenges, achievement toasts, and progress rings. Add points, badges, quests, leaderboards, levels/XP, streaks, and notifications with zero
2 lines • 6.03 kB
JavaScript
import {a,b,c,h as h$1}from'./chunk-3QNKEEDV.mjs';import {c as c$1}from'./chunk-R2GD7YTX.mjs';import {createContext,useState,useMemo,useEffect,useContext,useCallback}from'react';import {jsx,jsxs,Fragment}from'react/jsx-runtime';var u=class t{constructor(e){this.events=a();this.config=e,this.badges=new Map(e.badges.map(r=>[r.id,r])),this.state={userId:e.userId,unlockedBadges:[],progress:{}},this.initializeProgress();}initializeProgress(){this.config.badges.forEach(e=>{this.state.progress[e.id]||(this.state.progress[e.id]={badgeId:e.id,current:0,target:this.calculateTarget(e),percentage:0,completed:false});});}calculateTarget(e){return e.conditions.reduce((r,s)=>r+s.value,0)}getAllBadges(){return Array.from(this.badges.values())}getUnlockedBadges(){return [...this.state.unlockedBadges]}getLockedBadges(){let e=new Set(this.state.unlockedBadges.map(r=>r.badgeId));return Array.from(this.badges.values()).filter(r=>!e.has(r.id))}getBadgeById(e){return this.badges.get(e)}isBadgeUnlocked(e){return this.state.unlockedBadges.some(r=>r.badgeId===e)}getProgress(e){return this.state.progress[e]}unlockBadge(e){if(this.isBadgeUnlocked(e))return null;let r=this.badges.get(e);if(!r)return null;let s={id:c(),userId:this.state.userId,badgeId:e,unlockedAt:b(),createdAt:b(),updatedAt:b()};this.state.unlockedBadges.push(s);let d=this.state.progress[e];return d&&(d.completed=true,d.percentage=100),this.events.emit("badgeUnlocked",{badge:r,unlockedBadge:s}),this.config.onBadgeUnlocked?.(r),s}updateProgress(e,r){let s=this.state.progress[e];if(!s){let d=this.badges.get(e);if(!d)throw new Error(`Badge with id ${e} not found`);s={badgeId:e,current:0,target:this.calculateTarget(d),percentage:0,completed:false},this.state.progress[e]=s;}return s.current=Math.min(r,s.target),s.percentage=h$1(s.current,s.target),s.completed=s.current>=s.target,this.events.emit("progressUpdated",{badgeId:e,progress:s}),s.completed&&!this.isBadgeUnlocked(e)&&this.unlockBadge(e),s}checkAndUnlockBadges(){let e=[];return Array.from(this.badges.values()).forEach(r=>{if(!this.isBadgeUnlocked(r.id)&&this.state.progress[r.id]?.completed){let d=this.unlockBadge(r.id);d&&e.push(d);}}),e}reset(){this.state.unlockedBadges=[],this.state.progress={},this.initializeProgress();}toJSON(){return {userId:this.state.userId,unlockedBadges:[...this.state.unlockedBadges],progress:{...this.state.progress}}}static fromJSON(e,r){let s=new t(e);return s.state={userId:r.userId,unlockedBadges:[...r.unlockedBadges],progress:{...r.progress}},s}};var I=createContext(null);function M({children:t,config:e,storage:r=new c$1,storageKey:s=`badges:${e.userId}`}){let[d,n]=useState(true),o=useMemo(()=>new u(e),[e.userId,e.badges]);return useEffect(()=>{let i=true;return (async()=>{try{let a=await r.get(s);a&&i&&Object.assign(o,u.fromJSON(e,a));}catch(a){console.error("Failed to load badges state:",a);}finally{i&&n(false);}})(),()=>{i=false;}},[r,s,e,o]),useEffect(()=>{if(d)return;let i=o.events.on("badgeUnlocked",async()=>{try{await r.set(s,o.toJSON());}catch(a){console.error("Failed to save badges state:",a);}}),l=o.events.on("progressUpdated",async()=>{try{await r.set(s,o.toJSON());}catch(a){console.error("Failed to save badges state:",a);}});return ()=>{i(),l();}},[o,r,s,d]),d?null:jsx(I.Provider,{value:o,children:t})}function h(){let t=useContext(I);if(!t)throw new Error("useBadgesContext must be used within a BadgesProvider");return t}function p(){let t=h(),[e]=useState(t.getAllBadges()),[r,s]=useState(t.getUnlockedBadges()),[d,n]=useState(t.getLockedBadges());useEffect(()=>t.events.on("badgeUnlocked",()=>{s(t.getUnlockedBadges()),n(t.getLockedBadges());}),[t]);let o=useCallback(c=>t.unlockBadge(c),[t]),i=useCallback((c,x)=>t.updateProgress(c,x),[t]),l=useCallback(c=>t.isBadgeUnlocked(c),[t]),a=useCallback(c=>t.getProgress(c),[t]),f=useCallback(()=>t.checkAndUnlockBadges(),[t]),C=useCallback(()=>{t.reset(),s(t.getUnlockedBadges()),n(t.getLockedBadges());},[t]);return {allBadges:e,unlockedBadges:r,lockedBadges:d,unlockBadge:o,updateProgress:i,isBadgeUnlocked:l,getProgress:a,checkAndUnlockBadges:f,reset:C}}function S({badge:t,children:e,...r}){let{isBadgeUnlocked:s,getProgress:d}=p(),n=s(t.id),o=d(t.id);return e?jsx(Fragment,{children:e(t,n,o)}):jsxs("div",{"data-questro-badge":true,"data-unlocked":n,...r,children:[t.icon&&jsx("span",{"data-questro-badge-icon":true,children:t.icon}),jsxs("div",{"data-questro-badge-content":true,children:[jsx("h3",{"data-questro-badge-name":true,children:t.name}),jsx("p",{"data-questro-badge-description":true,children:t.description}),o&&!n&&jsxs("div",{"data-questro-badge-progress":true,children:["Progress: ",o.percentage.toFixed(0),"%"]})]})]})}function N({badges:t,showLocked:e=true,showHidden:r=false,children:s,...d}){let{allBadges:n,isBadgeUnlocked:o}=p(),l=(t??n).filter(a=>{let f=o(a.id);return !(!e&&!f||!r&&a.hidden&&!f)});return jsx("div",{"data-questro-badge-grid":true,...d,children:l.map(a=>s?s(a):jsx(S,{badge:a},a.id))})}function O({badgeId:t,showLabel:e=true,...r}){let{getProgress:s}=p(),d=s(t);return d?jsxs("div",{"data-questro-badge-progress-bar":true,...r,children:[e&&jsxs("div",{"data-questro-badge-progress-label":true,children:[d.current," / ",d.target]}),jsx("div",{"data-questro-badge-progress-track":true,style:{width:"100%",height:"8px",backgroundColor:"#e0e0e0"},children:jsx("div",{"data-questro-badge-progress-fill":true,style:{width:`${d.percentage}%`,height:"100%",backgroundColor:"#4caf50",transition:"width 0.3s ease"}})}),e&&jsxs("div",{"data-questro-badge-progress-percentage":true,children:[d.percentage.toFixed(0),"%"]})]}):null}function R({type:t="unlocked",...e}){let{allBadges:r,unlockedBadges:s,lockedBadges:d}=p(),n;switch(t){case "unlocked":n=s.length;break;case "locked":n=d.length;break;case "total":n=r.length;break}return jsx("span",{"data-questro-badge-count":true,"data-type":t,...e,children:n})}export{u as a,M as b,h as c,p as d,S as e,N as f,O as g,R as h};//# sourceMappingURL=chunk-CXFAZJV3.mjs.map
//# sourceMappingURL=chunk-CXFAZJV3.mjs.map