chore: initial commit

This commit is contained in:
devcodetools committed 2026-09-21 17:28:26 +02:00
commit 03f03f7ad2
39 files changed
+7292

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+1
View File
@@ -0,0 +1 @@
@import 'tailwindcss';
+63
View File
@@ -0,0 +1,63 @@
import { SerwistProvider } from '@serwist/turbopack/react'
import type { Metadata, Viewport } from 'next'
import type { ReactNode } from 'react'
import './globals.css'
const APP_NAME = 'flashcards'
const APP_DESCRIPTION = 'Application web PWA installable et utilisable hors connexion.'
const APP_TITLE_TEMPLATE = '%s · flashcards'
export const metadata: Metadata = {
applicationName: APP_NAME,
title: {
default: APP_NAME,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
manifest: '/manifest.webmanifest',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: APP_NAME,
},
formatDetection: {
telephone: false,
},
icons: {
icon: '/icon.svg',
apple: '/apple-touch-icon-180x180.png',
},
openGraph: {
type: 'website',
siteName: APP_NAME,
title: {
default: APP_NAME,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
},
twitter: {
card: 'summary',
title: {
default: APP_NAME,
template: APP_TITLE_TEMPLATE,
},
description: APP_DESCRIPTION,
},
}
export const viewport: Viewport = {
themeColor: '#0f172a',
}
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="fr">
<body>
<SerwistProvider swUrl="/serwist/sw.js" disable={process.env.NODE_ENV === 'development'}>
{children}
</SerwistProvider>
</body>
</html>
)
}
+27
View File
@@ -0,0 +1,27 @@
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'flashcards',
short_name: 'flashcards',
description: 'Application web PWA installable et utilisable hors connexion.',
lang: 'fr',
start_url: '/',
scope: '/',
display: 'standalone',
background_color: '#0f172a',
theme_color: '#0f172a',
orientation: 'portrait',
icons: [
{ src: '/pwa-64x64.png', sizes: '64x64', type: 'image/png' },
{ src: '/pwa-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png' },
{
src: '/maskable-icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
}
}
+10
View File
@@ -0,0 +1,10 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import Home from './page'
describe('Home', () => {
it('affiche le contenu minimal', () => {
render(<Home />)
expect(screen.getByRole('heading', { level: 1, name: 'Hello world' })).toBeInTheDocument()
})
})
+7
View File
@@ -0,0 +1,7 @@
export default function Home() {
return (
<main>
<h1>Hello world</h1>
</main>
)
}
+14
View File
@@ -0,0 +1,14 @@
import { spawnSync } from 'node:child_process'
import { createSerwistRoute } from '@serwist/turbopack'
// Révision du cache : change à chaque commit pour invalider le precache.
const revision =
spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf-8' }).stdout ?? crypto.randomUUID()
export const { dynamic, dynamicParams, revalidate, generateStaticParams, GET } = createSerwistRoute(
{
additionalPrecacheEntries: [{ url: '/~offline', revision }],
swSrc: 'src/app/sw.ts',
useNativeEsbuild: true,
},
)
+41
View File
@@ -0,0 +1,41 @@
/// <reference lib="webworker" />
import { defaultCache } from '@serwist/turbopack/worker'
import type { PrecacheEntry, RuntimeCaching, SerwistGlobalConfig } from 'serwist'
import { NetworkOnly, Serwist } from 'serwist'
declare global {
interface WorkerGlobalScope extends SerwistGlobalConfig {
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined
}
}
declare const self: ServiceWorkerGlobalScope
// Ne jamais mettre en cache les appels API
const runtimeCaching: RuntimeCaching[] = [
{
matcher: ({ sameOrigin, url }) => sameOrigin && url.pathname.startsWith('/api/'),
handler: new NetworkOnly(),
},
...defaultCache,
]
const serwist = new Serwist({
precacheEntries: self.__SW_MANIFEST,
skipWaiting: true,
clientsClaim: true,
navigationPreload: true,
runtimeCaching,
fallbacks: {
entries: [
{
url: '/~offline',
matcher({ request }) {
return request.mode === 'navigate'
},
},
],
},
})
serwist.addEventListeners()
+11
View File
@@ -0,0 +1,11 @@
import Link from 'next/link'
export default function OfflinePage() {
return (
<main>
<h1>Vous êtes hors connexion</h1>
<p>Reconnectez-vous pour continuer.</p>
<Link href="/">Accueil</Link>
</main>
)
}
+19
View File
@@ -0,0 +1,19 @@
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'
afterEach(() => cleanup())
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: (query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => false,
}),
})