Skip to content

Commit bb2d21c

Browse files
committed
wishlist form
1 parent a2e95f5 commit bb2d21c

File tree

13 files changed

+1506
-4
lines changed

13 files changed

+1506
-4
lines changed

src/components/forms/WishlistForm.tsx

Lines changed: 456 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { Box, Button, Grid, GridItem, Heading, Stack, Text, useToast } from '@chakra-ui/react';
2+
import { FC, useEffect, useState } from 'react';
3+
4+
import { PageText } from '../UI';
5+
import { WishlistItem } from './schemas/Wishlist';
6+
7+
interface WishlistSelectionProps {
8+
onSelectWishlist: (wishlistItem: WishlistItem) => void;
9+
}
10+
11+
export const WishlistSelection: FC<WishlistSelectionProps> = ({ onSelectWishlist }) => {
12+
const [wishlistItems, setWishlistItems] = useState<WishlistItem[]>([]);
13+
const [isLoading, setIsLoading] = useState(true);
14+
const [selectedItem, setSelectedItem] = useState<WishlistItem | null>(null);
15+
const toast = useToast();
16+
17+
useEffect(() => {
18+
const fetchWishlistItems = async () => {
19+
try {
20+
const response = await fetch('/api/wishlist-items');
21+
if (!response.ok) {
22+
throw new Error('Failed to fetch wishlist items');
23+
}
24+
const items = await response.json();
25+
setWishlistItems(items);
26+
} catch (error) {
27+
console.error('Error fetching wishlist items:', error);
28+
toast({
29+
title: 'Error loading wishlist items',
30+
description: 'Please refresh the page and try again.',
31+
status: 'error',
32+
duration: 5000,
33+
isClosable: true
34+
});
35+
} finally {
36+
setIsLoading(false);
37+
}
38+
};
39+
40+
fetchWishlistItems();
41+
}, [toast]);
42+
43+
const handleSelectItem = (item: WishlistItem) => {
44+
setSelectedItem(item);
45+
};
46+
47+
const handleContinue = () => {
48+
if (selectedItem) {
49+
onSelectWishlist(selectedItem);
50+
}
51+
};
52+
53+
if (isLoading) {
54+
return (
55+
<Stack spacing={8} align="center" py={16}>
56+
<Heading size="lg" color="brand.heading">
57+
Loading Wishlist Items...
58+
</Heading>
59+
<Text>Please wait while we fetch the available opportunities.</Text>
60+
</Stack>
61+
);
62+
}
63+
64+
if (wishlistItems.length === 0) {
65+
return (
66+
<Stack spacing={8} align="center" py={16}>
67+
<Heading size="lg" color="brand.heading">
68+
No Wishlist Items Available
69+
</Heading>
70+
<Text>There are currently no active wishlist items available for application.</Text>
71+
</Stack>
72+
);
73+
}
74+
75+
return (
76+
<Stack spacing={8}>
77+
<Box textAlign="center">
78+
<Heading size="lg" mb={4} color="brand.heading">
79+
Select a Wishlist Item
80+
</Heading>
81+
<PageText mb={6}>
82+
Choose from the wishlist items below that best matches your project or interests.
83+
Each item represents a specific need or opportunity that the Ethereum ecosystem is looking to address.
84+
</PageText>
85+
</Box>
86+
87+
<Grid templateColumns={{ base: '1fr', md: 'repeat(2, 1fr)', lg: 'repeat(3, 1fr)' }} gap={6}>
88+
{wishlistItems.map((item) => (
89+
<GridItem key={item.Id}>
90+
<Box
91+
p={6}
92+
border="2px solid"
93+
borderColor={selectedItem?.Id === item.Id ? 'brand.orange.100' : 'brand.border'}
94+
borderRadius="lg"
95+
cursor="pointer"
96+
transition="all 0.2s"
97+
bg={selectedItem?.Id === item.Id ? 'brand.orange.10' : 'white'}
98+
_hover={{
99+
borderColor: 'brand.orange.100',
100+
transform: 'translateY(-2px)',
101+
shadow: 'md'
102+
}}
103+
onClick={() => handleSelectItem(item)}
104+
h="full"
105+
>
106+
<Stack spacing={3} h="full">
107+
<Heading size="md" color="brand.heading" noOfLines={2}>
108+
{item.Name}
109+
</Heading>
110+
111+
{item.Category__c && (
112+
<Text fontSize="sm" color="brand.orange.100" fontWeight="600">
113+
{item.Category__c}
114+
</Text>
115+
)}
116+
117+
<Text fontSize="sm" color="brand.paragraph" flex="1" noOfLines={4}>
118+
{item.Description__c}
119+
</Text>
120+
121+
{item.Skills_Required__c && (
122+
<Box>
123+
<Text fontSize="xs" color="brand.helpText" fontWeight="600" mb={1}>
124+
Skills Required:
125+
</Text>
126+
<Text fontSize="xs" color="brand.helpText" noOfLines={2}>
127+
{item.Skills_Required__c}
128+
</Text>
129+
</Box>
130+
)}
131+
132+
{item.Estimated_Effort__c && (
133+
<Text fontSize="xs" color="brand.helpText">
134+
<Text as="span" fontWeight="600">Estimated Effort:</Text> {item.Estimated_Effort__c}
135+
</Text>
136+
)}
137+
</Stack>
138+
</Box>
139+
</GridItem>
140+
))}
141+
</Grid>
142+
143+
{selectedItem && (
144+
<Box
145+
mt={8}
146+
p={6}
147+
bg="brand.newsletter.bgGradient.start"
148+
borderRadius="lg"
149+
borderLeft="4px solid"
150+
borderLeftColor="brand.orange.100"
151+
>
152+
<Stack spacing={3}>
153+
<Heading size="md" color="brand.heading">
154+
Selected: {selectedItem.Name}
155+
</Heading>
156+
<Text color="brand.paragraph">
157+
{selectedItem.Description__c}
158+
</Text>
159+
{selectedItem.Expected_Deliverables__c && (
160+
<Box>
161+
<Text fontWeight="600" color="brand.heading" mb={1}>
162+
Expected Deliverables:
163+
</Text>
164+
<Text color="brand.paragraph">
165+
{selectedItem.Expected_Deliverables__c}
166+
</Text>
167+
</Box>
168+
)}
169+
</Stack>
170+
</Box>
171+
)}
172+
173+
<Box textAlign="center" mt={8}>
174+
<Button
175+
isDisabled={!selectedItem}
176+
onClick={handleContinue}
177+
bg="brand.orange.100"
178+
color="white"
179+
px={8}
180+
py={6}
181+
fontSize="input"
182+
fontWeight={700}
183+
borderRadius={0}
184+
_hover={{ bg: 'brand.orange.hover' }}
185+
_disabled={{
186+
bg: 'gray.300',
187+
cursor: 'not-allowed'
188+
}}
189+
>
190+
Continue with Application
191+
</Button>
192+
</Box>
193+
</Stack>
194+
);
195+
};

