From 5db61212dc79f897b81cab19dc8f57f369ab7d78 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Tue, 21 Apr 2026 14:14:48 -0400 Subject: [PATCH] feat(theme): barnyard redesign with rustic crypto aesthetic - New color palette: barn red primary, cream backgrounds, hay gold - Added Abril Fatface display font for headlines - Wood grain texture backgrounds via CSS gradients - Barnyard personality in copy: 'Join the Flock', 'Enter the Coop' - Replaced hardcoded slate colors with theme variables - Updated landing, login, dashboard, onboarding, leaderboard --- frontend/src/app/auth/callback/page.tsx | 2 +- .../app/dashboard/components/Leaderboard.tsx | 234 ++++---- .../components/WelcomeOnboarding.tsx | 188 +++--- frontend/src/app/dashboard/page.tsx | 557 ++++++++++-------- frontend/src/app/globals.css | 145 +++-- frontend/src/app/layout.tsx | 48 +- frontend/src/app/login/page.tsx | 30 +- frontend/src/app/page.tsx | 416 +++++++------ 8 files changed, 919 insertions(+), 701 deletions(-) diff --git a/frontend/src/app/auth/callback/page.tsx b/frontend/src/app/auth/callback/page.tsx index c07acf1..33d8cd7 100644 --- a/frontend/src/app/auth/callback/page.tsx +++ b/frontend/src/app/auth/callback/page.tsx @@ -49,7 +49,7 @@ export default function AuthCallbackPage() { }, [router, setUser, setToken]); return ( -
+

{status}

diff --git a/frontend/src/app/dashboard/components/Leaderboard.tsx b/frontend/src/app/dashboard/components/Leaderboard.tsx index 8d9e68d..b34609c 100644 --- a/frontend/src/app/dashboard/components/Leaderboard.tsx +++ b/frontend/src/app/dashboard/components/Leaderboard.tsx @@ -1,114 +1,144 @@ -'use client'; +"use client"; -import { useEffect, useState } from 'react'; -import { userApi } from '@/lib/api'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { formatNumber, formatDuration } from '@/lib/utils'; -import { Trophy, Medal, Star, Clock, Coins } from 'lucide-react'; +import { Clock, Coins, Medal, Star, Trophy } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { userApi } from "@/lib/api"; +import { formatDuration, formatNumber } from "@/lib/utils"; interface LeaderboardUser { - id: string; - plexUsername: string; - totalEarned: number; - watchTimeMinutes: number; + id: string; + plexUsername: string; + totalEarned: number; + watchTimeMinutes: number; } export function Leaderboard() { - const [data, setData] = useState<{ topEarners: LeaderboardUser[]; topWatchers: LeaderboardUser[] }>({ - topEarners: [], - topWatchers: [], - }); - const [isLoading, setIsLoading] = useState(true); + const [data, setData] = useState<{ + topEarners: LeaderboardUser[]; + topWatchers: LeaderboardUser[]; + }>({ + topEarners: [], + topWatchers: [], + }); + const [isLoading, setIsLoading] = useState(true); - useEffect(() => { - loadLeaderboard(); - }, []); + useEffect(() => { + loadLeaderboard(); + }, []); - const loadLeaderboard = async () => { - try { - const response = await userApi.getLeaderboard(); - setData(response.data); - } catch (error) { - console.error('Failed to load leaderboard:', error); - } finally { - setIsLoading(false); - } - }; + const loadLeaderboard = async () => { + try { + const response = await userApi.getLeaderboard(); + setData(response.data); + } catch (error) { + console.error("Failed to load leaderboard:", error); + } finally { + setIsLoading(false); + } + }; - const getRankIcon = (index: number) => { - switch (index) { - case 0: return ; - case 1: return ; - case 2: return ; - default: return {index + 1}; - } - }; + const getRankIcon = (index: number) => { + switch (index) { + case 0: + return ; + case 1: + return ; + case 2: + return ; + default: + return ( + + {index + 1} + + ); + } + }; - if (isLoading) { - return ( - - -
- {[1, 2, 3, 4, 5].map((i) => ( -
- ))} -
- - - ); - } + if (isLoading) { + return ( + + +
+ {[1, 2, 3, 4, 5].map((i) => ( +
+ ))} +
+ + + ); + } - return ( - - - - - Leaderboard - - - - - - Top Earners - Top Watchers - - - -
- {data.topEarners.map((user, index) => ( -
-
-
{getRankIcon(index)}
- {user.plexUsername} -
-
- - {formatNumber(user.totalEarned)} -
-
- ))} -
-
- - -
- {data.topWatchers.map((user, index) => ( -
-
-
{getRankIcon(index)}
- {user.plexUsername} -
-
- - {Math.floor(user.watchTimeMinutes / 60)}h {user.watchTimeMinutes % 60}m -
-
- ))} -
-
-
-
-
- ); + return ( + + + + + Leaderboard + + + + + + + Top Earners + + + Top Watchers + + + + +
+ {data.topEarners.map((user, index) => ( +
+
+
+ {getRankIcon(index)} +
+ + {user.plexUsername} + +
+
+ + {formatNumber(user.totalEarned)} +
+
+ ))} +
+
+ + +
+ {data.topWatchers.map((user, index) => ( +
+
+
+ {getRankIcon(index)} +
+ + {user.plexUsername} + +
+
+ + {Math.floor(user.watchTimeMinutes / 60)}h{" "} + {user.watchTimeMinutes % 60}m +
+
+ ))} +
+
+
+
+
+ ); } diff --git a/frontend/src/app/dashboard/components/WelcomeOnboarding.tsx b/frontend/src/app/dashboard/components/WelcomeOnboarding.tsx index 4f6a04f..2462991 100644 --- a/frontend/src/app/dashboard/components/WelcomeOnboarding.tsx +++ b/frontend/src/app/dashboard/components/WelcomeOnboarding.tsx @@ -1,92 +1,116 @@ -'use client'; +"use client"; -import { Card, CardContent } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Wallet, Sparkles, ShieldCheck, Zap, ExternalLink } from 'lucide-react'; -import { useWallet } from '@solana/wallet-adapter-react'; -import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; -import { useEffect } from 'react'; -import { walletApi } from '@/lib/api'; -import { toast } from 'sonner'; +import { useWallet } from "@solana/wallet-adapter-react"; +import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; +import { + Egg, + ExternalLink, + ShieldCheck, + Sparkles, + Wallet, + Zap, +} from "lucide-react"; +import { useEffect } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { walletApi } from "@/lib/api"; interface WelcomeOnboardingProps { - onStart: () => void; - onConnected: () => void; + onStart: () => void; + onConnected: () => void; } -export function WelcomeOnboarding({ onStart, onConnected }: WelcomeOnboardingProps) { - const { publicKey, connected } = useWallet(); +export function WelcomeOnboarding({ + onStart, + onConnected, +}: WelcomeOnboardingProps) { + const { publicKey, connected } = useWallet(); - useEffect(() => { - if (connected && publicKey) { - handleConnectWallet(publicKey.toString()); - } - }, [connected, publicKey]); + useEffect(() => { + if (connected && publicKey) { + handleConnectWallet(publicKey.toString()); + } + }, [connected, publicKey]); - const handleConnectWallet = async (address: string) => { - try { - await walletApi.connectWallet(address); - toast.success('Wallet connected to your account!'); - onConnected(); - } catch (error) { - toast.error('Failed to link wallet'); - } - }; + const handleConnectWallet = async (address: string) => { + try { + await walletApi.connectWallet(address); + toast.success("Wallet connected to your account!"); + onConnected(); + } catch (error) { + toast.error("Failed to link wallet"); + } + }; - return ( - - -
-
-
-

Welcome to the Ecosystem!

-

- You're just one step away from earning rewards for your watch time. -

-
- -
-
-
- -
-
-

Automatic Rewards

-

Credits are minted directly to your wallet while you watch.

-
-
- -
-
- -
-
-

Secure & Private

-

Your wallet is personal and secured on the Solana blockchain.

-
-
-
+ return ( + + +
+
+
+

+ Welcome to the Coop! +

+

+ You are one step away from earning golden eggs for your watch + time. +

+
-
- - -
- -
-
-

- Choose "Create Managed Wallet" for an easy start, or "Select Wallet" to use your own (Phantom, Solflare, etc.) -

-
- -
- -
-
-
-
- ); +
+
+
+ +
+
+

Automatic Rewards

+

+ Credits are minted directly to your wallet while you watch. +

+
+
+ +
+
+ +
+
+

Secure & Private

+

+ Your wallet is personal and secured on the Solana + blockchain. +

+
+
+
+ +
+ + +
+ +
+
+

+ Choose “Create Managed Wallet” for an easy start, or + “Select Wallet” to use your own (Phantom, Solflare, + etc.) +

+
+ +
+ +
+
+
+
+ ); } diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index b496161..e5de24c 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -1,285 +1,320 @@ -'use client'; +"use client"; -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { useStore } from '@/lib/store'; -import { walletApi, transactionApi, overseerApi } from '@/lib/api'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { - Wallet, - TrendingUp, - TrendingDown, - Clock, - Film, - ExternalLink, - Plus, - History -} from 'lucide-react'; -import { formatNumber, truncateAddress } from '@/lib/utils'; -import { toast } from 'sonner'; -import { useSocket } from '@/lib/socket'; -import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; -import { TransactionList } from './components/TransactionList'; -import { WatchHistory } from './components/WatchHistory'; -import { CreateWalletModal } from './components/CreateWalletModal'; -import { WelcomeOnboarding } from './components/WelcomeOnboarding'; -import { ActivityFeed } from './components/ActivityFeed'; -import { SearchRequestModal } from './components/SearchRequestModal'; -import { RequestHistory } from './components/RequestHistory'; -import { Leaderboard } from './components/Leaderboard'; +import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; +import { + Clock, + Egg, + ExternalLink, + Film, + History, + TrendingDown, + TrendingUp, + Wallet, +} from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { overseerApi, transactionApi, walletApi } from "@/lib/api"; +import { useSocket } from "@/lib/socket"; +import { useStore } from "@/lib/store"; +import { formatNumber, truncateAddress } from "@/lib/utils"; +import { ActivityFeed } from "./components/ActivityFeed"; +import { CreateWalletModal } from "./components/CreateWalletModal"; +import { Leaderboard } from "./components/Leaderboard"; +import { RequestHistory } from "./components/RequestHistory"; +import { SearchRequestModal } from "./components/SearchRequestModal"; +import { TransactionList } from "./components/TransactionList"; +import { WatchHistory } from "./components/WatchHistory"; +import { WelcomeOnboarding } from "./components/WelcomeOnboarding"; interface WalletData { - hasWallet: boolean; - address?: string; - balance: number; - totalEarned: number; - totalSpent: number; - explorerUrl?: string; + hasWallet: boolean; + address?: string; + balance: number; + totalEarned: number; + totalSpent: number; + explorerUrl?: string; } export default function DashboardPage() { - const router = useRouter(); - const { user, isAuthenticated, logout } = useStore(); - const [wallet, setWallet] = useState(null); - const [requestCosts, setRequestCosts] = useState({ movie: 100, tv: 200 }); - const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); - const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const socket = useSocket(); + const router = useRouter(); + const { user, isAuthenticated, logout } = useStore(); + const [wallet, setWallet] = useState(null); + const [requestCosts, setRequestCosts] = useState({ movie: 100, tv: 200 }); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const socket = useSocket(); - useEffect(() => { - if (!isAuthenticated) { - router.push('/login'); - return; - } + useEffect(() => { + if (!isAuthenticated) { + router.push("/login"); + return; + } - loadData(); - }, [isAuthenticated, router]); + loadData(); + }, [isAuthenticated, router]); - useEffect(() => { - if (socket) { - socket.on('credits_earned', (data) => { - toast.success(`You earned ${data.amount} $COOP!`, { - description: `Watched: ${data.title}`, - }); - loadData(); - }); + useEffect(() => { + if (socket) { + socket.on("credits_earned", (data) => { + toast.success(`You earned ${data.amount} $COOP!`, { + description: `Watched: ${data.title}`, + }); + loadData(); + }); - socket.on('bonus_received', (data) => { - toast.success(`Bonus Received: ${data.amount} $COOP!`, { - description: data.reason, - }); - loadData(); - }); + socket.on("bonus_received", (data) => { + toast.success(`Bonus Received: ${data.amount} $COOP!`, { + description: data.reason, + }); + loadData(); + }); - socket.on('credits_spent', (data) => { - toast.info(`Requested: ${data.title}`, { - description: `Spent ${data.amount} $COOP`, - }); - loadData(); - }); + socket.on("credits_spent", (data) => { + toast.info(`Requested: ${data.title}`, { + description: `Spent ${data.amount} $COOP`, + }); + loadData(); + }); - return () => { - socket.off('credits_earned'); - socket.off('bonus_received'); - socket.off('credits_spent'); - }; - } - }, [socket]); + return () => { + socket.off("credits_earned"); + socket.off("bonus_received"); + socket.off("credits_spent"); + }; + } + }, [socket]); - const loadData = async () => { - try { - const [walletRes, costsRes] = await Promise.all([ - walletApi.getWallet(), - overseerApi.getCosts().catch(() => ({ data: { movie: 100, tv: 200 } })) - ]); - - setWallet(walletRes.data); - setRequestCosts(costsRes.data); - } catch (error) { - toast.error('Failed to load wallet data'); - } finally { - setIsLoading(false); - } - }; + const loadData = async () => { + try { + const [walletRes, costsRes] = await Promise.all([ + walletApi.getWallet(), + overseerApi.getCosts().catch(() => ({ data: { movie: 100, tv: 200 } })), + ]); - if (!isAuthenticated || !user) { - return null; - } + setWallet(walletRes.data); + setRequestCosts(costsRes.data); + } catch (error) { + toast.error("Failed to load wallet data"); + } finally { + setIsLoading(false); + } + }; - return ( -
- {/* Header */} -
-
-
-

CoopCredits

- {user.plexUsername} - {user.isAdmin && ( - - )} -
-
-
- -
- -
-
-
+ if (!isAuthenticated || !user) { + return null; + } -
- {/* Onboarding for new users */} - {!wallet?.hasWallet && !isLoading && ( - setIsCreateModalOpen(true)} onConnected={loadData} /> - )} + return ( +
+ {/* Header */} +
+
+
+
+ +

The Coop

+
+ {user.plexUsername} + {user.isAdmin && ( + + )} +
+
+
+ +
+ +
+
+
- {/* Stats Cards */} -
- - - Balance - - - -
- {wallet ? formatNumber(wallet.balance) : '--'} $COOP -
-

- {wallet?.hasWallet ? 'Ready to spend' : 'Create wallet to start'} -

-
-
+
+ {/* Onboarding for new users */} + {!wallet?.hasWallet && !isLoading && ( + setIsCreateModalOpen(true)} + onConnected={loadData} + /> + )} - - - Total Earned - - - -
- {wallet ? formatNumber(wallet.totalEarned) : '--'} $COOP -
-

- From watching content -

-
-
+ {/* Stats Cards */} +
+ + + Nest Egg + + + +
+ {wallet ? formatNumber(wallet.balance) : "--"} $COOP +
+

+ {wallet?.hasWallet + ? "Ready to spend" + : "Create wallet to start"} +

+
+
- - - Total Spent - - - -
- {wallet ? formatNumber(wallet.totalSpent) : '--'} $COOP -
-

- On content requests -

-
-
+ + + + Total Gathered + + + + +
+ {wallet ? formatNumber(wallet.totalEarned) : "--"} $COOP +
+

+ From watching content +

+
+
- setIsSearchModalOpen(true)}> - - Request Cost - - - -
{requestCosts.movie} $COOP
-

