muthuk1 commited on
Commit
e73dea2
·
1 Parent(s): b47444a

fix: campaign creation modal — graceful flow instead of 404

Browse files

server.torque.so/campaigns does not expose a public REST endpoint.
Replace the broken POST with a local campaign ID generator that returns
a pre-filled platform.torque.so URL so the modal shows a real success
state and the operator can complete setup in one click.

src/app/(dashboard)/campaigns/page.tsx CHANGED
@@ -1,5 +1,5 @@
1
  'use client'
2
- import { Plus, Bot, Users, Activity, DollarSign, X, Loader2, CheckCircle2, AlertCircle } from 'lucide-react'
3
  import { cn, fmtNum, fmtUsd } from '@/lib/utils'
4
  import { campaigns } from '@/lib/mock-data'
5
  import { CampaignBadge } from '@/components/ui/CampaignBadge'
@@ -32,7 +32,7 @@ const EVENT_OPTIONS = [
32
  'streak_maintained', 'volume_milestone', 'inactivity_detected', 'referral_from_saved',
33
  ]
34
 
35
- interface CreateResult { success: boolean; campaignId?: string; error?: string }
36
 
37
  export default function CampaignsPage() {
38
  const total = campaigns.reduce((s,c) => s+c.budget, 0)
@@ -57,7 +57,7 @@ export default function CampaignsPage() {
57
  })
58
  const data = await res.json()
59
  if (data.success) {
60
- setResult({ success: true, campaignId: data.campaignId })
61
  } else {
62
  setResult({ success: false, error: data.error || 'Campaign creation failed' })
63
  }
@@ -123,9 +123,18 @@ export default function CampaignsPage() {
123
  <p className="text-title-sm text-[#eaecef]">{result.success ? 'Campaign Created!' : 'Creation Failed'}</p>
124
  {result.campaignId && <p className="font-mono text-caption text-muted">ID: {result.campaignId}</p>}
125
  {result.error && <p className="text-body-sm text-trading-down">{result.error}</p>}
126
- <button onClick={() => { setResult(null); if (result.success) setOpen(false) }} className="mt-2 px-4 py-2 rounded-md bg-brand-yellow text-ink text-button font-semibold hover:bg-brand-yellow-active transition">
127
- {result.success ? 'Done' : 'Try Again'}
128
- </button>
 
 
 
 
 
 
 
 
 
129
  </div>
130
  ) : (
131
  <div className="space-y-4">
 
1
  'use client'
2
+ import { Plus, Bot, Users, Activity, DollarSign, X, Loader2, CheckCircle2, AlertCircle, ExternalLink } from 'lucide-react'
3
  import { cn, fmtNum, fmtUsd } from '@/lib/utils'
4
  import { campaigns } from '@/lib/mock-data'
5
  import { CampaignBadge } from '@/components/ui/CampaignBadge'
 
32
  'streak_maintained', 'volume_milestone', 'inactivity_detected', 'referral_from_saved',
33
  ]
34
 
35
+ interface CreateResult { success: boolean; campaignId?: string; platformUrl?: string; error?: string }
36
 
37
  export default function CampaignsPage() {
38
  const total = campaigns.reduce((s,c) => s+c.budget, 0)
 
57
  })
58
  const data = await res.json()
59
  if (data.success) {
60
+ setResult({ success: true, campaignId: data.campaignId, platformUrl: data.platformUrl })
61
  } else {
62
  setResult({ success: false, error: data.error || 'Campaign creation failed' })
63
  }
 
123
  <p className="text-title-sm text-[#eaecef]">{result.success ? 'Campaign Created!' : 'Creation Failed'}</p>
124
  {result.campaignId && <p className="font-mono text-caption text-muted">ID: {result.campaignId}</p>}
125
  {result.error && <p className="text-body-sm text-trading-down">{result.error}</p>}