src/components/forms/api.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import {
2525
API_EPF_APPLICATION,
2626
API_PSE_APPLICATION,
2727
API_ACADEMIC_GRANTS,
28-
API_TEN_YEAR_ANNIVERSARY
28+
API_TEN_YEAR_ANNIVERSARY,
29+
API_WISHLIST
2930
} from './constants';
3031

3132
import type { EPFData } from './schemas/EPFApplication';
@@ -34,6 +35,7 @@ import type { AcademicGrantsData } from './schemas/AcademicGrants';
3435
import type { PectraPGRData } from './schemas/PectraPGR';
3536
import type { DestinoDevconnectData } from './schemas/DestinoDevconnect';
3637
import type { TenYearAnniversaryData } from './schemas/TenYearAnniversary';
38+
import type { WishlistData } from './schemas/Wishlist';
3739

3840
const methodOptions = {
3941
method: 'POST',
@@ -270,5 +272,19 @@ export const api = {
270272
body: JSON.stringify(data)
271273
});
272274
}
275+
},
276+
wishlist: {
277+
submit: (data: WishlistData) => {
278+
const wishlistRequestOptions: RequestInit = {
279+
...methodOptions,
280+
body: JSON.stringify({
281+
...data,
282+
company: data.company || `${data.firstName} ${data.lastName}`,
283+
repeatApplicant: data.repeatApplicant
284+
})
285+
};
286+
287+
return fetch(API_WISHLIST, wishlistRequestOptions);
288+
}
273289
}
274290
};

