From 656eadb5713a7eeb0bd4219252ccbba47d0cbec6 Mon Sep 17 00:00:00 2001 From: Kartikeya Nainkhwal Date: Thu, 9 Oct 2025 15:17:57 +0530 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A7=B9=20Implement=20Prettier=20as=20?= =?UTF-8?q?a=20GitHub=20Action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add .prettierrc with React-friendly configuration - Add .prettierignore to exclude build files and dependencies - Add GitHub Action workflow for automatic PR formatting checks - Add npm scripts for local formatting (format & format:check) - Update README with formatting documentation and badge - Add Prettier as dev dependency This ensures consistent code formatting across all contributions and automatically checks PRs for formatting issues. --- .github/workflows/prettier.yml | 66 ++++++++++++++++++++++++++++++++++ .prettierignore | 33 +++++++++++++++++ .prettierrc | 12 +++++++ README.md | 23 ++++++++++++ package-lock.json | 19 ++++++++++ package.json | 7 +++- 6 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/prettier.yml create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 0000000..992223f --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,66 @@ +name: Prettier Code Formatting Check + +on: + pull_request: + branches: [master, main] + push: + branches: [master, main] + +jobs: + prettier-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # Fetch full history for better context + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install Prettier + run: npm install --save-dev --save-exact prettier + + - name: Check Prettier formatting + run: | + echo "Checking code formatting with Prettier..." + npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" || { + echo "โŒ Code formatting issues found!" + echo "Run 'npx prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,json,md}\"' to fix formatting issues." + exit 1 + } + echo "โœ… All files are properly formatted!" + + - name: Comment PR with formatting issues (if any) + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `## ๐Ÿงน Code Formatting Issues Detected + + This PR has code formatting issues that need to be fixed. + + **To fix these issues locally:** + \`\`\`bash + npx prettier --write "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" + \`\`\` + + **Or run this command to check formatting:** + \`\`\`bash + npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" + \`\`\` + + Please fix the formatting and push your changes. โœจ` + }) diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c367b6e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,33 @@ +# Dependencies +node_modules/ + +# Build outputs +build/ +dist/ + +# Package files +package-lock.json +yarn.lock + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +*.log + +# Coverage reports +coverage/ + +# Public assets that shouldn't be formatted +public/ + +# IDE files +.vscode/ +.idea/ + +# Git +.git/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0490619 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "jsxSingleQuote": true, + "arrowParens": "avoid", + "endOfLine": "lf" +} diff --git a/README.md b/README.md index d7b8a93..886e780 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@
[![forthebadge](https://forthebadge.com/images/badges/open-source.svg)](https://forthebadge.com)   +![Code Formatting](https://github.com/KartikeyaNainkhwal/Couple-Memory/workflows/Prettier%20Code%20Formatting%20Check/badge.svg)

Hacktoberfest 2025

@@ -82,6 +83,28 @@ Lets Hack! Feel free to make PR and complete the readme with your changes +## ๐Ÿงน Code Formatting + +This project uses [Prettier](https://prettier.io/) to maintain consistent code formatting. All pull requests are automatically checked for proper formatting. + +### Local Development + +Before submitting a PR, make sure your code is properly formatted: + +```bash +# Check formatting +npm run format:check + +# Auto-fix formatting issues +npm run format +``` + +### Automatic Checks + +- โœ… Every PR automatically runs Prettier checks via GitHub Actions +- โŒ PRs with formatting issues will fail the check +- ๐Ÿ’ก The action will provide instructions on how to fix formatting issues + ## Credits Sound Effect from Pixabay diff --git a/package-lock.json b/package-lock.json index 7adf1e4..76c2576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,9 @@ "react-share": "^4.4.1", "react-use": "^17.4.0", "web-vitals": "^1.1.2" + }, + "devDependencies": { + "prettier": "^3.6.2" } }, "node_modules/@adobe/css-tools": { @@ -16709,6 +16712,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/package.json b/package.json index ff6cc6b..ad83776 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,12 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,json,md}\"", + "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,css,scss,json,md}\"" + }, + "devDependencies": { + "prettier": "^3.6.2" }, "eslintConfig": { "extends": [ From 994e26443790142d6633481234fdc1c914710340 Mon Sep 17 00:00:00 2001 From: Kartikeya Nainkhwal Date: Thu, 9 Oct 2025 17:13:46 +0530 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=9A=80=20Fix=20Prettier=20Action:=20A?= =?UTF-8?q?uto-format=20and=20auto-commit=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit โœ… Resolved failing GitHub Action by implementing auto-formatting workflow โœ… Action now formats code automatically instead of just failing โœ… Auto-commits formatted changes back to PR branch โœ… Formatted all existing source files for consistency โœ… Meets all requirements from issue #134 Changes: - Updated workflow to auto-format with 'prettier --write' - Added auto-commit functionality with proper git config - Formatted all 19 source files that had style issues - Action now succeeds and provides helpful PR comments - Fully addresses reviewer feedback for automatic formatting --- .github/workflows/prettier.yml | 63 +++--- src/App.css | 34 ++- src/App.js | 125 +++++------ src/components/CustomCursor/CustomCursor.css | 2 +- src/components/CustomCursor/CustomCursor.js | 12 +- src/components/celebration/Celebration.css | 44 ++-- src/components/celebration/Celebration.js | 16 +- src/components/confetti/Confetti.js | 6 +- src/components/gameover/GameOver.js | 12 +- src/components/sharebutton/ShareButton.js | 210 +++++++++---------- src/components/sharebutton/sharebutton.css | 175 +++++++++------- src/components/singlecard/SingleCard.css | 91 ++++---- src/components/singlecard/SingleCard.js | 91 ++++---- src/components/toggleTheme/toggleTheme.css | 21 +- src/components/toggleTheme/toggleTheme.js | 48 ++--- src/constants/numbers.js | 2 +- src/data/cardImages.js | 46 ++-- src/hooks/useTrackViewCounter.js | 12 +- src/index.css | 25 ++- src/index.js | 2 +- 20 files changed, 545 insertions(+), 492 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 992223f..4d965dc 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -1,4 +1,4 @@ -name: Prettier Code Formatting Check +name: Prettier Code Formatting on: pull_request: @@ -7,14 +7,15 @@ on: branches: [master, main] jobs: - prettier-check: + prettier-format: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: - # Fetch full history for better context + # Checkout with token to allow pushing changes + token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 - name: Setup Node.js @@ -26,21 +27,34 @@ jobs: - name: Install dependencies run: npm ci - - name: Install Prettier - run: npm install --save-dev --save-exact prettier + - name: Run Prettier formatting + run: | + echo "๐Ÿงน Running Prettier formatting..." + npx prettier --write "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" + echo "โœ… Prettier formatting completed!" + + - name: Check for changes + id: verify-changed-files + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ Files were formatted by Prettier" + else + echo "changed=false" >> $GITHUB_OUTPUT + echo "โœ… No formatting changes needed" + fi - - name: Check Prettier formatting + - name: Commit and push changes + if: steps.verify-changed-files.outputs.changed == 'true' run: | - echo "Checking code formatting with Prettier..." - npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" || { - echo "โŒ Code formatting issues found!" - echo "Run 'npx prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,json,md}\"' to fix formatting issues." - exit 1 - } - echo "โœ… All files are properly formatted!" + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add . + git commit -m "๐Ÿงน Auto-format code with Prettier [skip ci]" + git push - - name: Comment PR with formatting issues (if any) - if: failure() && github.event_name == 'pull_request' + - name: Comment on PR with success message + if: github.event_name == 'pull_request' && steps.verify-changed-files.outputs.changed == 'true' uses: actions/github-script@v7 with: script: | @@ -48,19 +62,16 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `## ๐Ÿงน Code Formatting Issues Detected + body: `## โœจ Code Automatically Formatted! - This PR has code formatting issues that need to be fixed. + Your code has been automatically formatted with Prettier. - **To fix these issues locally:** - \`\`\`bash - npx prettier --write "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" - \`\`\` + The changes have been committed and pushed to this PR. ๐ŸŽ‰ - **Or run this command to check formatting:** - \`\`\`bash - npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" - \`\`\` + **What happened:** + - ๐Ÿงน Prettier automatically formatted all source files + - ๐Ÿ“ Changes were committed with message: "๐Ÿงน Auto-format code with Prettier" + - ๐Ÿš€ Updated code was pushed to your branch - Please fix the formatting and push your changes. โœจ` + No further action needed! โœ…` }) diff --git a/src/App.css b/src/App.css index 6cf7eb7..4af2f42 100644 --- a/src/App.css +++ b/src/App.css @@ -3,7 +3,7 @@ margin: 40px auto; } button { - color: var(--text,#fff); + color: var(--text, #fff); background: var(--background, #1b1523); border: 2px solid; padding: 6px 12px; @@ -23,8 +23,7 @@ button:hover { grid-gap: 20px; } - -#theme-toggle{ +#theme-toggle { position: absolute; top: 1vh; right: 1vw; @@ -32,14 +31,27 @@ button:hover { } div.animation { - animation: shake 1s cubic-bezier(.36,.07,.19,.97); + animation: shake 1s cubic-bezier(0.36, 0.07, 0.19, 0.97); } @keyframes shake { - 10%, 90% { transform: translate3d(-1px, 0, 0); } - 20%, 80% { transform: translate3d(2px, 0, 0); } - 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } - 40%, 60% { transform: translate3d(4px, 0, 0); } + 10%, + 90% { + transform: translate3d(-1px, 0, 0); + } + 20%, + 80% { + transform: translate3d(2px, 0, 0); + } + 30%, + 50%, + 70% { + transform: translate3d(-4px, 0, 0); + } + 40%, + 60% { + transform: translate3d(4px, 0, 0); + } } .collapse-animation { animation: collapse 1.2s forwards; @@ -56,7 +68,7 @@ div.animation { -moz-transform: scale(0.2) rotate(360deg); -ms-transform: scale(0.2) rotate(360deg); -o-transform: scale(0.2) rotate(360deg); -} + } 100% { transform: scale(1) rotate(720deg); -webkit-transform: scale(1) rotate(720deg); @@ -87,10 +99,10 @@ div.animation { } @media screen and (max-width: 390px) { - .card-grid{ + .card-grid { display: grid; grid-template-columns: 1fr; align-items: center; align-content: center; } -} \ No newline at end of file +} diff --git a/src/App.js b/src/App.js index 571486d..8a14fa4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,18 +1,18 @@ // src/App.js -import { useEffect, useState, useMemo, useCallback, useRef } from "react"; -import { nanoid } from "nanoid"; -import "./App.css"; -import SingleCard from "./components/singlecard/SingleCard"; -import Celebration from "./components/celebration/Celebration"; -import ToggleTheme from "./components/toggleTheme/toggleTheme"; -import ShowConfetti from "./components/confetti/Confetti"; -import GameOver from "./components/gameover/GameOver"; -import CustomCursor from "./components/CustomCursor/CustomCursor"; +import { useEffect, useState, useMemo, useCallback, useRef } from 'react'; +import { nanoid } from 'nanoid'; +import './App.css'; +import SingleCard from './components/singlecard/SingleCard'; +import Celebration from './components/celebration/Celebration'; +import ToggleTheme from './components/toggleTheme/toggleTheme'; +import ShowConfetti from './components/confetti/Confetti'; +import GameOver from './components/gameover/GameOver'; +import CustomCursor from './components/CustomCursor/CustomCursor'; -import { cardImages } from "./data/cardImages"; -import { numbers } from "./constants/numbers"; -import { secureShuffleArray, pickRandomImages } from "./utils/logic"; -import useTrackViewCounter from "./hooks/useTrackViewCounter"; +import { cardImages } from './data/cardImages'; +import { numbers } from './constants/numbers'; +import { secureShuffleArray, pickRandomImages } from './utils/logic'; +import useTrackViewCounter from './hooks/useTrackViewCounter'; function App() { const [cards, setCards] = useState([]); @@ -35,7 +35,7 @@ function App() { const [gameOverMessage, setGameOverMessage] = useState(false); const viewCounter = useTrackViewCounter(); const REVEAL_DURATION = 2000; - const HINT_COOLDOWN = 5000; + const HINT_COOLDOWN = 5000; const soundEffect = useMemo(() => { const audio = new Audio(); @@ -44,7 +44,7 @@ function App() { }, []); const playSound = useCallback( - (src) => { + src => { soundEffect.src = src; soundEffect.load(); soundEffect.play().catch(() => {}); @@ -55,15 +55,15 @@ function App() { const resetTurn = useCallback(() => { setChoiceOne(null); setChoiceTwo(null); - setTurns((prev) => prev + 1); + setTurns(prev => prev + 1); setDisabled(false); }, []); - const handleTime = useCallback((start) => { + const handleTime = useCallback(start => { if (start) { if (!intervalRef.current) { intervalRef.current = setInterval(() => { - setElapsedTime((prev) => (prev || 0) + 1); + setElapsedTime(prev => (prev || 0) + 1); }, 1000); } } else if (intervalRef.current) { @@ -85,14 +85,14 @@ function App() { if (!cards || cards.length === 0) return; if (hintCount === 0) return; - setHintCount((c) => c - 1); + setHintCount(c => c - 1); hintLockedRef.current = true; if (elapsedTime === undefined) handleTime(true); const seconds = Math.floor(HINT_COOLDOWN / 1000); setHintCooldown(seconds); if (hintIntervalRef.current) clearInterval(hintIntervalRef.current); hintIntervalRef.current = setInterval(() => { - setHintCooldown((s) => { + setHintCooldown(s => { if (s <= 1) { clearInterval(hintIntervalRef.current); hintIntervalRef.current = null; @@ -103,7 +103,9 @@ function App() { }); }, 1000); - const available = cards.map((c, i) => ({ c, i })).filter(({ c }) => !c.matched); + const available = cards + .map((c, i) => ({ c, i })) + .filter(({ c }) => !c.matched); if (available.length === 0) { if (hintIntervalRef.current) { clearInterval(hintIntervalRef.current); @@ -131,9 +133,11 @@ function App() { return; } - const idxA = crypto.getRandomValues(new Uint32Array(1))[0] % available.length; + const idxA = + crypto.getRandomValues(new Uint32Array(1))[0] % available.length; - let idxB = crypto.getRandomValues(new Uint32Array(1))[0] % (available.length - 1); + let idxB = + crypto.getRandomValues(new Uint32Array(1))[0] % (available.length - 1); if (idxB >= idxA) idxB += 1; const cardA = available[idxA].c; @@ -158,7 +162,7 @@ function App() { const selected = pickRandomImages(cardImages, 6); const dup = [...selected, ...selected] .sort(() => nanoid(16).localeCompare(nanoid(16))) - .map((card) => { + .map(card => { const crypto = globalThis.crypto || globalThis.msCrypto; const rand = new Uint32Array(1); crypto.getRandomValues(rand); @@ -180,14 +184,14 @@ function App() { }, [clearTimer]); const handleNewGame = useCallback(() => { - setHintCount(3) + setHintCount(3); setHintCooldown(0); - playSound("audio/start.mp3"); + playSound('audio/start.mp3'); shuffledCards(); }, [playSound, shuffledCards]); const handleChoice = useCallback( - (card) => { + card => { if (disabled) return; choiceOne ? setChoiceTwo(card) : setChoiceOne(card); if (elapsedTime === undefined) handleTime(true); @@ -201,38 +205,36 @@ function App() { if (choiceOne && choiceTwo) { setDisabled(true); if (choiceOne.src === choiceTwo.src) { - playSound("/audio/match.wav"); - setCards((prev) => - prev.map((c) => - c.src === choiceOne.src ? { ...c, matched: true } : c - ) + playSound('/audio/match.wav'); + setCards(prev => + prev.map(c => (c.src === choiceOne.src ? { ...c, matched: true } : c)) ); - setMatched((prev) => prev + 2); + setMatched(prev => prev + 2); resetTurn(); } else { - playSound("/audio/fail.wav"); + playSound('/audio/fail.wav'); const t = setTimeout(() => resetTurn(), 1000); return () => clearTimeout(t); } } else if (choiceOne) { - playSound("/audio/swap.wav"); + playSound('/audio/swap.wav'); } }, [choiceOne, choiceTwo, resetTurn, playSound]); useEffect(() => { if (matched === cards.length && turns) { handleTime(false); - const storedHigh = globalThis.localStorage.getItem("highscore"); - const storedRun = globalThis.localStorage.getItem("runtime"); + const storedHigh = globalThis.localStorage.getItem('highscore'); + const storedRun = globalThis.localStorage.getItem('runtime'); const better = storedHigh === null || turns < Number(storedHigh) || (turns === Number(storedHigh) && elapsedTime < Number(storedRun)); if (better) { - globalThis.localStorage.setItem("highscore", turns); - globalThis.localStorage.setItem("runtime", elapsedTime); - playSound("audio/celebration.mp3"); + globalThis.localStorage.setItem('highscore', turns); + globalThis.localStorage.setItem('runtime', elapsedTime); + playSound('audio/celebration.mp3'); setCelebrationStatus(true); setHighScore(turns); setGameOverMessage(false); @@ -244,7 +246,7 @@ function App() { useEffect(() => { shuffledCards(); - const hs = Number(globalThis.localStorage.getItem("highscore") || 0); + const hs = Number(globalThis.localStorage.getItem('highscore') || 0); setHighScore(hs); return () => { clearTimer(); @@ -262,7 +264,7 @@ function App() { }, [shuffledCards, clearTimer]); return ( -
+
{celebrationStatus && ( )} {celebrationStatus && } - A&A Match + A&A Match
-
- -
- -

{hintCount === 1 ? "Hint Remaining: " : "Hints Remaining: "}{hintCount}

-
+
+ +
+ +

+ {hintCount === 1 ? 'Hint Remaining: ' : 'Hints Remaining: '} + {hintCount} +

+
- {cards.map((card) => ( + {cards.map(card => (

Turns: {turns}

-
+

HighScore: {highScore}

-

Runtime: {globalThis.localStorage.getItem("runtime") || 0}

+

Runtime: {globalThis.localStorage.getItem('runtime') || 0}

-

Time Elapsed: {elapsedTime || "Not started"}

+

Time Elapsed: {elapsedTime || 'Not started'}

{viewCounter !== null &&

This memory got {viewCounter} views

} {gameOverMessage && ( { const [position, setPosition] = useState({ x: 0, y: 0 }); // Update cursor position on mouse move useEffect(() => { - const moveHandler = (e) => { + const moveHandler = e => { setPosition({ x: e.clientX, y: e.clientY }); }; - globalThis.addEventListener("mousemove", moveHandler); - return () => globalThis.removeEventListener("mousemove", moveHandler); + globalThis.addEventListener('mousemove', moveHandler); + return () => globalThis.removeEventListener('mousemove', moveHandler); }, []); return (
-
+
+

Highscore achieved: {highScore}
@@ -21,10 +21,10 @@ export default function Celebration({ {elapsedTime}s

} -
+
-
diff --git a/src/components/confetti/Confetti.js b/src/components/confetti/Confetti.js index cd68c3a..56d4ace 100644 --- a/src/components/confetti/Confetti.js +++ b/src/components/confetti/Confetti.js @@ -1,6 +1,6 @@ -import React from "react"; -import Confetti from "react-confetti"; -import { useWindowSize } from "react-use"; +import React from 'react'; +import Confetti from 'react-confetti'; +import { useWindowSize } from 'react-use'; export default function ShowConfetti() { const { width, height } = useWindowSize(); diff --git a/src/components/gameover/GameOver.js b/src/components/gameover/GameOver.js index 871e3c4..8b1d3da 100644 --- a/src/components/gameover/GameOver.js +++ b/src/components/gameover/GameOver.js @@ -1,11 +1,11 @@ -import "./../celebration/Celebration.css"; -import React from "react"; -import PropTypes from "prop-types"; +import './../celebration/Celebration.css'; +import React from 'react'; +import PropTypes from 'prop-types'; export default function GameOver({ score, elapsedTime, handleRestartGame }) { return ( -
-
+
+

Game Over! Your Score: {score}
@@ -15,7 +15,7 @@ export default function GameOver({ score, elapsedTime, handleRestartGame }) { {elapsedTime}s

} -
diff --git a/src/components/sharebutton/ShareButton.js b/src/components/sharebutton/ShareButton.js index 8d7e4ca..aa76032 100644 --- a/src/components/sharebutton/ShareButton.js +++ b/src/components/sharebutton/ShareButton.js @@ -1,28 +1,28 @@ -import "./sharebutton.css"; -import React, { useState } from "react"; -import html2canvas from "html2canvas"; -import PropTypes from "prop-types"; +import './sharebutton.css'; +import React, { useState } from 'react'; +import html2canvas from 'html2canvas'; +import PropTypes from 'prop-types'; export default function ShareButton({ highScore, highScoreRef }) { const [screenshotImage, setScreenshotImage] = useState(null); - const shareDialog = document.querySelector(".share-dialog"); - const closeButton = document.querySelector(".close-button"); + const shareDialog = document.querySelector('.share-dialog'); + const closeButton = document.querySelector('.close-button'); const handleScreenshot = () => { html2canvas(highScoreRef.current, { useCORS: true, - backgroundColor: "black", + backgroundColor: 'black', }) - .then((canvas) => { - const image = canvas.toDataURL("image/jpeg"); + .then(canvas => { + const image = canvas.toDataURL('image/jpeg'); setScreenshotImage(image); - const a = document.createElement("a"); + const a = document.createElement('a'); a.href = image; - a.download = "high_score_screenshot.jpeg"; + a.download = 'high_score_screenshot.jpeg'; a.click(); }) - .catch((error) => { - console.error("Error capturing screenshot:", error); + .catch(error => { + console.error('Error capturing screenshot:', error); }); shareContent(); }; @@ -30,171 +30,171 @@ export default function ShareButton({ highScore, highScoreRef }) { if (navigator.share && screenshotImage) { try { await navigator.share({ - title: "High Score Screenshot", + title: 'High Score Screenshot', text: `I achieved a high score of ${highScore} in A&A Match!`, - url: "https://aa-memory.vercel.app/", + url: 'https://aa-memory.vercel.app/', files: [ - new File(["highScore"], screenshotImage, { type: "image/jpeg" }), + new File(['highScore'], screenshotImage, { type: 'image/jpeg' }), ], }); - console.log("Sharing successfully!"); + console.log('Sharing successfully!'); } catch (error) { - console.error("Error while sharing: ", error); + console.error('Error while sharing: ', error); } } else { - shareDialog.classList.add("is-open"); - closeButton.addEventListener("click", (event) => { - shareDialog.classList.remove("is-open"); + shareDialog.classList.add('is-open'); + closeButton.addEventListener('click', event => { + shareDialog.classList.remove('is-open'); }); } }; const closeDialog = () => { - shareDialog.classList.remove("is-open"); + shareDialog.classList.remove('is-open'); }; return ( -
-
+
+
-

Share this pen

-
-
- - - -
-
-
https://aa-memory.vercel.app/
- +
+
https://aa-memory.vercel.app/
+
- + - - - + + + - + - + - - + + - - - + + + - - - + + + diff --git a/src/components/sharebutton/sharebutton.css b/src/components/sharebutton/sharebutton.css index d690aa5..b86245b 100644 --- a/src/components/sharebutton/sharebutton.css +++ b/src/components/sharebutton/sharebutton.css @@ -1,115 +1,132 @@ html { - box-sizing: border-box; + box-sizing: border-box; } -*, *::before, *::after { - box-sizing: inherit; - padding: 0; - margin: 0; +*, +*::before, +*::after { + box-sizing: inherit; + padding: 0; + margin: 0; } body { - font-family: -apple-system,BlinkMacSystemFont,San Francisco,Helvetica Neue,Helvetica,Ubuntu,Roboto,Noto,Segoe UI,Arial,sans-serif; + font-family: + -apple-system, + BlinkMacSystemFont, + San Francisco, + Helvetica Neue, + Helvetica, + Ubuntu, + Roboto, + Noto, + Segoe UI, + Arial, + sans-serif; } .hidden { - display: none; + display: none; } svg { - width: 20px; - height: 20px; - margin-right: 7px; -} - -button, .button { - display: inline-flex; - align-items: center; - justify-content: center; - height: auto; - padding-top: 8px; - padding-bottom: 8px; - color: #777; - text-align: center; - font-size: 14px; - font-weight: 500; - line-height: 1.1; - letter-spacing: 2px; - text-transform: capitalize; - text-decoration: none; - white-space: nowrap; - border-radius: 4px; - border: 1px solid #ddd; -} - -button:hover, .button:hover { - border-color: #cdd; -} - -.share-button, .copy-link { - padding-left: 30px; - padding-right: 30px; -} - -.share-button, .share-dialog { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-107%, -50%); + width: 20px; + height: 20px; + margin-right: 7px; +} + +button, +.button { + display: inline-flex; + align-items: center; + justify-content: center; + height: auto; + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: center; + font-size: 14px; + font-weight: 500; + line-height: 1.1; + letter-spacing: 2px; + text-transform: capitalize; + text-decoration: none; + white-space: nowrap; + border-radius: 4px; + border: 1px solid #ddd; +} + +button:hover, +.button:hover { + border-color: #cdd; +} + +.share-button, +.copy-link { + padding-left: 30px; + padding-right: 30px; +} + +.share-button, +.share-dialog { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-107%, -50%); } .share-dialog { - display: none; - max-width: 500px; - box-shadow: 0 8px 16px rgba(0,0,0,.15); - z-index: -1; - border: 1px solid #ddd; - padding: 20px; - border-radius: 4px; - background-color: #fff; + display: none; + max-width: 500px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15); + z-index: -1; + border: 1px solid #ddd; + padding: 20px; + border-radius: 4px; + background-color: #fff; } .share-dialog.is-open { - display: block; - z-index: 2; + display: block; + z-index: 2; } header { - display: flex; - justify-content: space-between; - margin-bottom: 20px; + display: flex; + justify-content: space-between; + margin-bottom: 20px; } .targets { - display: grid; - grid-template-rows: 1fr 1fr; - grid-template-columns: 1fr 1fr; - grid-gap: 20px; - margin-bottom: 20px; + display: grid; + grid-template-rows: 1fr 1fr; + grid-template-columns: 1fr 1fr; + grid-gap: 20px; + margin-bottom: 20px; } .close-button { - background-color: transparent; - border: none; - padding: 0; + background-color: transparent; + border: none; + padding: 0; } .close-button svg { - margin-right: 0; + margin-right: 0; } .link { - display: flex; - justify-content: center; - align-items: center; - padding: 10px; - border-radius: 4px; - background-color: #eee; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + border-radius: 4px; + background-color: #eee; } .pen-url { - margin-right: 15px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} \ No newline at end of file + margin-right: 15px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/components/singlecard/SingleCard.css b/src/components/singlecard/SingleCard.css index cd92c32..9ba7888 100644 --- a/src/components/singlecard/SingleCard.css +++ b/src/components/singlecard/SingleCard.css @@ -1,70 +1,69 @@ .card { - position: relative; - display: grid; - place-items: center; + position: relative; + display: grid; + place-items: center; } .card img { - display: block; - border: 2px solid #fff; - border-radius: 6px; + display: block; + border: 2px solid #fff; + border-radius: 6px; } .card .front-container { - display: flex; - justify-content: center; /* Center the content horizontally */ - align-items: center; /* Center the content vertically */ + display: flex; + justify-content: center; /* Center the content horizontally */ + align-items: center; /* Center the content vertically */ } .card .front { - transform: rotateY(90deg); - object-fit: scale-down; - background-color: fff; - position: absolute; + transform: rotateY(90deg); + object-fit: scale-down; + background-color: fff; + position: absolute; } .front-background { - object-fit: cover; - width: 200px; - border: none !important; - filter: blur(2px); + object-fit: cover; + width: 200px; + border: none !important; + filter: blur(2px); } .flipped .front { - transform: rotateY(0deg) translateX(0); /* Keep translateX(0) to avoid horizontal shift */ - margin: 0 auto; /* Ensure margins don't cause shifting */ - + transform: rotateY(0deg) translateX(0); /* Keep translateX(0) to avoid horizontal shift */ + margin: 0 auto; /* Ensure margins don't cause shifting */ } .card .back { - transition: all ease-in 0.2s; - object-fit: cover; - transition-delay: 0.2s; + transition: all ease-in 0.2s; + object-fit: cover; + transition-delay: 0.2s; } .flipped .back { - transform: rotateY(90deg); - transition-delay: 0s; + transform: rotateY(90deg); + transition-delay: 0s; } /*Responsive design*/ @media screen and (max-width: 800px) { - .card .front { - object-fit: unset; - position: absolute; - } - .front-background { - object-fit: scale-down; - border: unset !important; - filter: unset; - display: none; - } - .card { - padding-top: 10px; - padding-bottom: 10px; - } - .card img { - display: block; - border: 2px solid #fff; - border-radius: 6px; - width: 90%; - } -} \ No newline at end of file + .card .front { + object-fit: unset; + position: absolute; + } + .front-background { + object-fit: scale-down; + border: unset !important; + filter: unset; + display: none; + } + .card { + padding-top: 10px; + padding-bottom: 10px; + } + .card img { + display: block; + border: 2px solid #fff; + border-radius: 6px; + width: 90%; + } +} diff --git a/src/components/singlecard/SingleCard.js b/src/components/singlecard/SingleCard.js index 25269bd..ff349ff 100644 --- a/src/components/singlecard/SingleCard.js +++ b/src/components/singlecard/SingleCard.js @@ -2,50 +2,61 @@ import './SingleCard.css'; import PropTypes from 'prop-types'; export default function SingleCard({ card, handleChoice, flipped, disabled }) { + const handleClick = () => { + if (!disabled) { + handleChoice(card); + } + }; - const handleClick = () => { - if (!disabled) { - handleChoice(card); - } - }; - - return ( -
-
-
- card front blur background - card front -
- -
+ return ( +
+
+
+ card front blur background + card front
- ); + +
+
+ ); } // PropTypes Validation SingleCard.propTypes = { - card: PropTypes.shape({ - src: PropTypes.string.isRequired, - matched: PropTypes.bool.isRequired, - }).isRequired, - handleChoice: PropTypes.func.isRequired, - flipped: PropTypes.bool.isRequired, - disabled: PropTypes.bool.isRequired, + card: PropTypes.shape({ + src: PropTypes.string.isRequired, + matched: PropTypes.bool.isRequired, + }).isRequired, + handleChoice: PropTypes.func.isRequired, + flipped: PropTypes.bool.isRequired, + disabled: PropTypes.bool.isRequired, }; diff --git a/src/components/toggleTheme/toggleTheme.css b/src/components/toggleTheme/toggleTheme.css index 666d225..5b17d37 100644 --- a/src/components/toggleTheme/toggleTheme.css +++ b/src/components/toggleTheme/toggleTheme.css @@ -1,11 +1,10 @@ -:root[data-applied-mode="light"] { - color-scheme: light; - --text: #1b1523; - --background: white; - } - :root[data-applied-mode="dark"] { - color-scheme: dark; - --text: white; - --background: #1b1523; - } - \ No newline at end of file +:root[data-applied-mode='light'] { + color-scheme: light; + --text: #1b1523; + --background: white; +} +:root[data-applied-mode='dark'] { + color-scheme: dark; + --text: white; + --background: #1b1523; +} diff --git a/src/components/toggleTheme/toggleTheme.js b/src/components/toggleTheme/toggleTheme.js index c4645a1..6708193 100644 --- a/src/components/toggleTheme/toggleTheme.js +++ b/src/components/toggleTheme/toggleTheme.js @@ -1,28 +1,28 @@ -import { useEffect, useState } from "react"; -import "./toggleTheme.css"; +import { useEffect, useState } from 'react'; +import './toggleTheme.css'; -const THEME_STORAGE_KEY = "theme"; +const THEME_STORAGE_KEY = 'theme'; export default function ToggleTheme() { const [preference, setPreference] = useState( - localStorage.getItem(THEME_STORAGE_KEY) || "system" + localStorage.getItem(THEME_STORAGE_KEY) || 'system' ); function getAppliedMode(userPreference) { - if (userPreference === "light") { - return "light"; + if (userPreference === 'light') { + return 'light'; } - if (userPreference === "dark") { - return "dark"; + if (userPreference === 'dark') { + return 'dark'; } // system - if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return "light"; + if (window.matchMedia('(prefers-color-scheme: light)').matches) { + return 'light'; } - return "dark"; + return 'dark'; } function setAppliedMode(mode) { @@ -30,7 +30,7 @@ export default function ToggleTheme() { } useEffect(() => { - if (preference === "system") { + if (preference === 'system') { const monitorSystemColorScheme = () => { const appliedMode = getAppliedMode(preference); if (appliedMode !== document.documentElement.dataset.appliedMode) { @@ -41,13 +41,13 @@ export default function ToggleTheme() { monitorSystemColorScheme(); window - .matchMedia("(prefers-color-scheme: light)") - .addEventListener("change", monitorSystemColorScheme); + .matchMedia('(prefers-color-scheme: light)') + .addEventListener('change', monitorSystemColorScheme); return () => { window - .matchMedia("(prefers-color-scheme: light)") - .removeEventListener("change", monitorSystemColorScheme); + .matchMedia('(prefers-color-scheme: light)') + .removeEventListener('change', monitorSystemColorScheme); }; } }, [preference]); @@ -62,17 +62,17 @@ export default function ToggleTheme() { }, [preference]); function rotatePreferences(userPreference) { - if (userPreference === "system") { - return "light"; + if (userPreference === 'system') { + return 'light'; } - if (userPreference === "light") { - return "dark"; + if (userPreference === 'light') { + return 'dark'; } - if (userPreference === "dark") { - return "system"; + if (userPreference === 'dark') { + return 'system'; } // for invalid values, just in case - return "system"; + return 'system'; } const handleClick = () => { @@ -81,7 +81,7 @@ export default function ToggleTheme() { }; return ( - ); diff --git a/src/constants/numbers.js b/src/constants/numbers.js index 73df131..c6a0244 100644 --- a/src/constants/numbers.js +++ b/src/constants/numbers.js @@ -1,4 +1,4 @@ // src/constants/numbers.js export const numbers = Array.from({ length: 10 }, (_, i) => - i + 1 < 10 ? `0${i + 1}` : `${i + 1}` + i + 1 < 10 ? `0${i + 1}` : `${i + 1}` ); diff --git a/src/data/cardImages.js b/src/data/cardImages.js index 60cb331..7410c2d 100644 --- a/src/data/cardImages.js +++ b/src/data/cardImages.js @@ -1,26 +1,26 @@ // src/data/cardImages.js export const cardImages = [ - { src: "/img/memory/a4-front.jpg", matched: false }, - { src: "/img/memory/a4-lights.jpg", matched: false }, - { src: "/img/memory/a4-otu.jpg", matched: false }, - { src: "/img/memory/bike-outside.jpg", matched: false }, - { src: "/img/memory/bike-trip.jpg", matched: false }, - { src: "/img/memory/car-glow-in.jpg", matched: false }, - { src: "/img/memory/car-sun.jpg", matched: false }, - { src: "/img/memory/front-a4.jpg", matched: false }, - { src: "/img/memory/front-glow.jpg", matched: false }, - { src: "/img/memory/front-stand.jpg", matched: false }, - { src: "/img/memory/painting-bike.jpg", matched: false }, - { src: "/img/memory/selfie-bike.jpg", matched: false }, - { src: "/img/memory/sitting-front.jpg", matched: false }, - { src: "/img/memory/fav-cud.jpg", matched: false }, - { src: "/img/memory/stand-ride.jpg", matched: false }, - { src: "/img/memory/close-gara.jpg", matched: false }, - { src: "/img/memory/moto-farming.jpg", matched: false }, - { src: "/img/memory/couple-paint-honda.jpg", matched: false }, - { src: "/img/memory/couple-moto-paint.jpg", matched: false }, - { src: "/img/memory/couple-moto.jpg", matched: false }, - { src: "/img/memory/couple-honda.jpg", matched: false }, - { src: "/img/memory/couple-church.jpg", matched: false }, - { src: "/img/memory/a4-close-couple.jpg", matched: false } + { src: '/img/memory/a4-front.jpg', matched: false }, + { src: '/img/memory/a4-lights.jpg', matched: false }, + { src: '/img/memory/a4-otu.jpg', matched: false }, + { src: '/img/memory/bike-outside.jpg', matched: false }, + { src: '/img/memory/bike-trip.jpg', matched: false }, + { src: '/img/memory/car-glow-in.jpg', matched: false }, + { src: '/img/memory/car-sun.jpg', matched: false }, + { src: '/img/memory/front-a4.jpg', matched: false }, + { src: '/img/memory/front-glow.jpg', matched: false }, + { src: '/img/memory/front-stand.jpg', matched: false }, + { src: '/img/memory/painting-bike.jpg', matched: false }, + { src: '/img/memory/selfie-bike.jpg', matched: false }, + { src: '/img/memory/sitting-front.jpg', matched: false }, + { src: '/img/memory/fav-cud.jpg', matched: false }, + { src: '/img/memory/stand-ride.jpg', matched: false }, + { src: '/img/memory/close-gara.jpg', matched: false }, + { src: '/img/memory/moto-farming.jpg', matched: false }, + { src: '/img/memory/couple-paint-honda.jpg', matched: false }, + { src: '/img/memory/couple-moto-paint.jpg', matched: false }, + { src: '/img/memory/couple-moto.jpg', matched: false }, + { src: '/img/memory/couple-honda.jpg', matched: false }, + { src: '/img/memory/couple-church.jpg', matched: false }, + { src: '/img/memory/a4-close-couple.jpg', matched: false }, ]; diff --git a/src/hooks/useTrackViewCounter.js b/src/hooks/useTrackViewCounter.js index a0da1e2..b69af96 100644 --- a/src/hooks/useTrackViewCounter.js +++ b/src/hooks/useTrackViewCounter.js @@ -1,5 +1,5 @@ -import { Counter } from "counterapi"; -import { useState, useEffect } from "react"; +import { Counter } from 'counterapi'; +import { useState, useEffect } from 'react'; const WORKSPACE_ID = process.env.REACT_APP_COUNTER_API_WORKSPACE_SLUG; const COUNTER_ID = process.env.REACT_APP_COUNTER_API_PAGE_VIEW_COUNTER_SLUG; @@ -11,8 +11,8 @@ const useTrackViewCounter = () => { if (!WORKSPACE_ID || !COUNTER_ID) { console.warn( `useTrackViewCounter: Missing environment variable(s):` + - (!WORKSPACE_ID ? " REACT_APP_COUNTER_API_WORKSPACE_SLUG" : "") + - (!COUNTER_ID ? " REACT_APP_COUNTER_API_PAGE_VIEW_COUNTER_SLUG" : "") + (!WORKSPACE_ID ? ' REACT_APP_COUNTER_API_WORKSPACE_SLUG' : '') + + (!COUNTER_ID ? ' REACT_APP_COUNTER_API_PAGE_VIEW_COUNTER_SLUG' : '') ); return; } @@ -24,10 +24,10 @@ const useTrackViewCounter = () => { }); counter .up(COUNTER_ID) - .then((result) => { + .then(result => { setCounter(result.data.up_count); }) - .catch((err) => { + .catch(err => { console.error( `Failed to increment counter (up) for COUNTER_ID: ${COUNTER_ID}`, err diff --git a/src/index.css b/src/index.css index f038a6b..6264002 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,3 @@ - @import url('https://fonts.googleapis.com/css2?family=Raleway:wght@100;200;300;400;500;600;700;800&display=swap'); /* base styles */ @@ -7,16 +6,17 @@ body { margin: 0; font-size: 1.5em; text-align: center; - color: var(--text,#fff); + color: var(--text, #fff); background: var(--background, #1b1523); } -.results-container{ +.results-container { display: flex; justify-content: center; column-gap: 1rem; } -body, html { +body, +html { cursor: none; } @@ -24,7 +24,7 @@ body, html { cursor: none; } -.button-box{ +.button-box { display: flex; margin-top: 10px; flex-direction: row; @@ -32,31 +32,30 @@ body, html { justify-content: center; } -.hint{ +.hint { margin-left: 10px; margin-right: 10px; } -.hint:hover{ +.hint:hover { background-color: yellow; color: black; - } -.hint-box{ +.hint-box { display: flex; align-items: center; justify-content: space-around; } -.hint-count{ +.hint-count { font-size: 15px; - color: var(--text,#fff); + color: var(--text, #fff); } @media (max-width: 600px) { - .button-box{ + .button-box { flex-direction: column; gap: 20px; } -} \ No newline at end of file +} diff --git a/src/index.js b/src/index.js index c15f402..6832e78 100644 --- a/src/index.js +++ b/src/index.js @@ -8,4 +8,4 @@ ReactDOM.render( , document.getElementById('root') -); \ No newline at end of file +);