@taskon/widget-react 0.0.1-beta.2 → 0.0.1-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +56 -17
  2. package/dist/CommunityTaskList.css +1593 -1741
  3. package/dist/EligibilityInfo.css +1275 -582
  4. package/dist/LeaderboardWidget.css +355 -152
  5. package/dist/PageBuilder.css +0 -2
  6. package/dist/Quest.css +1140 -903
  7. package/dist/TaskOnProvider.css +50 -31
  8. package/dist/UserCenterWidget.css +108 -237
  9. package/dist/UserCenterWidget2.css +2016 -711
  10. package/dist/chunks/{CommunityTaskList-BlH1Wdd5.js → CommunityTaskList-C9Gv8KOF.js} +962 -827
  11. package/dist/chunks/{EligibilityInfo-C7GZ2G5u.js → EligibilityInfo-D-Fuy9GE.js} +1137 -449
  12. package/dist/chunks/{LeaderboardWidget-CmYfDeHV.js → LeaderboardWidget-BV2D2q1N.js} +15 -10
  13. package/dist/chunks/{PageBuilder-Bw0zSkFh.js → PageBuilder-DQoU4Mwf.js} +5 -5
  14. package/dist/chunks/{Quest-DKFZ-pPU.js → Quest-B5NyVr3o.js} +516 -325
  15. package/dist/chunks/{TaskOnProvider-BD6Vp2x8.js → TaskOnProvider-93UxARFo.js} +2 -207
  16. package/dist/chunks/{ThemeProvider-wnSXrNQb.js → ThemeProvider-CPI_roeh.js} +249 -57
  17. package/dist/chunks/{UserCenterWidget-Cw6h_5hT.js → UserCenterWidget-BRtigY_S.js} +206 -1002
  18. package/dist/chunks/UserCenterWidget-cADBSVg7.js +8358 -0
  19. package/dist/chunks/{WidgetShell-D_5OjvNZ.js → dynamic-import-helper-DwXlQC0S.js} +607 -40
  20. package/dist/chunks/useToast-CaRkylKe.js +304 -0
  21. package/dist/chunks/{usercenter-ja-uu-XfVF9.js → usercenter-ja-B2465c1O.js} +4 -10
  22. package/dist/chunks/{usercenter-ko-DYgUOVzd.js → usercenter-ko-xAEYxqLg.js} +4 -10
  23. package/dist/community-task.d.ts +34 -3
  24. package/dist/community-task.js +1 -1
  25. package/dist/core.d.ts +40 -3
  26. package/dist/core.js +9 -10
  27. package/dist/dynamic-import-helper.css +596 -289
  28. package/dist/index.d.ts +207 -10
  29. package/dist/index.js +21 -19
  30. package/dist/leaderboard.d.ts +8 -1
  31. package/dist/leaderboard.js +2 -2
  32. package/dist/page-builder.js +1 -1
  33. package/dist/quest.d.ts +8 -2
  34. package/dist/quest.js +1 -1
  35. package/dist/user-center.d.ts +20 -136
  36. package/dist/user-center.js +19 -236
  37. package/package.json +10 -2
  38. package/dist/TipPopover.css +0 -210
  39. package/dist/WidgetShell.css +0 -182
  40. package/dist/chunks/TipPopover-BrW8jo71.js +0 -2926
  41. package/dist/chunks/UserCenterWidget-BE329iS7.js +0 -3546
  42. package/dist/chunks/dynamic-import-helper-DxEFwm31.js +0 -537
  43. package/dist/chunks/useToast-B-wyO5zL.js +0 -93
  44. package/dist/chunks/useWidgetLocale-JDelxtt8.js +0 -74
@@ -1,14 +1,11 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import React__default, { useState, useMemo, useCallback, useEffect, useRef } from "react";
3
- import { RewardType, UserEligibleStatus, EligibilityTemplateId, SnsType, ChainType, QuestAutomaticallyWinnerDrawType, QuestWinnerDrawType, QuestWinnerRangeType, createQuestApi, QuestRewardsDistributeType, QuestRewardType, createLeaderboardApi, MediaType, RewardDistributedByType } from "@taskon/core";
4
- import { b as useTaskOnContext } from "./ThemeProvider-wnSXrNQb.js";
5
- import { D as Dialog, u as useResolvedWidgetConfig, W as WidgetShell } from "./WidgetShell-D_5OjvNZ.js";
6
- import { b as useToast } from "./useToast-B-wyO5zL.js";
7
- import { R as Root2, b as Trigger, P as Portal, C as Content2, c as InfoIcon, A as Arrow2, T as TipPopover, a as useBindSocialAccount } from "./TipPopover-BrW8jo71.js";
8
- import { c as TaskItem, e as EligibilityList, g as getDefaultExportFromCjs, s as sanitizeHtml, a as useBindWallet, E as EligibilityInfo, b as BlindBoxDialog, B as BlindBoxRewardDialog } from "./EligibilityInfo-C7GZ2G5u.js";
9
- import { B as Button, T as Table, P as Pagination, _ as __variableDynamicImportRuntimeHelper } from "./dynamic-import-helper-DxEFwm31.js";
10
- import { m as enMessages, a as useTokenAssets, u as useRewardDetails, c as usePointsHistory, L as LoadingState, l as TokenRewardContent, k as PointsList, E as EmptyState, W as WithdrawForm } from "./UserCenterWidget-BE329iS7.js";
11
- import { u as useWidgetLocale } from "./useWidgetLocale-JDelxtt8.js";
3
+ import { RewardType, UserEligibleStatus, EligibilityTemplateId, SnsType, ChainType, QuestAutomaticallyWinnerDrawType, QuestWinnerDrawType, QuestWinnerRangeType, createQuestApi, QuestRewardsDistributeType, QuestRewardType, createLeaderboardApi, MediaType, RewardDistributedByType, ApiError, ErrorCode, CampaignType } from "@taskon/core";
4
+ import { s as useTaskOnPortalContainer, d as useTaskOnContext } from "./ThemeProvider-CPI_roeh.js";
5
+ import { D as Dialog, B as Button, T as Table, P as Pagination, a as useResolvedWidgetConfig, W as WidgetShell } from "./dynamic-import-helper-DwXlQC0S.js";
6
+ import { d as useToast } from "./useToast-CaRkylKe.js";
7
+ import { R as Root2, o as Trigger, p as Portal, q as Content2, v as InfoIcon, r as Arrow2, s as TipPopover, g as useBindSocialAccount } from "./UserCenterWidget-cADBSVg7.js";
8
+ import { c as TaskItem, e as EligibilityList, g as getDefaultExportFromCjs, s as sanitizeHtml, R as RewardModuleDialog, C as ConfirmNoticeDialog, u as useBindWallet, b as useNftClaimFlow, E as EligibilityInfo, B as BlindBoxDialog } from "./EligibilityInfo-D-Fuy9GE.js";
12
9
  import '../Quest.css';function ButtonTabs({
13
10
  items,
14
11
  activeKey,
@@ -134,7 +131,7 @@ function GreenCheckIcon() {
134
131
  "path",
135
132
  {
136
133
  d: "M1 4L4.5 7.5L11 1",
137
- stroke: "#22c55e",
134
+ stroke: "currentColor",
138
135
  strokeWidth: "2",
139
136
  strokeLinecap: "round",
140
137
  strokeLinejoin: "round"
@@ -198,6 +195,7 @@ function TaskList({
198
195
  optionalTasks = [],
199
196
  userTaskStatus,
200
197
  onTaskCompleted,
198
+ onVerifyAttempted,
201
199
  onBeforeVerify,
202
200
  disabled = false,
203
201
  minOptionalTasks,
@@ -236,6 +234,7 @@ function TaskList({
236
234
  task,
237
235
  userStatus: userTaskStatus == null ? void 0 : userTaskStatus[task.id],
238
236
  onCompleted: onTaskCompleted,
237
+ onVerifyAttempted,
239
238
  onBeforeVerify,
240
239
  disabled,
241
240
  isStarted,
@@ -283,6 +282,7 @@ function TaskList({
283
282
  task,
284
283
  userStatus: userTaskStatus == null ? void 0 : userTaskStatus[task.id],
285
284
  onCompleted: onTaskCompleted,
285
+ onVerifyAttempted,
286
286
  onBeforeVerify,
287
287
  disabled,
288
288
  isStarted,
@@ -299,6 +299,93 @@ function TaskList({
299
299
  mandatoryTasks.length === 0 && optionalTasks.length === 0 && /* @__PURE__ */ jsx("div", { className: "taskon-task-list-empty", children: /* @__PURE__ */ jsx("p", { children: "No tasks available" }) })
300
300
  ] });
301
301
  }
302
+ function formatAmount(amount) {
303
+ const num = typeof amount === "string" ? parseFloat(amount) : amount;
304
+ if (isNaN(num)) return "0";
305
+ if (num >= 1e9) {
306
+ return `${(num / 1e9).toFixed(2)}B`;
307
+ }
308
+ if (num >= 1e6) {
309
+ return `${(num / 1e6).toFixed(2)}M`;
310
+ }
311
+ if (num >= 1e3) {
312
+ return `${(num / 1e3).toFixed(2)}K`;
313
+ }
314
+ if (num < 1 && num > 0) {
315
+ return num.toFixed(6).replace(/\.?0+$/, "");
316
+ }
317
+ return num.toFixed(2).replace(/\.?0+$/, "");
318
+ }
319
+ function formatUsdValue(amount, price) {
320
+ if (!price || price <= 0) return null;
321
+ const num = typeof amount === "string" ? parseFloat(amount) : amount;
322
+ if (isNaN(num) || num <= 0) return null;
323
+ const value = num * price;
324
+ return `≈ $${formatAmount(value)}`;
325
+ }
326
+ function RewardCard$2({
327
+ reward,
328
+ tokenPrice
329
+ }) {
330
+ if (reward.reward_type !== RewardType.Token) {
331
+ return null;
332
+ }
333
+ const tokenValue = reward.reward_value;
334
+ const amount = tokenValue.amount || "0";
335
+ const tokenName = tokenValue.token_name || "Token";
336
+ const tokenLogo = tokenValue.token_logo;
337
+ const usdValue = formatUsdValue(amount, tokenPrice);
338
+ return /* @__PURE__ */ jsxs("div", { className: "taskon-quest-blindbox-reward-card", children: [
339
+ tokenLogo && /* @__PURE__ */ jsx(
340
+ "img",
341
+ {
342
+ className: "taskon-quest-blindbox-reward-card-icon",
343
+ src: tokenLogo,
344
+ alt: tokenName
345
+ }
346
+ ),
347
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-blindbox-reward-card-amount", children: [
348
+ "+ ",
349
+ formatAmount(amount),
350
+ " ",
351
+ tokenName
352
+ ] }),
353
+ usdValue && /* @__PURE__ */ jsx("div", { className: "taskon-quest-blindbox-reward-card-value", children: usdValue })
354
+ ] });
355
+ }
356
+ function BlindBoxRewardDialog(props) {
357
+ const { rewards, emptyReward, tokenPrice, loading, onClose } = props;
358
+ const hasRewards = rewards.length > 0;
359
+ const title = hasRewards ? "Congratulations!" : "Oops! Better Luck Next Time!";
360
+ const subtitle = hasRewards ? "You Have Won" : "Rewards Missed";
361
+ const displayRewards = useMemo(() => {
362
+ if (hasRewards) {
363
+ return rewards;
364
+ }
365
+ return emptyReward ? [emptyReward] : [];
366
+ }, [hasRewards, rewards, emptyReward]);
367
+ return /* @__PURE__ */ jsxs("div", { className: "taskon-quest-blindbox-reward", children: [
368
+ /* @__PURE__ */ jsx("h2", { className: "taskon-quest-blindbox-reward-title", children: title }),
369
+ /* @__PURE__ */ jsx("p", { className: "taskon-quest-blindbox-reward-subtitle", children: subtitle }),
370
+ displayRewards.length > 0 && /* @__PURE__ */ jsx("div", { className: "taskon-quest-blindbox-reward-list", children: displayRewards.map((reward, index) => /* @__PURE__ */ jsx(
371
+ RewardCard$2,
372
+ {
373
+ reward,
374
+ tokenPrice
375
+ },
376
+ `${reward.reward_id}-${index}`
377
+ )) }),
378
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-blindbox-reward-btn-wrap", children: /* @__PURE__ */ jsx(
379
+ "button",
380
+ {
381
+ className: "taskon-quest-blindbox-reward-btn",
382
+ disabled: loading,
383
+ onClick: onClose,
384
+ children: loading ? "Loading..." : "Back"
385
+ }
386
+ ) })
387
+ ] });
388
+ }
302
389
  const BLIND_BOX_DISTRIBUTE_TYPE = "BlindBox";
303
390
  function useBlindBoxLogic(params) {
304
391
  const { campaign, userStatus } = params;
@@ -536,9 +623,7 @@ function EligsBindDialog({
536
623
  chainTypes,
537
624
  snsTypes,
538
625
  campaign,
539
- skippable = false,
540
626
  onClose,
541
- onSkip,
542
627
  onAllBind,
543
628
  onBindChain,
544
629
  onBindSns
@@ -583,12 +668,8 @@ function EligsBindDialog({
583
668
  }
584
669
  }, [onBindSns]);
585
670
  const handleClose = useCallback(() => {
586
- if (skippable) {
587
- onSkip == null ? void 0 : onSkip();
588
- } else {
589
- onClose();
590
- }
591
- }, [skippable, onSkip, onClose]);
671
+ onClose();
672
+ }, [onClose]);
592
673
  return /* @__PURE__ */ jsx(
593
674
  Dialog,
594
675
  {
@@ -617,7 +698,7 @@ function EligsBindDialog({
617
698
  }
618
699
  )
619
700
  ] }),
620
- /* @__PURE__ */ jsx("p", { className: "taskon-eligs-bind-tip", children: skippable ? "Please bind the following accounts to verify eligibility. You can skip this step and continue with tasks." : "Please bind the following accounts to verify eligibility." }),
701
+ /* @__PURE__ */ jsx("p", { className: "taskon-eligs-bind-tip", children: "Please bind the following accounts to verify eligibility." }),
621
702
  chainTypes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "taskon-eligs-bind-section", children: [
622
703
  /* @__PURE__ */ jsx("h4", { className: "taskon-eligs-bind-section-subtitle", children: "Wallet Address" }),
623
704
  /* @__PURE__ */ jsx("div", { className: "taskon-eligs-bind-list", children: chainTypes.map((chainType) => /* @__PURE__ */ jsx(
@@ -645,15 +726,7 @@ function EligsBindDialog({
645
726
  },
646
727
  snsType
647
728
  )) })
648
- ] }),
649
- skippable && /* @__PURE__ */ jsx(
650
- "button",
651
- {
652
- className: "taskon-eligs-bind-skip-btn",
653
- onClick: onSkip,
654
- children: "Continue with Tasks"
655
- }
656
- )
729
+ ] })
657
730
  ] })