src/components/forms/constants.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,7 @@ export const COMMITMENT_OPTIONS = [
16881688

16891689
// API routes
16901690
export const API_OFFICE_HOURS = '/api/office-hours';
1691+
export const API_WISHLIST = '/api/wishlist';
16911692
export const API_PROJECT_GRANTS = '/api/project-grants';
16921693
export const API_SMALL_GRANTS_PROJECT = '/api/small-grants/project';
16931694
export const API_SMALL_GRANTS_EVENT = '/api/small-grants/event';
@@ -1733,3 +1734,42 @@ export const REFERRAL_SOURCE_OPTIONS = [
17331734
{ value: 'Social Media', label: 'Social Media' },
17341735
{ value: 'Other', label: 'Other' }
17351736
];
1737+
1738+
export const PROFILE_TYPE_OPTIONS = [
1739+
{ value: 'Individual', label: 'Individual' },
1740+
{ value: 'Team', label: 'Team' },
1741+
{ value: 'Company', label: 'Company' },
1742+
{ value: 'Organization', label: 'Organization' },
1743+
{ value: 'University', label: 'University' },
1744+
{ value: 'Consortium of Universities', label: 'Consortium of Universities' },
1745+
{ value: 'Research Center', label: 'Research Center' },
1746+
{ value: 'Other', label: 'Other' }
1747+
];
1748+
1749+
export const DOMAIN_OPTIONS = [
1750+
{ value: 'Application layer', label: 'Application layer' },
1751+
{ value: 'Cryptography', label: 'Cryptography' },
1752+
{ value: 'DAOs/Governance', label: 'DAOs/Governance' },
1753+
{ value: 'Decentralized Identity', label: 'Decentralized Identity' },
1754+
{ value: 'DeFi', label: 'DeFi' },
1755+
{ value: 'Economics', label: 'Economics' },
1756+
{ value: 'Ethereum Protocol', label: 'Ethereum Protocol' },
1757+
{ value: 'Government', label: 'Government' },
1758+
{ value: 'Layer 2', label: 'Layer 2' },
1759+
{ value: 'NFTs / Digital Art', label: 'NFTs / Digital Art' },
1760+
{ value: 'Nodes and Clients', label: 'Nodes and Clients' },
1761+
{ value: 'Privacy', label: 'Privacy' },
1762+
{ value: 'Security', label: 'Security' },
1763+
{ value: 'Society and Regulatory', label: 'Society and Regulatory' },
1764+
{ value: 'UX/UI', label: 'UX/UI' },
1765+
{ value: 'Zero-knowledge Proofs', label: 'Zero-knowledge Proofs' },
1766+
{ value: 'Other', label: 'Other' }
1767+
];
1768+
1769+
export const OUTPUT_OPTIONS = [
1770+
{ value: 'Application', label: 'Application' },
1771+
{ value: 'Dashboard', label: 'Dashboard' },
1772+
{ value: 'Developer tooling', label: 'Developer tooling' },
1773+
{ value: 'Ecosystem development', label: 'Ecosystem development' },
1774+
{ value: 'Research', label: 'Research' }
1775+
];

src/components/forms/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export * from './Forms';
22
export * from './GranteeFinanceForm';
33
export * from './NewsletterSignup';
44
export * from './OfficeHoursForm';
5+
export * from './WishlistForm';
6+
export * from './WishlistSelection';
57
export * from './ProjectGrantsForm';
68
export * from './SmallGrantsForm';
79
export * from './DevconGrantsForm';

0 commit comments

Comments
 (0)