Browse Source

touch ups

drawing-pad
Stephanie Gredell 1 month ago
parent
commit
5eef4096da
  1. 15
      frontend/index.html
  2. BIN
      frontend/public/tic-tac-toe.png
  3. 97
      frontend/src/App.tsx
  4. 28
      frontend/src/components/Navbar/Navbar.tsx
  5. 10
      frontend/src/config/apps.ts
  6. 19
      frontend/src/pages/LandingPage.tsx
  7. 16
      frontend/vite.config.ts

15
frontend/index.html

@ -4,9 +4,19 @@ @@ -4,9 +4,19 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Resource hints -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Butterfly+Kids&display=swap" rel="stylesheet">
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<!-- Critical images preload -->
<link rel="preload" href="/rainbow.png" as="image" type="image/png" fetchpriority="high">
<link rel="preload" href="/cupcake.png" as="image" type="image/png" fetchpriority="high">
<!-- Preload first game icon (likely LCP element) -->
<link rel="preload" href="/video-marketing.png" as="image" type="image/png" fetchpriority="high">
<!-- Async font loading -->
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Butterfly+Kids&display=swap&text=Rainbows%2CCupcakesUnicorns" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link href="https://fonts.googleapis.com/css2?family=Butterfly+Kids&display=swap" rel="stylesheet"></noscript>
<title>Rainbow, Cupcakes & Unicorns - Free Games for Kids</title>
</head>
<body class="font-sans antialiased">
@ -14,6 +24,3 @@ @@ -14,6 +24,3 @@
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

BIN
frontend/public/tic-tac-toe.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

97
frontend/src/App.tsx

