mirror of
https://github.com/skidoodle/erettsegi-browser.git
synced 2026-04-28 13:37:35 +02:00
Minor updates
This commit is contained in:
@@ -9,12 +9,46 @@ export async function GET(req: NextRequest) {
|
|||||||
const idoszak = searchParams.get("idoszak");
|
const idoszak = searchParams.get("idoszak");
|
||||||
const szint = searchParams.get("szint");
|
const szint = searchParams.get("szint");
|
||||||
|
|
||||||
|
if (!vizsgatargy && !ev && !idoszak && !szint) {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
return NextResponse.json({
|
||||||
|
parameters: {
|
||||||
|
vizsgatargy: {
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
values: subjects.map((s) => ({
|
||||||
|
value: s.value,
|
||||||
|
label: s.label,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
ev: {
|
||||||
|
type: "integer",
|
||||||
|
required: true,
|
||||||
|
values: `2013 - ${currentYear}`,
|
||||||
|
},
|
||||||
|
idoszak: {
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
values: ["tavasz", "osz"],
|
||||||
|
},
|
||||||
|
szint: {
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
values: ["kozep", "emelt"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
example:
|
||||||
|
"/api/erettsegi?vizsgatargy=mat&ev=2023&idoszak=tavasz&szint=kozep",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!vizsgatargy || !ev || !idoszak || !szint) {
|
if (!vizsgatargy || !ev || !idoszak || !szint) {
|
||||||
const missingParams = [];
|
const missingParams = [
|
||||||
if (!vizsgatargy) missingParams.push("vizsgatargy");
|
!vizsgatargy && "vizsgatargy",
|
||||||
if (!ev) missingParams.push("ev");
|
!ev && "ev",
|
||||||
if (!idoszak) missingParams.push("idoszak");
|
!idoszak && "idoszak",
|
||||||
if (!szint) missingParams.push("szint");
|
!szint && "szint",
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: `Hiányzó paraméterek: ${missingParams.join(", ")}` },
|
{ error: `Hiányzó paraméterek: ${missingParams.join(", ")}` },
|
||||||
@@ -34,41 +68,16 @@ export async function GET(req: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let honap: string;
|
const honap = idoszak === "osz" ? "okt" : "maj";
|
||||||
switch (idoszak) {
|
const prefix = szint === "emelt" ? `e_${vizsgatargy}` : `k_${vizsgatargy}`;
|
||||||
case "osz":
|
|
||||||
honap = "okt";
|
|
||||||
break;
|
|
||||||
case "tavasz":
|
|
||||||
honap = "maj";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Érvénytelen időszak" },
|
|
||||||
{ status: 400 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix: string;
|
|
||||||
switch (szint) {
|
|
||||||
case "emelt":
|
|
||||||
prefix = `e_${vizsgatargy}`;
|
|
||||||
break;
|
|
||||||
case "kozep":
|
|
||||||
prefix = `k_${vizsgatargy}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Érvénytelen szint" },
|
|
||||||
{ status: 400 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const protocol =
|
const protocol =
|
||||||
req.headers.get("x-forwarded-proto") === "https" ? "https" : "http";
|
req.headers.get("x-forwarded-proto") === "https" ? "https" : "http";
|
||||||
const host = req.headers.get("host");
|
const host = req.headers.get("host");
|
||||||
const baseUrl = `https://dload-oktatas.educatio.hu/erettsegi/feladatok_${ev}${idoszak}_${szint}/`;
|
const baseUrl = `https://dload-oktatas.educatio.hu/erettsegi/feladatok_${ev}${idoszak}_${szint}/`;
|
||||||
const proxiedUrl = `${protocol}://${host}/api/proxy?link=${encodeURI(baseUrl)}`;
|
const proxiedUrl = `${protocol}://${host}/api/proxy?link=${encodeURI(
|
||||||
|
baseUrl,
|
||||||
|
)}`;
|
||||||
|
|
||||||
const shortev = ev.slice(-2);
|
const shortev = ev.slice(-2);
|
||||||
const feladat = "fl";
|
const feladat = "fl";
|
||||||
@@ -76,40 +85,24 @@ export async function GET(req: NextRequest) {
|
|||||||
const forras = "for";
|
const forras = "for";
|
||||||
const megoldas = "meg";
|
const megoldas = "meg";
|
||||||
|
|
||||||
let flPdfUrl: string | undefined,
|
const urls = {
|
||||||
utPdfUrl: string | undefined,
|
flPdfUrl: `${proxiedUrl}${prefix}_${shortev}${honap}_${feladat}.pdf`,
|
||||||
flZipUrl: string | undefined,
|
utPdfUrl: `${proxiedUrl}${prefix}_${shortev}${honap}_${utmutato}.pdf`,
|
||||||
utZipUrl: string | undefined,
|
flZipUrl: ["inf", "infoism", "digkult"].includes(vizsgatargy)
|
||||||
flMp3Url: string | undefined;
|
? `${baseUrl}${prefix}${forras}_${shortev}${honap}_${feladat}.zip`
|
||||||
|
: undefined,
|
||||||
|
utZipUrl: ["inf", "infoism", "digkult"].includes(vizsgatargy)
|
||||||
|
? `${baseUrl}${prefix}${megoldas}_${shortev}${honap}_${utmutato}.zip`
|
||||||
|
: undefined,
|
||||||
|
flMp3Url: ["angol", "nemet"].includes(vizsgatargy)
|
||||||
|
? `${baseUrl}${prefix}_${shortev}${honap}_${feladat}.mp3`
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
switch (vizsgatargy) {
|
return NextResponse.json(urls, {
|
||||||
case "inf":
|
|
||||||
case "infoism":
|
|
||||||
case "digkult":
|
|
||||||
flZipUrl = `${baseUrl}${prefix}${forras}_${shortev}${honap}_${feladat}.zip`;
|
|
||||||
flPdfUrl = `${proxiedUrl}${prefix}_${shortev}${honap}_${feladat}.pdf`;
|
|
||||||
utZipUrl = `${baseUrl}${prefix}${megoldas}_${shortev}${honap}_${utmutato}.zip`;
|
|
||||||
utPdfUrl = `${proxiedUrl}${prefix}_${shortev}${honap}_${utmutato}.pdf`;
|
|
||||||
break;
|
|
||||||
case "angol":
|
|
||||||
case "nemet":
|
|
||||||
flPdfUrl = `${proxiedUrl}${prefix}_${shortev}${honap}_${feladat}.pdf`;
|
|
||||||
utPdfUrl = `${proxiedUrl}${prefix}_${shortev}${honap}_${utmutato}.pdf`;
|
|
||||||
flMp3Url = `${baseUrl}${prefix}_${shortev}${honap}_${feladat}.mp3`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
flPdfUrl = `${proxiedUrl}${prefix}_${shortev}${honap}_${feladat}.pdf`;
|
|
||||||
utPdfUrl = `${proxiedUrl}${prefix}_${shortev}${honap}_${utmutato}.pdf`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ flPdfUrl, utPdfUrl, flZipUrl, utZipUrl, flMp3Url },
|
|
||||||
{
|
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "Cache-Control": "s-maxage=31536000" },
|
headers: { "Cache-Control": "s-maxage=31536000" },
|
||||||
},
|
});
|
||||||
);
|
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
console.error("An unexpected error occurred in the API route:", e);
|
console.error("An unexpected error occurred in the API route:", e);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
+16
-14
@@ -13,10 +13,15 @@ export async function GET(req: NextRequest) {
|
|||||||
const link = searchParams.get("link");
|
const link = searchParams.get("link");
|
||||||
|
|
||||||
if (!link) {
|
if (!link) {
|
||||||
return NextResponse.json(
|
return NextResponse.json({
|
||||||
{ error: "Hiányzó paraméter: link" },
|
parameters: {
|
||||||
{ status: 400 },
|
link: {
|
||||||
);
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
example: `/api/proxy?link=https://dload-oktatas.educatio.hu/erettsegi/feladatok_2023tavasz_kozep/k_mat_23maj_fl.pdf`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let url: URL;
|
let url: URL;
|
||||||
@@ -48,8 +53,9 @@ export async function GET(req: NextRequest) {
|
|||||||
const body = await externalResponse.arrayBuffer();
|
const body = await externalResponse.arrayBuffer();
|
||||||
|
|
||||||
const contentType = externalResponse.headers.get("content-type");
|
const contentType = externalResponse.headers.get("content-type");
|
||||||
const headers = new Headers();
|
const headers = new Headers({
|
||||||
headers.set("Cache-Control", "s-maxage=31536000, stale-while-revalidate");
|
"Cache-Control": "s-maxage=31536000, stale-while-revalidate",
|
||||||
|
});
|
||||||
|
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
headers.set("Content-Type", contentType);
|
headers.set("Content-Type", contentType);
|
||||||
@@ -61,18 +67,14 @@ export async function GET(req: NextRequest) {
|
|||||||
|
|
||||||
return new Response(body, {
|
return new Response(body, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: headers,
|
headers,
|
||||||
});
|
});
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
console.error("Proxy Error:", e);
|
console.error("Proxy Error:", e);
|
||||||
if (e instanceof Error) {
|
const errorMessage =
|
||||||
|
e instanceof Error ? e.message : "An unexpected internal error occurred";
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Internal Server Error", message: e.message },
|
{ error: "Internal Server Error", message: errorMessage },
|
||||||
{ status: 500 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "An unexpected internal error occurred" },
|
|
||||||
{ status: 500 },
|
{ status: 500 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,16 @@ export async function GET(req: NextRequest) {
|
|||||||
const link = searchParams.get("link");
|
const link = searchParams.get("link");
|
||||||
|
|
||||||
if (!link) {
|
if (!link) {
|
||||||
return NextResponse.json(
|
return NextResponse.json({
|
||||||
{ error: "Hiányzó paraméter: link" },
|
parameters: {
|
||||||
{ status: 400 },
|
link: {
|
||||||
);
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
allowed_hosts: ALLOWED_HOSTS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
example: `/api/validate?link=https://dload-oktatas.educatio.hu/erettsegi/feladatok_2023tavasz_kozep/k_mat_23maj_fl.pdf`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let url: URL;
|
let url: URL;
|
||||||
@@ -48,26 +54,13 @@ export async function GET(req: NextRequest) {
|
|||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
console.error("Validation Error:", e);
|
console.error("Validation Error:", e);
|
||||||
|
|
||||||
if (e instanceof Error) {
|
const errorResponse = {
|
||||||
const cause = e.cause as { code?: unknown } | undefined;
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: "Internal Server Error",
|
error: "Internal Server Error",
|
||||||
message: e.message,
|
message: e instanceof Error ? e.message : "An unexpected error occurred",
|
||||||
cause: cause?.code ? String(cause.code) : undefined,
|
...(process.env.NODE_ENV === "development" &&
|
||||||
stack: process.env.NODE_ENV === "development" ? e.stack : undefined,
|
e instanceof Error && { stack: e.stack }),
|
||||||
},
|
};
|
||||||
{ status: 500 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(errorResponse, { status: 500 });
|
||||||
{
|
|
||||||
error: "Internal Server Error",
|
|
||||||
message: "An unexpected error occurred",
|
|
||||||
details: String(e),
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Script from "next/script";
|
|||||||
import { Providers } from "@/app/providers";
|
import { Providers } from "@/app/providers";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
import { AppProvider } from "@/ctx/app";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
@@ -45,7 +46,9 @@ export default function RootLayout({
|
|||||||
data-website-id="7b196f47-39c9-4b8e-8dfd-b6e707282eea"
|
data-website-id="7b196f47-39c9-4b8e-8dfd-b6e707282eea"
|
||||||
/>
|
/>
|
||||||
<body className={`${inter.variable} antialiased font-sans`}>
|
<body className={`${inter.variable} antialiased font-sans`}>
|
||||||
|
<AppProvider>
|
||||||
<Providers>{children}</Providers>
|
<Providers>{children}</Providers>
|
||||||
|
</AppProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+8
-16
@@ -8,23 +8,15 @@ export default function NotFound() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="dark:bg-[#121212] text-foreground bg-background py-5">
|
<main className="dark:bg-[#121212] text-foreground bg-background py-5">
|
||||||
<h1 className="text-7xl font-bold text-blue-400 text-center mt-16">
|
<div className="flex min-h-screen flex-col items-center justify-center">
|
||||||
404
|
<h1 className="text-7xl font-bold text-blue-400">404</h1>
|
||||||
</h1>
|
<div className="mt-5 mb-3 text-center">
|
||||||
<div className="flex min-h-screen flex-col items-center justify-between">
|
<p className="text-2xl font-semibold text-gray-600">
|
||||||
<div className="container mx-auto">
|
Az keresett oldal nem található.
|
||||||
<div className="flex flex-col items-center justify-center">
|
|
||||||
<div className="mt-5 mb-3">
|
|
||||||
<div className="text-2xl font-semibold text-gray-600">
|
|
||||||
<p className="mt-2">Az keresett oldal nem található.</p>
|
|
||||||
<p className="mt-8 text-center">
|
|
||||||
<Link href="/">
|
|
||||||
<Button color="primary">Vissza</Button>
|
|
||||||
</Link>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<Link href="/" className="mt-8 inline-block">
|
||||||
</div>
|
<Button color="primary">Vissza a főoldalra</Button>
|
||||||
</div>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
+56
-57
@@ -1,45 +1,38 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ButtonGroup, Divider } from "@heroui/react";
|
import { ButtonGroup, Divider } from "@heroui/react";
|
||||||
import { useEffect } from "react";
|
import { useContext, useEffect, useCallback } from "react";
|
||||||
import { Mp3Button, PdfButton, ZipButton } from "@/components/Buttons";
|
import { Resource } from "@/components/Resources";
|
||||||
import { Footer } from "@/components/Footer";
|
import { Footer } from "@/components/Footer";
|
||||||
import {
|
import { Selector, periodItems, levelItems } from "@/components/Selectors";
|
||||||
LevelSelector,
|
import { AppContext } from "@/ctx/app";
|
||||||
PeriodSelector,
|
|
||||||
SubjectSelector,
|
|
||||||
YearSelector,
|
|
||||||
} from "@/components/Selectors";
|
|
||||||
import { useAppState } from "@/hooks/useState";
|
|
||||||
import useYears from "@/hooks/useYears";
|
|
||||||
import { fetchData } from "@/utils/fetch";
|
import { fetchData } from "@/utils/fetch";
|
||||||
import { subjects } from "@/utils/subjects";
|
import { subjects } from "@/utils/subjects";
|
||||||
|
import useYears from "@/hooks/useYears";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const { state, dispatch } = useContext(AppContext);
|
||||||
const {
|
const {
|
||||||
flPdfLink,
|
flPdfLink,
|
||||||
setflPdfLink,
|
|
||||||
utPdfLink,
|
utPdfLink,
|
||||||
setutPdfLink,
|
|
||||||
flZipLink,
|
flZipLink,
|
||||||
setflZipLink,
|
|
||||||
utZipLink,
|
utZipLink,
|
||||||
setutZipLink,
|
|
||||||
flMp3Link,
|
flMp3Link,
|
||||||
setflMp3Link,
|
|
||||||
selectedSubject,
|
selectedSubject,
|
||||||
setSelectedSubject,
|
|
||||||
selectedYear,
|
selectedYear,
|
||||||
setSelectedYear,
|
|
||||||
selectedPeriod,
|
selectedPeriod,
|
||||||
setSelectedPeriod,
|
|
||||||
selectedLevel,
|
selectedLevel,
|
||||||
setSelectedLevel,
|
|
||||||
years,
|
years,
|
||||||
setYears,
|
} = state;
|
||||||
} = useAppState();
|
|
||||||
|
|
||||||
useYears(setYears);
|
const setYearsCallback = useCallback(
|
||||||
|
(newYears: string[]) => {
|
||||||
|
dispatch({ type: "SET_YEARS", payload: newYears });
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
useYears(setYearsCallback);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedLevel && selectedPeriod && selectedSubject && selectedYear) {
|
if (selectedLevel && selectedPeriod && selectedSubject && selectedYear) {
|
||||||
@@ -48,24 +41,16 @@ export default function Home() {
|
|||||||
selectedYear,
|
selectedYear,
|
||||||
selectedPeriod,
|
selectedPeriod,
|
||||||
selectedLevel,
|
selectedLevel,
|
||||||
setflZipLink,
|
dispatch,
|
||||||
setutZipLink,
|
|
||||||
setflPdfLink,
|
|
||||||
setutPdfLink,
|
|
||||||
setflMp3Link,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [selectedLevel, selectedPeriod, selectedSubject, selectedYear, dispatch]);
|
||||||
selectedLevel,
|
|
||||||
selectedPeriod,
|
const subjectItems = subjects.map((subject) => ({
|
||||||
selectedSubject,
|
value: subject.value,
|
||||||
selectedYear,
|
label: subject.label,
|
||||||
setutPdfLink,
|
}));
|
||||||
setflZipLink,
|
const yearItems = years.map((year) => ({ value: year, label: year }));
|
||||||
setutZipLink,
|
|
||||||
setflPdfLink,
|
|
||||||
setflMp3Link,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="dark:bg-[#121212] text-foreground bg-background py-5">
|
<main className="dark:bg-[#121212] text-foreground bg-background py-5">
|
||||||
@@ -76,50 +61,64 @@ export default function Home() {
|
|||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
<div className="mt-5 mb-3">
|
<div className="mt-5 mb-3">
|
||||||
<SubjectSelector
|
<Selector
|
||||||
selectedSubject={selectedSubject}
|
label="Tárgy"
|
||||||
setSelectedSubject={setSelectedSubject}
|
selectedValue={selectedSubject}
|
||||||
subjects={subjects}
|
onSelectionChange={(subject) =>
|
||||||
|
dispatch({ type: "SET_SELECTED_SUBJECT", payload: subject })
|
||||||
|
}
|
||||||
|
items={subjectItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<YearSelector
|
<Selector
|
||||||
selectedYear={selectedYear}
|
label="Év"
|
||||||
setSelectedYear={setSelectedYear}
|
selectedValue={selectedYear}
|
||||||
years={years}
|
onSelectionChange={(year) =>
|
||||||
|
dispatch({ type: "SET_SELECTED_YEAR", payload: year })
|
||||||
|
}
|
||||||
|
items={yearItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<PeriodSelector
|
<Selector
|
||||||
selectedPeriod={selectedPeriod}
|
label="Időszak"
|
||||||
setSelectedPeriod={setSelectedPeriod}
|
selectedValue={selectedPeriod}
|
||||||
|
onSelectionChange={(period) =>
|
||||||
|
dispatch({ type: "SET_SELECTED_PERIOD", payload: period })
|
||||||
|
}
|
||||||
|
items={periodItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<LevelSelector
|
<Selector
|
||||||
selectedLevel={selectedLevel}
|
label="Szint"
|
||||||
setSelectedLevel={setSelectedLevel}
|
selectedValue={selectedLevel}
|
||||||
|
onSelectionChange={(level) =>
|
||||||
|
dispatch({ type: "SET_SELECTED_LEVEL", payload: level })
|
||||||
|
}
|
||||||
|
items={levelItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<PdfButton label="Feladatlap" link={flPdfLink} />
|
<Resource label="Feladatlap" link={flPdfLink} />
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<PdfButton label="Útmutató" link={utPdfLink} />
|
<Resource label="Útmutató" link={utPdfLink} />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
{["inf", "infoism", "digkult"].includes(selectedSubject) && (
|
{["inf", "infoism", "digkult"].includes(selectedSubject) && (
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<ZipButton label="Forrás" link={flZipLink} />
|
<Resource label="Forrás" link={flZipLink} />
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
<ZipButton label="Megoldás" link={utZipLink} />
|
<Resource label="Megoldás" link={utZipLink} />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{["angol", "nemet"].includes(selectedSubject) && (
|
{["angol", "nemet"].includes(selectedSubject) && (
|
||||||
<div className="space-x-3">
|
<div className="space-x-3">
|
||||||
<Mp3Button label="Hang" link={flMp3Link} />
|
<Resource label="Hang" link={flMp3Link} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
import { Source } from "@/components/Source";
|
import { Source } from "@/components/Source";
|
||||||
import { ThemeSwitcher } from "@/components/ThemeSwitcher";
|
import { ThemeSwitcher } from "@/components/ThemeSwitcher";
|
||||||
|
|
||||||
export const Footer = () => {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-0 py-5 left-0 right-0 text-center space-x-5">
|
<div className="fixed bottom-0 py-5 left-0 right-0 text-center space-x-5">
|
||||||
<Source />
|
<Source />
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@heroui/react";
|
import { Button, type ButtonProps as HeroButtonProps } from "@heroui/react";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState, memo } from "react";
|
||||||
import type { ButtonProps } from "@/utils/props";
|
|
||||||
import type { ButtonColor } from "@/utils/types";
|
|
||||||
|
|
||||||
const CustomButton: React.FC<ButtonProps> = React.memo(({ label, link }) => {
|
export interface ResourceProps {
|
||||||
|
label: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ButtonColor = HeroButtonProps["color"];
|
||||||
|
|
||||||
|
const ResourceComponent = ({ label, link }: ResourceProps) => {
|
||||||
const [status, setStatus] = useState<number>();
|
const [status, setStatus] = useState<number>();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -21,6 +26,8 @@ const CustomButton: React.FC<ButtonProps> = React.memo(({ label, link }) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setStatus(undefined);
|
||||||
}
|
}
|
||||||
}, [link]);
|
}, [link]);
|
||||||
|
|
||||||
@@ -29,16 +36,10 @@ const CustomButton: React.FC<ButtonProps> = React.memo(({ label, link }) => {
|
|||||||
}, [checkLinkStatus]);
|
}, [checkLinkStatus]);
|
||||||
|
|
||||||
const getColor = useCallback((): ButtonColor => {
|
const getColor = useCallback((): ButtonColor => {
|
||||||
switch (true) {
|
if (isLoading) return "default";
|
||||||
case isLoading:
|
if (status === 200) return "primary";
|
||||||
|
if (status === 404) return "danger";
|
||||||
return "default";
|
return "default";
|
||||||
case status === 200:
|
|
||||||
return "primary";
|
|
||||||
case status === 404:
|
|
||||||
return "danger";
|
|
||||||
default:
|
|
||||||
return "default";
|
|
||||||
}
|
|
||||||
}, [isLoading, status]);
|
}, [isLoading, status]);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
@@ -60,16 +61,6 @@ const CustomButton: React.FC<ButtonProps> = React.memo(({ label, link }) => {
|
|||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
export const PdfButton: React.FC<ButtonProps> = React.memo(
|
export const Resource = memo(ResourceComponent);
|
||||||
({ label, link }) => <CustomButton label={label} link={link} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ZipButton: React.FC<ButtonProps> = React.memo(
|
|
||||||
({ label, link }) => <CustomButton label={label} link={link} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Mp3Button: React.FC<ButtonProps> = React.memo(
|
|
||||||
({ label, link }) => <CustomButton label={label} link={link} />,
|
|
||||||
);
|
|
||||||
@@ -1,79 +1,54 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Select, SelectItem } from "@heroui/react";
|
import { Select, SelectItem } from "@heroui/react";
|
||||||
import type { SelectorProps } from "@/utils/props";
|
|
||||||
import type { ChangeEvent } from "react";
|
|
||||||
|
|
||||||
export const SubjectSelector: React.FC<
|
interface SelectorItem {
|
||||||
Pick<SelectorProps, "selectedSubject" | "setSelectedSubject" | "subjects">
|
value: string;
|
||||||
> = ({ selectedSubject, setSelectedSubject, subjects }) => (
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectorProps {
|
||||||
|
label: string;
|
||||||
|
selectedValue: string;
|
||||||
|
onSelectionChange: (value: string) => void;
|
||||||
|
items: SelectorItem[];
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const periodItems = [
|
||||||
|
{ value: "tavasz", label: "Tavasz" },
|
||||||
|
{ value: "osz", label: "Ősz" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const levelItems = [
|
||||||
|
{ value: "kozep", label: "Közép" },
|
||||||
|
{ value: "emelt", label: "Emelt" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function Selector({
|
||||||
|
label,
|
||||||
|
selectedValue,
|
||||||
|
onSelectionChange,
|
||||||
|
items,
|
||||||
|
className = "w-56",
|
||||||
|
}: SelectorProps) {
|
||||||
|
return (
|
||||||
<Select
|
<Select
|
||||||
selectionMode="single"
|
selectionMode="single"
|
||||||
disallowEmptySelection={true}
|
disallowEmptySelection={true}
|
||||||
label="Tárgy"
|
label={label}
|
||||||
value={selectedSubject}
|
selectedKeys={selectedValue ? [selectedValue] : []}
|
||||||
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
onSelectionChange={(keys) => {
|
||||||
setSelectedSubject(e.target.value)
|
const key = Array.from(keys)[0];
|
||||||
|
if (key) {
|
||||||
|
onSelectionChange(String(key));
|
||||||
}
|
}
|
||||||
className="w-56"
|
}}
|
||||||
|
className={className}
|
||||||
>
|
>
|
||||||
{subjects.map((subject) => (
|
{items.map((item) => (
|
||||||
<SelectItem key={subject.value}>{subject.label}</SelectItem>
|
<SelectItem key={item.value}>{item.label}</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const YearSelector: React.FC<
|
|
||||||
Pick<SelectorProps, "selectedYear" | "setSelectedYear" | "years">
|
|
||||||
> = ({ selectedYear, setSelectedYear, years }) => (
|
|
||||||
<Select
|
|
||||||
selectionMode="single"
|
|
||||||
disallowEmptySelection={true}
|
|
||||||
label="Év"
|
|
||||||
value={selectedYear}
|
|
||||||
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
|
||||||
setSelectedYear(e.target.value)
|
|
||||||
}
|
}
|
||||||
className="w-56"
|
|
||||||
>
|
|
||||||
{years.map((year) => (
|
|
||||||
<SelectItem key={year}>{year}</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const PeriodSelector: React.FC<
|
|
||||||
Pick<SelectorProps, "selectedPeriod" | "setSelectedPeriod">
|
|
||||||
> = ({ selectedPeriod, setSelectedPeriod }) => (
|
|
||||||
<Select
|
|
||||||
selectionMode="single"
|
|
||||||
disallowEmptySelection={true}
|
|
||||||
label="Időszak"
|
|
||||||
value={selectedPeriod}
|
|
||||||
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
|
||||||
setSelectedPeriod(e.target.value)
|
|
||||||
}
|
|
||||||
className="w-56"
|
|
||||||
>
|
|
||||||
<SelectItem key={"tavasz"}>Tavasz</SelectItem>
|
|
||||||
<SelectItem key={"osz"}>Ősz</SelectItem>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const LevelSelector: React.FC<
|
|
||||||
Pick<SelectorProps, "selectedLevel" | "setSelectedLevel">
|
|
||||||
> = ({ selectedLevel, setSelectedLevel }) => (
|
|
||||||
<Select
|
|
||||||
selectionMode="single"
|
|
||||||
disallowEmptySelection={true}
|
|
||||||
label="Szint"
|
|
||||||
value={selectedLevel}
|
|
||||||
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
|
||||||
setSelectedLevel(e.target.value)
|
|
||||||
}
|
|
||||||
className="w-56"
|
|
||||||
>
|
|
||||||
<SelectItem key={"kozep"}>Közép</SelectItem>
|
|
||||||
<SelectItem key={"emelt"}>Emelt</SelectItem>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { Button } from "@heroui/button";
|
import { Button } from "@heroui/button";
|
||||||
import { VscGithubInverted } from "react-icons/vsc";
|
import { VscGithubInverted } from "react-icons/vsc";
|
||||||
|
|
||||||
export const Source = () => {
|
export function Source() {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Source Code"
|
aria-label="Source Code"
|
||||||
@@ -15,4 +15,4 @@ export const Source = () => {
|
|||||||
<VscGithubInverted size={20} />
|
<VscGithubInverted size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Button } from "@heroui/button";
|
|||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { VscColorMode } from "react-icons/vsc";
|
import { VscColorMode } from "react-icons/vsc";
|
||||||
|
|
||||||
export const ThemeSwitcher = () => {
|
export function ThemeSwitcher() {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
@@ -20,4 +20,4 @@ export const ThemeSwitcher = () => {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useReducer,
|
||||||
|
type Dispatch,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
flPdfLink: string;
|
||||||
|
utPdfLink: string;
|
||||||
|
flZipLink: string;
|
||||||
|
utZipLink: string;
|
||||||
|
flMp3Link: string;
|
||||||
|
selectedSubject: string;
|
||||||
|
selectedYear: string;
|
||||||
|
selectedPeriod: string;
|
||||||
|
selectedLevel: string;
|
||||||
|
years: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: State = {
|
||||||
|
flPdfLink: "",
|
||||||
|
utPdfLink: "",
|
||||||
|
flZipLink: "",
|
||||||
|
utZipLink: "",
|
||||||
|
flMp3Link: "",
|
||||||
|
selectedSubject: "",
|
||||||
|
selectedYear: "",
|
||||||
|
selectedPeriod: "",
|
||||||
|
selectedLevel: "",
|
||||||
|
years: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| { type: "SET_FL_PDF_LINK"; payload: string }
|
||||||
|
| { type: "SET_UT_PDF_LINK"; payload: string }
|
||||||
|
| { type: "SET_FL_ZIP_LINK"; payload: string }
|
||||||
|
| { type: "SET_UT_ZIP_LINK"; payload: string }
|
||||||
|
| { type: "SET_FL_MP3_LINK"; payload: string }
|
||||||
|
| { type: "SET_SELECTED_SUBJECT"; payload: string }
|
||||||
|
| { type: "SET_SELECTED_YEAR"; payload: string }
|
||||||
|
| { type: "SET_SELECTED_PERIOD"; payload: string }
|
||||||
|
| { type: "SET_SELECTED_LEVEL"; payload: string }
|
||||||
|
| { type: "SET_YEARS"; payload: string[] };
|
||||||
|
|
||||||
|
const reducer = (state: State, action: Action): State => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "SET_FL_PDF_LINK":
|
||||||
|
return { ...state, flPdfLink: action.payload };
|
||||||
|
case "SET_UT_PDF_LINK":
|
||||||
|
return { ...state, utPdfLink: action.payload };
|
||||||
|
case "SET_FL_ZIP_LINK":
|
||||||
|
return { ...state, flZipLink: action.payload };
|
||||||
|
case "SET_UT_ZIP_LINK":
|
||||||
|
return { ...state, utZipLink: action.payload };
|
||||||
|
case "SET_FL_MP3_LINK":
|
||||||
|
return { ...state, flMp3Link: action.payload };
|
||||||
|
case "SET_SELECTED_SUBJECT":
|
||||||
|
return { ...state, selectedSubject: action.payload };
|
||||||
|
case "SET_SELECTED_YEAR":
|
||||||
|
return { ...state, selectedYear: action.payload };
|
||||||
|
case "SET_SELECTED_PERIOD":
|
||||||
|
return { ...state, selectedPeriod: action.payload };
|
||||||
|
case "SET_SELECTED_LEVEL":
|
||||||
|
return { ...state, selectedLevel: action.payload };
|
||||||
|
case "SET_YEARS":
|
||||||
|
return { ...state, years: action.payload };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AppContext = createContext<{
|
||||||
|
state: State;
|
||||||
|
dispatch: Dispatch<Action>;
|
||||||
|
}>({
|
||||||
|
state: initialState,
|
||||||
|
dispatch: () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AppProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppContext.Provider value={{ state, dispatch }}>
|
||||||
|
{children}
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import useYears from "@/hooks/useYears";
|
|
||||||
|
|
||||||
export const useAppState = () => {
|
|
||||||
const [flPdfLink, setflPdfLink] = useState<string>("");
|
|
||||||
const [utPdfLink, setutPdfLink] = useState<string>("");
|
|
||||||
const [flZipLink, setflZipLink] = useState<string>("");
|
|
||||||
const [utZipLink, setutZipLink] = useState<string>("");
|
|
||||||
const [flMp3Link, setflMp3Link] = useState<string>("");
|
|
||||||
const [selectedSubject, setSelectedSubject] = useState<string>("");
|
|
||||||
const [selectedYear, setSelectedYear] = useState<string>("");
|
|
||||||
const [selectedPeriod, setSelectedPeriod] = useState<string>("");
|
|
||||||
const [selectedLevel, setSelectedLevel] = useState<string>("");
|
|
||||||
const [years, setYears] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useYears(setYears);
|
|
||||||
|
|
||||||
return {
|
|
||||||
flPdfLink,
|
|
||||||
setflPdfLink,
|
|
||||||
utPdfLink,
|
|
||||||
setutPdfLink,
|
|
||||||
flZipLink,
|
|
||||||
setflZipLink,
|
|
||||||
utZipLink,
|
|
||||||
setutZipLink,
|
|
||||||
flMp3Link,
|
|
||||||
setflMp3Link,
|
|
||||||
selectedSubject,
|
|
||||||
setSelectedSubject,
|
|
||||||
selectedYear,
|
|
||||||
setSelectedYear,
|
|
||||||
selectedPeriod,
|
|
||||||
setSelectedPeriod,
|
|
||||||
selectedLevel,
|
|
||||||
setSelectedLevel,
|
|
||||||
years,
|
|
||||||
setYears,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function useYears(
|
export default function useYears(setYears: (years: string[]) => void) {
|
||||||
setYears: React.Dispatch<React.SetStateAction<string[]>>,
|
|
||||||
) {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
const availableYears: string[] = [];
|
const availableYears: string[] = [];
|
||||||
|
|||||||
+18
-13
@@ -1,13 +1,18 @@
|
|||||||
|
import type { Dispatch } from "react";
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| { type: "SET_FL_ZIP_LINK"; payload: string }
|
||||||
|
| { type: "SET_UT_ZIP_LINK"; payload: string }
|
||||||
|
| { type: "SET_FL_PDF_LINK"; payload: string }
|
||||||
|
| { type: "SET_UT_PDF_LINK"; payload: string }
|
||||||
|
| { type: "SET_FL_MP3_LINK"; payload: string };
|
||||||
|
|
||||||
export const fetchData = async (
|
export const fetchData = async (
|
||||||
selectedSubject: string,
|
selectedSubject: string,
|
||||||
selectedYear: string,
|
selectedYear: string,
|
||||||
selectedPeriod: string,
|
selectedPeriod: string,
|
||||||
selectedLevel: string,
|
selectedLevel: string,
|
||||||
setflZipLink: (link: string) => void,
|
dispatch: Dispatch<Action>,
|
||||||
setutZipLink: (link: string) => void,
|
|
||||||
setflPdfLink: (link: string) => void,
|
|
||||||
setutPdfLink: (link: string) => void,
|
|
||||||
setflMp3Link: (link: string) => void,
|
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const url = `/api/erettsegi?vizsgatargy=${selectedSubject}&ev=${selectedYear}&idoszak=${selectedPeriod}&szint=${selectedLevel}`;
|
const url = `/api/erettsegi?vizsgatargy=${selectedSubject}&ev=${selectedYear}&idoszak=${selectedPeriod}&szint=${selectedLevel}`;
|
||||||
@@ -16,25 +21,25 @@ export const fetchData = async (
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = (await response.json()) as {
|
const data = (await response.json()) as {
|
||||||
flZipUrl: string;
|
flZipUrl?: string;
|
||||||
utZipUrl: string;
|
utZipUrl?: string;
|
||||||
flPdfUrl: string;
|
flPdfUrl: string;
|
||||||
utPdfUrl: string;
|
utPdfUrl: string;
|
||||||
flMp3Url: string;
|
flMp3Url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.utZipUrl && data.flZipUrl) {
|
if (data.utZipUrl && data.flZipUrl) {
|
||||||
setflZipLink(data.flZipUrl);
|
dispatch({ type: "SET_FL_ZIP_LINK", payload: data.flZipUrl });
|
||||||
setutZipLink(data.utZipUrl);
|
dispatch({ type: "SET_UT_ZIP_LINK", payload: data.utZipUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.utPdfUrl && data.flPdfUrl) {
|
if (data.utPdfUrl && data.flPdfUrl) {
|
||||||
setflPdfLink(data.flPdfUrl);
|
dispatch({ type: "SET_FL_PDF_LINK", payload: data.flPdfUrl });
|
||||||
setutPdfLink(data.utPdfUrl);
|
dispatch({ type: "SET_UT_PDF_LINK", payload: data.utPdfUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.flMp3Url) {
|
if (data.flMp3Url) {
|
||||||
setflMp3Link(data.flMp3Url);
|
dispatch({ type: "SET_FL_MP3_LINK", payload: data.flMp3Url });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("Hiba történt az API hívás során.");
|
console.error("Hiba történt az API hívás során.");
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
export interface SelectorProps {
|
|
||||||
years: string[];
|
|
||||||
subjects: { label: string; value: string }[];
|
|
||||||
selectedSubject: string;
|
|
||||||
selectedYear: string;
|
|
||||||
selectedPeriod: string;
|
|
||||||
selectedLevel: string;
|
|
||||||
setSelectedSubject: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setSelectedYear: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setSelectedPeriod: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setSelectedLevel: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ButtonProps {
|
|
||||||
label: string;
|
|
||||||
link: string;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export type ButtonColor =
|
|
||||||
| "primary"
|
|
||||||
| "danger"
|
|
||||||
| "default"
|
|
||||||
| "secondary"
|
|
||||||
| "success"
|
|
||||||
| "warning"
|
|
||||||
| undefined;
|
|
||||||
Reference in New Issue
Block a user