658
731
  }
659
732
  );
@@ -987,17 +1060,6 @@ function useEligibilityRefresh(options) {
987
1060
  bindPromiseResolveRef.current = null;
988
1061
  }
989
1062
  }, []);
990
- const skipEligsBind = useCallback(() => {
991
- setEligsBindInfo(null);
992
- if (continueAfterBindRef.current) {
993
- continueAfterBindRef.current();
994
- continueAfterBindRef.current = null;
995
- }
996
- if (bindPromiseResolveRef.current) {
997
- bindPromiseResolveRef.current(true);
998
- bindPromiseResolveRef.current = null;
999
- }
1000
- }, []);
1001
1063
  const onAllEligsBind = useCallback(() => {
1002
1064
  setEligsBindInfo(null);
1003
1065
  if (continueAfterBindRef.current) {
@@ -1010,7 +1072,9 @@ function useEligibilityRefresh(options) {
1010
1072
  }
1011
1073
  }, []);
1012
1074
  const checkEligibilityAndNotify = useCallback(async () => {
1013
- if (!api || !campaign) return true;
1075
+ if (!api || !campaign) {
1076
+ return false;
1077
+ }
1014
1078
  if (!campaign.eligs || campaign.eligs.length === 0) {
1015
1079
  return true;
1016
1080
  }
@@ -1033,7 +1097,7 @@ function useEligibilityRefresh(options) {
1033
1097
  return false;
1034
1098
  } catch (error) {
1035
1099
  console.error("[useEligibilityRefresh] checkEligibility failed:", error);
1036
- return true;
1100
+ return false;
1037
1101
  }
1038
1102
  }, [api, campaign, campaignId, onEligibilityFailed]);
1039
1103
  const refreshInDialog = useCallback(async () => {
@@ -1064,7 +1128,10 @@ function useEligibilityRefresh(options) {
1064
1128
  }
1065
1129
  setIsRefreshing(true);
1066
1130
  try {
1067
- await checkEligibilityAndNotify();
1131
+ const passed = await checkEligibilityAndNotify();
1132
+ if (!passed) {
1133
+ return;
1134
+ }
1068
1135
  await onRefresh();
1069
1136
  } finally {
1070
1137
  setIsRefreshing(false);
@@ -1084,8 +1151,7 @@ function useEligibilityRefresh(options) {
1084
1151
  continueAfterBindRef.current = runEligibilityCheck;
1085
1152
  setEligsBindInfo({
1086
1153
  chainTypes,
1087
- snsTypes,
1088
- skippable: false
1154
+ snsTypes
1089
1155
  });
1090
1156
  return;
1091
1157
  }
@@ -1113,8 +1179,7 @@ function useEligibilityRefresh(options) {
1113
1179
  bindPromiseResolveRef.current = resolve;
1114
1180
  setEligsBindInfo({
1115
1181
  chainTypes,
1116
- snsTypes,
1117
- skippable: false
1182
+ snsTypes
1118
1183
  });
1119
1184
  });
1120
1185
  if (!bindCompleted) {
@@ -1142,7 +1207,6 @@ function useEligibilityRefresh(options) {
1142
1207
  // 绑定弹窗相关
1143
1208
  eligsBindInfo,
1144
1209
  closeEligsBindDialog,
1145
- skipEligsBind,
1146
1210
  onAllEligsBind
1147
1211
  };
1148
1212
  }
@@ -1358,9 +1422,7 @@ function useCompleteValidation(options) {
1358
1422
  eligsBindResolveRef.current = resolve;
1359
1423
  setEligsBindInfo({
1360
1424
  chainTypes,
1361
- snsTypes,
1362
- skippable: false
1363
- // Eligs binding is not skippable
1425
+ snsTypes
1364
1426
  });
1365
1427
  });
1366
1428
  if (!bindCompleted) {
@@ -1374,17 +1436,16 @@ function useCompleteValidation(options) {
1374
1436
  campaign_id: campaign.id
1375
1437
  });
1376
1438
  if (!result.result) {
1377
- const handled = onEligibilityFailed == null ? void 0 : onEligibilityFailed({
1439
+ onEligibilityFailed == null ? void 0 : onEligibilityFailed({
1378
1440
  eligs: campaign.eligs,
1379
1441
  eligibilityExpress: campaign.eligibility_express || "and",
1380
1442
  details: result.details
1381
1443
  });
1382
- if (!handled) {
1383
- return false;
1384
- }
1444
+ return false;
1385
1445
  }
1386
1446
  } catch (error) {
1387
1447
  console.error("[useCompleteValidation] checkEligibility failed:", error);
1448
+ return false;
1388
1449
  }
1389
1450
  }
1390
1451
  const rewardChainTypes = getRewardChainTypes(campaign.winner_rewards);
@@ -1413,12 +1474,15 @@ function useCompleteValidation(options) {
1413
1474
  }
1414
1475
  if (hasDiscordRoleReward(campaign.winner_rewards)) {
1415
1476
  if (!isDiscordBound(userProfile)) {
1416
- await new Promise((resolve) => {
1477
+ const discordResult = await new Promise((resolve) => {
1417
1478
  discordBindResolveRef.current = resolve;
1418
1479
  setDiscordBindInfo({
1419
1480
  campaignId: campaign.id
1420
1481
  });
1421
1482
  });
1483
+ if (!discordResult) {
1484
+ return false;
1485
+ }
1422
1486
  }
1423
1487
  }
1424
1488
  return true;
@@ -3926,7 +3990,7 @@ function TwitterIcon() {
3926
3990
  "path",
3927
3991
  {
3928
3992
  d: "M23 8.66667C22.6516 9.33333 22.071 10 21.2581 10.4444V10.8889C21.2581 16.4444 16.4968 21 10.6903 21C8.71613 21 6.74194 20.4444 5 19.4444C5.34839 19.4444 5.58065 19.5556 5.92903 19.4444C7.67097 19.4444 9.29677 18.8889 10.5742 17.8889C8.94839 17.8889 7.55484 16.8889 7.09032 15.4444C7.32258 15.4444 7.55484 15.5556 7.7871 15.5556C8.13548 15.5556 8.48387 15.5556 8.71613 15.4444C6.97419 15.1111 5.69677 13.6667 5.69677 12C6.16129 12.2222 6.74194 12.4444 7.32258 12.4444C6.39355 11.7778 5.8129 10.6667 5.8129 9.44444C5.8129 8.77778 6.04516 8.22222 6.27742 7.66667C8.13548 9.88889 10.9226 11.2222 13.9419 11.3333C13.8258 11.1111 13.8258 10.7778 13.8258 10.5556C13.8258 8.55556 15.4516 7 17.5419 7C18.5871 7 19.5161 7.44444 20.2129 8.11111C21.0258 8 21.8387 7.66667 22.5355 7.22222C22.3032 8 21.7226 8.77778 20.9097 9.22222C21.6065 9.11111 22.4194 8.88889 23 8.66667Z",
3929
- fill: "#54AEFF"
3993
+ fill: "currentColor"
3930
3994
  }
3931
3995
  )
3932
3996
  }
@@ -3946,7 +4010,7 @@ function CopyLinkIcon() {
3946
4010
  "path",
3947
4011
  {
3948
4012
  d: "M11.6875 16.3149L17.3178 10.6846",
3949
- stroke: "#00FFA3",
4013
+ stroke: "currentColor",
3950
4014
  strokeWidth: "2",
3951
4015
  strokeLinecap: "round",
3952
4016
  strokeLinejoin: "round"
@@ -3956,7 +4020,7 @@ function CopyLinkIcon() {
3956
4020
  "path",
3957
4021
  {
3958
4022
  d: "M9.57364 12.7959L8.16607 14.2035C6.61131 15.7582 6.61131 18.279 8.16607 19.8338C9.72083 21.3885 12.2416 21.3885 13.7964 19.8338L15.2039 18.4262",
3959
- stroke: "#00FFA3",
4023
+ stroke: "currentColor",
3960
4024
  strokeWidth: "2",
3961
4025
  strokeLinecap: "round",
3962
4026
  strokeLinejoin: "round"
@@ -3966,7 +4030,7 @@ function CopyLinkIcon() {
3966
4030
  "path",
3967
4031
  {
3968
4032
  d: "M13.7969 8.57364L15.2044 7.16607C16.7592 5.61131 19.28 5.61131 20.8347 7.16607C22.3895 8.72083 22.3895 11.2416 20.8347 12.7964L19.4272 14.2039",
3969
- stroke: "#00FFA3",
4033
+ stroke: "currentColor",
3970
4034
  strokeWidth: "2",
3971
4035
  strokeLinecap: "round",
3972
4036
  strokeLinejoin: "round"
@@ -3989,7 +4053,7 @@ function QrCodeIcon() {
3989
4053
  "path",
3990
4054
  {
3991
4055
  d: "M22 14.4429V18.943H17.5V15.9547H16V22H14.5V14.4613H19V17.4486H20.5V14.4429H22ZM12.5 14.4613V21.9308H5V14.4613H12.5ZM22 20.4369V21.9308H17.5V20.4369H22ZM11 15.9552H6.5V20.4369H11V15.9552ZM9.5 17.4491V18.943H8V17.4491H9.5ZM12.5 5V12.4695H5V5H12.5ZM22 5V12.4695H14.5V5H22ZM11 6.49389H6.5V10.9756H11V6.49389ZM20.5 6.49389H16V10.9756H20.5V6.49389ZM9.5 7.98779V9.48168H8V7.98779H9.5ZM19 7.98779V9.48168H17.5V7.98779H19Z",
3992
- fill: "#FFD465"
4056
+ fill: "currentColor"
3993
4057
  }
3994
4058
  )
3995
4059
  }
@@ -4007,6 +4071,7 @@ function ShareDropdown({
4007
4071
  endTime,
4008
4072
  winnerRewardsSimple
4009
4073
  }) {
4074
+ const portalContainer = useTaskOnPortalContainer();
4010
4075
  const { toast } = useToast();
4011
4076
  const [qrDialogOpen, setQrDialogOpen] = useState(false);
4012
4077
  const [popoverOpen, setPopoverOpen] = useState(false);
@@ -4030,11 +4095,11 @@ function ShareDropdown({
4030
4095
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4031
4096
  /* @__PURE__ */ jsxs(Root2, { open: popoverOpen, onOpenChange: setPopoverOpen, children: [
4032
4097
  /* @__PURE__ */ jsx(Trigger, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "taskon-quest-share-trigger", "aria-label": "Share", children: /* @__PURE__ */ jsx(ShareIcon, {}) }) }),
4033
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(Content2, { className: "taskon-quest-share-menu", sideOffset: 8, children: [
4098
+ /* @__PURE__ */ jsx(Portal, { container: portalContainer ?? void 0, children: /* @__PURE__ */ jsxs(Content2, { className: "taskon-quest-share-menu", sideOffset: 8, children: [
4034
4099
  /* @__PURE__ */ jsxs(
4035
4100
  "button",
4036
4101
  {
4037
- className: "taskon-quest-share-menu-item",
4102
+ className: "taskon-quest-share-menu-item taskon-quest-share-menu-item--twitter",
4038
4103
  onClick: handleShareTwitter,
4039
4104
  children: [
4040
4105
  /* @__PURE__ */ jsx(TwitterIcon, {}),
@@ -4045,7 +4110,7 @@ function ShareDropdown({
4045
4110
  /* @__PURE__ */ jsxs(
4046
4111
  "button",
4047
4112
  {
4048
- className: "taskon-quest-share-menu-item",
4113
+ className: "taskon-quest-share-menu-item taskon-quest-share-menu-item--copy",
4049
4114
  onClick: handleCopyLink,
4050
4115
  children: [
4051
4116
  /* @__PURE__ */ jsx(CopyLinkIcon, {}),
@@ -4056,7 +4121,7 @@ function ShareDropdown({
4056
4121
  /* @__PURE__ */ jsxs(
4057
4122
  "button",
4058
4123
  {
4059
- className: "taskon-quest-share-menu-item",
4124
+ className: "taskon-quest-share-menu-item taskon-quest-share-menu-item--qr",
4060
4125
  onClick: handleShowQrCode,
4061
4126
  children: [
4062
4127
  /* @__PURE__ */ jsx(QrCodeIcon, {}),
@@ -4155,12 +4220,16 @@ function QuestTitle({
4155
4220
  ] }),
4156
4221
  /* @__PURE__ */ jsx("div", { className: "taskon-quest-countdown-divider" }),
4157
4222
  /* @__PURE__ */ jsxs("div", { className: "taskon-quest-time-range", children: [
4158
- "(UTC",
4159
- offsetString,
4160
- ") ",
4161
- formatDateTime(startDate),
4162
- " ~ ",
4163
- formatDateTime(endDate)
4223
+ /* @__PURE__ */ jsxs("span", { className: "taskon-quest-time-range-timezone", children: [
4224
+ "(UTC",
4225
+ offsetString,
4226
+ ")"
4227
+ ] }),
4228
+ /* @__PURE__ */ jsxs("span", { className: "taskon-quest-time-range-values", children: [
4229
+ /* @__PURE__ */ jsx("span", { children: formatDateTime(startDate) }),
4230
+ /* @__PURE__ */ jsx("span", { className: "taskon-quest-time-range-separator", "aria-hidden": "true", children: "~" }),
4231
+ /* @__PURE__ */ jsx("span", { children: formatDateTime(endDate) })
4232
+ ] })
4164
4233
  ] }),
4165
4234
  showShare && /* @__PURE__ */ jsx(
4166
4235
  ShareDropdown,
@@ -4283,8 +4352,8 @@ function ArrowDownIcon() {
4283
4352
  y2: "-4.69753e-06",
4284
4353
  gradientUnits: "userSpaceOnUse",
4285
4354
  children: [
4286
- /* @__PURE__ */ jsx("stop", { stopColor: "#00FFA3" }),
4287
- /* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#00FFA3", stopOpacity: "0.19" })
4355
+ /* @__PURE__ */ jsx("stop", { stopColor: "currentColor" }),
4356
+ /* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "currentColor", stopOpacity: "0.19" })
4288
4357
  ]
4289
4358
  }
4290
4359
  ) })
@@ -4771,6 +4840,7 @@ function ChainIcon({
4771
4840
  size = 16,
4772
4841
  chains
4773
4842
  }) {
4843
+ const portalContainer = useTaskOnPortalContainer();
4774
4844
  const [open, setOpen] = useState(false);
4775
4845
  const timeoutRef = useRef(null);
4776
4846
  const chainInfo = findChainInfo(chain, chains);
@@ -4802,7 +4872,7 @@ function ChainIcon({
4802
4872
  onMouseLeave: handleMouseLeave
4803
4873
  }
4804
4874
  ) }),
4805
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(
4875
+ /* @__PURE__ */ jsx(Portal, { container: portalContainer ?? void 0, children: /* @__PURE__ */ jsxs(
4806
4876
  Content2,
4807
4877
  {
4808
4878
  className: "taskon-quest-rewards-chain-tooltip",
@@ -4836,6 +4906,7 @@ function getIsBrc20(chain, tokenAddress, chains) {
4836
4906
  return (chainInfo == null ? void 0 : chainInfo.chain_type) === "btc" && tokenAddress !== BTC_CONTRACT;
4837
4907
  }
4838
4908
  function TokenInfo({ rewardInfo, chains }) {
4909
+ const portalContainer = useTaskOnPortalContainer();
4839
4910
  const params = rewardInfo.reward_params;
4840
4911
  const distributeType = rewardInfo.reward_distribute_type;
4841
4912
  const [open, setOpen] = useState(false);
@@ -4895,7 +4966,7 @@ function TokenInfo({ rewardInfo, chains }) {
4895
4966
  ]
4896
4967
  }
4897
4968
  ) }),
4898
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(
4969
+ /* @__PURE__ */ jsx(Portal, { container: portalContainer ?? void 0, children: /* @__PURE__ */ jsxs(
4899
4970
  Content2,
4900
4971
  {
4901
4972
  className: "taskon-quest-rewards-popover-content",
@@ -4944,6 +5015,7 @@ async function copyToClipboard$1(text) {
4944
5015
  }
4945
5016
  }
4946
5017
  function NftInfo({ rewardInfo, chains }) {
5018
+ const portalContainer = useTaskOnPortalContainer();
4947
5019
  const params = rewardInfo.reward_params;
4948
5020
  const [open, setOpen] = useState(false);
4949
5021
  const [copied, setCopied] = useState(false);
@@ -4990,7 +5062,7 @@ function NftInfo({ rewardInfo, chains }) {
4990
5062
  ]
4991
5063
  }
4992
5064
  ) }),
4993
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(
5065
+ /* @__PURE__ */ jsx(Portal, { container: portalContainer ?? void 0, children: /* @__PURE__ */ jsxs(
4994
5066
  Content2,
4995
5067
  {
4996
5068
  className: "taskon-quest-rewards-popover-content",
@@ -5031,6 +5103,7 @@ async function copyToClipboard(text) {
5031
5103
  }
5032
5104
  }
5033
5105
  function MintedNftInfo({ rewardInfo, chains }) {
5106
+ const portalContainer = useTaskOnPortalContainer();
5034
5107
  const params = rewardInfo.reward_params;
5035
5108
  const [open, setOpen] = useState(false);
5036
5109
  const [copied, setCopied] = useState(false);
@@ -5077,7 +5150,7 @@ function MintedNftInfo({ rewardInfo, chains }) {
5077
5150
  ]
5078
5151
  }
5079
5152
  ) }),
5080
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(
5153
+ /* @__PURE__ */ jsx(Portal, { container: portalContainer ?? void 0, children: /* @__PURE__ */ jsxs(
5081
5154
  Content2,
5082
5155
  {
5083
5156
  className: "taskon-quest-rewards-popover-content",
@@ -5891,7 +5964,6 @@ function LeaderboardModal({
5891
5964
  title: "Leaderboard",
5892
5965
  showCloseButton: true,
5893
5966
  className,
5894
- contentClassName: "taskon-quest-leaderboard-modal",
5895
5967
  maxWidth: 600,
5896
5968
  children: /* @__PURE__ */ jsxs("div", { className: "taskon-quest-leaderboard-content", children: [
5897
5969
  /* @__PURE__ */ jsx("h2", { className: "taskon-quest-leaderboard-title", children: "Leaderboard" }),
@@ -6237,6 +6309,32 @@ function formatLongStr(str, ellipsis = "...", prefixLen = 6, suffixLen = 4) {
6237
6309
  if (str.length <= prefixLen + suffixLen) return str;
6238
6310
  return `${str.slice(0, prefixLen)}${ellipsis}${str.slice(-suffixLen)}`;
6239
6311
  }
6312
+ function getOpenseaUrl(chainInfo) {
6313
+ if (!chainInfo) return "";
6314
+ const { name, nft_contract } = chainInfo;
6315
+ const chainName = name.toLowerCase();
6316
+ if (chainName === "core") return "";
6317
+ const openseaPrefix = "https://opensea.io/assets/";
6318
+ if (chainName === "polygon") {
6319
+ return `${openseaPrefix}matic/${nft_contract}`;
6320
+ }
6321
+ return `${openseaPrefix}${name}/${nft_contract}`;
6322
+ }
6323
+ function getIsOldCapNft(tokenId) {
6324
+ if (!tokenId) return false;
6325
+ try {
6326
+ return BigInt(tokenId) <= 2147483647n;
6327
+ } catch {
6328
+ return false;
6329
+ }
6330
+ }
6331
+ function getElementUrl(chainInfo, tokenId) {
6332
+ if (!chainInfo || !tokenId) return "";
6333
+ const chainName = chainInfo.name.toLowerCase();
6334
+ if (chainName === "core") return "";
6335
+ const nftContract = getIsOldCapNft(tokenId) ? chainInfo.old_nft_contract : chainInfo.nft_contract;
6336
+ return `https://element.market/assets/${chainInfo.name}/${nftContract}/${tokenId}`;
6337
+ }
6240
6338
  function getTxExplorerUrl(chainInfo, txHash) {
6241
6339
  if (!chainInfo || !txHash) return "";
6242
6340
  const explorerBase = chainInfo.explorer || `https://etherscan.io`;
@@ -6510,114 +6608,6 @@ function ClaimButton({
6510
6608
  }
6511
6609
  );
6512
6610
  }
6513
- function RewardModuleDialog({
6514
- open,
6515
- onOpenChange,
6516
- type,
6517
- pointsInfo
6518
- }) {
6519
- const isToken = type === "token";
6520
- const isPoints = type === "points";
6521
- const { messages, isLoading } = useWidgetLocale({
6522
- widgetId: "UserCenterWidget",
6523
- defaultMessages: enMessages,
6524
- loadMessages: (locale) => __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "../../../../UserCenter/locales/en.json": () => import("./UserCenterWidget-BE329iS7.js").then((n) => n.n), "../../../../UserCenter/locales/ja.json": () => import("./usercenter-ja-uu-XfVF9.js"), "../../../../UserCenter/locales/ko.json": () => import("./usercenter-ko-DYgUOVzd.js") }), `../../../../UserCenter/locales/${locale}.json`, 7)
6525
- });
6526
- const tokenAssets = useTokenAssets({
6527
- autoLoad: open && isToken
6528
- });
6529
- const tokenHistory = useRewardDetails({
6530
- rewardType: RewardType.Token,
6531
- autoLoad: open && isToken
6532
- });
6533
- const [showWithdrawForm, setShowWithdrawForm] = useState(false);
6534
- const [selectedTokenForWithdraw, setSelectedTokenForWithdraw] = useState(null);
6535
- const handleWithdraw = useCallback((token) => {
6536
- setSelectedTokenForWithdraw(token);
6537
- setShowWithdrawForm(true);
6538
- }, []);
6539
- const handleBatchWithdraw = useCallback(() => {
6540
- setSelectedTokenForWithdraw(null);
6541
- setShowWithdrawForm(true);
6542
- }, []);
6543
- const pointsHistory = usePointsHistory({
6544
- pointsId: (pointsInfo == null ? void 0 : pointsInfo.points_id) ?? 0,
6545
- autoLoad: open && isPoints && Boolean(pointsInfo == null ? void 0 : pointsInfo.points_id)
6546
- });
6547
- const dialogTitle = useMemo(() => {
6548
- if (isToken) return messages.rewardToken ?? "Token";
6549
- if (isPoints) return messages.pointsHistory ?? "Points History";
6550
- return "";
6551
- }, [isToken, isPoints, messages.rewardToken, messages.pointsHistory]);
6552
- const handleOpenChange = useCallback(
6553
- (nextOpen) => {
6554
- if (!nextOpen) {
6555
- setShowWithdrawForm(false);
6556
- setSelectedTokenForWithdraw(null);
6557
- }
6558
- onOpenChange(nextOpen);
6559
- },
6560
- [onOpenChange]
6561
- );
6562
- return /* @__PURE__ */ jsxs(Fragment, { children: [
6563
- /* @__PURE__ */ jsx(
6564
- Dialog,
6565
- {
6566
- open,
6567
- onOpenChange: handleOpenChange,
6568
- title: dialogTitle,
6569
- showCloseButton: true,
6570
- maxWidth: 960,
6571
- contentClassName: "taskon-quest-reward-dialog",
6572
- children: isLoading ? /* @__PURE__ */ jsx(LoadingState, { message: messages.loading }) : /* @__PURE__ */ jsxs("div", { className: "taskon-user-center taskon-user-center--popup", children: [
6573
- isToken && /* @__PURE__ */ jsx(
6574
- TokenRewardContent,
6575
- {
6576
- tokenAssets: tokenAssets.data,
6577
- tokenAssetsLoading: tokenAssets.loading,
6578
- tokenAssetsError: tokenAssets.error,
6579
- pendingWithdrawals: tokenAssets.pendingWithdrawals,
6580
- tokenHistory: tokenHistory.data,
6581
- tokenHistoryLoading: tokenHistory.loading,
6582
- tokenHistoryError: tokenHistory.error,
6583
- tokenHistoryPagination: tokenHistory.pagination,
6584
- messages,
6585
- onWithdraw: handleWithdraw,
6586
- onBatchWithdraw: handleBatchWithdraw
6587
- }
6588
- ),
6589
- isPoints && (pointsInfo ? /* @__PURE__ */ jsx(
6590
- PointsList,
6591
- {
6592
- pointsInfo,
6593
- data: pointsHistory.data,
6594
- loading: pointsHistory.loading,
6595
- error: pointsHistory.error,
6596
- pagination: pointsHistory.pagination,
6597
- messages
6598
- }
6599
- ) : /* @__PURE__ */ jsx(EmptyState, { message: messages.emptyPoints }))
6600
- ] })
6601
- }
6602
- ),
6603
- isToken && /* @__PURE__ */ jsx(
6604
- WithdrawForm,
6605
- {
6606
- open: showWithdrawForm,
6607
- messages,
6608
- tokenAssets: tokenAssets.data,
6609
- tokenAssetsLoading: tokenAssets.loading,
6610
- initialTokenId: selectedTokenForWithdraw == null ? void 0 : selectedTokenForWithdraw.token_id,
6611
- initialChain: selectedTokenForWithdraw == null ? void 0 : selectedTokenForWithdraw.chain,
6612
- onClose: () => setShowWithdrawForm(false),
6613
- onSuccess: () => {
6614
- tokenAssets.refresh();
6615
- setShowWithdrawForm(false);
6616
- }
6617
- }
6618
- )
6619
- ] });
6620
- }
6621
6611
  function RewardToken({
6622
6612
  layer,
6623
6613
  reward,
@@ -6691,10 +6681,66 @@ function RewardToken({
6691
6681
  )
6692
6682
  ] });
6693
6683
  }
6684
+ const ArrowIcon = () => /* @__PURE__ */ jsx(
6685
+ "svg",
6686
+ {
6687
+ className: "taskon-quest-footer-action-arrow",
6688
+ width: "6",
6689
+ height: "10",
6690
+ viewBox: "0 0 6 10",
6691
+ fill: "none",
6692
+ xmlns: "http://www.w3.org/2000/svg",
6693
+ children: /* @__PURE__ */ jsx(
6694
+ "path",
6695
+ {
6696
+ d: "M1 1L5 5L1 9",
6697
+ stroke: "currentColor",
6698
+ strokeWidth: "1.5",
6699
+ strokeLinecap: "round",
6700
+ strokeLinejoin: "round"
6701
+ }
6702
+ )
6703
+ }
6704
+ );
6705
+ function ActionButton({
6706
+ onClick,
6707
+ disabled = false,
6708
+ children,
6709
+ href
6710
+ }) {
6711
+ const className = `taskon-quest-footer-action-btn ${disabled ? "taskon-quest-footer-action-btn--disabled" : ""}`;
6712
+ if (href && !disabled) {
6713
+ return /* @__PURE__ */ jsxs(
6714
+ "a",
6715
+ {
6716
+ href,
6717
+ target: "_blank",
6718
+ rel: "noopener noreferrer",
6719
+ className,
6720
+ children: [
6721
+ /* @__PURE__ */ jsx("span", { children }),
6722
+ /* @__PURE__ */ jsx(ArrowIcon, {})
6723
+ ]
6724
+ }
6725
+ );
6726
+ }
6727
+ return /* @__PURE__ */ jsxs(
6728
+ "div",
6729
+ {
6730
+ className,
6731
+ onClick: disabled ? void 0 : onClick,
6732
+ role: onClick && !disabled ? "button" : void 0,
6733
+ tabIndex: onClick && !disabled ? 0 : void 0,
6734
+ children: [
6735
+ /* @__PURE__ */ jsx("span", { children }),
6736
+ !disabled && /* @__PURE__ */ jsx(ArrowIcon, {})
6737
+ ]
6738
+ }
6739
+ );
6740
+ }
6694
6741
  function RewardNft({
6695
6742
  layer,
6696
6743
  reward,
6697
- campaign,
6698
6744
  chains,
6699
6745
  onRefresh,
6700
6746
  onClaimNft
@@ -6703,22 +6749,17 @@ function RewardNft({
6703
6749
  const params = reward.reward_value;
6704
6750
  const chainInfo = useMemo(() => {
6705
6751
  if (!chains || !params.chain) return null;
6706
- return chains[params.chain] || null;
6752
+ return chains[params.chain] || chains[params.chain.toLowerCase()] || null;
6707
6753
  }, [chains, params.chain]);
6708
- const rewardInfo = useMemo(() => {
6709
- const winnerReward = campaign.winner_rewards[layer.winner_index];
6710
- if (!winnerReward) return null;
6711
- const layerReward = winnerReward.winner_layer_rewards[layer.layer_no];
6712
- if (!layerReward) return null;
6713
- return layerReward.rewards.find((item) => item.reward_id === reward.reward_id) || null;
6714
- }, [campaign, layer, reward.reward_id]);
6715
- const isDeposited = useMemo(() => {
6716
- const distributedByType = rewardInfo == null ? void 0 : rewardInfo.reward_distributed_by_type;
6717
- return distributedByType === RewardDistributedByType.Taskon;
6718
- }, [rewardInfo]);
6754
+ const openseaLink = useMemo(() => {
6755
+ if (!params.token_id) {
6756
+ return "";
6757
+ }
6758
+ return getOpenseaUrl(chainInfo);
6759
+ }, [chainInfo, params.token_id]);
6719
6760
  const handleClaim = useCallback(async () => {
6720
6761
  if (!onClaimNft) {
6721
- console.warn("onClaimNft callback not provided");
6762
+ console.warn("NFT claim handler not provided");
6722
6763
  return;
6723
6764
  }
6724
6765
  setLoading(true);
@@ -6731,8 +6772,6 @@ function RewardNft({
6731
6772
  setLoading(false);
6732
6773
  }
6733
6774
  }, [onClaimNft, reward, layer, onRefresh]);
6734
- const buttonText = params.tx_hash ? "Retry" : "Claim";
6735
- const showButton = params.claimable || isDeposited && params.tx_hash;
6736
6775
  return /* @__PURE__ */ jsxs(RewardCard, { children: [
6737
6776
  /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-earned-single-row", children: [
6738
6777
  /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-earned-single-info", children: [
@@ -6747,9 +6786,9 @@ function RewardNft({
6747
6786
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-earned-single-type", children: "NFT" })
6748
6787
  ] }),
6749
6788
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-earned-single-name", children: params.collection_name }),
6750
- showButton && /* @__PURE__ */ jsx(ClaimButton, { onClick: handleClaim, loading, children: buttonText })
6789
+ params.claimable && /* @__PURE__ */ jsx(ClaimButton, { onClick: handleClaim, loading, children: "Claim" })
6751
6790
  ] }),
6752
- params.tx_hash && chainInfo && /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-reward-tx", children: [
6791
+ openseaLink ? /* @__PURE__ */ jsx(ActionButton, { href: openseaLink, children: "Check Collection on OpenSea" }) : params.tx_hash && chainInfo && /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-reward-tx", children: [
6753
6792
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-reward-tx-label", children: "Txn Hash:" }),
6754
6793
  /* @__PURE__ */ jsx(
6755
6794
  "a",
@@ -6775,14 +6814,20 @@ function RewardMintedNft({
6775
6814
  const params = reward.reward_value;
6776
6815
  const chainInfo = useMemo(() => {
6777
6816
  if (!chains || !params.chain) return null;
6778
- return chains[params.chain] || null;
6817
+ return chains[params.chain] || chains[params.chain.toLowerCase()] || null;
6779
6818
  }, [chains, params.chain]);
6819
+ const openseaLink = useMemo(() => {
6820
+ if (!params.token_id) {
6821
+ return "";
6822
+ }
6823
+ return getOpenseaUrl(chainInfo);
6824
+ }, [chainInfo, params.token_id]);
6780
6825
  const isGasStationDropping = useMemo(() => {
6781
6826
  return !!(params.gs_req_id && !params.tx_hash);
6782
6827
  }, [params.gs_req_id, params.tx_hash]);
6783
6828
  const handleClaim = useCallback(async () => {
6784
6829
  if (!onClaimNft) {
6785
- console.warn("onClaimNft callback not provided");
6830
+ console.warn("NFT claim handler not provided");
6786
6831
  return;
6787
6832
  }
6788
6833
  setLoading(true);
@@ -6795,8 +6840,6 @@ function RewardMintedNft({
6795
6840
  setLoading(false);
6796
6841
  }
6797
6842
  }, [onClaimNft, reward, layer, onRefresh]);
6798
- const buttonText = params.tx_hash ? "Retry" : "Claim";
6799
- const showButton = params.claimable;
6800
6843
  return /* @__PURE__ */ jsxs(RewardCard, { children: [
6801
6844
  /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-earned-single-row", children: [
6802
6845
  /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-earned-single-info", children: [
@@ -6811,9 +6854,9 @@ function RewardMintedNft({
6811
6854
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-earned-single-type", children: "NFT" })
6812
6855
  ] }),
6813
6856
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-earned-single-name", children: params.collection_name }),
6814
- showButton && /* @__PURE__ */ jsx(ClaimButton, { onClick: handleClaim, loading, children: buttonText })
6857
+ params.claimable && /* @__PURE__ */ jsx(ClaimButton, { onClick: handleClaim, loading, children: "Claim" })
6815
6858
  ] }),
6816
- params.tx_hash && chainInfo && /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-reward-tx", children: [
6859
+ openseaLink ? /* @__PURE__ */ jsx(ActionButton, { href: openseaLink, children: "Check Collection on OpenSea" }) : params.tx_hash && chainInfo && /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer-reward-tx", children: [
6817
6860
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-reward-tx-label", children: "Txn Hash:" }),
6818
6861
  /* @__PURE__ */ jsx(
6819
6862
  "a",
@@ -6840,14 +6883,17 @@ function RewardCap({
6840
6883
  const params = reward.reward_value;
6841
6884
  const chainInfo = useMemo(() => {
6842
6885
  if (!chains || !params.chain) return null;
6843
- return chains[params.chain] || null;
6886
+ return chains[params.chain] || chains[params.chain.toLowerCase()] || null;
6844
6887
  }, [chains, params.chain]);
6888
+ const elementLink = useMemo(() => {
6889
+ return getElementUrl(chainInfo, params.token_id);
6890
+ }, [chainInfo, params.token_id]);
6845
6891
  const isGasStationDropping = useMemo(() => {
6846
6892
  return !!(params.gs_req_id && !params.tx_hash);
6847
6893
  }, [params.gs_req_id, params.tx_hash]);
6848
6894
  const handleClaim = useCallback(async () => {
6849
6895
  if (!onClaimNft) {
6850
- console.warn("onClaimNft callback not provided");
6896
+ console.warn("NFT claim handler not provided");
6851
6897
  return;
6852
6898
  }
6853
6899
  setLoading(true);
@@ -6875,6 +6921,7 @@ function RewardCap({
6875
6921
  ] }),
6876
6922
  params.claimable && /* @__PURE__ */ jsx(ClaimButton, { onClick: handleClaim, loading, children: "Claim" })
6877
6923
  ] }),
6924
+ elementLink && /* @__PURE__ */ jsx(ActionButton, { href: elementLink, children: "Check CAP on Element" }),
6878
6925
  isGasStationDropping && /* @__PURE__ */ jsx("div", { className: "taskon-quest-footer-reward-dropping", children: "Cap will be airdropped soon..." })
6879
6926
  ] });
6880
6927
  }
@@ -6899,63 +6946,6 @@ function RewardWhitelist({
6899
6946
  /* @__PURE__ */ jsx("span", { className: "taskon-quest-footer-earned-single-type", children: "Whitelist" })
6900
6947
  ] }) }) });
6901
6948
  }
6902
- const ArrowIcon = () => /* @__PURE__ */ jsx(
6903
- "svg",
6904
- {
6905
- className: "taskon-quest-footer-action-arrow",
6906
- width: "6",
6907
- height: "10",
6908
- viewBox: "0 0 6 10",
6909
- fill: "none",
6910
- xmlns: "http://www.w3.org/2000/svg",
6911
- children: /* @__PURE__ */ jsx(
6912
- "path",
6913
- {
6914
- d: "M1 1L5 5L1 9",
6915
- stroke: "currentColor",
6916
- strokeWidth: "1.5",
6917
- strokeLinecap: "round",
6918
- strokeLinejoin: "round"
6919
- }
6920
- )
6921
- }
6922
- );
6923
- function ActionButton({
6924
- onClick,
6925
- disabled = false,
6926
- children,
6927
- href
6928
- }) {
6929
- const className = `taskon-quest-footer-action-btn ${disabled ? "taskon-quest-footer-action-btn--disabled" : ""}`;
6930
- if (href && !disabled) {
6931
- return /* @__PURE__ */ jsxs(
6932
- "a",
6933
- {
6934
- href,
6935
- target: "_blank",
6936
- rel: "noopener noreferrer",
6937
- className,
6938
- children: [
6939
- /* @__PURE__ */ jsx("span", { children }),
6940
- /* @__PURE__ */ jsx(ArrowIcon, {})
6941
- ]
6942
- }
6943
- );
6944
- }
6945
- return /* @__PURE__ */ jsxs(
6946
- "div",
6947
- {
6948
- className,
6949
- onClick: disabled ? void 0 : onClick,
6950
- role: onClick && !disabled ? "button" : void 0,
6951
- tabIndex: onClick && !disabled ? 0 : void 0,
6952
- children: [
6953
- /* @__PURE__ */ jsx("span", { children }),
6954
- !disabled && /* @__PURE__ */ jsx(ArrowIcon, {})
6955
- ]
6956
- }
6957
- );
6958
- }
6959
6949
  function RewardPoint({
6960
6950
  reward,
6961
6951
  rewardDisplayMode = "popup",
@@ -7339,6 +7329,20 @@ function StatusCard(props) {
7339
7329
  ) : /* @__PURE__ */ jsx("div", { className: "taskon-quest-footer-card-message", children: description }) })
7340
7330
  ] });
7341
7331
  }
7332
+ const REPEAT_SUBMIT_ERROR_MESSAGE = "Oops! Seems like you already finished this quest";
7333
+ const DEFAULT_COMPLETE_ERROR_MESSAGE = "Failed to complete quest";
7334
+ function normalizeCompleteSubmitError(error) {
7335
+ if (error instanceof ApiError) {
7336
+ if (error.code === ErrorCode.RUSER_REPEAT_SUBMIT) {
7337
+ return new ApiError(error.code, REPEAT_SUBMIT_ERROR_MESSAGE, error.data);
7338
+ }
7339
+ return error;
7340
+ }
7341
+ if (error instanceof Error) {
7342
+ return error;
7343
+ }
7344
+ return new Error(DEFAULT_COMPLETE_ERROR_MESSAGE);
7345
+ }
7342
7346
  function CompleteButton({
7343
7347
  campaignId,
7344
7348
  hasPointProportionally: hasPointProportionally2 = false,
@@ -7361,33 +7365,35 @@ function CompleteButton({
7361
7365
  if (!api || isLoading || disabled) {
7362
7366
  return;
7363
7367
  }
7364
- if (onBeforeComplete) {
7365
- try {
7366
- const canContinue = await onBeforeComplete();
7367
- if (!canContinue) {
7368
+ setIsLoading(true);
7369
+ try {
7370
+ if (onBeforeComplete) {
7371
+ try {
7372
+ const canContinue = await onBeforeComplete();
7373
+ if (!canContinue) {
7374
+ return;
7375
+ }
7376
+ } catch (error) {
7377
+ console.error("[CompleteButton] onBeforeComplete error:", error);
7368
7378
  return;
7369
7379
  }
7370
- } catch (error) {
7371
- console.error("[CompleteButton] onBeforeComplete error:", error);
7380
+ }
7381
+ if (requiresRecaptcha && !captchaToken) {
7382
+ onError == null ? void 0 : onError(new Error("reCAPTCHA verification required"));
7372
7383
  return;
7373
7384
  }
7374
- }
7375
- if (requiresRecaptcha && !captchaToken) {
7376
- onError == null ? void 0 : onError(new Error("reCAPTCHA verification required"));
7377
- return;
7378
- }
7379
- setIsLoading(true);
7380
- try {
7381
- await api.submitCampaign({
7382
- campaign_id: campaignId,
7383
- // Widget version: always false (no auto-join checkbox)
7384
- auto_follow_community: false,
7385
- captcha_token: captchaToken
7386
- });
7387
- onComplete == null ? void 0 : onComplete();
7388
- } catch (error) {
7389
- const errorMessage = error instanceof Error ? error.message : "Failed to complete quest";
7390
- onError == null ? void 0 : onError(new Error(errorMessage));
7385
+ try {
7386
+ await api.submitCampaign({
7387
+ campaign_id: campaignId,
7388
+ // Widget version: always false (no auto-join checkbox)
7389
+ auto_follow_community: false,
7390
+ captcha_token: captchaToken
7391
+ });
7392
+ onComplete == null ? void 0 : onComplete();
7393
+ } catch (error) {
7394
+ const normalizedError = normalizeCompleteSubmitError(error);
7395
+ onError == null ? void 0 : onError(normalizedError);
7396
+ }
7391
7397
  } finally {
7392
7398
  setIsLoading(false);
7393
7399
  }
@@ -7416,6 +7422,9 @@ function CompleteButton({
7416
7422
  }
7417
7423
  );
7418
7424
  }
7425
+ const REPEAT_SUBMIT_NOTICE_TITLE = "Oops! Seems like You already finished this quest";
7426
+ const REPEAT_SUBMIT_NOTICE_DESC = "To ensure a fair experience, submissions with identical wallet addresses, X, Discord, or Telegram accounts, and email addresses are not permitted.";
7427
+ const REPEAT_SUBMIT_NOTICE_CONFIRM = "Confirm";
7419
7428
  function OperateFooter(props) {
7420
7429
  const {
7421
7430
  campaign,
@@ -7436,6 +7445,8 @@ function OperateFooter(props) {
7436
7445
  rewardDisplayMode,
7437
7446
  rewardRedirectUrl
7438
7447
  } = props;
7448
+ const { toast } = useToast();
7449
+ const [isRepeatSubmitDialogVisible, setIsRepeatSubmitDialogVisible] = useState(false);
7439
7450
  const hasRanking = useMemo(() => {
7440
7451
  return hasPointRanking(campaign.winner_rewards);
7441
7452
  }, [campaign.winner_rewards]);
@@ -7483,6 +7494,16 @@ function OperateFooter(props) {
7483
7494
  children: "Connect Wallet to Participate"
7484
7495
  }
7485
7496
  ) });
7497
+ const handleCompleteError = useCallback(
7498
+ (error) => {
7499
+ if (error instanceof ApiError && error.code === ErrorCode.RUSER_REPEAT_SUBMIT) {
7500
+ setIsRepeatSubmitDialogVisible(true);
7501
+ return;
7502
+ }
7503
+ toast.error(error.message);
7504
+ },
7505
+ [toast]
7506
+ );
7486
7507
  const renderCompleteButton = () => /* @__PURE__ */ jsx(
7487
7508
  CompleteButton,
7488
7509
  {
@@ -7492,9 +7513,7 @@ function OperateFooter(props) {
7492
7513
  disabled: !isActive,
7493
7514
  onBeforeComplete,
7494
7515
  onComplete,
7495
- onError: (error) => {
7496
- console.error("Complete quest error:", error.message);
7497
- }
7516
+ onError: handleCompleteError
7498
7517
  }
7499
7518
  );
7500
7519
  const renderStatusCard = () => {
@@ -7520,19 +7539,35 @@ function OperateFooter(props) {
7520
7539
  }
7521
7540
  );
7522
7541
  };
7542
+ const renderRepeatSubmitNoticeDialog = () => /* @__PURE__ */ jsx(
7543
+ ConfirmNoticeDialog,
7544
+ {
7545
+ open: isRepeatSubmitDialogVisible,
7546
+ onOpenChange: setIsRepeatSubmitDialogVisible,
7547
+ type: "warn",
7548
+ title: REPEAT_SUBMIT_NOTICE_TITLE,
7549
+ desc: REPEAT_SUBMIT_NOTICE_DESC,
7550
+ confirmButton: REPEAT_SUBMIT_NOTICE_CONFIRM,
7551
+ onConfirm: () => setIsRepeatSubmitDialogVisible(false),
7552
+ accessibilityTitle: REPEAT_SUBMIT_NOTICE_TITLE,
7553
+ accessibilityDescription: REPEAT_SUBMIT_NOTICE_DESC
7554
+ }
7555
+ );
7556
+ let footerContent = null;
7523
7557
  if (participationStatus === "not-logged-in") {
7524
- return /* @__PURE__ */ jsx("div", { className: "taskon-quest-footer", id: "detail-operate-footer", children: renderConnectWallet() });
7558
+ footerContent = renderConnectWallet();
7559
+ } else if (participationStatus === "can-complete") {
7560
+ footerContent = renderCompleteButton();
7561
+ } else if (participationStatus !== "none" && cardConfig) {
7562
+ footerContent = renderStatusCard();
7525
7563
  }
7526
- if (participationStatus === "none") {
7564
+ if (!footerContent && !isRepeatSubmitDialogVisible) {
7527
7565
  return null;
7528
7566
  }
7529
- if (participationStatus === "can-complete") {
7530
- return /* @__PURE__ */ jsx("div", { className: "taskon-quest-footer", id: "detail-operate-footer", children: renderCompleteButton() });
7531
- }
7532
- if (cardConfig) {
7533
- return /* @__PURE__ */ jsx("div", { className: "taskon-quest-footer", id: "detail-operate-footer", children: renderStatusCard() });
7534
- }
7535
- return null;
7567
+ return /* @__PURE__ */ jsxs("div", { className: "taskon-quest-footer", id: "detail-operate-footer", children: [
7568
+ footerContent,
7569
+ renderRepeatSubmitNoticeDialog()
7570
+ ] });
7536
7571
  }
7537
7572
  function getDialogTitle(errorType) {
7538
7573
  switch (errorType) {
@@ -7557,17 +7592,17 @@ function getDialogIcon(errorType) {
7557
7592
  fill: "none",
7558
7593
  xmlns: "http://www.w3.org/2000/svg",
7559
7594
  children: [
7560
- /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "22", stroke: "#FF9500", strokeWidth: "4" }),
7595
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "22", stroke: "currentColor", strokeWidth: "4" }),
7561
7596
  /* @__PURE__ */ jsx(
7562
7597
  "path",
7563
7598
  {
7564
7599
  d: "M24 14V28",
7565
- stroke: "#FF9500",
7600
+ stroke: "currentColor",
7566
7601
  strokeWidth: "4",
7567
7602
  strokeLinecap: "round"
7568
7603
  }
7569
7604
  ),
7570
- /* @__PURE__ */ jsx("circle", { cx: "24", cy: "35", r: "2.5", fill: "#FF9500" })
7605
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "35", r: "2.5", fill: "currentColor" })
7571
7606
  ]
7572
7607
  }
7573
7608
  );
@@ -7759,12 +7794,12 @@ function DiscordIcon() {
7759
7794
  fill: "none",
7760
7795
  xmlns: "http://www.w3.org/2000/svg",
7761
7796
  children: [
7762
- /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "24", fill: "#5865F2" }),
7797
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "24", fill: "var(--taskon-color-link)" }),
7763
7798
  /* @__PURE__ */ jsx(
7764
7799
  "path",
7765
7800
  {
7766
7801
  d: "M32.6 17.3C31.1 16.6 29.5 16.1 27.8 15.8C27.6 16.2 27.3 16.7 27.1 17.1C25.3 16.8 23.5 16.8 21.7 17.1C21.5 16.7 21.2 16.2 21 15.8C19.3 16.1 17.7 16.6 16.2 17.3C13.2 21.8 12.4 26.2 12.8 30.5C14.8 32 16.7 32.8 18.6 33.4C19.1 32.7 19.5 31.9 19.9 31.1C19.1 30.8 18.4 30.4 17.7 29.9C17.9 29.8 18.1 29.6 18.2 29.5C22 31.3 26.2 31.3 29.9 29.5C30.1 29.7 30.3 29.8 30.4 29.9C29.7 30.4 29 30.8 28.2 31.1C28.6 31.9 29 32.7 29.5 33.4C31.4 32.8 33.3 32 35.3 30.5C35.8 25.5 34.5 21.2 32.6 17.3ZM19.4 28.1C18.4 28.1 17.6 27.2 17.6 26.1C17.6 25 18.4 24.1 19.4 24.1C20.4 24.1 21.2 25 21.2 26.1C21.2 27.2 20.4 28.1 19.4 28.1ZM28.6 28.1C27.6 28.1 26.8 27.2 26.8 26.1C26.8 25 27.6 24.1 28.6 24.1C29.6 24.1 30.4 25 30.4 26.1C30.4 27.2 29.6 28.1 28.6 28.1Z",
7767
- fill: "white"
7802
+ fill: "var(--taskon-color-text)"
7768
7803
  }
7769
7804
  )
7770
7805
  ]
@@ -7814,6 +7849,46 @@ function DiscordBindDialog({
7814
7849
  }
7815
7850
  );
7816
7851
  }
7852
+ function QuestLoadingSkeleton() {
7853
+ return /* @__PURE__ */ jsx("div", { className: "taskon-quest-loading", "aria-busy": "true", "aria-label": "Loading quest", children: /* @__PURE__ */ jsxs("div", { className: "taskon-quest-layout taskon-quest-skeleton-layout", children: [
7854
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-content taskon-quest-skeleton-content", children: [
7855
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-skeleton-group", children: [
7856
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-title" }),
7857
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-meta" }),
7858
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-banner" }),
7859
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-description" }),
7860
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-description taskon-quest-skeleton-description--short" })
7861
+ ] }),
7862
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-content-divider taskon-quest-skeleton-content-divider" }),
7863
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-skeleton-group taskon-quest-skeleton-group--tasks", children: [
7864
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-section-title" }),
7865
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-progress" }),
7866
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-task" }),
7867
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-task" }),
7868
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-task" }),
7869
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-complete-button" })
7870
+ ] })
7871
+ ] }),
7872
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-divider taskon-quest-skeleton-divider" }),
7873
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-sidebar taskon-quest-skeleton-sidebar", children: [
7874
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-skeleton-panel", children: [
7875
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-title" }),
7876
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-line" }),
7877
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-line taskon-quest-skeleton-panel-line--short" })
7878
+ ] }),
7879
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-skeleton-panel", children: [
7880
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-title" }),
7881
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-line" }),
7882
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-line" }),
7883
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-line taskon-quest-skeleton-panel-line--short" })
7884
+ ] }),
7885
+ /* @__PURE__ */ jsxs("div", { className: "taskon-quest-skeleton-panel", children: [
7886
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-title" }),
7887
+ /* @__PURE__ */ jsx("div", { className: "taskon-quest-skeleton-block taskon-quest-skeleton-panel-line" })
7888
+ ] })
7889
+ ] })
7890
+ ] }) });
7891
+ }
7817
7892
  function ChevronRightIcon() {
7818
7893
  return /* @__PURE__ */ jsx(
7819
7894
  "svg",
@@ -8108,6 +8183,33 @@ function mergeQuestConfig(props, cloud) {
8108
8183
  rewardRedirectUrl: props.rewardRedirectUrl ?? (cloud == null ? void 0 : cloud.rewardRedirectUrl) ?? ""
8109
8184
  };
8110
8185
  }
8186
+ const QUEST_NFT_CLAIM_MESSAGES = {
8187
+ claimDialog: {
8188
+ claimNft: "Claim NFT",
8189
+ claimingNft: "Claiming NFT...",
8190
+ claimConnectingWallet: "Connecting wallet...",
8191
+ claimSwitchingNetwork: "Switching network...",
8192
+ claimGettingSignature: "Getting signature...",
8193
+ claimConfirmInWallet: "Please confirm in your wallet",
8194
+ claimTransactionPending: "Transaction pending...",
8195
+ claimSuccess: "Claim successful!",
8196
+ claimFailed: "Claim failed",
8197
+ claimCanceled: "Transaction was rejected by user.",
8198
+ viewOnExplorer: "View on Explorer",
8199
+ retry: "Retry",
8200
+ close: "Close"
8201
+ },
8202
+ pendingDialog: {
8203
+ pendingTransaction: "Pending Transaction",
8204
+ claimPendingTitle: "You have already claimed this NFT, please wait for this transaction to be confirmed.",
8205
+ claimPendingCheckExplorer: "You can check this transaction on explorer:",
8206
+ claimPendingHashLabel: "Transaction hash:",
8207
+ claimPendingClaimAgainWarn: '"Claim Again" will send a new transaction it is only recommended when you are sure there is something wrong with the current transaction.',
8208
+ claimPendingReceiveAddressNoChange: "This receive address can’t be changed:",
8209
+ claimAgain: "Claim Again",
8210
+ continueWaiting: "Continue Waiting"
8211
+ }
8212
+ };
8111
8213
  function getQuestStatusDisplay(campaignStatus, startTime, endTime, isEnd) {
8112
8214
  const now = Date.now();
8113
8215
  const startMs = startTime;
@@ -8144,7 +8246,7 @@ function getHasRanking(winnerRewards) {
8144
8246
  );
8145
8247
  }
8146
8248
  function QuestWidget(props) {
8147
- const { widgetId } = props;
8249
+ const { widgetId, themeMode } = props;
8148
8250
  const { functionConfig, cloudTheme, isConfigLoading, configError } = useResolvedWidgetConfig(widgetId);
8149
8251
  const mergedConfig = useMemo(() => {
8150
8252
  return mergeQuestConfig(
@@ -8171,6 +8273,7 @@ function QuestWidget(props) {
8171
8273
  widgetId,
8172
8274
  isConfigLoading,
8173
8275
  cloudTheme,
8276
+ themeMode,
8174
8277
  className: "taskon-quest",
8175
8278
  errorMessage: configError ?? (!mergedConfig.campaignId ? "Campaign ID is required. Please provide campaignId via props or widgetId." : void 0),
8176
8279
  children: /* @__PURE__ */ jsx(
@@ -8211,7 +8314,6 @@ function QuestWidgetInner(props) {
8211
8314
  onSubmitSuccess,
8212
8315
  onError,
8213
8316
  onConnectWallet,
8214
- onClaimNft,
8215
8317
  onClaimDiscordRole,
8216
8318
  // onBindChainRequired 和 onBindSnsRequired 已弃用,绑定现在由 Widget 内部处理
8217
8319
  shareUrl,
@@ -8283,6 +8385,34 @@ function QuestWidgetInner(props) {
8283
8385
  kolHandle,
8284
8386
  enabled: !isPreview
8285
8387
  });
8388
+ const {
8389
+ claimNftReward,
8390
+ isSupportedNftRewardType,
8391
+ dialogs: nftClaimDialogs
8392
+ } = useNftClaimFlow({
8393
+ campaignId,
8394
+ targetType: (campaign == null ? void 0 : campaign.campaign_type) === CampaignType.Event ? "event" : "campaign",
8395
+ messages: QUEST_NFT_CLAIM_MESSAGES,
8396
+ onClaimSuccess: async () => {
8397
+ refetchUserStatus();
8398
+ refetchStatus();
8399
+ },
8400
+ onClaimError: (error2) => {
8401
+ toast.error(error2.message || "Failed to claim NFT");
8402
+ },
8403
+ onWalletError: (errorMessage) => {
8404
+ toast.error(errorMessage);
8405
+ }
8406
+ });
8407
+ const handleQuestClaimNft = useCallback(
8408
+ async (reward, layer) => {
8409
+ if (!isSupportedNftRewardType(reward.reward_type)) {
8410
+ return;
8411
+ }
8412
+ await claimNftReward(reward, layer);
8413
+ },
8414
+ [isSupportedNftRewardType, claimNftReward]
8415
+ );
8286
8416
  const {
8287
8417
  mandatoryTasks,
8288
8418
  optionalTasks,
@@ -8458,6 +8588,22 @@ function QuestWidgetInner(props) {
8458
8588
  refetchUserStatus();
8459
8589
  }
8460
8590
  };
8591
+ const handleTaskVerifyAttempted = useCallback(
8592
+ (taskId, success) => {
8593
+ var _a2;
8594
+ if (success || isPreview) {
8595
+ return;
8596
+ }
8597
+ const targetTask = (_a2 = campaign == null ? void 0 : campaign.tasks) == null ? void 0 : _a2.find((task) => task.id === taskId);
8598
+ if (!targetTask) {
8599
+ return;
8600
+ }
8601
+ if (targetTask.template_id === "SwapDexContractInteractive") {
8602
+ refetchUserStatus();
8603
+ }
8604
+ },
8605
+ [campaign == null ? void 0 : campaign.tasks, isPreview, refetchUserStatus]
8606
+ );
8461
8607
  const handleSubmitSuccess = () => {
8462
8608
  refetchDetail();
8463
8609
  refetchUserStatus();
@@ -8474,7 +8620,6 @@ function QuestWidgetInner(props) {
8474
8620
  // 绑定弹窗相关
8475
8621
  eligsBindInfo,
8476
8622
  closeEligsBindDialog,
8477
- skipEligsBind,
8478
8623
  onAllEligsBind
8479
8624
  } = useEligibilityRefresh({
8480
8625
  api,
@@ -8493,6 +8638,42 @@ function QuestWidgetInner(props) {
8493
8638
  await refetchUserStatus();
8494
8639
  }
8495
8640
  });
8641
+ const [completeEligibilityFailedInfo, setCompleteEligibilityFailedInfo] = useState(null);
8642
+ const [isRefreshingCompleteEligs, setIsRefreshingCompleteEligs] = useState(false);
8643
+ const closeCompleteEligibilityFailedDialog = useCallback(() => {
8644
+ setCompleteEligibilityFailedInfo(null);
8645
+ }, []);
8646
+ const refreshCompleteEligibilityInDialog = useCallback(async () => {
8647
+ if (!api || !campaign) {
8648
+ return;
8649
+ }
8650
+ setIsRefreshingCompleteEligs(true);
8651
+ try {
8652
+ const result = await api.checkUserCampaignEligibility({
8653
+ campaign_id: campaign.id
8654
+ });
8655
+ if (result.result) {
8656
+ setCompleteEligibilityFailedInfo(null);
8657
+ await refetchUserStatus();
8658
+ return;
8659
+ }
8660
+ setCompleteEligibilityFailedInfo((previousInfo) => {
8661
+ if (!previousInfo) {
8662
+ return {
8663
+ eligs: campaign.eligs,
8664
+ eligibilityExpress: campaign.eligibility_express || "and",
8665
+ details: result.details
8666
+ };
8667
+ }
8668
+ return {
8669
+ ...previousInfo,
8670
+ details: result.details
8671
+ };
8672
+ });
8673
+ } finally {
8674
+ setIsRefreshingCompleteEligs(false);
8675
+ }
8676
+ }, [api, campaign, refetchUserStatus]);
8496
8677
  const {
8497
8678
  validateBeforeComplete,
8498
8679
  // Task validation error dialog
@@ -8528,9 +8709,10 @@ function QuestWidgetInner(props) {
8528
8709
  onBindSns: async (snsType) => {
8529
8710
  return await bindSnsAndWait(snsType);
8530
8711
  },
8531
- // When eligibility check fails, show the existing EligsNotPassDialog
8532
- onEligibilityFailed: () => {
8533
- return false;
8712
+ // Complete eligibility 检查失败:
8713
+ // 弹出 EligsNotPassDialog 并阻断 complete,行为对齐 Vue。
8714
+ onEligibilityFailed: (info) => {
8715
+ setCompleteEligibilityFailedInfo(info);
8534
8716
  }
8535
8717
  });
8536
8718
  const hasEligibility = (campaign == null ? void 0 : campaign.eligs) && campaign.eligs.length > 0;
@@ -8540,10 +8722,7 @@ function QuestWidgetInner(props) {
8540
8722
  return userStatus.campaign_eligible !== UserEligibleStatus.Eligible;
8541
8723
  }, [statusDisplay == null ? void 0 : statusDisplay.isActive, userStatus]);
8542
8724
  if (isLoading && !campaign) {
8543
- return /* @__PURE__ */ jsx("div", { className: "taskon-quest", children: /* @__PURE__ */ jsxs("div", { className: "taskon-quest-loading", children: [
8544
- /* @__PURE__ */ jsx("div", { className: "taskon-quest-loading-spinner" }),
8545
- /* @__PURE__ */ jsx("span", { children: "Loading quest..." })
8546
- ] }) });
8725
+ return /* @__PURE__ */ jsx("div", { className: "taskon-quest", children: /* @__PURE__ */ jsx(QuestLoadingSkeleton, {}) });
8547
8726
  }
8548
8727
  if (error && !campaign) {
8549
8728
  return /* @__PURE__ */ jsx("div", { className: "taskon-quest", children: /* @__PURE__ */ jsxs("div", { className: "taskon-quest-error", children: [
@@ -8609,6 +8788,7 @@ function QuestWidgetInner(props) {
8609
8788
  optionalTasks: optionalTaskData,
8610
8789
  userTaskStatus: userTaskStatusMap,
8611
8790
  onTaskCompleted: handleTaskCompleted,
8791
+ onVerifyAttempted: handleTaskVerifyAttempted,
8612
8792
  onBeforeVerify: checkBeforeSubmitTask,
8613
8793
  disabled: isTaskDisabled,
8614
8794
  isStarted: statusDisplay == null ? void 0 : statusDisplay.isStarted,
@@ -8640,7 +8820,7 @@ function QuestWidgetInner(props) {
8640
8820
  refetchStatus();
8641
8821
  },
8642
8822
  onConnectWallet,
8643
- onClaimNft,
8823
+ onClaimNft: handleQuestClaimNft,
8644
8824
  onClaimDiscordRole,
8645
8825
  onBeforeComplete: validateBeforeComplete,
8646
8826
  rewardDisplayMode,
@@ -8769,6 +8949,21 @@ function QuestWidgetInner(props) {
8769
8949
  isRefreshing: isRefreshingEligs
8770
8950
  }
8771
8951
  ),
8952
+ completeEligibilityFailedInfo && campaign && /* @__PURE__ */ jsx(
8953
+ EligsNotPassDialog,
8954
+ {
8955
+ open: !!completeEligibilityFailedInfo,
8956
+ onClose: closeCompleteEligibilityFailedDialog,
8957
+ eligs: completeEligibilityFailedInfo.eligs,
8958
+ eligibilityExpress: completeEligibilityFailedInfo.eligibilityExpress,
8959
+ details: completeEligibilityFailedInfo.details,
8960
+ campaignId,
8961
+ communityKey: campaign.community_key,
8962
+ communityName: campaign.community_name,
8963
+ onRefresh: refreshCompleteEligibilityInDialog,
8964
+ isRefreshing: isRefreshingCompleteEligs
8965
+ }
8966
+ ),
8772
8967
  eligsBindInfo && campaign && /* @__PURE__ */ jsx(
8773
8968
  EligsBindDialog,
8774
8969
  {
@@ -8776,9 +8971,7 @@ function QuestWidgetInner(props) {
8776
8971
  chainTypes: eligsBindInfo.chainTypes,
8777
8972
  snsTypes: eligsBindInfo.snsTypes,
8778
8973
  campaign,
8779
- skippable: eligsBindInfo.skippable,
8780
8974
  onClose: closeEligsBindDialog,
8781
- onSkip: skipEligsBind,
8782
8975
  onAllBind: onAllEligsBind,
8783
8976
  onBindChain: bindChainAndWait,
8784
8977
  onBindSns: bindSnsAndWait
@@ -8820,15 +9013,13 @@ function QuestWidgetInner(props) {
8820
9013
  chainTypes: completeEligsBindInfo.chainTypes,
8821
9014
  snsTypes: completeEligsBindInfo.snsTypes,
8822
9015
  campaign,
8823
- skippable: completeEligsBindInfo.skippable,
8824
9016
  onClose: closeCompleteEligsBindDialog,
8825
- onSkip: () => {
8826
- },
8827
9017
  onAllBind: onAllCompleteEligsBind,
8828
9018
  onBindChain: bindChainAndWait,
8829
9019
  onBindSns: bindSnsAndWait
8830
9020
  }
8831
- )
9021
+ ),
9022
+ nftClaimDialogs
8832
9023
  ] });
8833
9024
  }
8834
9025
  export {