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
16 changes: 10 additions & 6 deletions components/crowdfunding/CampaignStatusBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import { extractApiErrorMessage } from '@/lib/api/api';
import {
useWithdrawSubmission,
usePublishCampaign,
Expand Down Expand Up @@ -193,11 +194,9 @@ export function CampaignStatusBanner({ campaign, onStatusChange }: Props) {
setLaunchOpen(false);
onStatusChange?.();
},
onError: err =>
onError: (err: unknown) =>
toast.error(
err instanceof Error
? err.message
: 'Failed to launch. Please try again.'
extractApiErrorMessage(err, 'Failed to launch. Please try again.')
),
});
};
Expand Down Expand Up @@ -286,8 +285,13 @@ export function CampaignStatusBanner({ campaign, onStatusChange }: Props) {
toast.success('Submission withdrawn.');
onStatusChange?.();
},
onError: () =>
toast.error('Something went wrong. Please try again.'),
onError: (err: unknown) =>
toast.error(
extractApiErrorMessage(
err,
'Something went wrong. Please try again.'
)
),
})
}
className='text-zinc-400 hover:text-white'
Expand Down
5 changes: 2 additions & 3 deletions components/crowdfunding/ContributeSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Switch } from '@/components/ui/switch';
import { Loader2, Wallet, Zap, CheckCircle2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import { extractApiErrorMessage } from '@/lib/api/api';
import { useQueryClient } from '@tanstack/react-query';
import {
contributeV2,
Expand Down Expand Up @@ -118,9 +119,7 @@ export function ContributeSheet({
setDone(true);
} catch (err) {
toast.error(
err instanceof Error
? err.message
: 'Contribution failed. Please try again.'
extractApiErrorMessage(err, 'Contribution failed. Please try again.')
);
} finally {
setSubmitting(false);
Expand Down
10 changes: 6 additions & 4 deletions components/crowdfunding/VotePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { ThumbsUp, ThumbsDown } from 'lucide-react';
import { toast } from 'sonner';
import { extractApiErrorMessage } from '@/lib/api/api';

import { useCastVote, useMyVote } from '@/features/crowdfunding';
import type { Crowdfunding } from '@/features/projects/types';
Expand Down Expand Up @@ -31,11 +32,12 @@ export function VotePanel({ campaign }: { campaign: Crowdfunding }) {
toast.success(
choice === 'UP' ? 'Thanks, your vote is in.' : 'Your vote is in.'
),
onError: err =>
onError: (err: unknown) =>
toast.error(
err instanceof Error
? err.message
: 'Could not record your vote. Make sure you are signed in.'
extractApiErrorMessage(
err,
'Could not record your vote. Make sure you are signed in.'
)
),
}
);
Expand Down
13 changes: 12 additions & 1 deletion components/crowdfunding/new/NewCampaignSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ interface NewCampaignSidebarProps {
activeStep: number;
completedSteps: Set<number>;
campaignId?: string;
onStepClick?: (step: number) => void;
}

interface SidebarContentProps {
activeStep: number;
completedSteps: Set<number>;
onStepClick?: (step: number) => void;
}

function SidebarContent({ activeStep, completedSteps }: SidebarContentProps) {
function SidebarContent({
activeStep,
completedSteps,
onStepClick,
}: SidebarContentProps) {
return (
<nav className='flex h-full flex-col overflow-y-auto px-4 py-6'>
<div className='mb-8'>
Expand All @@ -45,6 +51,9 @@ function SidebarContent({ activeStep, completedSteps }: SidebarContentProps) {
return (
<div
key={step.key}
onClick={() => {
if (!isLocked && !isActive) onStepClick?.(stepNumber);
}}
className={cn(
'group relative flex items-center gap-3 rounded-xl px-3 py-3 text-sm font-medium transition-all',
isActive
Expand Down Expand Up @@ -136,6 +145,7 @@ function SidebarContent({ activeStep, completedSteps }: SidebarContentProps) {
export default function NewCampaignSidebar({
activeStep,
completedSteps,
onStepClick,
}: NewCampaignSidebarProps) {
const { height } = useWindowSize();
const headerHeight = 64;
Expand All @@ -157,6 +167,7 @@ export default function NewCampaignSidebar({
<SidebarContent
activeStep={activeStep}
completedSteps={completedSteps}
onStepClick={onStepClick}
/>
</SheetContent>
</Sheet>
Expand Down
50 changes: 36 additions & 14 deletions components/crowdfunding/new/NewCampaignWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
updateCrowdfundingProject,
submitCampaignForReview,
} from '@/features/projects/api';
import { extractApiErrorMessage } from '@/lib/api/api';

export interface WizardMilestone {
title: string;
Expand Down Expand Up @@ -83,14 +84,14 @@ function validateStep(step: number, d: CampaignWizardData): string | null {
case 1:
if (!d.title.trim() || d.title.length < 3)
return 'Campaign title is required (min 3 characters)';
if (!d.tagline.trim() || d.tagline.length < 10)
return 'Tagline is required (min 10 characters)';
if (!d.category) return 'Please select a category';
if (!d.logoUrl) return 'Please upload a campaign logo before continuing';
return null;
case 2:
if (!d.tagline.trim() || d.tagline.length < 10)
return 'Vision is required (min 10 characters)';
if (!d.vision.trim() || d.vision.length < 50)
return 'Project story must be at least 50 characters';
return 'Project description must be at least 50 characters';
return null;
case 3:
if (!d.fundingGoal || d.fundingGoal < 1)
Expand Down Expand Up @@ -144,13 +145,18 @@ function buildDraftPayload(data: CampaignWizardData): Record<string, unknown> {
if (data.fundingGoal >= 1) payload.fundingAmount = data.fundingGoal;
if (data.githubUrl) payload.githubUrl = data.githubUrl;
if (data.gitlabUrl) payload.gitlabUrl = data.gitlabUrl;
if (data.websiteUrl) {
payload.websiteUrl = data.websiteUrl;
payload.projectWebsite = data.websiteUrl;
}
if (data.websiteUrl) payload.projectWebsite = data.websiteUrl;
if (data.demoVideoUrl) payload.demoVideo = data.demoVideoUrl;

const socialLinks = data.socialLinks.filter(s => s.platform && s.url);
// Deduplicate by platform so @ArrayUnique never fires.
const seen = new Set<string>();
const socialLinks = data.socialLinks
.filter(s => s.platform && s.url)
.filter(s => {
if (seen.has(s.platform)) return false;
seen.add(s.platform);
return true;
});
if (socialLinks.length) payload.socialLinks = socialLinks;

const team = data.teamMembers
Expand Down Expand Up @@ -305,8 +311,13 @@ export default function NewCampaignWizard() {
setCompletedSteps(done);
setActiveStep(landing);
})
.catch(() =>
toast.error('Could not load your draft. Starting a fresh form.')
.catch((err: unknown) =>
toast.error(
extractApiErrorMessage(
err,
'Could not load your draft. Starting a fresh form.'
)
)
)
.finally(() => setIsHydrating(false));
}, []);
Expand Down Expand Up @@ -359,8 +370,13 @@ export default function NewCampaignWizard() {
await persistDraft(data);
markStepDone(activeStep);
setActiveStep(s => s + 1);
} catch {
toast.error('Could not save your progress. Please try again.');
} catch (err) {
toast.error(
extractApiErrorMessage(
err,
'Could not save your progress. Please try again.'
)
);
} finally {
setIsSaving(false);
}
Expand All @@ -384,8 +400,13 @@ export default function NewCampaignWizard() {
toast.success('Campaign submitted for review!');
}
router.push(`/me/crowdfunding/${id}`);
} catch {
toast.error('Failed to submit for review. Please try again.');
} catch (err) {
toast.error(
extractApiErrorMessage(
err,
'Failed to submit for review. Please try again.'
)
);
} finally {
setIsSubmitting(false);
}
Expand Down Expand Up @@ -440,6 +461,7 @@ export default function NewCampaignWizard() {
activeStep={activeStep}
completedSteps={completedSteps}
campaignId={campaignId}
onStepClick={setActiveStep}
/>

<WizardHelpButton activeStep={activeStep} />
Expand Down
Loading