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
80 changes: 77 additions & 3 deletions apps/web/app/bounties/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ const STATUS_COLORS: Record<string, string> = {
paid: 'bg-gray-100 text-gray-500 border-gray-200',
};

const FAQ = [
{
q: 'What is a coupon bounty?',
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.',
},
{
q: 'How much can I earn?',
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.',
},
{
q: 'How do I post a bounty?',
a: 'Connect your CoinPay account, then click "Post Bounty". Name the store, describe the discount you\'re looking for, and set your reward amount.',
},
{
q: 'What happens if no code is found?',
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.',
},
];

export default async function BountiesPage() {
const [bounties, did] = await Promise.all([getBounties(), getSessionDid()]);
const storeName = (b: Bounty) => b.store_name_resolved ?? b.store_name ?? 'Any store';
Expand Down Expand Up @@ -92,9 +111,64 @@ export default async function BountiesPage() {

{/* Bounty list */}
{bounties.length === 0 ? (
<div className="text-center py-16 border border-dashed border-gray-200 rounded-2xl">
<p className="text-gray-400 font-medium">No open bounties yet.</p>
<p className="text-sm text-gray-400 mt-1">Be the first to post one!</p>
<div className="flex flex-col gap-8">
<div className="text-center py-14 border border-dashed border-gray-200 rounded-2xl">
<p className="text-gray-500 font-medium text-lg">No open bounties yet.</p>
<p className="text-sm text-gray-400 mt-1 mb-5">
Be the first to post one — set any reward amount and let the community find your code.
</p>
{did ? (
<Link
href="/bounties/new"
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"
>
Post the first bounty
</Link>
) : (
<a
href="/api/auth/coinpay?returnTo=/bounties/new"
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"
>
Connect &amp; post a bounty
</a>
)}
</div>

{/* FAQ — keeps the page content-rich for crawlers even when list is empty */}
<section>
<h2 className="text-xl font-bold text-gray-900 mb-5">Frequently asked questions</h2>
<dl className="flex flex-col gap-5">
{FAQ.map(({ q, a }) => (
<div key={q} className="bg-gray-50 border border-gray-200 rounded-xl p-5">
<dt className="font-semibold text-gray-900 text-sm mb-1">{q}</dt>
<dd className="text-sm text-gray-500 leading-relaxed">{a}</dd>
</div>
))}
</dl>
</section>

{/* Benefits */}
<section className="bg-orange-50 rounded-2xl p-8">
<h2 className="text-lg font-bold text-gray-900 mb-3">Why use c0upons bounties?</h2>
<ul className="flex flex-col gap-2 text-sm text-gray-600">
<li className="flex items-start gap-2">
<span className="text-orange-500 font-bold mt-0.5">✓</span>
Instant crypto payouts — no waiting for bank transfers or gift cards
</li>
<li className="flex items-start gap-2">
<span className="text-orange-500 font-bold mt-0.5">✓</span>
You only pay when a working code is confirmed — no risk of paying for duds
</li>
<li className="flex items-start gap-2">
<span className="text-orange-500 font-bold mt-0.5">✓</span>
The whole community is incentivised to find the best coupon for your store
</li>
<li className="flex items-start gap-2">
<span className="text-orange-500 font-bold mt-0.5">✓</span>
Found codes are added to c0upons so future shoppers benefit too
</li>
</ul>
</section>
</div>
) : (
<div className="flex flex-col gap-3">
Expand Down
145 changes: 143 additions & 2 deletions apps/web/app/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
export const dynamic = 'force-dynamic';

import type { Metadata } from 'next';
import Link from 'next/link';
import CouponCard from '@/components/CouponCard';
import SearchBar from '@/components/SearchBar';
import { getDb } from '@/lib/db';
import { Coupon } from '@/lib/types';

export const metadata: Metadata = {
title: 'Search Coupons',
description:
'Search thousands of community-submitted coupon codes and promo deals. Find discounts for any store — electronics, fashion, groceries, travel and more.',
alternates: { canonical: 'https://c0upons.com/search' },
};

async function search(q: string): Promise<Coupon[]> {
if (!q) return [];
try {
Expand All @@ -26,6 +35,24 @@ async function search(q: string): Promise<Coupon[]> {
}
}

const SEARCH_TIPS = [
{ tip: 'Search by store name', example: '"Amazon", "Nike", "Spotify"' },
{ tip: 'Search by discount type', example: '"free shipping", "20% off", "BOGO"' },
{ tip: 'Search by product category', example: '"shoes", "electronics", "software"' },
{ tip: 'Search by coupon code', example: 'paste a code you already have to check it' },
];

const POPULAR_SEARCHES = [
'free shipping',
'20% off',
'electronics',
'fashion',
'groceries',
'travel',
'software',
'subscription',
];

export default async function SearchPage({ searchParams }: { searchParams: Promise<{ q?: string }> }) {
const { q = '' } = await searchParams;
const results = q ? await search(q) : [];
Expand All @@ -37,13 +64,40 @@ export default async function SearchPage({ searchParams }: { searchParams: Promi
<SearchBar defaultValue={q} />
</div>

{q && (
{q ? (
<div>
<p className="text-gray-500 mb-4">
{results.length} result{results.length !== 1 ? 's' : ''} for &ldquo;{q}&rdquo;
</p>
{results.length === 0 ? (
<p className="text-gray-400">No coupons found. Try a different search term.</p>
<div className="flex flex-col gap-8">
<div className="text-center py-12 border border-dashed border-gray-200 rounded-2xl">
<p className="text-gray-500 font-medium">No coupons found for &ldquo;{q}&rdquo;.</p>
<p className="text-sm text-gray-400 mt-1 mb-5">
Try a different search term, or be the first to submit a coupon for this store.
</p>
<Link
href="/submit"
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"
>
Submit a coupon code
</Link>
</div>
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-3">Try a popular search</h2>
<div className="flex flex-wrap gap-2">
{POPULAR_SEARCHES.map((term) => (
<Link
key={term}
href={`/search?q=${encodeURIComponent(term)}`}
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"
>
{term}
</Link>
))}
</div>
</section>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{results.map((c) => (
Expand All @@ -52,6 +106,93 @@ export default async function SearchPage({ searchParams }: { searchParams: Promi
</div>
)}
</div>
) : (
/* Landing state — shown when no query is present; must be content-rich for crawlers */
<div className="flex flex-col gap-10">
{/* Popular searches */}
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-3">Popular searches</h2>
<div className="flex flex-wrap gap-2">
{POPULAR_SEARCHES.map((term) => (
<Link
key={term}
href={`/search?q=${encodeURIComponent(term)}`}
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"
>
{term}
</Link>
))}
</div>
</section>

{/* Search tips */}
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-4">Search tips</h2>
<div className="grid sm:grid-cols-2 gap-4">
{SEARCH_TIPS.map(({ tip, example }) => (
<div
key={tip}
className="bg-gray-50 border border-gray-200 rounded-xl p-4 flex flex-col gap-1"
>
<span className="font-semibold text-gray-900 text-sm">{tip}</span>
<span className="text-xs text-gray-500">{example}</span>
</div>
))}
</div>
</section>

{/* About the search */}
<section className="bg-orange-50 rounded-2xl p-8">
<h2 className="text-lg font-bold text-gray-900 mb-3">How c0upons search works</h2>
<p className="text-sm text-gray-600 leading-relaxed mb-4">
c0upons searches across coupon titles, descriptions, store names, and coupon codes
simultaneously. Results are ranked by community votes, so the most reliable, recently
verified codes always appear first.
</p>
<p className="text-sm text-gray-600 leading-relaxed">
Can&apos;t find what you&apos;re looking for?{' '}
<Link href="/submit" className="text-orange-600 hover:underline font-medium">
Submit a coupon
</Link>{' '}
and help the community save, or post a{' '}
<Link href="/bounties" className="text-orange-600 hover:underline font-medium">
bounty
</Link>{' '}
and pay someone to find it for you.
</p>
</section>

{/* Links to other sections */}
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-4">Browse without searching</h2>
<div className="grid sm:grid-cols-3 gap-4">
<Link
href="/stores"
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"
>
<span className="text-2xl">🏪</span>
<span className="font-semibold text-gray-900 text-sm mt-1">Browse by store</span>
<span className="text-xs text-gray-500">Find all coupons for a specific retailer</span>
</Link>
<Link
href="/bounties"
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"
>
<span className="text-2xl">💰</span>
<span className="font-semibold text-gray-900 text-sm mt-1">View bounties</span>
<span className="text-xs text-gray-500">Earn rewards by hunting coupon codes</span>
</Link>
<Link
href="/submit"
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"
>
<span className="text-2xl">➕</span>
<span className="font-semibold text-gray-900 text-sm mt-1">Submit a coupon</span>
<span className="text-xs text-gray-500">Share a working code with the community</span>
</Link>
</div>
</section>
</div>
)}
</div>
);
Expand Down
Loading
Loading