Skip to content

Commit 2d17518

Browse files
committed
style: fix formatting with prettier
1 parent d1fa5a0 commit 2d17518

File tree

12 files changed

+158
-85
lines changed

12 files changed

+158
-85
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Builddit provides a streamlined interface for browsing and interacting with buil
99
## Features
1010

1111
### Core Features
12+
1213
- Browse build requests from `/someone-build` channel
1314
- View text posts with author information
1415
- Sort by Newest and Top (Day, Week, Month, All)
@@ -37,17 +38,20 @@ Builddit provides a streamlined interface for browsing and interacting with buil
3738
## Development Setup
3839

3940
1. Clone the repository:
41+
4042
```bash
4143
git clone https://github.com/andrewjiang/builddit.git
4244
cd builddit
4345
```
4446

4547
2. Install dependencies:
48+
4649
```bash
4750
npm install
4851
```
4952

5053
3. Create a `.env` file with the following variables:
54+
5155
```
5256
# Neynar API Configuration
5357
NEYNAR_API_KEY=your_api_key
@@ -444,14 +448,17 @@ The project uses a combination of Farcaster Auth Kit and Next-Auth to provide a
444448
The application uses a hybrid data fetching approach to balance performance and data freshness:
445449

446450
### Frontend Polling
451+
447452
- The frontend polls every 30 seconds for new content
448453
- Each poll first attempts to fetch from MongoDB for performance
449454
- Falls back to Neynar API if MongoDB query fails or returns no results
450455
- New posts are merged at the top during polling
451456
- Additional posts are appended at the bottom during infinite scroll
452457

453458
### Database Updates
459+
454460
1. **Primary Path (MongoDB)**
461+
455462
- Most reads hit MongoDB first for better performance
456463
- Stores complete build request data including:
457464
- Cast metadata (text, timestamp, author)
@@ -466,19 +473,23 @@ The application uses a hybrid data fetching approach to balance performance and
466473
- Uses upsert operations to ensure data consistency
467474

468475
### Historical Sync
476+
469477
A separate script (`scripts/sync-historical-builds.cjs`) handles complete historical data synchronization:
478+
470479
- Processes builds in batches (100 per batch)
471480
- Includes full engagement metrics
472481
- Syncs all replies and recasts
473482
- Updates claim counts
474483
- Maintains user profiles
475484

476485
### Known Limitations
486+
477487
- Frontend polling might miss updates if MongoDB always returns results
478488
- Engagement metrics might be stale between polls
479489
- No real-time updates for likes/recasts
480490

481491
### Future Improvements
492+
482493
- [ ] Add timestamp checks to force Neynar refresh for stale data
483494
- [ ] Implement background job for periodic Neynar sync
484495
- [ ] Add Neynar webhook support for real-time updates
@@ -489,6 +500,7 @@ A separate script (`scripts/sync-historical-builds.cjs`) handles complete histor
489500
For reliable execution of background tasks and data synchronization, we use Digital Ocean App Platform Jobs:
490501

491502
1. **Historical Build Sync Job**
503+
492504
```yaml
493505
jobs:
494506
- name: sync-historical-builds
@@ -523,6 +535,7 @@ For reliable execution of background tasks and data synchronization, we use Digi
523535
```
524536

525537
#### Benefits of Digital Ocean Jobs:
538+
526539
- No execution time limits
527540
- Reliable scheduling
528541
- Detailed logging and monitoring
@@ -531,6 +544,7 @@ For reliable execution of background tasks and data synchronization, we use Digi
531544
- Cost-effective for background tasks
532545

533546
#### Setup Instructions:
547+
534548
1. Create a new App Platform project
535549
2. Add the jobs section to your `app.yaml`
536550
3. Configure environment variables

app/api/build-requests/[hash]/claims/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export async function POST(
8383
// Increment the claimsCount on the build request
8484
await BuildRequest.findOneAndUpdate(
8585
{ hash: params.hash },
86-
{ $inc: { claimsCount: 1 } }
86+
{ $inc: { claimsCount: 1 } },
8787
);
8888

8989
return NextResponse.json({ claim });

components/BountyModal.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ interface BountyModalProps {
99
onClose: () => void;
1010
}
1111

12-
export function BountyModal({ buildRequest, isOpen, onClose }: BountyModalProps) {
12+
export function BountyModal({
13+
buildRequest,
14+
isOpen,
15+
onClose,
16+
}: BountyModalProps) {
1317
const [amount, setAmount] = useState("");
1418
const [currency, setCurrency] = useState("USDC");
1519
const [deadline, setDeadline] = useState("");
@@ -18,17 +22,17 @@ export function BountyModal({ buildRequest, isOpen, onClose }: BountyModalProps)
1822
// Calculate default deadline (2 weeks from now)
1923
const twoWeeksFromNow = new Date();
2024
twoWeeksFromNow.setDate(twoWeeksFromNow.getDate() + 14);
21-
const defaultDeadline = twoWeeksFromNow.toISOString().split('T')[0];
25+
const defaultDeadline = twoWeeksFromNow.toISOString().split("T")[0];
2226

2327
const handleSubmit = (e: React.FormEvent) => {
2428
e.preventDefault();
25-
29+
2630
// Create the Warpcast intent URL
2731
const intentUrl = new URL("https://warpcast.com/~/compose");
28-
32+
2933
// Format the text for the bounty
30-
const bountyText = `I'd love for someone to build this!\n\nAmount: ${amount} ${currency}${deadline ? `\nDeadline: ${deadline}` : ''}${description ? `\n\n${description}` : ''}\n\n@bountybot`;
31-
34+
const bountyText = `I'd love for someone to build this!\n\nAmount: ${amount} ${currency}${deadline ? `\nDeadline: ${deadline}` : ""}${description ? `\n\n${description}` : ""}\n\n@bountybot`;
35+
3236
intentUrl.searchParams.set("text", bountyText);
3337
intentUrl.searchParams.set(
3438
"embeds[]",
@@ -143,7 +147,7 @@ export function BountyModal({ buildRequest, isOpen, onClose }: BountyModalProps)
143147
id="deadline"
144148
value={deadline}
145149
onChange={(e) => setDeadline(e.target.value)}
146-
min={new Date().toISOString().split('T')[0]}
150+
min={new Date().toISOString().split("T")[0]}
147151
placeholder={defaultDeadline}
148152
className="w-full px-4 py-2 bg-purple-800/50 border border-purple-700 rounded-lg
149153
text-purple-100 placeholder-purple-400 focus:outline-none focus:ring-2
@@ -202,4 +206,4 @@ export function BountyModal({ buildRequest, isOpen, onClose }: BountyModalProps)
202206
</Dialog>
203207
</Transition>
204208
);
205-
}
209+
}