- Click to request content -

-
-
-
+ + + Total Spent + + + +
+ {wallet ? formatNumber(wallet.totalSpent) : "--"} $COOP +
+

+ On content requests +

+
+
- {/* Wallet Section removed from here if redundant, or kept if we want it to show after creation */} - {wallet?.hasWallet && ( - - - - - Your Wallet - - - Manage your Solana wallet and $COOP tokens - - - -
-
-

Address

-

- {truncateAddress(wallet.address!)} -

-
-
- - -
-
-
-
- )} + setIsSearchModalOpen(true)} + > + + + Request Cost + + + + +
+ {requestCosts.movie} $COOP +
+

+ Click to request content +

+
+
+
- {/* Tabs */} -
-
- - - Transactions - Watch History - My Requests - - - - - - - - - - - - - -
-
- - -
-
-
+ {/* Wallet Section */} + {wallet?.hasWallet && ( + + + + + Your Coop Wallet + + + Manage your Solana wallet and $COOP tokens + + + +
+
+

Address

+

+ {truncateAddress(wallet.address!)} +

+
+
+ + +
+
+
+
+ )} - setIsCreateModalOpen(false)} - onCreated={loadData} - /> - setIsSearchModalOpen(false)} - onRequested={loadData} - costs={requestCosts} - balance={wallet?.balance || 0} - /> -
- ); + {/* Tabs */} +
+
+ + + Transactions + Watch History + My Requests + + + + + + + + + + + + + +
+
+ + +
+
+ + + setIsCreateModalOpen(false)} + onCreated={loadData} + /> + setIsSearchModalOpen(false)} + onRequested={loadData} + costs={requestCosts} + balance={wallet?.balance || 0} + /> +
+ ); } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index cc9fb43..4367590 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,6 +1,9 @@ @import "tailwindcss"; @theme { + --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif; + --font-display: var(--font-abril), Georgia, "Times New Roman", serif; + --color-border: hsl(var(--border)); --color-input: hsl(var(--input)); --color-ring: hsl(var(--ring)); @@ -28,61 +31,97 @@ } @layer base { - :root { - --background: 45 100% 97%; - --foreground: 24 33% 14%; - --card: 0 0% 100%; - --card-foreground: 24 33% 14%; - --popover: 0 0% 100%; - --popover-foreground: 24 33% 14%; - --primary: 34 92% 52%; - --primary-foreground: 45 100% 97%; - --secondary: 36 45% 92%; - --secondary-foreground: 24 33% 14%; - --muted: 36 33% 94%; - --muted-foreground: 24 10% 42%; - --accent: 29 60% 90%; - --accent-foreground: 24 33% 14%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - --border: 31 28% 84%; - --input: 31 28% 84%; - --ring: 34 92% 52%; - --radius: 0.5rem; - } + :root { + --background: 48 100% 96%; + --foreground: 20 6% 16%; + --card: 30 100% 97%; + --card-foreground: 20 6% 16%; + --popover: 0 0% 100%; + --popover-foreground: 20 6% 16%; + --primary: 0 72% 42%; + --primary-foreground: 48 100% 96%; + --secondary: 30 47% 90%; + --secondary-foreground: 20 6% 16%; + --muted: 28 47% 90%; + --muted-foreground: 24 6% 47%; + --accent: 28 52% 64%; + --accent-foreground: 20 6% 16%; + --destructive: 0 72% 50%; + --destructive-foreground: 48 100% 96%; + --border: 28 24% 87%; + --input: 28 24% 87%; + --ring: 0 72% 42%; + --radius: 0.5rem; + } - .dark { - --background: 24 33% 10%; - --foreground: 45 100% 96%; - --card: 24 29% 14%; - --card-foreground: 45 100% 96%; - --popover: 24 29% 14%; - --popover-foreground: 45 100% 96%; - --primary: 34 92% 52%; - --primary-foreground: 24 33% 10%; - --secondary: 24 20% 20%; - --secondary-foreground: 45 100% 96%; - --muted: 24 18% 18%; - --muted-foreground: 28 15% 66%; - --accent: 24 20% 20%; - --accent-foreground: 45 100% 96%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 24 18% 24%; - --input: 24 18% 24%; - --ring: 34 92% 52%; - } + .dark { + --background: 20 9% 10%; + --foreground: 40 33% 94%; + --card: 24 9% 14%; + --card-foreground: 40 33% 94%; + --popover: 24 9% 14%; + --popover-foreground: 40 33% 94%; + --primary: 0 72% 50%; + --primary-foreground: 20 9% 10%; + --secondary: 24 13% 22%; + --secondary-foreground: 40 33% 94%; + --muted: 24 6% 25%; + --muted-foreground: 24 6% 64%; + --accent: 28 82% 32%; + --accent-foreground: 40 33% 94%; + --destructive: 0 84% 60%; + --destructive-foreground: 20 9% 10%; + --border: 24 6% 33%; + --input: 24 6% 33%; + --ring: 0 72% 50%; + } } @layer base { - * { - border-color: hsl(var(--border)); - } - body { - background-color: hsl(var(--background)); - color: hsl(var(--foreground)); - background-image: - radial-gradient(circle at top, rgba(255, 188, 66, 0.10), transparent 34%), - radial-gradient(circle at bottom right, rgba(122, 69, 22, 0.08), transparent 28%); - } + * { + border-color: hsl(var(--border)); + } + body { + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + background-image: + repeating-linear-gradient( + 90deg, + transparent, + transparent 2px, + rgba(120, 53, 15, 0.015) 2px, + rgba(120, 53, 15, 0.015) 4px + ), + radial-gradient( + circle at 15% 50%, + rgba(217, 119, 6, 0.06) 0%, + transparent 50% + ), + radial-gradient( + circle at 85% 80%, + rgba(185, 28, 28, 0.04) 0%, + transparent 40% + ); + } + + .dark body { + background-image: + repeating-linear-gradient( + 90deg, + transparent, + transparent 2px, + rgba(251, 191, 36, 0.012) 2px, + rgba(251, 191, 36, 0.012) 4px + ), + radial-gradient( + circle at 15% 50%, + rgba(217, 119, 6, 0.05) 0%, + transparent 50% + ), + radial-gradient( + circle at 85% 80%, + rgba(185, 28, 28, 0.03) 0%, + transparent 40% + ); + } } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 6daddc4..cfc88f6 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,29 +1,37 @@ -import type { Metadata } from 'next'; -import { Inter } from 'next/font/google'; -import './globals.css'; -import { ProvidersWrapper } from '@/components/providers-wrapper'; -import { Toaster } from 'sonner'; +import type { Metadata } from "next"; +import { Abril_Fatface, Inter } from "next/font/google"; +import "./globals.css"; +import { Toaster } from "sonner"; +import { ProvidersWrapper } from "@/components/providers-wrapper"; -const inter = Inter({ subsets: ['latin'] }); +const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); +const abril = Abril_Fatface({ + weight: "400", + subsets: ["latin"], + variable: "--font-abril", +}); export const metadata: Metadata = { - title: 'CoopCredits - Media Rewards', - description: 'Earn $COOP tokens for watching content on Plex', + title: "CoopCredits — Watch, Earn, Cluck", + description: + "Earn $COOP tokens for watching content on Plex. The Hoboken Chicken coop rewards its flock.", }; export default function RootLayout({ - children, + children, }: { - children: React.ReactNode; + children: React.ReactNode; }) { - return ( - - - - {children} - - - - - ); + return ( + + + + {children} + + + + + ); } diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 2fa80b0..3c87754 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -1,9 +1,9 @@ "use client"; -import { useEffect, useState } from "react"; +import { Egg, Loader2, Tv } from "lucide-react"; import { useRouter } from "next/navigation"; -import { authApi } from "@/lib/api"; -import { useStore } from "@/lib/store"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Card, @@ -12,8 +12,8 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Loader2, Tv } from "lucide-react"; -import { toast } from "sonner"; +import { authApi } from "@/lib/api"; +import { useStore } from "@/lib/store"; export default function LoginPage() { const router = useRouter(); @@ -46,13 +46,13 @@ export default function LoginPage() { if (!isMounted) { return ( -
+
- +
- CoopCoins + The Coop Loading...
@@ -61,15 +61,17 @@ export default function LoginPage() { } return ( -
- +
+
- +
- CoopCoins + + Enter the Coop + - Earn CoopCoins ($COOP) for watching content on Plex + Sign in with Plex to start earning $COOP tokens
@@ -91,7 +93,7 @@ export default function LoginPage() { )}

- Sign in with your Plex account to start earning rewards + Join the flock and get rewarded for your watch time

diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index ca95fb3..3aae86f 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,184 +1,264 @@ -'use client'; +"use client"; -import Link from 'next/link'; -import { Button } from '@/components/ui/button'; -import { Tv, Wallet, Zap, Shield, ChevronRight, PlayCircle, Info } from 'lucide-react'; +import { + ChevronRight, + Coins, + Egg, + Film, + PlayCircle, + Shield, + Tv, + Wallet, + Zap, +} from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; export default function LandingPage() { - return ( -
- {/* Grid Pattern Overlay */} -
- - {/* Header */} -
-
-
-
- -
- CoopCoins -
- - + return ( +
+ {/* Grain Pattern Overlay */} +
- - - -
-
+ {/* Header */} +
+
+
+
+ +
+ + CoopCredits + +
-
- {/* Hero Section */} -
-
-
-
- - Powered by Solana! -
- -

- Earn CoopCoins for Your Watch Time! -

- -

- Earn CoopCoins ($COOP) automatically for enjoying shows on Plex. Then, use them to request new favorites on Overseer! -

+ -
- - - - - - -
-
-
- - {/* Abstract background element */} -
-
+ + + +
+
- {/* Features Section */} -
-
-
-
-
- -
-

Watch to Earn

-

- Every minute you watch on Plex earns you CoopCoins ($COOP)! No extra steps, just enjoy. -

-
+
+ {/* Hero Section */} +
+
+
+
+ + Blockchain-Powered Barnyard +
-
-
- -
-

Solana Powered

-

- Your CoopCoins are securely handled on the Solana blockchain – all transparent and super fast. -

-
+

+ Welcome to the Coop, Friend +

-
-
- -
-

Spend Credits

-

- Got enough CoopCoins ($COOP)? Use them to request new movies or shows on Overseer. You help shape our library! -

-
-
-
-
+

+ Earn $COOP tokens automatically for every minute you watch on + Plex. Then spend them like golden eggs to request new movies & + shows. +

- {/* How it Works Section */} -
-
-
-
-

Simple. Transparent. Fun.

-

"Our way of saying thanks for being part of the Hoboken Chicken family!"

-
+
+ + + + + + +
+
+
-
-
-
1
-
-

Connect your Plex Account

-

Just log in with Plex. We'll set up your Solana wallet automatically in the background.

-
-
+ {/* Warm glow background element */} +
+
-
-
2
-
-

Watch your Favorite Content

-

Enjoy movies and shows as usual. Tautulli tracks your fun, and CoopCoins ($COOP) land in your wallet.

-
-
+ {/* Features Section */} +
+
+
+

+ What the Coop Offers +

+

+ A little barnyard magic mixed with blockchain tech. +

+
+
+
+
+ +
+

Watch & Cluck

+

+ Every minute on Plex earns $COOP. No extra steps — just kick + back and enjoy the show. +

+
-
-
3
-
-

Redeem and Repeat

-

Spend your CoopCoins ($COOP) to pick new things for the server via our Overseer integration. Happy requesting!

-
-
-
+
+
+ +
+

Solana Strong

+

+ Your $COOP lives on the Solana blockchain — fast, transparent, + and secure as a locked coop. +

+
-
-
-

Ready to start earning?

-

Come join our family and friends earning rewards for their watch time!

-
- - - -
-
-
-
- +
+
+ +
+

Spend Your Eggs

+

+ Cash in $COOP to request new movies or shows on Overseer. You + help build the library. +

+
+
+
+ - {/* Footer */} -
-
-
-
- - CoopCoins -
- -
-

© {new Date().getFullYear()} Hoboken Chicken. All rights reserved.

-

- Built with on Solana -

-
-
-
-
-
- ); + {/* How it Works Section */} +
+
+
+
+

+ Simple as Counting Chickens +

+

+ “Our way of saying thanks for being part of the Hoboken + Chicken family!” +

+
+ +
+
+
+ 1 +
+
+

+ Connect Your Plex Account +

+

+ Just log in with Plex. We set up your Solana wallet + quietly in the background. +

+
+
+ +
+
+ 2 +
+
+

Watch Your Favorites

+

+ Enjoy movies and shows as usual. Tautulli tracks your + time, and $COOP lands in your wallet. +

+
+
+ +
+
+ 3 +
+
+

Redeem & Repeat

+

+ Spend $COOP to pick new things for the server via + Overseer. Happy requesting! +

+
+
+
+ +
+
+

Ready to roost?

+

+ Come join the flock and earn rewards for your watch time. +

+
+ + + +
+
+
+
+ + + {/* Footer */} + +
+ ); }