Skip to content

Commit 6cc3763

Browse files
committed
feat: added version switcher, updated to Next v15
1 parent 7ced2ab commit 6cc3763

File tree

11 files changed

+1163
-857
lines changed

11 files changed

+1163
-857
lines changed

.eslintrc.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
{
2-
"extends": "next/core-web-vitals"
2+
"extends": ["next/core-web-vitals", "next/typescript"],
3+
"rules": {
4+
"@typescript-eslint/no-unused-vars": "warn",
5+
"@typescript-eslint/no-explicit-any": "warn"
6+
}
37
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ To deploy the project, just push the current branch to the `main` branch. Vercel
1919

2020
## Copyright
2121

22-
Copyright © 2001–2024 Thorsten Rinne and the phpMyFAQ Team
22+
Copyright © 2001–2025 Thorsten Rinne and the phpMyFAQ Team

app/globals.css

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,99 @@ body {
3131
text-wrap: balance;
3232
}
3333
}
34+
35+
/* Light mode Swagger UI styles */
36+
.swagger-container-light {
37+
background: white;
38+
border: 1px solid #e5e7eb;
39+
}
40+
41+
.swagger-container-light .swagger-ui .topbar {
42+
display: none;
43+
}
44+
45+
.swagger-container-light .swagger-ui .info {
46+
margin: 20px 0;
47+
color: #374151;
48+
}
49+
50+
.swagger-container-light .swagger-ui .info .title {
51+
color: #111827;
52+
}
53+
54+
.swagger-container-light .swagger-ui .info .description {
55+
color: #6b7280;
56+
}
57+
58+
.swagger-container-light .swagger-ui .scheme-container {
59+
background: #f9fafb;
60+
padding: 10px;
61+
border-radius: 4px;
62+
border: 1px solid #e5e7eb;
63+
}
64+
65+
/* Dark mode Swagger UI styles */
66+
.swagger-container-dark {
67+
background: #1f2937;
68+
border: 1px solid #374151;
69+
}
70+
71+
.swagger-container-dark .swagger-ui {
72+
filter: invert(1) hue-rotate(180deg);
73+
}
74+
75+
.swagger-container-dark .swagger-ui .topbar {
76+
display: none;
77+
}
78+
79+
.swagger-container-dark .swagger-ui .info {
80+
margin: 20px 0;
81+
filter: invert(1) hue-rotate(180deg);
82+
}
83+
84+
.swagger-container-dark .swagger-ui .info .title {
85+
color: #f9fafb !important;
86+
}
87+
88+
.swagger-container-dark .swagger-ui .info .description {
89+
color: #d1d5db !important;
90+
}
91+
92+
.swagger-container-dark .swagger-ui .scheme-container {
93+
background: #374151 !important;
94+
padding: 10px;
95+
border-radius: 4px;
96+
border: 1px solid #4b5563;
97+
filter: invert(1) hue-rotate(180deg);
98+
}
99+
100+
/* Fix for inverted elements in dark mode */
101+
.swagger-container-dark .swagger-ui img,
102+
.swagger-container-dark .swagger-ui svg {
103+
filter: invert(1) hue-rotate(180deg);
104+
}
105+
106+
/* Smooth transitions for theme switching */
107+
* {
108+
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
109+
}
110+
111+
/* Dark mode toggle button styles */
112+
.dark-mode-toggle {
113+
transition: all 0.3s ease;
114+
}
115+
116+
.dark-mode-toggle:hover {
117+
transform: scale(1.05);
118+
}
119+
120+
/* Responsive improvements */
121+
@media (max-width: 640px) {
122+
.swagger-container-light,
123+
.swagger-container-dark {
124+
margin: 0 -1rem;
125+
border-radius: 0;
126+
border-left: none;
127+
border-right: none;
128+
}
129+
}