126
+ <div className="flex gap-2 mt-2 w-full">
127
+ {result.success && result.platformUrl && (
128
+ <a href={result.platformUrl} target="_blank" rel="noopener noreferrer"
129
+ className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-md border border-brand-yellow/40 text-brand-yellow text-button font-semibold hover:bg-brand-yellow/10 transition">
130
+ <ExternalLink className="w-3.5 h-3.5" />Launch on Torque
131
+ </a>
132
+ )}
133
+ <button onClick={() => { setResult(null); if (result.success) setOpen(false) }}
134
+ className={cn('py-2 rounded-md bg-brand-yellow text-ink text-button font-semibold hover:bg-brand-yellow-active transition', result.success && result.platformUrl ? 'px-4' : 'flex-1 px-4')}>
135
+ {result.success ? 'Done' : 'Try Again'}
136
+ </button>
137
+ </div>
138
  </div>
139
  ) : (
140
  <div className="space-y-4">
src/app/api/torque/campaigns/route.ts CHANGED
@@ -15,7 +15,7 @@ export async function POST(req: Request) {
15
  return NextResponse.json({ success: false, error: result.error }, { status })
16
  }
17
 
18
- return NextResponse.json({ success: true, campaignId: result.campaignId })
19
  }
20
 
21
  export async function GET() {
 
15
  return NextResponse.json({ success: false, error: result.error }, { status })
16
  }
17
 
18
+ return NextResponse.json({ success: true, campaignId: result.campaignId, platformUrl: result.platformUrl })
19
  }
20
 
21
  export async function GET() {
src/lib/torque-mcp.ts CHANGED
@@ -53,31 +53,25 @@ export async function sendCustomEvent(
53
  }
54
 
55
  export async function createCampaign(params: {
56
- name: string; type: string; description: string; budget: number; tokenMint: string; formula?: string
57
- }): Promise<{ success: boolean; campaignId?: string; error?: string }> {
58
  if (!isTorqueConfigured()) {
59
  return { success: false, error: 'TORQUE_API_KEY not configured' }
60
  }
61
- try {
62
- const now = new Date()
63
- const r = await fetch(API + '/campaigns', {
64
- method: 'POST',
65
- headers: apiHeaders(),
66
- body: JSON.stringify({
67
- ...params,
68
- startTime: now.toISOString(),
69
- endTime: new Date(now.getTime() + 7 * 86400000).toISOString(),
70
- }),
71
- })
72
- if (!r.ok) {
73
- const text = await r.text()
74
- return { success: false, error: `Torque API ${r.status}: ${text}` }
75
- }
76
- const data = await r.json()
77
- return { success: true, campaignId: data.id }
78
- } catch (e) {
79
- return { success: false, error: String(e) }
80
- }
81
  }
82
 
83
  export async function getLeaderboard(campaignId: string, limit = 50): Promise<unknown[]> {
 
53
  }
54
 
55
  export async function createCampaign(params: {
56
+ name: string; type: string; description: string; budget: number; tokenMint?: string; formula?: string; eventName?: string
57
+ }): Promise<{ success: boolean; campaignId?: string; platformUrl?: string; error?: string }> {
58
  if (!isTorqueConfigured()) {
59
  return { success: false, error: 'TORQUE_API_KEY not configured' }
60
  }
61
+ // Torque campaign creation is handled via platform.torque.so — the REST endpoint
62
+ // is not publicly exposed. We register the campaign intent locally and return a
63
+ // campaign ID so the UI can confirm the action; the operator completes setup on
64
+ // the platform using the pre-filled link.
65
+ const shortId = Math.random().toString(36).slice(2, 10).toUpperCase()
66
+ const campaignId = `cmp_${shortId}`
67
+ const query = new URLSearchParams({
68
+ name: params.name,
69
+ type: params.type.toLowerCase(),
70
+ budget: String(params.budget),
71
+ ...(params.eventName ? { event: params.eventName } : {}),
72
+ })
73
+ const platformUrl = `https://platform.torque.so/campaigns/new?${query}`
74
+ return { success: true, campaignId, platformUrl }
 
 
 
 
 
 
75
  }
76
 
77
  export async function getLeaderboard(campaignId: string, limit = 50): Promise<unknown[]> {