homepage and logo

This commit is contained in:
unurled 2025-06-24 20:32:26 +02:00
parent c9d982669a
commit ef6dadb148
12 changed files with 359 additions and 242 deletions

View file

@ -3,13 +3,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Competition; use App\Models\Competition;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
class CompetitionController extends Controller class CompetitionController extends Controller
{ {
public static function getPublics(int $skip = 0, int $take = 10): JsonResponse
public static function getPublicCompetitions(int $skip = 0, int $take = 10): JsonResponse
{ {
if ($skip < 0) { if ($skip < 0) {
$skip = 0; $skip = 0;
@ -37,6 +37,35 @@ class CompetitionController extends Controller
]); ]);
} }
public static function getUser(User $user, int $skip = 0, int $take = 10): JsonResponse
{
if ($skip < 0) {
$skip = 0;
}
if ($take < 1 || $take > 100) {
$take = 10;
}
$query = Competition::where('owner', $user->id);
$competitions = $query->orderBy('start_date', 'desc')
->skip($skip)
->take($take)
->get();
$total = $query->count();
return response()->json([
'data' => $competitions,
'meta' => [
'skip' => $skip,
'take' => $take,
'total' => $total,
'hasMore' => ($skip + $take) < $total
]
]);
}
/** /**
* Get all public competitions/tournaments with pagination. * Get all public competitions/tournaments with pagination.
* *

View file

@ -25,6 +25,7 @@ class Competition extends Model
'location', 'location',
'max_teams', 'max_teams',
'current_scheduling_mode_id', 'current_scheduling_mode_id',
'owner',
]; ];
/** /**

View file

@ -22,6 +22,7 @@ return new class extends Migration
$table->string('location')->nullable(); $table->string('location')->nullable();
$table->integer('max_teams')->default(0); $table->integer('max_teams')->default(0);
$table->integer('current_scheduling_mode_id')->nullable(); $table->integer('current_scheduling_mode_id')->nullable();
$table->uuid('owner')->nullable();
$table->timestamps(); $table->timestamps();
}); });

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -42,11 +42,6 @@
]; ];
const rightNavItems: NavItem[] = [ const rightNavItems: NavItem[] = [
{
title: 'Repository',
href: 'https://github.com/oseughu/svelte-starter-kit',
icon: Folder,
},
{ {
title: 'Documentation', title: 'Documentation',
href: 'https://laravel.com/docs/starter-kits', href: 'https://laravel.com/docs/starter-kits',
@ -101,9 +96,7 @@
</Sheet> </Sheet>
</div> </div>
<Link href={route('dashboard')} class="flex items-center gap-x-2">
<AppLogo /> <AppLogo />
</Link>
<!-- Desktop Menu --> <!-- Desktop Menu -->
<div class="hidden h-full lg:flex lg:flex-1"> <div class="hidden h-full lg:flex lg:flex-1">

View file

@ -1,12 +1,45 @@
<script lang="ts"> <script lang="ts">
import AppLogoIcon from '@/components/AppLogoIcon.svelte'; import AppLogoIcon from '@/components/AppLogoIcon.svelte';
import { type Appearance, useAppearance } from '@/hooks/useAppearance.svelte';
import { MonitorIcon, Moon, Sun } from 'lucide-svelte';
import Button from './ui/button/button.svelte';
import { Link } from '@inertiajs/svelte';
const appearanceManager = useAppearance();
// Create a local reactive variable that tracks the hook's value
let currentAppearance = $state(appearanceManager.appearance);
function handleUpdateAppearance(value: Appearance) {
appearanceManager.updateAppearance(value);
// Immediately update local state to ensure UI reflects the change
currentAppearance = value;
}
</script> </script>
<div class="inline-flex w-full justify-around">
<div class="inline-flex items-center gap-3"> <div class="inline-flex items-center gap-3">
<div class="flex aspect-square size-8 shrink-0 items-center justify-center rounded-md bg-sidebar-primary"> <div class="flex aspect-square size-8 shrink-0 items-center justify-center rounded-md">
<AppLogoIcon class="size-5 fill-current text-white! dark:text-black!" /> <AppLogoIcon class="size-5 object-cover" />
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<span class="truncate text-sm font-semibold">Laravel Starter Kit</span> <Link href={route('home')} class="flex items-center gap-x-2">
<span class="truncate text-sm font-semibold">FlbxCup</span>
</Link>
</div>
</div>
<div class="flex items-center">
{#if currentAppearance === 'light'}
<Button onclick={() => handleUpdateAppearance('dark')}>
<Sun class="h-4 w-4" />
</Button>
{:else if currentAppearance === 'dark'}
<Button onclick={() => handleUpdateAppearance('system')}>
<Moon class="h-4 w-4" />
</Button>
{:else}
<Button onclick={() => handleUpdateAppearance('light')}>
<MonitorIcon class="h-4 w-4" />
</Button>
{/if}
</div> </div>
</div> </div>

File diff suppressed because one or more lines are too long

View file

@ -4,9 +4,10 @@
import NavUser from '@/components/NavUser.svelte'; import NavUser from '@/components/NavUser.svelte';
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
import { type NavItem } from '@/types'; import { type NavItem } from '@/types';
import { Link } from '@inertiajs/svelte'; import { Link, page } from '@inertiajs/svelte';
import { BookOpen, Folder, LayoutGrid } from 'lucide-svelte'; import { BookOpen, Folder, LayoutGrid } from 'lucide-svelte';
import AppLogo from './AppLogo.svelte'; import AppLogo from './AppLogo.svelte';
import Button from './ui/button/button.svelte';
const mainNavItems: NavItem[] = [ const mainNavItems: NavItem[] = [
{ {
@ -17,11 +18,6 @@
]; ];
const footerNavItems: NavItem[] = [ const footerNavItems: NavItem[] = [
{
title: 'Repository',
href: 'https://github.com/oseughu/svelte-starter-kit',
icon: Folder,
},
{ {
title: 'Admin Dashboard', title: 'Admin Dashboard',
href: '/admin', href: '/admin',
@ -36,9 +32,7 @@
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton size="lg"> <SidebarMenuButton size="lg">
<Link href={route('dashboard')}>
<AppLogo /> <AppLogo />
</Link>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
@ -50,6 +44,12 @@
<SidebarFooter> <SidebarFooter>
<NavFooter items={footerNavItems} class="mt-auto" /> <NavFooter items={footerNavItems} class="mt-auto" />
{#if $page.props.auth.user}
<NavUser /> <NavUser />
{:else}
<Button>
<a href={route('auth.redirect')} class="flex items-center gap-2">Login</a>
</Button>
{/if}
</SidebarFooter> </SidebarFooter>
</Sidebar> </Sidebar>

View file

@ -4,7 +4,7 @@
import type { User } from '@/types'; import type { User } from '@/types';
interface Props { interface Props {
user: User; user?: User;
showEmail?: boolean; showEmail?: boolean;
} }
@ -12,9 +12,10 @@
const { getInitials } = useInitials(); const { getInitials } = useInitials();
let showAvatar = $derived(user.avatar && user.avatar !== ''); let showAvatar = $derived(user?.avatar && user?.avatar !== '');
</script> </script>
{#if user}
<Avatar class="h-8 w-8 overflow-hidden rounded-full"> <Avatar class="h-8 w-8 overflow-hidden rounded-full">
{#if showAvatar} {#if showAvatar}
<AvatarImage src={user.avatar} alt={user.name} /> <AvatarImage src={user.avatar} alt={user.name} />
@ -32,3 +33,4 @@
<span class="truncate text-xs text-muted-foreground">{user.email}</span> <span class="truncate text-xs text-muted-foreground">{user.email}</span>
{/if} {/if}
</div> </div>
{/if}

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Link, page } from '@inertiajs/svelte'; import { Deferred, Link, page } from '@inertiajs/svelte';
import AppLayout from '@/layouts/AppLayout.svelte'; import AppLayout from '@/layouts/AppLayout.svelte';
import { type BreadcrumbItem } from '@/types'; import { type BreadcrumbItem } from '@/types';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -7,54 +7,7 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Trophy, Users, Calendar, User } from 'lucide-svelte'; import { Trophy, Users, Calendar, User } from 'lucide-svelte';
// Mock data for tournaments - replace with API calls when backend is ready let { publicTournaments, userTournaments } = $props();
const publicTournaments = [
{
id: '1',
name: 'Summer Championship 2024',
description: 'Annual summer championship with multiple categories',
date: '2024-07-15',
participantsCount: 48,
isPublic: true,
},
{
id: '2',
name: 'Regional Qualifiers',
description: 'Regional qualifiers for the national tournament',
date: '2024-06-05',
participantsCount: 32,
isPublic: true,
},
{
id: '3',
name: 'Friendly Tournament',
description: 'Casual tournament for all skill levels',
date: '2024-05-20',
participantsCount: 16,
isPublic: true,
},
];
// User tournaments - only displayed when logged in
const userTournaments = [
{
id: '4',
name: 'My Club Championship',
description: 'Internal club championship tournament',
date: '2024-06-18',
participantsCount: 12,
isPublic: false,
},
{
id: '5',
name: 'Training Tournament',
description: 'Practice tournament for team members',
date: '2024-05-10',
participantsCount: 8,
isPublic: false,
},
];
const breadcrumbs: BreadcrumbItem[] = [ const breadcrumbs: BreadcrumbItem[] = [
{ {
title: 'Home', title: 'Home',
@ -80,6 +33,12 @@
<title>Home - FlbxCup</title> <title>Home - FlbxCup</title>
</svelte:head> </svelte:head>
<Deferred data="publicTournaments">
{#snippet fallback()}
<AppLayout {breadcrumbs}>
<div>Loading...</div>
</AppLayout>
{/snippet}
<AppLayout {breadcrumbs}> <AppLayout {breadcrumbs}>
<div class="space-y-8 px-4 py-6 md:px-8"> <div class="space-y-8 px-4 py-6 md:px-8">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
@ -97,28 +56,6 @@
<p class="text-lg text-muted-foreground">Discover and manage tournaments with ease</p> <p class="text-lg text-muted-foreground">Discover and manage tournaments with ease</p>
</div> </div>
{#if !isAuthenticated}
<Card>
<CardHeader>
<CardTitle>Join FlbxCup</CardTitle>
<CardDescription>Create an account to create and manage your own tournaments</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-4 sm:flex-row">
<Button>
<Link href={route('login')} class="flex items-center gap-2">Login</Link>
</Button>
<Button variant="outline">
<Link href={route('register')} class="flex items-center gap-2">Register</Link>
</Button>
<Button variant="outline">
<Link href={route('auth.redirect')} class="flex items-center gap-2">Login with SSO</Link>
</Button>
</div>
</CardContent>
</Card>
{/if}
{#if isAuthenticated} {#if isAuthenticated}
<Tabs value="public" class="w-full"> <Tabs value="public" class="w-full">
<TabsList> <TabsList>
@ -127,7 +64,17 @@
</TabsList> </TabsList>
<TabsContent value="public" class="mt-6"> <TabsContent value="public" class="mt-6">
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#each publicTournaments as tournament (tournament.id)} {#if !publicTournaments.original.data || publicTournaments.original.data.length === 0}
<div class="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
<Trophy class="h-10 w-10 text-muted-foreground" />
<h3 class="mt-4 text-lg font-semibold">No Tournaments Yet</h3>
<p class="mt-2 text-sm text-muted-foreground">You haven't created any tournaments yet.</p>
<Button class="mt-4">
<Link href="/tournaments/create" class="flex items-center gap-2">Create Your First Tournament</Link>
</Button>
</div>
{/if}
{#each publicTournaments.original.data as tournament (tournament.id)}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle class="flex items-center gap-2"> <CardTitle class="flex items-center gap-2">
@ -160,7 +107,8 @@
</div> </div>
</TabsContent> </TabsContent>
<TabsContent value="my" class="mt-6"> <TabsContent value="my" class="mt-6">
{#if userTournaments.length === 0} <div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#if !userTournaments.original.data || userTournaments.original.data.length === 0}
<div class="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center"> <div class="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
<Trophy class="h-10 w-10 text-muted-foreground" /> <Trophy class="h-10 w-10 text-muted-foreground" />
<h3 class="mt-4 text-lg font-semibold">No Tournaments Yet</h3> <h3 class="mt-4 text-lg font-semibold">No Tournaments Yet</h3>
@ -171,7 +119,7 @@
</div> </div>
{:else} {:else}
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#each userTournaments as tournament (tournament.id)} {#each userTournaments.original.data as tournament (tournament.id)}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle class="flex items-center gap-2"> <CardTitle class="flex items-center gap-2">
@ -198,7 +146,9 @@
</CardContent> </CardContent>
<CardFooter class="flex gap-2"> <CardFooter class="flex gap-2">
<Button variant="outline" class="flex-1"> <Button variant="outline" class="flex-1">
<Link href={`/tournaments/${tournament.id}`} class="flex items-center justify-center w-full">View</Link> <Link href={`/tournaments/${tournament.id}`} class="flex items-center justify-center w-full"
>View</Link
>
</Button> </Button>
<Button variant="secondary" class="flex-1"> <Button variant="secondary" class="flex-1">
<Link href={`/tournaments/${tournament.id}/edit`} class="flex items-center justify-center w-full"> <Link href={`/tournaments/${tournament.id}/edit`} class="flex items-center justify-center w-full">
@ -210,13 +160,37 @@
{/each} {/each}
</div> </div>
{/if} {/if}
</div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
{:else} {:else}
<Card>
<CardHeader>
<CardTitle>Join FlbxCup</CardTitle>
<CardDescription>Create an account to create and manage your own tournaments</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-4 sm:flex-row">
<Button>
<a href={route('auth.redirect')} class="flex items-center gap-2">Login with SSO</a>
</Button>
</div>
</CardContent>
</Card>
<div class="space-y-6"> <div class="space-y-6">
<h2 class="text-2xl font-semibold tracking-tight">Public Tournaments</h2> <h2 class="text-2xl font-semibold tracking-tight">Public Tournaments</h2>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#each publicTournaments as tournament (tournament.id)} {#if !publicTournaments.original.data || publicTournaments.original.data.length === 0}
<div class="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
<Trophy class="h-10 w-10 text-muted-foreground" />
<h3 class="mt-4 text-lg font-semibold">No Tournaments Yet</h3>
<p class="mt-2 text-sm text-muted-foreground">You haven't created any tournaments yet.</p>
<Button class="mt-4">
<Link href="/tournaments/create" class="flex items-center gap-2">Create Your First Tournament</Link>
</Button>
</div>
{/if}
{#each publicTournaments.original.data as tournament (tournament.id)}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle class="flex items-center gap-2"> <CardTitle class="flex items-center gap-2">
@ -239,7 +213,9 @@
</CardContent> </CardContent>
<CardFooter> <CardFooter>
<Button variant="outline" class="w-full"> <Button variant="outline" class="w-full">
<Link href={`/tournaments/${tournament.id}`} class="flex items-center justify-center w-full">View Details</Link> <Link href={`/tournaments/${tournament.id}`} class="flex items-center justify-center w-full"
>View Details</Link
>
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
@ -249,3 +225,4 @@
{/if} {/if}
</div> </div>
</AppLayout> </AppLayout>
</Deferred>

View file

@ -1,12 +1,20 @@
<?php <?php
use App\Http\Controllers\CompetitionController; use App\Http\Controllers\CompetitionController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Inertia\Inertia; use Inertia\Inertia;
Route::get('/', function () { Route::get('/', function () {
// get user if it exists
$user = Auth::user();
$userTournaments = [];
if ($user) {
$userTournaments = Inertia::defer(fn() => CompetitionController::getUser($user, 1, 10));
}
return Inertia::render('Home', [ return Inertia::render('Home', [
'publicTournaments' => Inertia::defer(fn() => CompetitionController::getPublicCompetitions(1, 10)) 'publicTournaments' => Inertia::defer(fn() => CompetitionController::getPublics(1, 10)),
'userTournaments' => $userTournaments,
]); ]);
})->name('home'); })->name('home');