app/layout.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1-
import type { Metadata } from "next";
2-
import { Inter } from "next/font/google";
1+
import type { Metadata } from 'next';
2+
import { Inter } from 'next/font/google';
33
import React from 'react';
4+
import './globals.css';
45

5-
const inter = Inter({ subsets: ["latin"] });
6+
const inter = Inter({
7+
subsets: ["latin"],
8+
display: 'swap',
9+
});
610

711
export const metadata: Metadata = {
812
title: "API Docs for phpMyFAQ",
913
description: "phpMyFAQ API documentation",
1014
applicationName: "phpMyFAQ API",
15+
keywords: ["phpMyFAQ", "API", "documentation", "FAQ", "REST"],
16+
authors: [{ name: "phpMyFAQ Team" }],
17+
creator: "phpMyFAQ Team",
18+
metadataBase: new URL('https://api-docs.phpmyfaq.de'),
19+
openGraph: {
20+
title: "API Docs for phpMyFAQ",
21+
description: "phpMyFAQ API documentation",
22+
type: "website",
23+
},
1124
};
1225

1326
export default function RootLayout({

app/not-found.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Link from 'next/link';
2+
3+
export default function NotFound() {
4+
return (
5+
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
6+
<div className="text-center">
7+
<h1 className="text-6xl font-bold text-gray-900 dark:text-white mb-4">404</h1>
8+
<h2 className="text-2xl font-semibold text-gray-700 dark:text-gray-300 mb-4">
9+
Page Not Found
10+
</h2>
11+
<p className="text-gray-600 dark:text-gray-400 mb-8">
12+
The page you are looking for does not exist.
13+
</p>
14+
<Link
15+
href="/"
16+
className="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md transition-colors"
17+
>
18+
Back to API Documentation
19+
</Link>
20+
</div>
21+
</div>
22+
);
23+
}

app/page.tsx

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,140 @@
1+
"use client";
2+
3+
import React, { useState, useCallback, useEffect } from 'react';
14
import SwaggerUI from 'swagger-ui-react';
25
import 'swagger-ui-react/swagger-ui.css';
36

4-
export default function Home() {
7+
interface ApiUrls {
8+
[key: string]: string;
9+
}
10+
11+
export default function Home(): React.ReactElement {
12+
const [version, setVersion] = useState<string>('4.0');
13+
const [isDarkMode, setIsDarkMode] = useState<boolean>(false);
14+
15+
// Initialize dark mode based on system preference
16+
useEffect(() => {
17+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
18+
setIsDarkMode(prefersDark);
19+
20+
// Apply dark mode to the document
21+
if (prefersDark) {
22+
document.documentElement.classList.add('dark');
23+
}
24+
}, []);
25+
26+
const handleVersionChange = useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
27+
setVersion(event.target.value);
28+
}, []);
29+
30+
const handleDarkModeToggle = useCallback(() => {
31+
setIsDarkMode(prev => {
32+
const newMode = !prev;
33+
if (newMode) {
34+
document.documentElement.classList.add('dark');
35+
} else {
36+
document.documentElement.classList.remove('dark');
37+
}
38+
return newMode;
39+
});
40+
}, []);
41+
42+
const apiUrls: ApiUrls = {
43+
'4.0': 'https://raw.githubusercontent.com/thorsten/phpMyFAQ/4.0/docs/openapi.json',
44+
'4.1': 'https://raw.githubusercontent.com/thorsten/phpMyFAQ/main/docs/openapi.json',
45+
};
46+
547
return (
6-
<SwaggerUI url="https://raw.githubusercontent.com/thorsten/phpMyFAQ/4.0/docs/openapi.json" />
48+
<div className={`min-h-screen transition-colors duration-300 ${
49+
isDarkMode
50+
? 'bg-gray-900'
51+
: 'bg-gray-50'
52+
}`}>
53+
<div className="swagger-ui mx-auto px-4 py-8">
54+
{/* Main Swagger UI Container with integrated controls */}
55+
<div className={`${
56+
isDarkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'
57+
} rounded-lg border shadow-lg overflow-hidden wrapper`}>
58+
59+
{/* Controls Header inside Swagger container */}
60+
<div className={`${
61+
isDarkMode ? 'bg-gray-700 border-gray-600' : 'bg-gray-50 border-gray-200'
62+
} mx-4 mt-4 mb-2 px-6 py-4 border rounded-lg flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4`}>
63+
64+
{/* Version Selector */}
65+
<div className="flex items-center space-x-3">
66+
<label htmlFor="api-version" className={`font-medium ${
67+
isDarkMode ? 'text-gray-200' : 'text-gray-700'
68+
}`}>
69+
API Version:
70+
</label>
71+
<select
72+
id="api-version"
73+
value={version}
74+
onChange={handleVersionChange}
75+
className={`${
76+
isDarkMode
77+
? 'bg-gray-600 border-gray-500 text-white focus:border-blue-400'
78+
: 'bg-white border-gray-300 text-gray-900 focus:border-blue-500'
79+
} px-3 py-1.5 rounded-md border focus:outline-none focus:ring-2 focus:ring-blue-500/20 transition-colors text-sm`}
80+
aria-label="Select API version"
81+
>
82+
<option value="4.0">Version 4.0</option>
83+
<option value="4.1">Version 4.1</option>
84+
</select>
85+
</div>
86+
87+
{/* Dark Mode Toggle */}
88+
<div className="flex items-center space-x-3">
89+
<span className={`font-medium text-sm ${
90+
isDarkMode ? 'text-gray-200' : 'text-gray-700'
91+
}`}>
92+
Theme:
93+
</span>
94+
<button
95+
onClick={handleDarkModeToggle}
96+
className={`${
97+
isDarkMode
98+
? 'bg-blue-600 hover:bg-blue-700'
99+
: 'bg-gray-600 hover:bg-gray-700'
100+
} relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2`}
101+
aria-label="Toggle dark mode"
102+
>
103+
<span
104+
className={`${
105+
isDarkMode ? 'translate-x-5' : 'translate-x-1'
106+
} inline-block h-3 w-3 transform rounded-full bg-white transition-transform`}
107+
/>
108+
</button>
109+
<span className={`text-xs ${
110+
isDarkMode ? 'text-gray-300' : 'text-gray-600'
111+
}`}>
112+
{isDarkMode ? '🌙' : '☀️'}
113+
</span>
114+
</div>
115+
</div>
116+
117+
{/* Swagger UI Content */}
118+
<div className={`${
119+
isDarkMode ? 'swagger-container-dark' : 'swagger-container-light'
120+
}`}>
121+
<SwaggerUI
122+
url={apiUrls[version]}
123+
deepLinking={true}
124+
displayOperationId={false}
125+
defaultModelsExpandDepth={1}
126+
defaultModelExpandDepth={1}
127+
defaultModelRendering="example"
128+
displayRequestDuration={true}
129+
docExpansion="list"
130+
filter={true}
131+
showExtensions={true}
132+
showCommonExtensions={true}
133+
tryItOutEnabled={true}
134+
/>
135+
</div>
136+
</div>
137+
</div>
138+
</div>
7139
);
8140
}

next.config.mjs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,31 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {};
2+
const nextConfig = {
3+
turbopack: {
4+
5+
},
6+
poweredByHeader: false,
7+
reactStrictMode: true,
8+
async headers() {
9+
return [
10+
{
11+
source: '/(.*)',
12+
headers: [
13+
{
14+
key: 'X-Frame-Options',
15+
value: 'DENY',
16+
},
17+
{
18+
key: 'X-Content-Type-Options',
19+
value: 'nosniff',
20+
},
21+
{
22+
key: 'Referrer-Policy',
23+
value: 'origin-when-cross-origin',
24+
},
25+
],
26+
},
27+
]
28+
},
29+
};
330

431
export default nextConfig;

0 commit comments

Comments
 (0)