redo of entire app
No files matched your search
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="identifier.sqlite" uuid="c7434bd6-ec43-49a7-bbac-7cea6b408619">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:identifier.sqlite</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxBlameSettings">
|
||||
<option name="version" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/clue-hunt-app.iml" filepath="$PROJECT_DIR$/.idea/clue-hunt-app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,36 +1,47 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# Next.js Workshop Project
|
||||
|
||||
## Getting Started
|
||||
## running it locally
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
## Figma Design page:
|
||||
[The figma design](https://www.figma.com/design/aD2uqt6nOnxRnH8LhOzztD/PE-Treasure-Hunt-Design?node-id=0-1&t=qVGjYO9k4xWNhjuw-1)
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
## Expected features for the app.
|
||||
- Record team names – Must have.
|
||||
- Record names of team members – Could have.
|
||||
- A start button to initiate the treasure hunt, which remains disabled until activated by an
|
||||
admin or a specific time – Could have.
|
||||
- Display treasure hunt clues sequentially once started – Must have.
|
||||
- Store a minimum of 10 and a maximum of 20 clues centrally, to be displayed in the app –
|
||||
Could have.
|
||||
- For each clue, provide options to mark as "Found" (QR code located) or "Pass" (QR code not
|
||||
found) - Should have.
|
||||
- Allow QR code scanning, retrieve and display the associated value – Must have.
|
||||
- Store the captured value with a timestamp – Could have.
|
||||
- Display the number of clues completed, remaining clues, and time left throughout the hunt
|
||||
– Could have.
|
||||
- Show a summary of completed clues, QR codes, and total time taken at the end of the hunt
|
||||
– Could have.
|
||||
- Display an alert and prompt users to return to the venue if time runs out – Should have.
|
||||
- Disable all interactions after the allotted time expires – Could have.
|
||||
- Enable the CSM induction team to monitor the progress of all teams centrally – Could have.
|
||||
- Allow the CSM induction team to send messages to the winning teams, including rankings
|
||||
and congratulatory notes for the top three teams – Could have. -- thinking of not actually having it... way too much work.
|
||||
- Allow the CSM induction team to configure the treasure hunt parameters centrally – Could
|
||||
have. -> no clue what this means???
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
## To learn next.js
|
||||
|
||||
Next.js documentation:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
@@ -0,0 +1,53 @@
|
||||
'use client'; // Mark as a Client Component
|
||||
import { useState } from 'react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleLogin = async (e) => {
|
||||
e.preventDefault();
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
} else {
|
||||
router.push('/team-selection'); // Redirect to the team selection page after login
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
<form onSubmit={handleLogin}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<button type="submit">Log In</button>
|
||||
</form>
|
||||
{error && <p className="error">{error}</p>}
|
||||
<p>
|
||||
Don't have an account? <a href="/register">Register</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client'; // Mark as a Client Component
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { query } from '../../../lib/db';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export default function Register() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleRegister = async (e) => {
|
||||
e.preventDefault();
|
||||
const hashedPassword = bcrypt.hashSync(password, 10);
|
||||
|
||||
try {
|
||||
await query(
|
||||
'INSERT INTO users (username, password, role) VALUES (?, ?, ?)',
|
||||
[username, hashedPassword, 'user']
|
||||
);
|
||||
router.push('/login'); // Redirect to the login page after registration
|
||||
} catch (err) {
|
||||
setError('Registration failed. Username may already exist.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Register</h1>
|
||||
<form onSubmit={handleRegister}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
{error && <p className="error">{error}</p>}
|
||||
<p>
|
||||
Already have an account? <a href="/login">Login</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'use client'; // Mark as a Client Component
|
||||
import { useState } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
export default function Admin() {
|
||||
const { data: session } = useSession();
|
||||
const [timerStarted, setTimerStarted] = useState(false);
|
||||
|
||||
if (session?.user?.role !== 'admin') {
|
||||
return <p>Access denied</p>;
|
||||
}
|
||||
|
||||
const startTimer = async () => {
|
||||
// Implement timer logic (e.g., update database or state)
|
||||
setTimerStarted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin Console</h1>
|
||||
<button onClick={startTimer} disabled={timerStarted}>
|
||||
Start Timer
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { query } from '../../../lib/db';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
export default async function Progress() {
|
||||
const { data: session } = useSession();
|
||||
|
||||
// Fetch progress from the database
|
||||
const progress = await query(
|
||||
'SELECT COUNT(*) as found FROM clues WHERE found = TRUE AND team_id = ?',
|
||||
[session.user.teamId]
|
||||
);
|
||||
const remaining = 20 - progress.found;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Progress</h1>
|
||||
<p>Clues Found: {progress.found}</p>
|
||||
<p>Clues Remaining: {remaining}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'; // Mark as a Client Component
|
||||
import { useState } from 'react';
|
||||
import QRScanner from '../../../components/QRScanner';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
export default function Scan() {
|
||||
const { data: session } = useSession();
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
const handleScan = async (clueId) => {
|
||||
const response = await fetch('/api/mark-clue', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ clueId, teamId: session.user.teamId }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setMessage('Clue marked as found!');
|
||||
} else {
|
||||
setMessage('Error marking clue.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Scan QR Code</h1>
|
||||
<QRScanner onScan={handleScan} />
|
||||
{message && <p>{message}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { query } from '../../../lib/db';
|
||||
|
||||
export default async function Stats({ params }) {
|
||||
const { teamId } = params;
|
||||
|
||||
// Fetch team and clues from the database
|
||||
const team = await query('SELECT * FROM teams WHERE id = ?', [teamId]);
|
||||
const clues = await query('SELECT * FROM clues WHERE team_id = ?', [teamId]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Stats for {team.name}</h1>
|
||||
<p>Team Code: {team.code}</p>
|
||||
<h2>Clues</h2>
|
||||
<ul>
|
||||
{clues.map((clue) => (
|
||||
<li key={clue.id}>
|
||||
{clue.description} - {clue.found ? 'Found' : 'Not Found'}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'; // Mark as a Client Component
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { query } from '../../../lib/db';
|
||||
|
||||
export default function TeamSelection() {
|
||||
const [teamCode, setTeamCode] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleJoinTeam = async (e) => {
|
||||
e.preventDefault();
|
||||
const team = await query('SELECT * FROM teams WHERE code = ?', [teamCode]);
|
||||
if (team) {
|
||||
router.push(`/stats/${team.id}`);
|
||||
} else {
|
||||
setError('Team not found');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Team Selection</h1>
|
||||
<form onSubmit={handleJoinTeam}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Team Code"
|
||||
value={teamCode}
|
||||
onChange={(e) => setTeamCode(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<button type="submit">Join Team</button>
|
||||
</form>
|
||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||||
<p>
|
||||
<a href="/create-team">Create a new team</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import NextAuth from 'next-auth';
|
||||
import Providers from 'next-auth/providers';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { query } from '../../../lib/db';
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
Providers.Credentials({
|
||||
name: 'Credentials',
|
||||
credentials: {
|
||||
username: { label: 'Username', type: 'text' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
const user = await query('SELECT * FROM users WHERE username = ?', [credentials.username]);
|
||||
if (user && bcrypt.compareSync(credentials.password, user.password)) {
|
||||
return { id: user.id, name: user.username, role: user.role };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
jwt: async (token, user) => {
|
||||
if (user) {
|
||||
token.role = user.role;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
session: async (session, token) => {
|
||||
session.user.role = token.role;
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: '/login', // Redirect to the login page
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { query } from '../../../lib/db';
|
||||
|
||||
export async function POST(request) {
|
||||
const { clueId, teamId } = await request.json();
|
||||
|
||||
await query('UPDATE clues SET found = TRUE WHERE id = ? AND team_id = ?', [
|
||||
clueId,
|
||||
teamId,
|
||||
]);
|
||||
|
||||
return Response.json({ message: 'Clue marked as found' });
|
||||
}
|
||||
|
Before Width: | Height: | Size: 25 KiB |
@@ -1,26 +1,48 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Global Styles */
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
margin: 10px 0;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #0070f3;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #005bb5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import './globals.css';
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default function Home() {
|
||||
redirect('/login'); // Redirect to the login page
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
'use client'; // Mark as a Client Component
|
||||
import { useState } from 'react';
|
||||
import QrReader from 'react-qr-reader';
|
||||
|
||||
export default function QRScanner({ onScan }) {
|
||||
const [result, setResult] = useState('');
|
||||
|
||||
const handleScan = (data) => {
|
||||
if (data) {
|
||||
setResult(data);
|
||||
onScan(parseInt(data, 10));
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (err) => {
|
||||
console.error(err);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<QrReader
|
||||
delay={300}
|
||||
onError={handleError}
|
||||
onScan={handleScan}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<p>{result}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO users (username, password, role) VALUES ('admin', 'hashed_password', 'admin');
|
||||
INSERT INTO teams (name, code) VALUES ('Team Alpha', 'ALPHA123');
|
||||
INSERT INTO clues (description, team_id) VALUES ('Find the red door', 1);
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO users (username, password, role) VALUES ('admin', 'hashed_password', 'admin');
|
||||
INSERT INTO teams (name, code) VALUES ('Team Alpha', 'ALPHA123');
|
||||
INSERT INTO clues (description, team_id) VALUES ('Find the red door', 1);
|
||||
@@ -0,0 +1,17 @@
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
// Initialize the database connection
|
||||
const db = new Database('./clue_hunt.db');
|
||||
|
||||
// Helper function to run queries
|
||||
function query(sql, params) {
|
||||
try {
|
||||
const stmt = db.prepare(sql);
|
||||
return params ? stmt.all(params) : stmt.all();
|
||||
} catch (err) {
|
||||
console.error('Error executing query:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { query };
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
webpack5: true,
|
||||
webpack: (config) => {
|
||||
config.resolve.fallback = { fs: false };
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
@@ -4,4 +4,13 @@ const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
webpack5: true,
|
||||
webpack: (config: { resolve: { fallback: { fs: boolean; }; }; }) => {
|
||||
config.resolve.fallback = { fs: false };
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -9,19 +9,27 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^3.0.2",
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"next": "15.2.2",
|
||||
"next-auth": "^4.24.11",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.2.2"
|
||||
"react-qr-reader": "^3.0.0-beta-1",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.2.2",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg width="402" height="874" viewBox="0 0 402 874" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="402" height="874" fill="#A1887F"/>
|
||||
<line x1="200.5" x2="200.5" y2="874" stroke="#917A72" stroke-width="3"/>
|
||||
<line x1="306.5" x2="306.5" y2="874" stroke="#917A72" stroke-width="3"/>
|
||||
<line x1="98.5" x2="98.5" y2="874" stroke="#917A72" stroke-width="3"/>
|
||||
<line x1="400.5" x2="400.5" y2="874" stroke="#917A72" stroke-width="3"/>
|
||||
<path d="M0 721H402V784H0V721Z" fill="#917A72"/>
|
||||
<circle cx="49.5" cy="752.5" r="14" stroke="black" stroke-width="3"/>
|
||||
<circle cx="200.5" cy="752.5" r="14" stroke="black" stroke-width="3"/>
|
||||
<circle cx="351.5" cy="752.5" r="14" stroke="black" stroke-width="3"/>
|
||||
<path d="M0 107H402V170H0V107Z" fill="#917A72"/>
|
||||
<circle cx="49.5" cy="138.5" r="14" stroke="black" stroke-width="3"/>
|
||||
<circle cx="200.5" cy="138.5" r="14" stroke="black" stroke-width="3"/>
|
||||
<circle cx="351.5" cy="138.5" r="14" stroke="black" stroke-width="3"/>
|
||||
<line y1="168.5" x2="402" y2="168.5" stroke="black" stroke-width="3"/>
|
||||
<line y1="105.5" x2="402" y2="105.5" stroke="black" stroke-width="3"/>
|
||||
<line y1="782.5" x2="402" y2="782.5" stroke="black" stroke-width="3"/>
|
||||
<line y1="719.5" x2="402" y2="719.5" stroke="black" stroke-width="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="125" height="122" viewBox="0 0 125 122" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect x="3" width="122" height="122" fill="url(#pattern0_68_91)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_68_91" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_68_91" transform="scale(0.00195312)"/>
|
||||
</pattern>
|
||||
<image id="image0_68_91" width="512" height="512" preserveAspectRatio="none" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="122" height="122" viewBox="0 0 122 122" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="122" height="122" fill="url(#pattern0_68_98)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_68_98" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_68_98" transform="scale(0.00195312)"/>
|
||||
</pattern>
|
||||
<image id="image0_68_98" width="512" height="512" preserveAspectRatio="none" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="132" height="132" viewBox="0 0 132 132" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect x="52.3086" width="95" height="95" transform="rotate(33.4094 52.3086 0)" fill="url(#pattern0_0_343)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_0_343" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_0_343" transform="scale(0.00195312)"/>
|
||||
</pattern>
|
||||
<image id="image0_0_343" width="512" height="512" preserveAspectRatio="none" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 51 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
Before Width: | Height: | Size: 385 B |
@@ -0,0 +1,61 @@
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { open } from 'sqlite';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
async function initDb() {
|
||||
const db = await open({
|
||||
filename: './clue_hunt.db',
|
||||
driver: sqlite3.Database,
|
||||
});
|
||||
|
||||
// Create tables
|
||||
await db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'user'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
code TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS clues (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
description TEXT NOT NULL,
|
||||
found BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
skipped BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
team_id INTEGER,
|
||||
FOREIGN KEY (team_id) REFERENCES teams(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS progress (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_id INTEGER,
|
||||
clue_id INTEGER,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (team_id) REFERENCES teams(id),
|
||||
FOREIGN KEY (clue_id) REFERENCES clues(id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Seed initial data
|
||||
const hashedPassword = bcrypt.hashSync('admin', 10);
|
||||
await db.run(
|
||||
'INSERT OR IGNORE INTO users (username, password, role) VALUES (?, ?, ?)',
|
||||
['admin', hashedPassword, 'admin']
|
||||
);
|
||||
|
||||
await db.run(
|
||||
'INSERT OR IGNORE INTO teams (name, code) VALUES (?, ?)',
|
||||
['Team Alpha', 'ALPHA123']
|
||||
);
|
||||
|
||||
console.log('Database initialized successfully.');
|
||||
await db.close();
|
||||
}
|
||||
|
||||
initDb().catch((err) => console.error('Error initializing database:', err));
|
||||
@@ -0,0 +1,18 @@
|
||||
const { query } = require('../lib/db');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function seedDb() {
|
||||
try {
|
||||
const hashedPassword = bcrypt.hashSync('admin', 10);
|
||||
await query(
|
||||
'INSERT OR IGNORE INTO users (username, password, role) VALUES (?, ?, ?)',
|
||||
['admin', hashedPassword, 'admin']
|
||||
);
|
||||
|
||||
console.log('Database seeded successfully.');
|
||||
} catch (err) {
|
||||
console.error('Error seeding database:', err);
|
||||
}
|
||||
}
|
||||
|
||||
seedDb();
|
||||
@@ -0,0 +1,12 @@
|
||||
const { query } = require('../lib/db');
|
||||
|
||||
async function testDb() {
|
||||
try {
|
||||
const rows = await query('SELECT 1 + 1 AS result');
|
||||
console.log('Database test successful:', rows);
|
||||
} catch (err) {
|
||||
console.error('Database test failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
testDb();
|
||||
@@ -0,0 +1,16 @@
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
// Use an in-memory database for testing
|
||||
const db = new Database(':memory:');
|
||||
|
||||
// Create a table
|
||||
db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)');
|
||||
|
||||
// Insert data
|
||||
const insert = db.prepare('INSERT INTO test (name) VALUES (?)');
|
||||
insert.run('Alice');
|
||||
insert.run('Bob');
|
||||
|
||||
// Query data
|
||||
const rows = db.prepare('SELECT * FROM test').all();
|
||||
console.log(rows); // Should output: [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]
|
||||