Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions packages/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import { AuthModule } from './auth/auth.module';
import { PriceModule } from './price/price.module';
import { FeatureRequestModule } from './feature-request/feature-request.module';
import { AdminModule } from './admin/admin.module';
import { PartnerModule } from './partner/partner.module';
import { QuestModule } from './quest/quest.module';
import { RewardModule } from './reward/reward.module';
// Hidden: Partner account-report endpoint disabled per ops request.
// import { PartnerModule } from './partner/partner.module';
// Hidden: Quest + Leaderboard + Reward/Claim flows disabled (FE already hidden in #245).
// import { QuestModule } from './quest/quest.module';
// import { RewardModule } from './reward/reward.module';
import { BalanceAlertModule } from './balance-alert/balance-alert.module';
import { ScheduleModule } from '@nestjs/schedule';
import { X402Module } from './x402/x402.module';
Expand All @@ -45,9 +47,9 @@ const featureX402 = process.env.FEATURE_X402_DEPOSIT === 'true';
PriceModule,
FeatureRequestModule,
AdminModule,
PartnerModule,
QuestModule,
RewardModule,
// PartnerModule,
// QuestModule,
// RewardModule,
BalanceAlertModule,
ScheduleModule.forRoot(),
...(featureX402 ? [X402Module] : []),
Expand Down
33 changes: 6 additions & 27 deletions packages/backend/src/transaction/transaction-executor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {
import { EventsService } from '@/events/events.service';
import { Transaction } from '@/generated/prisma/client';
import { AnalyticsLoggerService } from '@/common/analytics-logger.service';
import { QuestService } from '@/quest/quest.service';
// Quest disabled — see app.module.ts.
// import { QuestService } from '@/quest/quest.service';

@Injectable()
export class TransactionExecutorService {
Expand All @@ -46,7 +47,7 @@ export class TransactionExecutorService {
private readonly relayerService: RelayerService,
private readonly eventsService: EventsService,
private readonly analyticsLogger: AnalyticsLoggerService,
private readonly questService: QuestService,
// private readonly questService: QuestService,
) {}

/**
Expand Down Expand Up @@ -106,15 +107,15 @@ export class TransactionExecutorService {
);

// 3. Mark as executed only on success
const { pointsAwarded } = await this.markExecuted(txId, txHash);
await this.markExecuted(txId, txHash);

this.analyticsLogger.logExecute(
userAddress,
transaction.accountAddress,
txHash,
);

return { txId, txHash, status: TxStatus.EXECUTED, pointsAwarded };
return { txId, txHash, status: TxStatus.EXECUTED };
} catch (error) {
this.logger.error(`Execute failed for txId ${txId}: ${error.message}`);

Expand Down Expand Up @@ -265,27 +266,6 @@ export class TransactionExecutorService {
},
});

// Award quest points (only for TRANSFER and BATCH transactions)
let pointsAwarded = 0;
if (
transaction.type === TxType.TRANSFER ||
transaction.type === TxType.BATCH
) {
try {
const successfulTxPoints = await this.questService.awardSuccessfulTx(
txId,
transaction.createdBy,
);
const firstTxPoints = await this.questService.awardAccountFirstTx(
transaction.accountAddress,
txId,
);
pointsAwarded = successfulTxPoints + firstTxPoints;
} catch (error) {
this.logger.error(`Failed to award quest points: ${error.message}`);
}
}

// Emit event for status update
const eventData: TxStatusEventData = {
txId,
Expand All @@ -298,8 +278,7 @@ export class TransactionExecutorService {
eventData,
);

const result = await updatedTx;
return { ...result, pointsAwarded };
return updatedTx;
});
}

Expand Down
5 changes: 3 additions & 2 deletions packages/backend/src/transaction/transaction.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { RelayerModule } from '@/relayer-wallet/relayer-wallet.module';
import { BatchItemModule } from '@/batch-item/batch-item.module';
import { EventsModule } from '@/events/events.module';
import { AnalyticsLoggerService } from '@/common/analytics-logger.service';
import { QuestModule } from '@/quest/quest.module';
// Quest disabled — see app.module.ts.
// import { QuestModule } from '@/quest/quest.module';

@Module({
imports: [
Expand All @@ -18,7 +19,7 @@ import { QuestModule } from '@/quest/quest.module';
RelayerModule,
BatchItemModule,
EventsModule,
QuestModule,
// QuestModule,
],
controllers: [TransactionController],
providers: [
Expand Down
162 changes: 5 additions & 157 deletions packages/nextjs/app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,158 +1,6 @@
"use client";
// Leaderboard is hidden — see #245. Original page kept in git history (commit 894eeac) for restore.
import { redirect } from "next/navigation";

import { useState } from "react";
import Image from "next/image";
import { formatCampaignStartDate, getAvailableWeeks, getCurrentWeek, getLastCompletedWeek } from "@polypay/shared";
import type { LeaderboardFilter } from "@polypay/shared";
import { NextPage } from "next";
import { SectionAvatar } from "~~/components/leader-board";
import { ClaimSection } from "~~/components/leader-board/ClaimSection";
import { LeaderBoardTable } from "~~/components/leader-board/LeaderBoardTable";
import { QuestFloatingButton } from "~~/components/quest/QuestFloatingButton";
import { useModalApp } from "~~/hooks";
import { useClaimSummary } from "~~/hooks/api/useClaim";

const WEEKS = [1, 2];

const LeaderBoardPage: NextPage = () => {
const [filter, setFilter] = useState<LeaderboardFilter>("weekly");
const [selectedWeek, setSelectedWeek] = useState<number>(() => {
const lastCompleted = getLastCompletedWeek();
const available = getAvailableWeeks();
return lastCompleted || available[0] || 1;
});
const { openModal } = useModalApp();

// Get campaign state
const currentWeek = getCurrentWeek();
const availableWeeks = getAvailableWeeks();
const lastCompletedWeek = getLastCompletedWeek();
const campaignNotStarted = currentWeek === 0;

// Fetch claim summary to check if user has claimed
const { data: claimSummary } = useClaimSummary();

// Check if selected week has reward (claimed or not)
const selectedWeekData = claimSummary?.weeks.find(w => w.week === selectedWeek);

// Get week param only when filter is weekly
const weekParam = filter === "weekly" ? selectedWeek : undefined;

const handleClaim = () => {
if (!claimSummary) return;
openModal("claimReward", { week: selectedWeek });
};

return (
<div className="max-w-4xl mx-auto py-8 px-4 space-y-8">
{/* Header */}
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h3 className="font-barlow font-normal text-4xl leading-[100%] tracking-[-0.03em] capitalize text-grey-1000">
Leaderboard
</h3>

{/* Filter Tabs */}
<div className="flex items-center p-1 gap-2 bg-grey-100 rounded-xl">
<button
onClick={() => setFilter("weekly")}
className={`px-6 py-2 rounded-lg font-barlow text-sm tracking-[-0.04em] transition-colors ${
filter === "weekly"
? "bg-white font-semibold text-grey-1000"
: "bg-transparent font-medium text-grey-800 hover:text-grey-1000"
}`}
>
Weekly
</button>
<button
onClick={() => setFilter("all-time")}
className={`px-6 py-2 rounded-lg font-barlow text-sm tracking-[-0.04em] transition-colors ${
filter === "all-time"
? "bg-white font-semibold text-grey-1000"
: "bg-transparent font-medium text-grey-800 hover:text-grey-1000"
}`}
>
All Time
</button>
</div>
</div>

{/* Week Buttons - Only show when filter is "weekly" */}
{filter === "weekly" && (
<div className="flex flex-col gap-3">
{/* Week buttons */}
<div className="flex items-start gap-4">
{WEEKS.map(week => {
const isAvailable = availableWeeks.includes(week);
const isSelected = selectedWeek === week;

// Check if this week has unclaimed reward (for pink dot)
const weekData = claimSummary?.weeks.find(w => w.week === week);
const isCompleted = lastCompletedWeek !== null && week <= lastCompletedWeek;
const showPinkDot = isCompleted && weekData && !weekData.isClaimed && weekData.rewardZen > 0;

return (
<button
key={week}
onClick={() => isAvailable && setSelectedWeek(week)}
disabled={!isAvailable}
className={`relative px-4 py-2 rounded-lg font-barlow font-medium text-sm leading-5 tracking-[-0.04em] transition-colors ${
isSelected
? "bg-grey-1000 text-white"
: isAvailable
? "bg-grey-100 text-grey-1000 hover:bg-grey-200"
: "bg-grey-50 text-grey-400 cursor-not-allowed"
}`}
>
Week {week}
{/* Pink dot indicator for unclaimed reward */}
{showPinkDot && <span className="absolute -top-1 -right-1 w-3 h-3 bg-main-pink rounded-full" />}
</button>
);
})}
</div>
</div>
)}

{/* Claim Section - Only show when filter is "weekly" */}
{filter === "weekly" && (
<ClaimSection
week={selectedWeek}
claimData={
selectedWeekData
? {
isClaimed: selectedWeekData.isClaimed,
rewardZen: selectedWeekData.rewardZen,
txHash: selectedWeekData.txHash,
}
: undefined
}
onClaim={handleClaim}
/>
)}
</div>

{/* Content */}
<div className=" w-[650px] p-10 rounded-3xl bg-white border border-grey-100 space-y-10">
{campaignNotStarted ? (
<div className="flex flex-col items-center justify-center h-[400px] gap-6">
<Image src="/leader-board/empty-leader-board.svg" alt="Campaign not started" width={150} height={150} />
<div className="flex flex-col items-center gap-2">
<span className="text-xl font-semibold text-main-violet">Campaign has not started yet</span>
<span className="text-grey-500">Please check back on {formatCampaignStartDate()}</span>
</div>
</div>
) : (
<>
<SectionAvatar filter={filter} week={weekParam} />
<LeaderBoardTable filter={filter} week={weekParam} />
</>
)}
</div>

<QuestFloatingButton />
</div>
);
};

export default LeaderBoardPage;
export default function LeaderboardPage() {
redirect("/dashboard");
}
Loading
Loading