Skip to content

Commit

Permalink
feat: add token claim page with Christmas offer (#299)
Browse files Browse the repository at this point in the history
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ben <ben@prologe.io>
  • Loading branch information
1 parent 2db3dc4 commit 061d09c
Show file tree
Hide file tree
Showing 7 changed files with 458 additions and 0 deletions.
72 changes: 72 additions & 0 deletions packages/web/app/claim-offer/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use server';

import { db, UserUsageTable, createEmptyUserUsage, christmasClaims, hasClaimedChristmasTokens } from "@/drizzle/schema";
import { eq, sql } from "drizzle-orm";
import { revalidatePath } from 'next/cache';
import { auth } from "@clerk/nextjs/server";

export async function claimTokens() {
try {
// Check if after deadline
const deadline = new Date('2025-01-01');
if (new Date() > deadline) {
return { error: "This offer has expired" };
}

// Get authenticated user
const { userId } = auth();
if (!userId) {
return { error: "You must be logged in to claim tokens" };
}

// Get or create user usage record
let [usage] = await db.select().from(UserUsageTable).where(eq(UserUsageTable.userId, userId));

if (!usage) {
await createEmptyUserUsage(userId);
[usage] = await db.select().from(UserUsageTable).where(eq(UserUsageTable.userId, userId));
}

// Check if already claimed
const hasClaimed = await hasClaimedChristmasTokens(userId);
if (hasClaimed) {
return { error: "You have already claimed this offer" };
}

// Record the claim
await db.insert(christmasClaims).values({ userId });

// Increase maxTokenUsage by 5M
const result = await db
.update(UserUsageTable)
.set({
maxTokenUsage: sql`COALESCE(${UserUsageTable.maxTokenUsage}, 0) + 5000000`
})
.where(eq(UserUsageTable.userId, userId))
.returning({ newMaxTokens: UserUsageTable.maxTokenUsage });

revalidatePath('/claim-offer');

// Track successful claim in PostHog
const PostHogClient = (await import("@/lib/posthog")).default;
const posthog = PostHogClient();
if (posthog) {
await posthog.capture({
distinctId: userId,
event: 'christmas_tokens_claimed',
properties: {
tokens_amount: 5000000,
new_max_tokens: result[0].newMaxTokens
}
});
}

return {
success: true,
maxTokens: result[0].newMaxTokens
};
} catch (error: any) {
console.error("Error claiming tokens:", error);
return { error: error.message || "Failed to claim tokens" };
}
}
56 changes: 56 additions & 0 deletions packages/web/app/claim-offer/claim-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { useState } from "react";
import { claimTokens } from "./actions";
import { Button } from "@/components/ui/button";

export function ClaimButton() {
const [loading, setLoading] = useState(false);
const [claimed, setClaimed] = useState(false);
const [error, setError] = useState("");
const [maxTokens, setMaxTokens] = useState(0);

async function handleClaim() {
try {
setLoading(true);
setError("");

const result = await claimTokens();

if (result.success) {
setClaimed(true);
setMaxTokens(result.maxTokens);
} else {
setError(result.error || "Failed to claim tokens");
}
} catch (err: any) {
setError(err.message || "Failed to claim tokens");
} finally {
setLoading(false);
}
}

if (claimed) {
return (
<div className="text-center">
<p className="text-green-600 font-semibold mb-2">🎉 Successfully claimed 5M tokens!</p>
<p className="text-sm text-gray-600">Your new token limit: {maxTokens.toLocaleString()}</p>
</div>
);
}

return (
<div className="text-center">
<Button
onClick={handleClaim}
disabled={loading}
size="lg"
>
{loading ? "Claiming..." : "Claim 5M Tokens"}
</Button>
{error && (
<p className="mt-2 text-red-600 text-sm">{error}</p>
)}
</div>
);
}
18 changes: 18 additions & 0 deletions packages/web/app/claim-offer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ClaimButton } from './claim-button';

export default function ClaimOfferPage() {
return (
<div className="max-w-md mx-auto p-6 mt-8 text-center space-y-6">
<h1 className="text-2xl font-bold">Free 5M Token Offer</h1>
<p>Thank you for supporting File Organizer. Here's $15 worth of credits on us!</p>
<img
src="https://i.giphy.com/sZjZz0NjdQuOdjPmGY.webp"
alt="Christmas GIF"
width={300}
height={180}
style={{ maxWidth: '100%', height: 'auto' }}
/>
<ClaimButton />
</div>
);
}
7 changes: 7 additions & 0 deletions packages/web/drizzle/0016_quick_vanisher.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS "christmas_claims" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"claimed_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "unique_christmas_claim_idx" ON "christmas_claims" USING btree ("user_id");
Loading

0 comments on commit 061d09c

Please sign in to comment.