mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
Credits changes, Glossary register, Memory game link copy
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
-- Add impressum page for system pages
|
||||
INSERT INTO html_pages (page_group, slug, title, description, content, page_order)
|
||||
VALUES (
|
||||
'system',
|
||||
'impressum',
|
||||
'Impressum',
|
||||
'Rechtliche Informationen und Impressum',
|
||||
'<p>Diese Webseiten entstanden in einem Autorenkollektiv der Universitäten Helsinki, Oulu und Turku unter der Leitung von <a href="mailto:michi@dlc.fi">Dr. Michael Möbius</a>.</p><br><p>Die Entwicklung wurde gefördert durch die <a href="https://foundationhollo.fi/" target="_blank" rel="noreferrer">Stiftung Erkki J. Hollo</a>.</p><p>Programmierung durch IT-Studierende der Universität Helsinki</p><p>Technischer Support und Hosting: Universität Helsinki</p>',
|
||||
1
|
||||
);
|
||||
@@ -1,3 +1,23 @@
|
||||
.glossary-index {
|
||||
background-color: var(--bg2);
|
||||
padding: var(--u-md);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--bg3);
|
||||
}
|
||||
|
||||
.glossary-index h3 {
|
||||
margin: 0 0 var(--u-sm);
|
||||
}
|
||||
|
||||
.glossary-index-item {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background-color: var(--bg3);
|
||||
color: var(--fg1);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.glossary-card {
|
||||
background-color: var(--bg3);
|
||||
padding: var(--u-md);
|
||||
|
||||
@@ -53,6 +53,10 @@ export default function GlossaryList() {
|
||||
}
|
||||
};
|
||||
|
||||
const sortedEntries = [...(entries || [])].sort((a, b) =>
|
||||
a.word.localeCompare(b.word)
|
||||
);
|
||||
|
||||
return (
|
||||
<Column gap="md" width="100%">
|
||||
<Row justify="space-between" gap="xl" width="100%" align="center">
|
||||
@@ -61,7 +65,25 @@ export default function GlossaryList() {
|
||||
<LinkButton href="/admin/glossary/create">
|
||||
Neuen Eintrag erstellen
|
||||
</LinkButton>
|
||||
{entries?.length === 0 ? (
|
||||
|
||||
{sortedEntries.length > 0 && (
|
||||
<Container className="glossary-index" mb="md">
|
||||
<h3>Register</h3>
|
||||
<Container
|
||||
display="grid"
|
||||
templateColumns="repeat(auto-fit, minmax(160px, 1fr))"
|
||||
gap="sm"
|
||||
>
|
||||
{sortedEntries.map((entry) => (
|
||||
<span key={entry.id} className="glossary-index-item">
|
||||
{entry.word}
|
||||
</span>
|
||||
))}
|
||||
</Container>
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{sortedEntries.length === 0 ? (
|
||||
<p>
|
||||
Keine Einträge gefunden. Erstellen Sie einen neuen Eintrag, um zu
|
||||
beginnen.
|
||||
@@ -72,46 +94,43 @@ export default function GlossaryList() {
|
||||
templateColumns="repeat(auto-fill, minmax(300px, 1fr))"
|
||||
gap="md"
|
||||
>
|
||||
{entries
|
||||
?.sort((a, b) => a.word.localeCompare(b.word))
|
||||
?.map((entry) => (
|
||||
<Container key={entry.id} className="glossary-card">
|
||||
<Column gap="sm">
|
||||
<h4 className="glossary-word">{entry.word}</h4>
|
||||
<RenderHTML data={entry.word_definition} disableGlossary />
|
||||
<Row justify="space-between" className="glossary-meta">
|
||||
<span>
|
||||
Erstellt:{" "}
|
||||
{new Date(entry.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
<Row gap="sm">
|
||||
<LinkButton
|
||||
href={`/admin/glossary/create?id=${entry.id}`}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Bearbeiten
|
||||
</LinkButton>
|
||||
<Button
|
||||
variant={
|
||||
confirmDelete === entry.id ? "danger" : "outline"
|
||||
}
|
||||
size="sm"
|
||||
width="fit"
|
||||
onClick={() => handleDelete(entry.id)}
|
||||
disabled={deleting === entry.id}
|
||||
>
|
||||
{deleting === entry.id
|
||||
? "Löschen..."
|
||||
: confirmDelete === entry.id
|
||||
? "Bestätigen"
|
||||
: "Löschen"}
|
||||
</Button>
|
||||
</Row>
|
||||
{sortedEntries.map((entry) => (
|
||||
<Container key={entry.id} className="glossary-card">
|
||||
<Column gap="sm">
|
||||
<h4 className="glossary-word">{entry.word}</h4>
|
||||
<RenderHTML data={entry.word_definition} disableGlossary />
|
||||
<Row justify="space-between" className="glossary-meta">
|
||||
<span>
|
||||
Erstellt: {new Date(entry.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
<Row gap="sm">
|
||||
<LinkButton
|
||||
href={`/admin/glossary/create?id=${entry.id}`}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Bearbeiten
|
||||
</LinkButton>
|
||||
<Button
|
||||
variant={
|
||||
confirmDelete === entry.id ? "danger" : "outline"
|
||||
}
|
||||
size="sm"
|
||||
width="fit"
|
||||
onClick={() => handleDelete(entry.id)}
|
||||
disabled={deleting === entry.id}
|
||||
>
|
||||
{deleting === entry.id
|
||||
? "Löschen..."
|
||||
: confirmDelete === entry.id
|
||||
? "Bestätigen"
|
||||
: "Löschen"}
|
||||
</Button>
|
||||
</Row>
|
||||
</Column>
|
||||
</Container>
|
||||
))}
|
||||
</Row>
|
||||
</Column>
|
||||
</Container>
|
||||
))}
|
||||
</Container>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
@@ -2,7 +2,12 @@ import { DB } from "@/backend/db";
|
||||
import { withAuth } from "@/backend/middleware/withAuth";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const VALID_PAGE_GROUPS = new Set(["resources", "communications", "grammar"]);
|
||||
const VALID_PAGE_GROUPS = new Set([
|
||||
"resources",
|
||||
"communications",
|
||||
"grammar",
|
||||
"system",
|
||||
]);
|
||||
|
||||
function normalizePageDescription(description) {
|
||||
if (typeof description !== "string") return null;
|
||||
|
||||
@@ -9,7 +9,12 @@ import DOMPurify from "dompurify";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// Allowed page_group values stored in the consolidated `html_pages` table.
|
||||
const VALID_PAGE_GROUPS = new Set(["resources", "communications", "grammar"]);
|
||||
const VALID_PAGE_GROUPS = new Set([
|
||||
"resources",
|
||||
"communications",
|
||||
"grammar",
|
||||
"system",
|
||||
]);
|
||||
|
||||
/*
|
||||
Route behavior (summary):
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { useUser } from "@/context/user.context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Row } from "@/components/ui/layout/container";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { withBasePath } from "@/shared/utils/basePath";
|
||||
|
||||
export default function AdminButtons({ type, slug, pageExists = true }) {
|
||||
const { auth } = useUser();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const view = searchParams.get("view");
|
||||
|
||||
// Check if user is superadmin without causing redirects
|
||||
const isSuperAdmin = auth?.isLoggedIn && auth?.user?.is_superadmin;
|
||||
|
||||
/** Reset: empties the page content but keeps the page row. */
|
||||
const handleReset = async () => {
|
||||
if (!confirm("Möchten Sie den Inhalt dieser Seite wirklich leeren?"))
|
||||
return;
|
||||
const res = await fetch(withBasePath(`/api/admin/pages/${type}/${slug}`), {
|
||||
method: "PATCH",
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
router.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await res.text();
|
||||
alert(
|
||||
`Fehler beim Zurücksetzen (HTTP ${res.status}): ${message || "Unbekannter Fehler"}`
|
||||
);
|
||||
};
|
||||
|
||||
/** Delete: removes the whole page row from the database. */
|
||||
const handleDelete = async () => {
|
||||
if (!confirm("Möchten Sie diese Seite wirklich endgültig löschen?")) return;
|
||||
|
||||
const res = await fetch(withBasePath(`/api/admin/pages/${type}/${slug}`), {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
router.push("/"); // Redirect to home page
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await res.text();
|
||||
alert(
|
||||
`Fehler beim Löschen (HTTP ${res.status}): ${message || "Unbekannter Fehler"}`
|
||||
);
|
||||
};
|
||||
|
||||
if (isSuperAdmin) {
|
||||
return (
|
||||
<Row pb="xl" mt="2xl" justify="space-between">
|
||||
<Button
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/admin/edit-page/${type}/${slug}${view ? `?view=${view}` : ""}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
Seite bearbeiten
|
||||
</Button>
|
||||
{pageExists && (
|
||||
<Row gap="sm">
|
||||
<Button
|
||||
onClick={handleReset}
|
||||
style={{
|
||||
background: "var(--tertiary1)",
|
||||
color: "var(--fg1)",
|
||||
border: "var(--tertiary5)",
|
||||
borderRadius: "5px",
|
||||
padding: "8px 16px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Zurücksetzen
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDelete}
|
||||
style={{
|
||||
background: "#dc2626",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "5px",
|
||||
padding: "8px 16px",
|
||||
cursor: "pointer",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Löschen
|
||||
</Button>
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
.masculine {
|
||||
color: hsla(240, 100%, 50%, 1);
|
||||
}
|
||||
|
||||
.neutral {
|
||||
color: hsla(120, 100%, 50%, 1);
|
||||
}
|
||||
|
||||
.feminine {
|
||||
color: hsla(0, 100%, 50%, 1);
|
||||
}
|
||||
|
||||
.chapter-page-title,
|
||||
.chapter-rendered-content>h1,
|
||||
.chapter-rendered-content>h2,
|
||||
.chapter-rendered-content>h3 {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.chapter-page-title,
|
||||
.chapter-rendered-content>h1 {
|
||||
font-size: 2rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--fg1);
|
||||
}
|
||||
|
||||
.chapter-rendered-content>h2 {
|
||||
font-size: 1.75rem;
|
||||
color: var(--fg1);
|
||||
}
|
||||
|
||||
.chapter-rendered-content>h3 {
|
||||
font-size: 1.35rem;
|
||||
color: var(--fg1);
|
||||
}
|
||||
|
||||
.chapter-content {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.ql-size-small {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.ql-size-large {
|
||||
font-size: 1.5em;
|
||||
@@ -1,42 +1,27 @@
|
||||
"use client";
|
||||
import { Column, Container } from "@/components/ui/layout/container";
|
||||
import "./chapters.css";
|
||||
import layout from "@/shared/styles/layout.module.css";
|
||||
import RenderHTML from "@/components/ui/render-html/render-html";
|
||||
import { getPageData } from "@/backend/html-services";
|
||||
import { transformHtmlToGlossaryTags } from "@/backend/html-transform";
|
||||
import AdminButtons from "./admin-buttons";
|
||||
|
||||
import styles from "../page.module.css";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function Credits() {
|
||||
const pageData = await getPageData("system", "impressum");
|
||||
|
||||
// Transform HTML to include glossary tags
|
||||
const transformedContent = transformHtmlToGlossaryTags(pageData.content);
|
||||
|
||||
export default function Credits() {
|
||||
return (
|
||||
<div className="container">
|
||||
<section className={styles.creditsSection}>
|
||||
<h1>Credits</h1>
|
||||
<div className={styles.creditsList}>
|
||||
<p className={styles.creditsItem}>
|
||||
Diese Webseiten entstanden in einem Autorenkollektiv der
|
||||
Universitäten Helsinki, Oulu und Turku unter der Leitung von{" "}
|
||||
<a href="mailto:michi@dlc.fi" className={styles.creditsLink}>
|
||||
Dr. Michael Möbius
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<br></br>
|
||||
<p className={styles.creditsItem}>
|
||||
Die Entwicklung wurde gefördert durch die{" "}
|
||||
<a
|
||||
href="https://foundationhollo.fi/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.creditsLink}
|
||||
>
|
||||
Stiftung Erkki J. Hollo
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p className={styles.creditsItem}>
|
||||
Programmierung durch IT-Studierende der Universität Helsinki
|
||||
</p>
|
||||
<p className={styles.creditsItem}>
|
||||
Technischer Support und Hosting: Universität Helsinki
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<Column className={layout.viewContent}>
|
||||
<AdminButtons type="system" slug="impressum" />
|
||||
<h1 className="chapter-page-title">{pageData.title}</h1>
|
||||
|
||||
<Container className="chapter-rendered-content">
|
||||
<RenderHTML data={transformedContent} />
|
||||
</Container>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
.glossary-index {
|
||||
background-color: var(--bg2);
|
||||
padding: var(--u-md);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--bg3);
|
||||
}
|
||||
|
||||
.glossary-index h3 {
|
||||
margin: 0 0 var(--u-sm);
|
||||
}
|
||||
|
||||
.glossary-index-item {
|
||||
display: inline-block;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: var(--radius-sm);
|
||||
background-color: var(--bg3);
|
||||
color: var(--fg1);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.glossary-card {
|
||||
background-color: var(--bg3);
|
||||
padding: var(--u-md);
|
||||
|
||||
@@ -15,12 +15,34 @@ export default function GlossaryPage() {
|
||||
return <p className="error">Fehler beim Laden der Glossareinträge</p>;
|
||||
}
|
||||
|
||||
const sortedEntries = [...(entries || [])].sort((a, b) =>
|
||||
a.word.localeCompare(b.word)
|
||||
);
|
||||
|
||||
return (
|
||||
<Column gap="md" width="100%">
|
||||
<Row justify="space-between" gap="xl" width="100%" align="center">
|
||||
<h2>Glossar</h2>
|
||||
</Row>
|
||||
{entries?.length === 0 ? (
|
||||
|
||||
{sortedEntries.length > 0 && (
|
||||
<Container className="glossary-index" mb="md">
|
||||
<h3>Register</h3>
|
||||
<Container
|
||||
display="grid"
|
||||
templateColumns="repeat(auto-fit, minmax(160px, 1fr))"
|
||||
gap="sm"
|
||||
>
|
||||
{sortedEntries.map((entry) => (
|
||||
<span key={entry.id} className="glossary-index-item">
|
||||
{entry.word}
|
||||
</span>
|
||||
))}
|
||||
</Container>
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{sortedEntries.length === 0 ? (
|
||||
<p>Keine Einträge gefunden.</p>
|
||||
) : (
|
||||
<Container
|
||||
@@ -28,16 +50,14 @@ export default function GlossaryPage() {
|
||||
templateColumns="repeat(auto-fill, minmax(300px, 1fr))"
|
||||
gap="md"
|
||||
>
|
||||
{entries
|
||||
?.sort((a, b) => a.word.localeCompare(b.word))
|
||||
?.map((entry) => (
|
||||
<Container key={entry.id} className="glossary-card">
|
||||
<Column gap="sm">
|
||||
<h4 className="glossary-word">{entry.word}</h4>
|
||||
<RenderHTML data={entry.word_definition} disableGlossary />
|
||||
</Column>
|
||||
</Container>
|
||||
))}
|
||||
{sortedEntries.map((entry) => (
|
||||
<Container key={entry.id} className="glossary-card">
|
||||
<Column gap="sm">
|
||||
<h4 className="glossary-word">{entry.word}</h4>
|
||||
<RenderHTML data={entry.word_definition} disableGlossary />
|
||||
</Column>
|
||||
</Container>
|
||||
))}
|
||||
</Container>
|
||||
)}
|
||||
</Column>
|
||||
|
||||
@@ -13,6 +13,7 @@ export default function MemoryGamePlayerPage() {
|
||||
const { memory_game_id } = useParams();
|
||||
const [result, setResult] = useState(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [resetSignal, setResetSignal] = useState(0);
|
||||
|
||||
const {
|
||||
data: exercise,
|
||||
@@ -90,6 +91,7 @@ export default function MemoryGamePlayerPage() {
|
||||
pairs={exercise.pairs || []}
|
||||
onComplete={handleComplete}
|
||||
onReset={() => setResult(null)}
|
||||
resetSignal={resetSignal}
|
||||
/>
|
||||
|
||||
{isSubmitting && <p>Ergebnisse werden gespeichert...</p>}
|
||||
@@ -113,7 +115,14 @@ export default function MemoryGamePlayerPage() {
|
||||
<Link href="/grammar/exercises/memory-game">
|
||||
<Button variant="outline">Zurück zur Liste</Button>
|
||||
</Link>
|
||||
<Button type="button" onClick={() => refetch()}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setResult(null);
|
||||
setResetSignal((prev) => prev + 1);
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
Übung neu laden
|
||||
</Button>
|
||||
</Row>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { Container, Column, Row } from "@/components/ui/layout/container";
|
||||
import useQuery from "@/shared/hooks/useQuery";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ExerciseLinkButton } from "@/components/ui/button/exercise-link-button";
|
||||
|
||||
export default function MemoryGameExercisesPage() {
|
||||
const {
|
||||
@@ -90,6 +91,12 @@ export default function MemoryGameExercisesPage() {
|
||||
</Container>
|
||||
</Column>
|
||||
<Column gap="sm" align="flex-end">
|
||||
<ExerciseLinkButton
|
||||
href={`/grammar/exercises/memory-game/${exercise.memory_game_exercise_id}`}
|
||||
size="sm"
|
||||
>
|
||||
Link kopieren
|
||||
</ExerciseLinkButton>
|
||||
<Link
|
||||
href={`/grammar/exercises/memory-game/${exercise.memory_game_exercise_id}`}
|
||||
>
|
||||
|
||||
+30
-1
@@ -49,8 +49,37 @@ export default function Home() {
|
||||
|
||||
<div className="container">
|
||||
<section className={styles.creditsSection}>
|
||||
<div className={styles.creditsList}>
|
||||
<p className={styles.creditsItem}>
|
||||
Diese Webseiten entstanden in einem Autorenkollektiv der
|
||||
Universitäten Helsinki, Oulu und Turku unter der Leitung von{" "}
|
||||
<a href="mailto:michi@dlc.fi" className={styles.creditsLink}>
|
||||
Dr. Michael Möbius
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<br></br>
|
||||
<p className={styles.creditsItem}>
|
||||
Die Entwicklung wurde gefördert durch die{" "}
|
||||
<a
|
||||
href="https://foundationhollo.fi/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.creditsLink}
|
||||
>
|
||||
Stiftung Erkki J. Hollo
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p className={styles.creditsItem}>
|
||||
Programmierung durch IT-Studierende der Universität Helsinki
|
||||
</p>
|
||||
<p className={styles.creditsItem}>
|
||||
Technischer Support und Hosting: Universität Helsinki
|
||||
</p>
|
||||
</div>
|
||||
<LinkButton href="/credits" size="md">
|
||||
Credits
|
||||
Impressum
|
||||
</LinkButton>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
.creditsSection {
|
||||
width: min(100%, 1080px);
|
||||
margin: 0 auto;
|
||||
padding: 1.75rem 2rem;
|
||||
padding: 1rem 2rem 1.75rem;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 14px 32px rgba(0, 0, 0, 0.08);
|
||||
text-align: center;
|
||||
@@ -73,6 +73,7 @@
|
||||
.creditsList {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.creditsItem {
|
||||
@@ -96,7 +97,7 @@
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.creditsSection {
|
||||
padding: 1.25rem;
|
||||
padding: 0.75rem 1.25rem 1.25rem;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ function shuffle(items) {
|
||||
return [...items].sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
export function MemoryGame({ pairs, onComplete, onReset }) {
|
||||
export function MemoryGame({ pairs, onComplete, onReset, resetSignal }) {
|
||||
const [cards, setCards] = useState([]);
|
||||
const [flipped, setFlipped] = useState(new Set());
|
||||
const [selectedIds, setSelectedIds] = useState([]);
|
||||
@@ -120,6 +120,13 @@ export function MemoryGame({ pairs, onComplete, onReset }) {
|
||||
onReset?.();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof resetSignal === "number") {
|
||||
resetGame();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [resetSignal]);
|
||||
|
||||
return (
|
||||
<div className="memory-game-shell">
|
||||
<div className="memory-game-header">
|
||||
|
||||
Reference in New Issue
Block a user