components/BuildRequestCard.tsx

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,16 @@ function EmbeddedCastCard({
6868
)}
6969
{embed.url && (
7070
<div className="mt-2 overflow-hidden">
71-
{isImageUrl(embed.url) || embed.metadata?.html?.ogImage?.[0]?.url ? (
71+
{isImageUrl(embed.url) ||
72+
embed.metadata?.html?.ogImage?.[0]?.url ? (
7273
<div className="relative w-full max-w-[480px] rounded-lg overflow-hidden bg-purple-800/50">
7374
<div className="relative aspect-[16/9] max-h-[320px]">
7475
<SafeImage
75-
src={isImageUrl(embed.url) ? embed.url : embed.metadata?.html?.ogImage?.[0]?.url}
76+
src={
77+
isImageUrl(embed.url)
78+
? embed.url
79+
: embed.metadata?.html?.ogImage?.[0]?.url
80+
}
7681
alt={embed.metadata?.html?.ogTitle || "Embedded image"}
7782
className="absolute inset-0 w-full h-full object-cover rounded-lg"
7883
/>
@@ -124,22 +129,22 @@ function EmbeddedCastCard({
124129
function isImageUrl(url: string): boolean {
125130
// Check for common image extensions
126131
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(url)) return true;
127-
132+
128133
// Check for common image hosting domains
129-
if (url.includes('imagedelivery.net')) return true;
130-
if (url.includes('openseauserdata.com')) return true;
131-
if (url.includes('i.imgur.com')) return true;
132-
if (url.includes('cdn.discordapp.com')) return true;
133-
134+
if (url.includes("imagedelivery.net")) return true;
135+
if (url.includes("openseauserdata.com")) return true;
136+
if (url.includes("i.imgur.com")) return true;
137+
if (url.includes("cdn.discordapp.com")) return true;
138+
134139
// Google Docs image URLs
135-
if (url.includes('googleusercontent.com/docs')) return true;
136-
140+
if (url.includes("googleusercontent.com/docs")) return true;
141+
137142
// Firefly media URLs
138-
if (url.includes('media.firefly.land/farcaster')) return true;
139-
143+
if (url.includes("media.firefly.land/farcaster")) return true;
144+
140145
// Empire Builder OG image URLs
141-
if (url.includes('empirebuilder.world/api/og')) return true;
142-
146+
if (url.includes("empirebuilder.world/api/og")) return true;
147+
143148
return false;
144149
}
145150

@@ -303,12 +308,19 @@ export function BuildRequestCard({ buildRequest }: BuildRequestCardProps) {
303308
<EmbeddedCastCard cast={embed.cast} />
304309
) : embed.url ? (
305310
<div className="mt-2 overflow-hidden">
306-
{(embed.metadata?.html?.ogImage?.[0]?.url || isImageUrl(embed.url)) ? (
311+
{embed.metadata?.html?.ogImage?.[0]?.url ||
312+
isImageUrl(embed.url) ? (
307313
<div className="relative w-full max-w-[480px] rounded-lg overflow-hidden bg-purple-800/50">
308314
<div className="relative aspect-[16/9] max-h-[320px]">
309315
<SafeImage
310-
src={embed.metadata?.html?.ogImage?.[0]?.url || embed.url}
311-
alt={embed.metadata?.html?.ogTitle || "Embedded image"}
316+
src={
317+
embed.metadata?.html?.ogImage?.[0]?.url ||
318+
embed.url
319+
}
320+
alt={
321+
embed.metadata?.html?.ogTitle ||
322+
"Embedded image"
323+
}
312324
className="absolute inset-0 w-full h-full object-cover rounded-lg"
313325
/>
314326
</div>

components/BuildRequestDetails.tsx

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ function EmbeddedCastCard({
5959
)}
6060
</div>
6161
</div>
62-
<p className="text-purple-100 whitespace-pre-wrap break-words overflow-hidden overflow-wrap-anywhere mb-3">{cast.text}</p>
62+
<p className="text-purple-100 whitespace-pre-wrap break-words overflow-hidden overflow-wrap-anywhere mb-3">
63+
{cast.text}
64+
</p>
6365
{cast.embeds && cast.embeds.length > 0 && (
6466
<div className="space-y-2">
6567
{cast.embeds.map((embed: Embed, index: number) => (
@@ -99,23 +101,23 @@ function EmbeddedCastCard({
99101
function isImageUrl(url: string): boolean {
100102
// Check for common image extensions
101103
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(url)) return true;
102-
104+
103105
// Check for common image hosting domains
104-
if (url.includes('imagedelivery.net')) return true;
105-
if (url.includes('openseauserdata.com')) return true;
106-
if (url.includes('i.imgur.com')) return true;
107-
if (url.includes('cdn.discordapp.com')) return true;
108-
if (url.includes('i.seadn.io')) return true;
109-
106+
if (url.includes("imagedelivery.net")) return true;
107+
if (url.includes("openseauserdata.com")) return true;
108+
if (url.includes("i.imgur.com")) return true;
109+
if (url.includes("cdn.discordapp.com")) return true;
110+
if (url.includes("i.seadn.io")) return true;
111+
110112
// Google Docs image URLs
111-
if (url.includes('googleusercontent.com/docs')) return true;
112-
113+
if (url.includes("googleusercontent.com/docs")) return true;
114+
113115
// Firefly media URLs
114-
if (url.includes('media.firefly.land/farcaster')) return true;
115-
116+
if (url.includes("media.firefly.land/farcaster")) return true;
117+
116118
// Empire Builder OG image URLs
117-
if (url.includes('empirebuilder.world/api/og')) return true;
118-
119+
if (url.includes("empirebuilder.world/api/og")) return true;
120+
119121
return false;
120122
}
121123

@@ -203,12 +205,21 @@ export function BuildRequestDetails({
203205
<EmbeddedCastCard cast={embed.cast} />
204206
) : embed.url ? (
205207
<div className="mt-2 overflow-hidden">
206-
{(isImageUrl(embed.url) || embed.metadata?.html?.ogImage?.[0]?.url) ? (
208+
{isImageUrl(embed.url) ||
209+
embed.metadata?.html?.ogImage?.[0]?.url ? (
207210
<div className="relative w-full rounded-lg overflow-hidden bg-purple-800/50">
208211
<div className="relative aspect-[16/9]">
209212
<SafeImage
210-
src={isImageUrl(embed.url) ? embed.url : (embed.metadata?.html?.ogImage?.[0]?.url || embed.url)}
211-
alt={embed.metadata?.html?.ogTitle || "Embedded image"}
213+
src={
214+
isImageUrl(embed.url)
215+
? embed.url
216+
: embed.metadata?.html?.ogImage?.[0]?.url ||
217+
embed.url
218+
}
219+
alt={
220+
embed.metadata?.html?.ogTitle ||
221+
"Embedded image"
222+
}
212223
className="absolute inset-0 w-full h-full object-cover rounded-lg"
213224
/>
214225
</div>
@@ -382,17 +393,26 @@ export function BuildRequestDetails({
382393
timestamp: buildRequest.timestamp,
383394
embeds: buildRequest.embeds,
384395
reactions: {
385-
likes_count: "reactions" in buildRequest ? buildRequest.reactions.likes_count : (buildRequest as any).engagement?.likes || 0,
386-
recasts_count: "reactions" in buildRequest ? buildRequest.reactions.recasts_count : (buildRequest as any).engagement?.recasts || 0,
396+
likes_count:
397+
"reactions" in buildRequest
398+
? buildRequest.reactions.likes_count
399+
: (buildRequest as any).engagement?.likes || 0,
400+
recasts_count:
401+
"reactions" in buildRequest
402+
? buildRequest.reactions.recasts_count
403+
: (buildRequest as any).engagement?.recasts || 0,
387404
likes: [],
388-
recasts: []
405+
recasts: [],
389406
},
390407
replies: {
391-
count: "replies" in buildRequest ? buildRequest.replies.count : (buildRequest as any).engagement?.replies || 0
408+
count:
409+
"replies" in buildRequest
410+
? buildRequest.replies.count
411+
: (buildRequest as any).engagement?.replies || 0,
392412
},
393413
mentioned_profiles: buildRequest.mentioned_profiles || [],
394414
author: buildRequest.author,
395-
channel: buildRequest.channel
415+
channel: buildRequest.channel,
396416
}}
397417
isOpen={isClaimModalOpen}
398418
onClose={() => setIsClaimModalOpen(false)}

0 commit comments

Comments
 (0)