Skip to content

Commit 06d0d76

Browse files
CrawlProof fix: Navigation links point to functional pages — but /bounties, /stores, /search return empty or stub content (#14)
* Fix links.internal_completeness: Navigation links point to functional pages — but /bounties, /stores, /search return empty or stub content * Fix links.internal_completeness: Navigation links point to functional pages — but /bounties, /stores, /search return empty or stub content * Fix links.internal_completeness: Navigation links point to functional pages — but /bounties, /stores, /search return empty or stub content --------- Co-authored-by: crawlproof[bot] <286981042+crawlproof[bot]@users.noreply.github.com>
1 parent b71f248 commit 06d0d76

3 files changed

Lines changed: 328 additions & 18 deletions

File tree

apps/web/app/bounties/page.tsx

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ const STATUS_COLORS: Record<string, string> = {
4444
paid: 'bg-gray-100 text-gray-500 border-gray-200',
4545
};
4646

47+
const FAQ = [
48+
{
49+
q: 'What is a coupon bounty?',
50+
a: 'A bounty is a cash reward posted by a shopper who wants a working coupon code for a specific store. Anyone can submit a valid code to claim the reward.',
51+
},
52+
{
53+
q: 'How much can I earn?',
54+
a: 'Bounty creators set their own reward amount (minimum $0.10). Rewards are paid instantly to your CoinPay DID as soon as a valid code is accepted.',
55+
},
56+
{
57+
q: 'How do I post a bounty?',
58+
a: 'Connect your CoinPay account, then click "Post Bounty". Name the store, describe the discount you\'re looking for, and set your reward amount.',
59+
},
60+
{
61+
q: 'What happens if no code is found?',
62+
a: 'Open bounties remain listed until a valid code is submitted or the creator cancels. Funds are only released when a working code is confirmed.',
63+
},
64+
];
65+
4766
export default async function BountiesPage() {
4867
const [bounties, did] = await Promise.all([getBounties(), getSessionDid()]);
4968
const storeName = (b: Bounty) => b.store_name_resolved ?? b.store_name ?? 'Any store';
@@ -92,9 +111,64 @@ export default async function BountiesPage() {
92111

93112
{/* Bounty list */}
94113
{bounties.length === 0 ? (
95-
<div className="text-center py-16 border border-dashed border-gray-200 rounded-2xl">
96-
<p className="text-gray-400 font-medium">No open bounties yet.</p>
97-
<p className="text-sm text-gray-400 mt-1">Be the first to post one!</p>
114+
<div className="flex flex-col gap-8">
115+
<div className="text-center py-14 border border-dashed border-gray-200 rounded-2xl">
116+
<p className="text-gray-500 font-medium text-lg">No open bounties yet.</p>
117+
<p className="text-sm text-gray-400 mt-1 mb-5">
118+
Be the first to post one — set any reward amount and let the community find your code.
119+
</p>
120+
{did ? (
121+
<Link
122+
href="/bounties/new"
123+
className="inline-flex bg-orange-500 hover:bg-orange-600 text-white font-semibold px-6 py-2.5 rounded-xl text-sm transition-colors"
124+
>
125+
Post the first bounty
126+
</Link>
127+
) : (
128+
<a
129+
href="/api/auth/coinpay?returnTo=/bounties/new"
130+
className="inline-flex bg-gray-900 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl text-sm transition-colors"
131+
>
132+
Connect &amp; post a bounty
133+
</a>
134+
)}
135+
</div>
136+
137+
{/* FAQ — keeps the page content-rich for crawlers even when list is empty */}
138+
<section>
139+
<h2 className="text-xl font-bold text-gray-900 mb-5">Frequently asked questions</h2>
140+
<dl className="flex flex-col gap-5">
141+
{FAQ.map(({ q, a }) => (
142+
<div key={q} className="bg-gray-50 border border-gray-200 rounded-xl p-5">
143+
<dt className="font-semibold text-gray-900 text-sm mb-1">{q}</dt>
144+
<dd className="text-sm text-gray-500 leading-relaxed">{a}</dd>
145+
</div>
146+
))}
147+
</dl>
148+
</section>
149+
150+
{/* Benefits */}
151+
<section className="bg-orange-50 rounded-2xl p-8">
152+
<h2 className="text-lg font-bold text-gray-900 mb-3">Why use c0upons bounties?</h2>
153+
<ul className="flex flex-col gap-2 text-sm text-gray-600">
154+
<li className="flex items-start gap-2">
155+
<span className="text-orange-500 font-bold mt-0.5"></span>
156+
Instant crypto payouts — no waiting for bank transfers or gift cards
157+
</li>
158+
<li className="flex items-start gap-2">
159+
<span className="text-orange-500 font-bold mt-0.5"></span>
160+
You only pay when a working code is confirmed — no risk of paying for duds
161+
</li>
162+
<li className="flex items-start gap-2">
163+
<span className="text-orange-500 font-bold mt-0.5"></span>
164+
The whole community is incentivised to find the best coupon for your store
165+
</li>
166+
<li className="flex items-start gap-2">
167+
<span className="text-orange-500 font-bold mt-0.5"></span>
168+
Found codes are added to c0upons so future shoppers benefit too
169+
</li>
170+
</ul>
171+
</section>
98172
</div>
99173
) : (
100174
<div className="flex flex-col gap-3">

apps/web/app/search/page.tsx

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
export const dynamic = 'force-dynamic';
22

3+
import type { Metadata } from 'next';
4+
import Link from 'next/link';
35
import CouponCard from '@/components/CouponCard';
46
import SearchBar from '@/components/SearchBar';
57
import { getDb } from '@/lib/db';
68
import { Coupon } from '@/lib/types';
79

10+
export const metadata: Metadata = {
11+
title: 'Search Coupons',
12+
description:
13+
'Search thousands of community-submitted coupon codes and promo deals. Find discounts for any store — electronics, fashion, groceries, travel and more.',
14+
alternates: { canonical: 'https://c0upons.com/search' },
15+
};
16+
817
async function search(q: string): Promise<Coupon[]> {
918
if (!q) return [];
1019
try {
@@ -26,6 +35,24 @@ async function search(q: string): Promise<Coupon[]> {
2635
}
2736
}
2837

38+
const SEARCH_TIPS = [
39+
{ tip: 'Search by store name', example: '"Amazon", "Nike", "Spotify"' },
40+
{ tip: 'Search by discount type', example: '"free shipping", "20% off", "BOGO"' },
41+
{ tip: 'Search by product category', example: '"shoes", "electronics", "software"' },
42+
{ tip: 'Search by coupon code', example: 'paste a code you already have to check it' },
43+
];
44+
45+
const POPULAR_SEARCHES = [
46+
'free shipping',
47+
'20% off',
48+
'electronics',
49+
'fashion',
50+
'groceries',
51+
'travel',
52+
'software',
53+
'subscription',
54+
];
55+
2956
export default async function SearchPage({ searchParams }: { searchParams: Promise<{ q?: string }> }) {
3057
const { q = '' } = await searchParams;
3158
const results = q ? await search(q) : [];
@@ -37,13 +64,40 @@ export default async function SearchPage({ searchParams }: { searchParams: Promi
3764
<SearchBar defaultValue={q} />
3865
</div>
3966

40-
{q && (
67+
{q ? (
4168
<div>
4269
<p className="text-gray-500 mb-4">
4370
{results.length} result{results.length !== 1 ? 's' : ''} for &ldquo;{q}&rdquo;
4471
</p>
4572
{results.length === 0 ? (
46-
<p className="text-gray-400">No coupons found. Try a different search term.</p>
73+
<div className="flex flex-col gap-8">
74+
<div className="text-center py-12 border border-dashed border-gray-200 rounded-2xl">
75+
<p className="text-gray-500 font-medium">No coupons found for &ldquo;{q}&rdquo;.</p>
76+
<p className="text-sm text-gray-400 mt-1 mb-5">
77+
Try a different search term, or be the first to submit a coupon for this store.
78+
</p>
79+
<Link
80+
href="/submit"
81+
className="inline-flex bg-orange-500 hover:bg-orange-600 text-white font-semibold px-6 py-2.5 rounded-xl text-sm transition-colors"
82+
>
83+
Submit a coupon code
84+
</Link>
85+
</div>
86+
<section>
87+
<h2 className="text-lg font-semibold text-gray-900 mb-3">Try a popular search</h2>
88+
<div className="flex flex-wrap gap-2">
89+
{POPULAR_SEARCHES.map((term) => (
90+
<Link
91+
key={term}
92+
href={`/search?q=${encodeURIComponent(term)}`}
93+
className="bg-gray-100 hover:bg-orange-100 hover:text-orange-700 text-gray-700 text-sm font-medium px-4 py-1.5 rounded-full transition-colors"
94+
>
95+
{term}
96+
</Link>
97+
))}
98+
</div>
99+
</section>
100+
</div>
47101
) : (
48102
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
49103
{results.map((c) => (
@@ -52,6 +106,93 @@ export default async function SearchPage({ searchParams }: { searchParams: Promi
52106
</div>
53107
)}
54108
</div>
109+
) : (
110+
/* Landing state — shown when no query is present; must be content-rich for crawlers */
111+
<div className="flex flex-col gap-10">
112+
{/* Popular searches */}
113+
<section>
114+
<h2 className="text-lg font-semibold text-gray-900 mb-3">Popular searches</h2>
115+
<div className="flex flex-wrap gap-2">
116+
{POPULAR_SEARCHES.map((term) => (
117+
<Link
118+
key={term}
119+
href={`/search?q=${encodeURIComponent(term)}`}
120+
className="bg-gray-100 hover:bg-orange-100 hover:text-orange-700 text-gray-700 text-sm font-medium px-4 py-1.5 rounded-full transition-colors"
121+
>
122+
{term}
123+
</Link>
124+
))}
125+
</div>
126+
</section>
127+
128+
{/* Search tips */}
129+
<section>
130+
<h2 className="text-lg font-semibold text-gray-900 mb-4">Search tips</h2>
131+
<div className="grid sm:grid-cols-2 gap-4">
132+
{SEARCH_TIPS.map(({ tip, example }) => (
133+
<div
134+
key={tip}
135+
className="bg-gray-50 border border-gray-200 rounded-xl p-4 flex flex-col gap-1"
136+
>
137+
<span className="font-semibold text-gray-900 text-sm">{tip}</span>
138+
<span className="text-xs text-gray-500">{example}</span>
139+
</div>
140+
))}
141+
</div>
142+
</section>
143+
144+
{/* About the search */}
145+
<section className="bg-orange-50 rounded-2xl p-8">
146+
<h2 className="text-lg font-bold text-gray-900 mb-3">How c0upons search works</h2>
147+
<p className="text-sm text-gray-600 leading-relaxed mb-4">
148+
c0upons searches across coupon titles, descriptions, store names, and coupon codes
149+
simultaneously. Results are ranked by community votes, so the most reliable, recently
150+
verified codes always appear first.
151+
</p>
152+
<p className="text-sm text-gray-600 leading-relaxed">
153+
Can&apos;t find what you&apos;re looking for?{' '}
154+
<Link href="/submit" className="text-orange-600 hover:underline font-medium">
155+
Submit a coupon
156+
</Link>{' '}
157+
and help the community save, or post a{' '}
158+
<Link href="/bounties" className="text-orange-600 hover:underline font-medium">
159+
bounty
160+
</Link>{' '}
161+
and pay someone to find it for you.
162+
</p>
163+
</section>
164+
165+
{/* Links to other sections */}
166+
<section>
167+
<h2 className="text-lg font-semibold text-gray-900 mb-4">Browse without searching</h2>
168+
<div className="grid sm:grid-cols-3 gap-4">
169+
<Link
170+
href="/stores"
171+
className="bg-white border border-gray-200 rounded-xl p-5 hover:border-orange-300 hover:shadow-md transition-all flex flex-col gap-1"
172+
>
173+
<span className="text-2xl">🏪</span>
174+
<span className="font-semibold text-gray-900 text-sm mt-1">Browse by store</span>
175+
<span className="text-xs text-gray-500">Find all coupons for a specific retailer</span>
176+
</Link>
177+
<Link
178+
href="/bounties"
179+
className="bg-white border border-gray-200 rounded-xl p-5 hover:border-orange-300 hover:shadow-md transition-all flex flex-col gap-1"
180+
>
181+
<span className="text-2xl">💰</span>
182+
<span className="font-semibold text-gray-900 text-sm mt-1">View bounties</span>
183+
<span className="text-xs text-gray-500">Earn rewards by hunting coupon codes</span>
184+
</Link>
185+
<Link
186+
href="/submit"
187+
className="bg-white border border-gray-200 rounded-xl p-5 hover:border-orange-300 hover:shadow-md transition-all flex flex-col gap-1"
188+
>
189+
<span className="text-2xl"></span>
190+
<span className="font-semibold text-gray-900 text-sm mt-1">Submit a coupon</span>
191+
<span className="text-xs text-gray-500">Share a working code with the community</span>
192+
</Link>
193+
</div>
194+
</section>
195+
</div>
55196
)}
56197
</div>
57198
);

0 commit comments

Comments
 (0)