Skip to content

Commit b4550d3

Browse files
committed
refactor: standardize campaign status types and rename milestone name to title across components
1 parent 80e097a commit b4550d3

16 files changed

Lines changed: 182 additions & 138 deletions

File tree

app/me/crowdfunding/[slug]/edit/components/MilestonesSection.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const SortableMilestoneItem = ({
6262
};
6363

6464
const hasContent =
65-
milestone.name ||
65+
milestone.title ||
6666
milestone.description ||
6767
milestone.startDate ||
6868
milestone.endDate;
@@ -118,7 +118,7 @@ const SortableMilestoneItem = ({
118118
className='hover:text-primary flex items-center gap-2 text-white transition-colors'
119119
>
120120
<span className='text-sm font-medium'>
121-
{milestone.name || `Milestone ${index + 1}`}
121+
{milestone.title || `Milestone ${index + 1}`}
122122
</span>
123123
<svg
124124
width='16'
@@ -154,11 +154,11 @@ const SortableMilestoneItem = ({
154154
<div className='space-y-2'>
155155
<Input
156156
placeholder='Enter milestone name/title'
157-
value={milestone.name}
157+
value={milestone.title}
158158
onChange={e =>
159159
onMilestoneChange(
160160
milestone.id || '',
161-
'name',
161+
'title',
162162
e.target.value
163163
)
164164
}
@@ -257,14 +257,13 @@ export function MilestonesSection({
257257
const addMilestone = () => {
258258
const newMilestone: Milestone = {
259259
id: `milestone-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
260-
name: '',
260+
title: '',
261261
description: '',
262262
amount: 0,
263263
fundingPercentage: 0,
264264
reviewStatus: 'pending',
265265
startDate: '',
266266
endDate: '',
267-
title: '',
268267
};
269268
onChange([...milestones, newMilestone]);
270269
};

components/crowdfunding/campaign-milestones-tab.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
55
import { Badge } from '@/components/ui/badge';
66
import { Progress } from '@/components/ui/progress';
77
import { Target, Calendar } from 'lucide-react';
8-
9-
interface Milestone {
10-
name: string;
11-
description: string;
12-
amount: number;
13-
reviewStatus: string;
14-
endDate: string;
15-
progress?: number;
16-
}
8+
import { Milestone } from '@/features/projects/types';
179

1810
interface CampaignMilestonesTabProps {
1911
milestones: Milestone[];
@@ -91,7 +83,7 @@ export function CampaignMilestonesTab({
9183
<div className='mb-4 flex items-start justify-between'>
9284
<div className='flex-1'>
9385
<h4 className='mb-2 text-lg font-semibold text-white'>
94-
{milestone.name}
86+
{milestone.title}
9587
</h4>
9688
<p className='mb-3 text-white/80'>
9789
{milestone.description}

components/crowdfunding/milestone-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export function MilestoneCard({
8888
{/* Milestone Info */}
8989
<div className='min-w-0 flex-1'>
9090
<h3 className='text-foreground truncate text-lg font-semibold transition-colors group-hover:text-amber-500'>
91-
{milestone.name}
91+
{milestone.title}
9292
</h3>
9393
<p className='text-muted-foreground mt-1 line-clamp-2 text-sm'>
9494
{milestone.description}

components/crowdfunding/milestone-detail-header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function MilestoneDetailHeader({
5050
</Button>
5151
<div className='flex-1'>
5252
<h1 className='text-3xl font-bold tracking-tight'>
53-
{milestone.name}
53+
{milestone.title}
5454
</h1>
5555
</div>
5656
</div>

components/project-details/project-milestone/index.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import { Milestone } from '@/features/projects/types';
1515
import type { ProjectViewModel } from '@/features/projects/types/view-model';
1616
import Link from 'next/link';
1717
import { getCrowdfundingMilestones } from '@/features/projects/api';
18+
import {
19+
CampaignStatus,
20+
normalizeCampaignStatus,
21+
} from '@/features/projects/types';
1822
import { useOptionalAuth } from '@/hooks/use-auth';
1923
import { MilestoneSubmissionModal } from './MilestoneSubmissionModal';
2024
import { MilestoneDisputeModal } from './MilestoneDisputeModal';
@@ -46,12 +50,11 @@ const ProjectMilestone = ({ vm }: ProjectMilestoneProps) => {
4650
const isBacker = !!(
4751
user && vm.campaign?.contributors?.some(c => c.userId === user.id)
4852
);
49-
// Milestones can only be submitted/disputed when the campaign is funded or executing
50-
const campaignStatus = (vm.status || '').toUpperCase();
53+
const campaignStatus = normalizeCampaignStatus(vm.status);
5154
const isFundedOrExecuting =
52-
campaignStatus === 'LIVE' ||
53-
campaignStatus === 'FUNDED' ||
54-
campaignStatus === 'EXECUTING' ||
55+
campaignStatus === CampaignStatus.LIVE ||
56+
campaignStatus === CampaignStatus.FUNDED ||
57+
campaignStatus === CampaignStatus.EXECUTING ||
5558
vm.campaign?.trustlessWorkStatus === 'funded';
5659
const inlineMilestones =
5760
vm.campaign?.milestones ?? vm.submission?.milestones ?? [];

components/project-details/project-sidebar/ProjectSidebarActions.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
Loader2,
1313
} from 'lucide-react';
1414
import { ProjectSidebarActionsProps } from './types';
15+
import { CampaignStatus } from './utils';
16+
import type { CampaignStatusValue } from '@/features/projects/types';
1517
import { BoundlessButton } from '@/components/buttons';
1618
import { SharePopup } from './SharePopup';
1719
import { FollowButton } from '@/components/follow';
@@ -46,12 +48,15 @@ export function ProjectSidebarActions({
4648
useCrowdfundContract();
4749

4850
const isCreator = !!(user && user.id === vm.creatorId);
51+
const cancellableStatuses: CampaignStatusValue[] = [
52+
CampaignStatus.CAMPAIGNING,
53+
CampaignStatus.IDEA,
54+
CampaignStatus.REVIEWING,
55+
];
4956
const canCancel =
5057
isCreator &&
5158
vm.campaign?.onChainId &&
52-
['CAMPAIGNING', 'Validation', 'idea', 'pending', 'SUBMITTED'].includes(
53-
projectStatus
54-
);
59+
cancellableStatuses.includes(projectStatus);
5560

5661
const handleShareClick = () => {
5762
setIsSharePopupOpen(true);
@@ -66,10 +71,7 @@ export function ProjectSidebarActions({
6671

6772
setIsCancelling(true);
6873
try {
69-
// Sign cancel_campaign transaction on-chain
7074
const transactionHash = await cancelCampaign(vm.campaign.onChainId);
71-
72-
// Record in backend
7375
await deleteCrowdfundingProject(vm.campaign.campaignId);
7476

7577
toast.success('Campaign cancelled', {
@@ -87,7 +89,7 @@ export function ProjectSidebarActions({
8789

8890
return (
8991
<div className='flex flex-wrap gap-2 sm:gap-3'>
90-
{projectStatus === 'VOTING' && (
92+
{projectStatus === CampaignStatus.VOTING && (
9193
<div className='group relative inline-block'>
9294
<BoundlessButton
9395
onClick={() => onVote(1)}
@@ -120,8 +122,7 @@ export function ProjectSidebarActions({
120122
</div>
121123
)}
122124

123-
{/* Back Project — only for campaigns in funding phase */}
124-
{vm.campaign && projectStatus === 'CAMPAIGNING' && (
125+
{vm.campaign && projectStatus === CampaignStatus.CAMPAIGNING && (
125126
<FundingModal
126127
campaignId={vm.campaign.campaignId}
127128
onChainId={vm.campaign.onChainId}
@@ -140,7 +141,7 @@ export function ProjectSidebarActions({
140141
</FundingModal>
141142
)}
142143

143-
{projectStatus === 'Completed' && (
144+
{projectStatus === CampaignStatus.COMPLETED && (
144145
<BoundlessButton
145146
disabled
146147
className='bg-success-75 border-success-600 text-success-600 flex h-10 flex-1 items-center justify-center gap-2 rounded-lg border text-sm font-semibold shadow-lg transition-all duration-200 sm:h-12 sm:text-base'
@@ -151,7 +152,7 @@ export function ProjectSidebarActions({
151152
</BoundlessButton>
152153
)}
153154

154-
{projectStatus === 'Funded' && (
155+
{projectStatus === CampaignStatus.FUNDED && (
155156
<BoundlessButton
156157
disabled
157158
className='bg-secondary-75 border-secondary-600 text-secondary-600 flex h-10 flex-1 items-center justify-center gap-2 rounded-lg border text-sm font-semibold shadow-lg transition-all duration-200 sm:h-12 sm:text-base'
@@ -162,7 +163,6 @@ export function ProjectSidebarActions({
162163
</BoundlessButton>
163164
)}
164165

165-
{/* Cancel Campaign — only for campaign creator in cancellable states */}
166166
{canCancel && (
167167
<AlertDialog>
168168
<AlertDialogTrigger asChild>

components/project-details/project-sidebar/ProjectSidebarHeader.tsx

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Calendar, Rocket, Trophy, Layers } from 'lucide-react';
44
import Image from 'next/image';
55
import { ProjectSidebarHeaderProps } from './types';
6+
import { CampaignStatus } from './utils';
67
import type { ProjectOrigin } from '@/features/projects/types/view-model';
78

89
const ORIGIN_CONFIG: Record<
@@ -26,32 +27,43 @@ const ORIGIN_CONFIG: Record<
2627
},
2728
};
2829

30+
const STATUS_LABELS: Record<string, string> = {
31+
[CampaignStatus.IDEA]: 'Validation',
32+
[CampaignStatus.REVIEWING]: 'Reviewing',
33+
[CampaignStatus.VOTING]: 'Voting',
34+
[CampaignStatus.CAMPAIGNING]: 'Funding',
35+
[CampaignStatus.FUNDED]: 'Funded',
36+
[CampaignStatus.EXECUTING]: 'Executing',
37+
[CampaignStatus.LIVE]: 'Live',
38+
[CampaignStatus.COMPLETED]: 'Completed',
39+
[CampaignStatus.FAILED]: 'Failed',
40+
[CampaignStatus.CANCELLED]: 'Cancelled',
41+
[CampaignStatus.DRAFT]: 'Draft',
42+
};
43+
44+
const STATUS_STYLES: Record<string, string> = {
45+
[CampaignStatus.CAMPAIGNING]:
46+
'bg-secondary-75 border-secondary-600 text-secondary-600',
47+
[CampaignStatus.FUNDED]: 'bg-active-bg border-primary text-primary',
48+
[CampaignStatus.COMPLETED]:
49+
'bg-success-75 border-success-600 text-success-600',
50+
[CampaignStatus.IDEA]: 'bg-warning-75 border-warning-600 text-warning-600',
51+
[CampaignStatus.VOTING]: 'bg-warning-75 border-warning-600 text-warning-600',
52+
[CampaignStatus.LIVE]: 'bg-active-bg border-primary text-primary',
53+
[CampaignStatus.REVIEWING]: 'bg-gray-800 border-gray-700 text-white',
54+
[CampaignStatus.DRAFT]: 'bg-gray-800 border-gray-700 text-white',
55+
[CampaignStatus.EXECUTING]: 'bg-active-bg border-primary text-primary',
56+
[CampaignStatus.FAILED]: 'bg-red-500/10 border-red-500/30 text-red-400',
57+
[CampaignStatus.CANCELLED]: 'bg-gray-800 border-gray-700 text-white/50',
58+
};
59+
2960
export function ProjectSidebarHeader({
3061
vm,
3162
projectStatus,
3263
}: ProjectSidebarHeaderProps) {
33-
const getStatusStyles = () => {
34-
switch (projectStatus) {
35-
case 'CAMPAIGNING':
36-
return 'bg-secondary-75 border-secondary-600 text-secondary-600';
37-
case 'Funded':
38-
return 'bg-active-bg border-primary text-primary';
39-
case 'Completed':
40-
return 'bg-success-75 border-success-600 text-success-600';
41-
case 'Validation':
42-
return 'bg-warning-75 border-warning-600 text-warning-600';
43-
case 'idea':
44-
return 'bg-warning-75 border-warning-600 text-warning-600';
45-
case 'Live':
46-
return 'bg-active-bg border-primary text-primary';
47-
case 'pending':
48-
case 'SUBMITTED':
49-
return 'bg-gray-800 border-gray-700 text-white';
50-
default:
51-
return 'text-white border-gray-700';
52-
}
53-
};
54-
64+
const statusStyle =
65+
STATUS_STYLES[projectStatus] ?? 'text-white border-gray-700';
66+
const statusLabel = STATUS_LABELS[projectStatus] ?? projectStatus;
5567
const originCfg = ORIGIN_CONFIG[vm.origin];
5668

5769
return (
@@ -74,22 +86,19 @@ export function ProjectSidebarHeader({
7486
</h1>
7587

7688
<div className='flex flex-wrap items-center gap-1.5 sm:gap-2'>
77-
{/* Origin badge */}
7889
<div
7990
className={`flex items-center gap-1 rounded-lg border px-2 py-0.5 text-[10px] font-semibold sm:px-2.5 sm:py-1 sm:text-xs ${originCfg.className}`}
8091
>
8192
{originCfg.icon}
8293
{originCfg.label}
8394
</div>
84-
{/* Category badge */}
8595
<div className='rounded-lg border border-orange-500/30 bg-orange-500/10 px-2 py-0.5 text-[10px] font-semibold text-orange-400 sm:px-2.5 sm:py-1 sm:text-xs'>
8696
{vm.category}
8797
</div>
88-
{/* Status badge */}
8998
<div
90-
className={`rounded-lg border px-2 py-0.5 text-[10px] font-semibold sm:px-2.5 sm:py-1 sm:text-xs ${getStatusStyles()}`}
99+
className={`rounded-lg border px-2 py-0.5 text-[10px] font-semibold sm:px-2.5 sm:py-1 sm:text-xs ${statusStyle}`}
91100
>
92-
{projectStatus}
101+
{statusLabel}
93102
</div>
94103
</div>
95104
</div>

components/project-details/project-sidebar/ProjectSidebarProgress.tsx

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useEffect, useState } from 'react';
44
import { ProjectSidebarProgressProps } from './types';
5+
import { CampaignStatus } from './utils';
56
import { Progress } from '@/components/ui/progress';
67
import { useCrowdfundContract } from '@/hooks/use-crowdfund-contract';
78
import { Shield } from 'lucide-react';
@@ -19,7 +20,6 @@ export function ProjectSidebarProgress({
1920
const fundingPercentage =
2021
fundingGoal > 0 ? (fundingRaised / fundingGoal) * 100 : 0;
2122

22-
// On-chain verification data
2323
const { getCampaignOnChain } = useCrowdfundContract();
2424
const [onChainData, setOnChainData] = useState<{
2525
currentFunding: number;
@@ -33,8 +33,6 @@ export function ProjectSidebarProgress({
3333
getCampaignOnChain(campaign.onChainId)
3434
.then(result => {
3535
if (cancelled || !result) return;
36-
// Result may be a Soroban Result<Campaign> or direct Campaign object
37-
// Use unknown intermediate to safely extract fields
3836
const raw = result as unknown as Record<string, unknown>;
3937
let campaignData: Record<string, unknown> | null = null;
4038

@@ -51,21 +49,15 @@ export function ProjectSidebarProgress({
5149
});
5250
}
5351
})
54-
.catch(() => {
55-
// Silently fail — on-chain data is supplementary
56-
});
52+
.catch(() => {});
5753

5854
return () => {
5955
cancelled = true;
6056
};
6157
}, [campaign?.onChainId, getCampaignOnChain]);
6258

6359
const renderProgressSection = () => {
64-
// Funding progress — only for campaigns in funding phase
65-
if (
66-
campaign &&
67-
(projectStatus === 'Funding' || projectStatus === 'CAMPAIGNING')
68-
) {
60+
if (campaign && projectStatus === CampaignStatus.CAMPAIGNING) {
6961
return (
7062
<div className='space-y-3'>
7163
<div className='flex items-center justify-between text-sm'>
@@ -89,8 +81,10 @@ export function ProjectSidebarProgress({
8981
);
9082
}
9183

92-
// Validation vote progress — all project types
93-
if (projectStatus === 'Validation' || projectStatus === 'VOTING') {
84+
if (
85+
projectStatus === CampaignStatus.IDEA ||
86+
projectStatus === CampaignStatus.VOTING
87+
) {
9488
const validationProgress = Math.min(
9589
((voteCounts?.upvotes || 0) / voteGoal) * 100,
9690
100
@@ -111,10 +105,11 @@ export function ProjectSidebarProgress({
111105
);
112106
}
113107

114-
// Milestone progress — only for funded/completed campaigns
115108
if (
116109
campaign &&
117-
(projectStatus === 'Completed' || projectStatus === 'Funded')
110+
(projectStatus === CampaignStatus.COMPLETED ||
111+
projectStatus === CampaignStatus.FUNDED ||
112+
projectStatus === CampaignStatus.EXECUTING)
118113
) {
119114
const completedMilestones =
120115
campaign.milestones?.filter(m => m.reviewStatus === 'completed')
@@ -154,7 +149,6 @@ export function ProjectSidebarProgress({
154149
);
155150
}
156151

157-
// Default: vote progress
158152
const defaultProgress = Math.min(
159153
((voteCounts?.totalVotes || 0) / voteGoal) * 100,
160154
100

0 commit comments

Comments
 (0)