diff --git a/.github/workflows/github-pr.yml b/.github/workflows/github-pr.yml deleted file mode 100644 index 217803f..0000000 --- a/.github/workflows/github-pr.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: PR โ†’ Discord -on: - pull_request: - types: [opened, reopened, ready_for_review] -permissions: - contents: read - pull-requests: read - -jobs: - notify: - # Block untrusted forks from using secrets: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-latest - steps: - - name: Send embed to Discord - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} - TITLE: ${{ github.event.pull_request.title }} - URL: ${{ github.event.pull_request.html_url }} - USER: ${{ github.event.pull_request.user.login }} - BASE: ${{ github.event.pull_request.base.ref }} - HEAD: ${{ github.event.pull_request.head.ref }} - run: | - payload=$(jq -n \ - --arg title "$TITLE" \ - --arg url "$URL" \ - --arg user "$USER" \ - --arg base "$BASE" \ - --arg head "$HEAD" \ - '{embeds:[{ - title:$title, - url:$url, - description:("by @" + $user), - fields:[ - {name:"Branch", value: $head + " โ†’ " + $base, inline:true} - ] - }]}) - curl -sS -X POST -H "Content-Type: application/json" \ - -d "$payload" "$DISCORD_WEBHOOK" diff --git a/.github/workflows/vercel-production.yml b/.github/workflows/vercel-production.yml new file mode 100644 index 0000000..ea6509f --- /dev/null +++ b/.github/workflows/vercel-production.yml @@ -0,0 +1,18 @@ +name: Deploy ๐Ÿ Snake to Vercel + +on: + push: + branches: [develop] + +jobs: + deploy: + runs-on: ubuntu-latest + environment: production + + steps: + - uses: actions/checkout@v4 + - uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} diff --git a/README.md b/README.md index 3e0e3af..b4d70c1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,13 @@ A minimalist Snake clone built with React, TypeScript, and HTML Canvas โ€” focus ![Gameplay preview](./src/app/assets/snake-gameplay.gif) -> Try it yourself โ€” `npm run dev` then open [http://localhost:5173](http://localhost:5173) +--- + +๐ŸŒ Live Demo + +Try Snake here: https://snake-beryl-six.vercel.app/ + +--- ## ๐ŸŽฏ Features diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index cc36ada..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,82 +0,0 @@ -// eslint.config.js -import js from "@eslint/js"; -import react from "eslint-plugin-react"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactRefresh from "eslint-plugin-react-refresh"; -import globals from "globals"; -import tseslint from "typescript-eslint"; - -export default [ - // ๐Ÿงน Ignore build + coverage folders - { ignores: ["dist", "coverage"] }, - - // โœ… Base + TypeScript + React presets - js.configs.recommended, - ...tseslint.configs.recommended, - reactHooks.configs["recommended-latest"], - reactRefresh.configs.vite, - - // ๐Ÿงฉ App code (browser) - { - files: ["src/**/*.{ts,tsx,js,jsx}"], - languageOptions: { - parser: tseslint.parser, - ecmaVersion: "latest", - sourceType: "module", - parserOptions: { ecmaFeatures: { jsx: true } }, - globals: globals.browser, - }, - plugins: { react }, - rules: { - // ๐Ÿง  React 17+ no longer requires importing React in JSX - "react/react-in-jsx-scope": "off", - "react/prop-types": "off", - - // ๐Ÿงน Lint polish - "@typescript-eslint/no-unused-vars": [ - "warn", - { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, - ], - "@typescript-eslint/no-unused-expressions": [ - "error", - { allowShortCircuit: true, allowTernary: true }, - ], - "react-hooks/exhaustive-deps": "warn", - }, - settings: { - react: { version: "detect" }, - }, - }, - - // ๐Ÿงช Tests (Vitest) - { - files: ["tests/**/*.{ts,tsx,js,jsx}", "**/*.test.{ts,tsx,js,jsx}"], - languageOptions: { - parser: tseslint.parser, - ecmaVersion: "latest", - sourceType: "module", - parserOptions: { ecmaFeatures: { jsx: true } }, - globals: { ...globals.browser, ...globals.vitest }, - }, - plugins: { react }, - rules: { - "react/react-in-jsx-scope": "off", - }, - }, - - // โš™๏ธ Config + scripts (Node env) - { - files: [ - "*.config.{js,cjs,mjs,ts}", - "vite.config.*", - "vitest.config.*", - "scripts/**", - ], - languageOptions: { - parser: tseslint.parser, - ecmaVersion: "latest", - sourceType: "module", - globals: globals.node, - }, - }, -]; \ No newline at end of file diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..1d29924 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,82 @@ +// eslint.config.js +import js from '@eslint/js'; +import react from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default [ + // ๐Ÿงน Ignore build + coverage folders + { ignores: ['dist', 'coverage'] }, + + // โœ… Base + TypeScript + React presets + js.configs.recommended, + ...tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + + // ๐Ÿงฉ App code (browser) + { + files: ['src/**/*.{ts,tsx,js,jsx}'], + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { ecmaFeatures: { jsx: true } }, + globals: globals.browser, + }, + plugins: { react }, + rules: { + // ๐Ÿง  React 17+ no longer requires importing React in JSX + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + + // ๐Ÿงน Lint polish + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-unused-expressions': [ + 'error', + { allowShortCircuit: true, allowTernary: true }, + ], + 'react-hooks/exhaustive-deps': 'warn', + }, + settings: { + react: { version: 'detect' }, + }, + }, + + // ๐Ÿงช Tests (Vitest) + { + files: ['tests/**/*.{ts,tsx,js,jsx}', '**/*.test.{ts,tsx,js,jsx}'], + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { ecmaFeatures: { jsx: true } }, + globals: { ...globals.browser, ...globals.vitest }, + }, + plugins: { react }, + rules: { + 'react/react-in-jsx-scope': 'off', + }, + }, + + // โš™๏ธ Config + scripts (Node env) + { + files: [ + '*.config.{js,cjs,mjs,ts}', + 'vite.config.*', + 'vitest.config.*', + 'scripts/**', + ], + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 'latest', + sourceType: 'module', + globals: globals.node, + }, + }, +]; diff --git a/src/app/components/SnakeCanvas.tsx b/src/app/components/SnakeCanvas.tsx index 67303f6..656f819 100644 --- a/src/app/components/SnakeCanvas.tsx +++ b/src/app/components/SnakeCanvas.tsx @@ -39,7 +39,9 @@ export default function SnakeCanvas() { try { a.currentTime = 0; void a.play(); - } catch {} + } catch { + console.error('Audio play exception:', err); + } }, []); // random free cell based on current tuning