UPDATE: Update readme and Next 16.07 (CVE-2025-66478)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadAS } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const asData = await loadAS([id], locale)
|
||||
const as = asData[id]
|
||||
|
||||
if (!as) {
|
||||
return NextResponse.json({ error: 'AS info not found' }, { status: 404 })
|
||||
}
|
||||
return new NextResponse(JSON.stringify(as), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadAS } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const asIds = body.asIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(asIds) || asIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid asIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const asData = await loadAS(asIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(asData), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load as data' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadCharacters } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const characters = await loadCharacters([id], locale)
|
||||
const char = characters[id]
|
||||
|
||||
if (!char) {
|
||||
return NextResponse.json({ error: 'Character not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(char), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadCharacters } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const charIds = body.charIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(charIds) || charIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid charIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const characters = await loadCharacters(charIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(characters), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load characters' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadLightcones } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const lightcones = await loadLightcones([id], locale)
|
||||
const lightcone = lightcones[id]
|
||||
|
||||
if (!lightcone) {
|
||||
return NextResponse.json({ error: 'Lightcone not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(lightcone), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadLightcones } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const lightconeIds = body.lightconeIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(lightconeIds) || lightconeIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid lightconeIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const lightcones = await loadLightcones(lightconeIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(lightcones), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load lightcones' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadMOC } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const mocData = await loadMOC([id], locale)
|
||||
const moc = mocData[id]
|
||||
|
||||
if (!moc) {
|
||||
return NextResponse.json({ error: 'MOC info not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(moc), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadMOC } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const mocIds = body.mocIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(mocIds) || mocIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid mocIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const mocData = await loadMOC(mocIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(mocData), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load moc data' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadMonster } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const monsterData = await loadMonster([id], locale)
|
||||
const monster = monsterData[id]
|
||||
|
||||
if (!monster) {
|
||||
return NextResponse.json({ error: 'Monster info not found' }, { status: 404 })
|
||||
}
|
||||
return new NextResponse(JSON.stringify(monster), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadMonster } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const monsterIds = body.monsterIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(monsterIds) || monsterIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid monsterIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const monsterData = await loadMonster(monsterIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(monsterData), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load monster data' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadPeak } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const peakData = await loadPeak([id], locale)
|
||||
const peak = peakData[id]
|
||||
|
||||
if (!peak) {
|
||||
return NextResponse.json({ error: 'Peak info not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(peak), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadPeak } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const peakIds = body.peakIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(peakIds) || peakIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid peakIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const peakData = await loadPeak(peakIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(peakData), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load peak data' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadPF } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const pfData = await loadPF([id], locale)
|
||||
const pf = pfData[id]
|
||||
|
||||
if (!pf) {
|
||||
return NextResponse.json({ error: 'PF info not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
|
||||
return new NextResponse(JSON.stringify(pf), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { loadPF } from "@/lib/loader";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const pfIds = body.pfIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(pfIds) || pfIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid pfIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const pfData = await loadPF(pfIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(pfData), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load pf data' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { loadRelics } from '@/lib/loader'
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string, locale: string }> }
|
||||
) {
|
||||
|
||||
const { id, locale } = await params
|
||||
const relics = await loadRelics([id], locale)
|
||||
const relic = relics[id]
|
||||
|
||||
if (!relic) {
|
||||
return NextResponse.json({ error: 'Relic not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return new NextResponse(JSON.stringify(relic), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { loadRelics } from "@/lib/loader";
|
||||
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ locale: string }> }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const relicIds = body.relicIds as string[];
|
||||
const { locale } = await params;
|
||||
|
||||
if (!Array.isArray(relicIds) || relicIds.some(id => typeof id !== 'string')) {
|
||||
return NextResponse.json({ error: 'Invalid relicIds' }, { status: 400 });
|
||||
}
|
||||
|
||||
const relics = await loadRelics(relicIds, locale);
|
||||
|
||||
return new NextResponse(JSON.stringify(relics), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load relics' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { loadConfigMaze } from "@/lib/loader";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const configMaze = await loadConfigMaze();
|
||||
return new NextResponse(JSON.stringify(configMaze), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load config maze' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { loadMainAffix } from "@/lib/loader";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const mainAffix = await loadMainAffix();
|
||||
return new NextResponse(JSON.stringify(mainAffix), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load main affix' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import axios from 'axios'
|
||||
import net from 'net'
|
||||
|
||||
function isPrivateHost(hostname: string): boolean {
|
||||
if (
|
||||
hostname === 'localhost' ||
|
||||
hostname.startsWith('127.') ||
|
||||
hostname.startsWith('10.') ||
|
||||
hostname.startsWith('192.168.') ||
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (net.isIP(hostname)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const targetUrl = searchParams.get("url")
|
||||
if (!targetUrl) {
|
||||
return NextResponse.json({ error: "Missing url" }, { status: 400 })
|
||||
}
|
||||
|
||||
const response = await fetch(targetUrl)
|
||||
|
||||
if (!response.ok) {
|
||||
return NextResponse.json({ error: "Failed to fetch image" }, { status: 500 })
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer()
|
||||
|
||||
return new NextResponse(buffer, {
|
||||
headers: {
|
||||
"Content-Type": response.headers.get("content-type") || "image/png",
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
})
|
||||
} catch (e: unknown) {
|
||||
return NextResponse.json({ error: (e as Error)?.message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { serverUrl, method, ...payload } = body
|
||||
|
||||
if (!serverUrl) {
|
||||
return NextResponse.json({ error: 'Missing serverUrl' }, { status: 400 })
|
||||
}
|
||||
if (!method) {
|
||||
return NextResponse.json({ error: 'Missing method' }, { status: 400 })
|
||||
}
|
||||
|
||||
let url = serverUrl.trim()
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = `http://${url}`
|
||||
}
|
||||
|
||||
const parsed = new URL(url)
|
||||
if (isPrivateHost(parsed.hostname)) {
|
||||
return NextResponse.json(
|
||||
{ error: `Connection to private/internal address (${parsed.hostname}) is not allowed` },
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
let response
|
||||
|
||||
switch (method.toUpperCase()) {
|
||||
case 'GET':
|
||||
const queryString = new URLSearchParams(payload as Record<string, string>).toString()
|
||||
const fullUrl = queryString ? `${url}?${queryString}` : url
|
||||
response = await axios.get(fullUrl)
|
||||
break
|
||||
case 'POST':
|
||||
response = await axios.post(url, payload)
|
||||
break
|
||||
case 'PUT':
|
||||
response = await axios.put(url, payload)
|
||||
break
|
||||
case 'DELETE':
|
||||
response = await axios.delete(url, { data: payload })
|
||||
break
|
||||
default:
|
||||
return NextResponse.json({ error: `Unsupported method: ${method}` }, { status: 405 })
|
||||
}
|
||||
|
||||
return NextResponse.json(response.data)
|
||||
} catch (err: unknown) {
|
||||
return NextResponse.json({ error: (err as Error)?.message || 'Proxy failed' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { loadSubAffix } from "@/lib/loader";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const subAffix = await loadSubAffix();
|
||||
return new NextResponse(JSON.stringify(subAffix), {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ error: 'Failed to load sub affix' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
"use client"
|
||||
import EidolonsInfo from "@/components/eidolonsInfo";
|
||||
export default function EidolonsInfoPage() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<EidolonsInfo/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "daisyui" {
|
||||
themes: winter --default, night --prefersdark, cupcake, coffee;
|
||||
}
|
||||
@plugin 'tailwind-scrollbar' {
|
||||
nocompatible: true;
|
||||
}
|
||||
|
||||
:root {
|
||||
--size-big: 4vw;
|
||||
--size-medium: 3vw;
|
||||
--size-small: 2vw;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #9ca3af;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
/* .my-react-select-container {
|
||||
} */
|
||||
.my-react-select-container .my-react-select__control {
|
||||
@apply bg-white dark:bg-neutral-700 border-2 border-neutral-300 dark:border-neutral-700 hover:border-neutral-400 dark:hover:border-neutral-500;
|
||||
}
|
||||
|
||||
.my-react-select-container .my-react-select__control--is-focused {
|
||||
@apply border-neutral-500 hover:border-neutral-500 dark:border-neutral-400 dark:hover:border-neutral-400 shadow-none;
|
||||
}
|
||||
|
||||
.my-react-select-container .my-react-select__menu {
|
||||
@apply bg-neutral-100 dark:bg-neutral-700 border-2 border-neutral-300 dark:border-neutral-600;
|
||||
}
|
||||
|
||||
.my-react-select-container .my-react-select__option {
|
||||
@apply text-neutral-600 dark:text-neutral-200 bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-700 dark:hover:bg-neutral-800;
|
||||
}
|
||||
/* .my-react-select-container .my-react-select__option--is-focused {
|
||||
@apply bg-neutral-200 dark:bg-neutral-800;
|
||||
} */
|
||||
|
||||
.my-react-select-container .my-react-select__indicator-separator {
|
||||
@apply bg-neutral-400;
|
||||
}
|
||||
.my-react-select-container .my-react-select__menu {
|
||||
z-index: 999;
|
||||
}
|
||||
.my-react-select-container .my-react-select__input-container,
|
||||
.my-react-select-container .my-react-select__placeholder,
|
||||
.my-react-select-container .my-react-select__single-value {
|
||||
@apply text-neutral-600 dark:text-neutral-200;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Header from "../components/header";
|
||||
import { ClientThemeWrapper, ThemeProvider } from "@/components/themeController";
|
||||
import Footer from "@/components/footer";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getLocale, getMessages } from "next-intl/server";
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import QueryProviderWrapper from "@/components/queryProvider";
|
||||
import ClientDataFetcher from "@/components/clientDataFetcher";
|
||||
import AvatarBar from "@/components/avatarBar";
|
||||
import ActionBar from "@/components/actionBar";
|
||||
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Firefly SrTools",
|
||||
description: "SrTools by Kain",
|
||||
icons: {
|
||||
icon: "/ff-srtool.png",
|
||||
shortcut: "/ff-srtool.ico",
|
||||
apple: "/ff-srtool.png",
|
||||
},
|
||||
openGraph: {
|
||||
title: "Firefly SrTools",
|
||||
description: "SrTools by Kain",
|
||||
url: "https://srtools.kain.id.vn",
|
||||
siteName: "Firefly SrTools",
|
||||
images: [
|
||||
{
|
||||
url: "https://srtools.kain.id.vn/ff-srtool.png",
|
||||
width: 312,
|
||||
height: 312,
|
||||
alt: "Firefly SrTools Logo",
|
||||
},
|
||||
],
|
||||
locale: "en_US",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Firefly SrTools",
|
||||
description: "SrTools by Kain",
|
||||
images: ["https://srtools.kain.id.vn/ff-srtool.png"],
|
||||
},
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const messages = await getMessages();
|
||||
const locale = await getLocale()
|
||||
|
||||
return (
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<QueryProviderWrapper>
|
||||
<ThemeProvider>
|
||||
<ClientThemeWrapper>
|
||||
<ClientDataFetcher />
|
||||
<div className="min-h-screen w-full">
|
||||
<Header />
|
||||
<div className="grid grid-cols-12 w-full">
|
||||
<div className="hidden sm:block col-span-3 sticky top-0 self-start h-fit">
|
||||
<AvatarBar />
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-9 lg:col-span-9">
|
||||
<ActionBar />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
</ClientThemeWrapper>
|
||||
</ThemeProvider>
|
||||
</QueryProviderWrapper>
|
||||
</NextIntlClientProvider>
|
||||
<ToastContainer />
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
"use client"
|
||||
import AvatarInfo from "@/components/avatarInfo";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<AvatarInfo></AvatarInfo>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import RelicsInfo from "@/components/relicsInfo";
|
||||
|
||||
export default function RelicsInfoPage() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<RelicsInfo></RelicsInfo>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import ShowCaseInfo from "@/components/showcaseCard"
|
||||
|
||||
export default function ShowcaseCard() {
|
||||
return (
|
||||
<div>
|
||||
<ShowCaseInfo />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import SkillsInfo from "@/components/skillsInfo";
|
||||
|
||||
export default function SkillsInfoPage() {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<SkillsInfo></SkillsInfo>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user