diff --git a/web/package-lock.json b/web/package-lock.json
index 2cc7803f..c6632523 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "web",
- "version": "0.10.2",
+ "version": "0.11.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "web",
- "version": "0.10.2",
+ "version": "0.11.1",
"dependencies": {
"@base-ui/react": "^1.6.0",
"@codemirror/lang-json": "^6.0.2",
diff --git a/web/public/goal_ping.ogg b/web/public/goal_ping.ogg
new file mode 100644
index 00000000..7fa299c3
Binary files /dev/null and b/web/public/goal_ping.ogg differ
diff --git a/web/src/app/(main)/legal/credits/page.tsx b/web/src/app/(main)/legal/credits/page.tsx
new file mode 100644
index 00000000..fa020da0
--- /dev/null
+++ b/web/src/app/(main)/legal/credits/page.tsx
@@ -0,0 +1,73 @@
+import { GitHub } from '@mui/icons-material';
+import { Box, Container, Typography } from '@mui/material';
+import Link from 'next/link';
+
+const contributors = [
+ {
+ name: 'cjs07',
+ github: 'https://github.com/cjs8487',
+ },
+ {
+ name: 'Floha258',
+ github: 'https://github.com/floha258',
+ },
+ {
+ name: 'lepelog',
+ github: 'https://github.com/lepelog',
+ },
+ {
+ name: 'tricksnl',
+ github: 'https://github.com/devbeoservice',
+ },
+];
+
+export default function Credits() {
+ return (
+
+
+ Credits
+
+
+ PlayBingo would not be possible without the contributions our
+ community.
+
+
+ {contributors.map((contributor) => (
+
+ {contributor.name}
+
+
+
+
+
+
+ ))}
+
+
+
+ An additional, special rhanks to all the community members
+ who have helped make PlayBingo better by providing feedback,
+ finding bugs, and working with us to make PlayBingo the best
+ it can be.
+
+
+
+
+ PlayBingo makes use of parts of the following works made
+ available under the Creative Commons license.
+
+
+ Sound Effects
+
+
+
+ UI Sound Effects by lolurio (CC BY 4.0)
+
+
+
+
+ );
+}
diff --git a/web/src/components/board/Cell.tsx b/web/src/components/board/Cell.tsx
index c6a2ebd8..e82cc87d 100644
--- a/web/src/components/board/Cell.tsx
+++ b/web/src/components/board/Cell.tsx
@@ -57,6 +57,7 @@ radial-gradient(circle at 40% 30%, rgba(147, 137, 137, 0.13) 0%, rgba(0,0,0,0) 9
pointerEvents: 'none',
},
};
+
interface BoardCellProps {
goal?: Goal;
completedPlayers: string[];
diff --git a/web/src/components/footer/Footer.tsx b/web/src/components/footer/Footer.tsx
index c64d80c3..b9ec2a2e 100644
--- a/web/src/components/footer/Footer.tsx
+++ b/web/src/components/footer/Footer.tsx
@@ -85,6 +85,10 @@ function LargeFooter() {
| All Rights Reserved |{' '}
Privacy Policy
+ {' '}
+ |{' '}
+
+ Credits
diff --git a/web/src/context/RoomContext.tsx b/web/src/context/RoomContext.tsx
index c2d707f3..f5fe0d65 100644
--- a/web/src/context/RoomContext.tsx
+++ b/web/src/context/RoomContext.tsx
@@ -42,6 +42,8 @@ export enum ConnectionStatus {
CLOSED, // connection was manually closed, and the user is completely disconnected
}
+const notificationSound = new Audio('/goal_ping.ogg');
+
interface CardRegenerateOptions {
seed?: number;
generationMode?: string;
@@ -264,7 +266,28 @@ export function RoomContextProvider({
!payload.cell
)
return;
- onCellUpdate(payload.row, payload.col, payload.cell);
+ if (!board.hidden) {
+ const newMarks =
+ payload.cell.completedPlayers.filter(
+ (player) =>
+ !board.board[payload.row][
+ payload.col
+ ].completedPlayers.includes(player),
+ );
+ if (
+ newMarks.length > 0 &&
+ (newMarks.length > 1 ||
+ newMarks[0] !== connectedPlayer?.id)
+ ) {
+ notificationSound.play();
+ }
+
+ onCellUpdate(
+ payload.row,
+ payload.col,
+ payload.cell,
+ );
+ }
break;
case 'syncBoard':
if (!payload.board) return;