@ -1,17 +1,30 @@ @@ -1,17 +1,30 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Suspense, lazy } from 'react';
import { AuthProvider } from './hooks/useAuth';
import { ErrorBoundary } from './components/ErrorBoundary';
import { Navbar } from './components/Navbar/Navbar';
import { Footer } from './components/Footer/Footer';
import { ProtectedRoute } from './components/ProtectedRoute';
import { LandingPage } from './pages/LandingPage';
import { AdminPage } from './pages/AdminPage';
import { VideosAdminPage } from './pages/VideosAdminPage';
import { SpeechSoundsAdminPage } from './pages/SpeechSoundsAdminPage';
import { LoginPage } from './pages/LoginPage';
import { APPS } from './config/apps';
import './globals.css';
// Lazy load admin and login pages
const AdminPage = lazy(() => import('./pages/AdminPage').then(module => ({ default: module.AdminPage })));
const VideosAdminPage = lazy(() => import('./pages/VideosAdminPage').then(module => ({ default: module.VideosAdminPage })));
const SpeechSoundsAdminPage = lazy(() => import('./pages/SpeechSoundsAdminPage').then(module => ({ default: module.SpeechSoundsAdminPage })));
const LoginPage = lazy(() => import('./pages/LoginPage').then(module => ({ default: module.LoginPage })));
// Loading fallback component
const PageLoader = () => (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary mb-4"></div>
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
);
function App() {
return (
<ErrorBoundary>
@ -20,43 +33,49 @@ function App() { @@ -20,43 +33,49 @@ function App() {
<div className="min-h-screen flex flex-col">
<Navbar />
<main className="flex-1">
<Routes>
<Route path="/" element={<LandingPage />} />
{/* Dynamically generate routes for enabled apps */}
{APPS.filter(app => !app.disabled).map(app => (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<LandingPage />} />
{/* Dynamically generate routes for enabled apps */}
{APPS.filter(app => !app.disabled).map(app => (
<Route
key={app.id}
path={app.link}
element={
<Suspense fallback={<PageLoader />}>
<app.component />
</Suspense>
}
/>
))}
{/* Keep non-app routes separate */}
<Route path="/login" element={<LoginPage />} />
<Route
path="/admin"
element={
<ProtectedRoute>
<AdminPage />
</ProtectedRoute>
}
/>
<Route
path="/admin/videos"
element={
<ProtectedRoute>
<VideosAdminPage />
</ProtectedRoute>
}
/>
<Route
key={app.id}
path={app.link}
element={<app.component />}
path="/admin/speech-sounds"
element={
<ProtectedRoute>
<SpeechSoundsAdminPage />
</ProtectedRoute>
}
/>
))}
{/* Keep non-app routes separate */}
<Route path="/login" element={<LoginPage />} />
<Route
path="/admin"
element={
<ProtectedRoute>
<AdminPage />
</ProtectedRoute>
}
/>
<Route
path="/admin/videos"
element={
<ProtectedRoute>
<VideosAdminPage />
</ProtectedRoute>
}
/>
<Route
path="/admin/speech-sounds"
element={
<ProtectedRoute>
<SpeechSoundsAdminPage />
</ProtectedRoute>
}
/>
</Routes>
</Routes>
</Suspense>
</main>
<Footer />
</div>

28
frontend/src/components/Navbar/Navbar.tsx

@ -72,17 +72,25 @@ export function Navbar() { @@ -72,17 +72,25 @@ export function Navbar() {
<div className="max-w-5xl mx-auto px-4 py-5">
<div className="flex items-center gap-3 justify-between">
<Link to="/" className="flex items-center gap-3">
<img
src="/rainbow.png"
alt="Rainbow"
className="h-10 w-10 md:h-12 md:w-12 object-contain"
/>
<img
src="/rainbow.png"
alt="Rainbow"
className="h-10 w-10 md:h-12 md:w-12 object-contain"
width="48"
height="48"
loading="eager"
fetchPriority="high"
/>
<h1 className="text-3xl md:text-4xl font-bold text-foreground" style={{ fontFamily: "'Butterfly Kids', cursive" }}>Rainbows, Cupcakes & Unicorns</h1>
<img
src="/cupcake.png"
alt="Cupcake"
className="h-10 w-10 md:h-12 md:w-12 object-contain"
/>
<img
src="/cupcake.png"
alt="Cupcake"
className="h-10 w-10 md:h-12 md:w-12 object-contain"
width="48"
height="48"
loading="eager"
fetchPriority="high"
/>
</Link>
<div className="flex items-center gap-3">

10
frontend/src/config/apps.ts

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
import React from 'react';
import { VideoApp } from '../pages/VideoApp';
import { SpeechSoundsApp } from '../pages/SpeechSoundsApp';
import { TicTacToeApp } from '../pages/TicTacToeApp';
import React, { lazy } from 'react';
// Lazy load game pages for code splitting
const VideoApp = lazy(() => import('../pages/VideoApp').then(module => ({ default: module.VideoApp })));
const SpeechSoundsApp = lazy(() => import('../pages/SpeechSoundsApp').then(module => ({ default: module.SpeechSoundsApp })));
const TicTacToeApp = lazy(() => import('../pages/TicTacToeApp').then(module => ({ default: module.TicTacToeApp })));
export type App = {
id: string;

19
frontend/src/pages/LandingPage.tsx

@ -28,6 +28,7 @@ export function LandingPage() { @@ -28,6 +28,7 @@ export function LandingPage() {
<div className="bg-background">
<section className="px-4 py-8">
<div className="max-w-5xl mx-auto">
{/* First card is likely LCP element - prioritize it */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
{APPS.map(app => {
const color = categoryColors[app.id] || 'pink';
@ -52,12 +53,30 @@ export function LandingPage() { @@ -52,12 +53,30 @@ export function LandingPage() {
src="/video-marketing.png"
alt="Video App"
className="w-20 h-20 object-contain"
width="80"
height="80"
loading="eager"
fetchPriority={app.id === 'videos' ? 'high' : 'auto'}
/>
) : app.id === 'speechsounds' ? (
<img
src="/unicorn-talking.png"
alt="Speech Sounds"
className="w-20 h-20 object-contain"
width="80"
height="80"
loading="eager"
fetchPriority="auto"
/>
) : app.id === 'tictactoe' ? (
<img
src="/tic-tac-toe.png"
alt="Tic Tac Toe"
className="w-20 h-20 object-contain"
width="80"
height="80"
loading="eager"
fetchPriority="auto"
/>
) : (
<span className="text-5xl">{emoji}</span>

16
frontend/vite.config.ts

@ -5,6 +5,22 @@ export default defineConfig({ @@ -5,6 +5,22 @@ export default defineConfig({
plugins: [react()],
server: {
port: 5173
},
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'ui-vendor': ['axios']
}
}
},
chunkSizeWarningLimit: 1000,
cssCodeSplit: true,
minify: 'esbuild'
},
optimizeDeps: {
include: ['react', 'react-dom', 'react-router-dom']
}
})

Loading…
Cancel
Save