Skip to content

Commit 2bc6105

Browse files
committed
chore: add precommit and pr action
1 parent f6a9a66 commit 2bc6105

34 files changed

Lines changed: 412 additions & 362 deletions

.github/workflows/prs.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Check PR
2+
3+
on: [pull_request]
4+
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
9+
jobs:
10+
check-code:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
fetch-depth: 0
16+
- name: "Setup yarn"
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: "22"
20+
cache: "yarn"
21+
- name: Install dependencies
22+
run: yarn install --frozen-lockfile
23+
- name: Biome check
24+
run: yarn lint
25+
- name: Generate Next types
26+
run: yarn next typegen
27+
- name: Typescript check
28+
run: yarn type-check

.husky/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
yarn lint
2+
yarn type-check

app/[lang]/dictionaries.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import 'server-only'
1+
import 'server-only';
22

33
const dictionaries = {
4-
en: () => import('../../dictionaries/en.json').then((module) => module.default),
5-
es: () => import('../../dictionaries/es.json').then((module) => module.default),
6-
}
4+
en: () =>
5+
import('../../dictionaries/en.json').then((module) => module.default),
6+
es: () =>
7+
import('../../dictionaries/es.json').then((module) => module.default),
8+
};
79

8-
export type Locale = keyof typeof dictionaries
10+
export type Locale = keyof typeof dictionaries;
911

1012
export const hasLocale = (locale: string): locale is Locale =>
11-
locale in dictionaries
13+
locale in dictionaries;
1214

13-
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
15+
export const getDictionary = async (locale: Locale) => dictionaries[locale]();
1416

1517
export type Dictionary = Awaited<ReturnType<typeof getDictionary>>;

app/[lang]/game/_components/card.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { type FC, useEffect, useRef, useState } from "react";
2-
import { cn } from "@/lib/cls";
3-
import { Button } from "@/primitives/components/ui/button";
4-
import { Dictionary } from "@/dictionaries";
1+
import { type FC, useEffect, useRef, useState } from 'react';
2+
import type { Dictionary } from '@/dictionaries';
3+
import { cn } from '@/lib/cls';
4+
import { Button } from '@/primitives/components/ui/button';
55

6-
const transitionClass = "duration-700";
6+
const transitionClass = 'duration-700';
77
// biome-ignore lint/style/noNonNullAssertion: Trust me bro
8-
const changeOnMs = Number(transitionClass.split("-")[1]!) / 3;
8+
const changeOnMs = Number(transitionClass.split('-')[1]!) / 3;
99

1010
export const GameCard: FC<{
1111
player: { name: string; isSpy: boolean };
@@ -46,10 +46,10 @@ export const GameCard: FC<{
4646
<div className="perspective-midrange relative h-96 w-80 cursor-pointer border-0">
4747
<div
4848
className={cn(
49-
"transform-3d relative h-full w-full transition-transform",
49+
'transform-3d relative h-full w-full transition-transform',
5050
transitionClass,
5151
{
52-
"rotate-y-180": isFlipped,
52+
'rotate-y-180': isFlipped,
5353
},
5454
)}>
5555
{/* Front Face - Player Name */}
@@ -64,7 +64,9 @@ export const GameCard: FC<{
6464
{player.isSpy ? (
6565
<>
6666
<div className="mb-4 text-6xl">🕵</div>
67-
<h3 className="font-bold text-4xl text-destructive">{dict.card.spy}</h3>
67+
<h3 className="font-bold text-4xl text-destructive">
68+
{dict.card.spy}
69+
</h3>
6870
</>
6971
) : (
7072
<>

app/[lang]/game/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FC, PropsWithChildren } from "react";
1+
import type { FC, PropsWithChildren } from 'react';
22

33
const Layout: FC<PropsWithChildren> = ({ children }) => (
44
<div className="flex h-screen w-screen items-center justify-center">
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { atom } from "jotai";
2-
import type z from "zod";
3-
import type { localGameFormSchema } from "../setup/_components/form";
1+
import { atom } from 'jotai';
2+
import type z from 'zod';
3+
import type { localGameFormSchema } from '../setup/_components/form';
44

55
export const gameSettingsAtom = atom<z.infer<typeof localGameFormSchema>>({
66
players: [],
7-
numberOfSpies: "0",
7+
numberOfSpies: '0',
88
randomNumberOfSpies: false,
99
});

app/[lang]/game/local/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { FC, PropsWithChildren } from "react";
2-
import { StoreProvider } from "@/providers/store";
1+
import type { FC, PropsWithChildren } from 'react';
2+
import { StoreProvider } from '@/providers/store';
33

44
const Layout: FC<PropsWithChildren> = ({ children }) => (
55
<StoreProvider>{children}</StoreProvider>

app/[lang]/game/local/play/_components/game.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
"use client";
1+
'use client';
22

3-
import { useAtomValue } from "jotai";
4-
import { type FC, useCallback, useMemo, useState } from "react";
5-
import { GameCard } from "@/game/_components/card";
6-
import { gameSettingsAtom } from "../../_store/game-settings";
7-
import { GameTimer } from "./timer";
8-
import { Dictionary } from "@/dictionaries";
3+
import { useAtomValue } from 'jotai';
4+
import { type FC, useCallback, useMemo, useState } from 'react';
5+
import type { Dictionary } from '@/dictionaries';
6+
import { GameCard } from '@/game/_components/card';
7+
import { gameSettingsAtom } from '../../_store/game-settings';
8+
import { GameTimer } from './timer';
99

1010
export const Game: FC<{ dict: Dictionary }> = ({ dict }) => {
1111
const gameSettings = useAtomValue(gameSettingsAtom);
@@ -42,7 +42,11 @@ export const Game: FC<{ dict: Dictionary }> = ({ dict }) => {
4242
// Show timer once all players have checked their cards
4343
if (allPlayersChecked) {
4444
return (
45-
<GameTimer playerRoles={playerRoles} onPlayAgain={handlePlayAgain} dict={dict} />
45+
<GameTimer
46+
playerRoles={playerRoles}
47+
onPlayAgain={handlePlayAgain}
48+
dict={dict}
49+
/>
4650
);
4751
}
4852

@@ -60,12 +64,10 @@ export const Game: FC<{ dict: Dictionary }> = ({ dict }) => {
6064
<div className="text-center">
6165
<h2 className="mb-2 font-bold text-2xl">
6266
{dict.play.playerProgress
63-
.replace("{current}", String(currentPlayerIndex + 1))
64-
.replace("{total}", String(playerRoles.length))}
67+
.replace('{current}', String(currentPlayerIndex + 1))
68+
.replace('{total}', String(playerRoles.length))}
6569
</h2>
66-
<p className="text-muted-foreground">
67-
{dict.play.cardInstruction}
68-
</p>
70+
<p className="text-muted-foreground">{dict.play.cardInstruction}</p>
6971
</div>
7072

7173
<GameCard

app/[lang]/game/local/play/_components/timer.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
"use client";
1+
'use client';
22

3-
import { type FC, useEffect, useState } from "react";
4-
import { cn } from "@/lib/cls";
5-
import { Button } from "@/primitives/components/ui/button";
6-
import { Dictionary } from "@/dictionaries";
3+
import { type FC, useEffect, useState } from 'react';
4+
import type { Dictionary } from '@/dictionaries';
5+
import { cn } from '@/lib/cls';
6+
import { Button } from '@/primitives/components/ui/button';
77

88
const TIMER_DURATION = 120; // 2 minutes in seconds
99

@@ -18,7 +18,11 @@ interface GameTimerProps {
1818
dict: Dictionary;
1919
}
2020

21-
export const GameTimer: FC<GameTimerProps> = ({ playerRoles, onPlayAgain, dict }) => {
21+
export const GameTimer: FC<GameTimerProps> = ({
22+
playerRoles,
23+
onPlayAgain,
24+
dict,
25+
}) => {
2226
const [timeLeft, setTimeLeft] = useState(TIMER_DURATION);
2327
const [isExpired, setIsExpired] = useState(false);
2428
const [showSpies, setShowSpies] = useState(false);
@@ -44,13 +48,13 @@ export const GameTimer: FC<GameTimerProps> = ({ playerRoles, onPlayAgain, dict }
4448

4549
const minutes = Math.floor(timeLeft / 60);
4650
const seconds = timeLeft % 60;
47-
const formattedTime = `${minutes}:${seconds.toString().padStart(2, "0")}`;
51+
const formattedTime = `${minutes}:${seconds.toString().padStart(2, '0')}`;
4852

4953
const getTimerColor = () => {
50-
if (isExpired) return "text-destructive";
51-
if (timeLeft <= 30) return "text-destructive";
52-
if (timeLeft <= 60) return "text-warning";
53-
return "text-foreground";
54+
if (isExpired) return 'text-destructive';
55+
if (timeLeft <= 30) return 'text-destructive';
56+
if (timeLeft <= 60) return 'text-warning';
57+
return 'text-foreground';
5458
};
5559

5660
const spies = playerRoles.filter((player) => player.isSpy);
@@ -61,29 +65,29 @@ export const GameTimer: FC<GameTimerProps> = ({ playerRoles, onPlayAgain, dict }
6165
<div className="text-center">
6266
<h2 className="mb-2 font-bold text-2xl">{dict.timer.title}</h2>
6367
<p className="text-muted-foreground">
64-
{isExpired
65-
? dict.timer.timeUpDescription
66-
: dict.timer.description}
68+
{isExpired ? dict.timer.timeUpDescription : dict.timer.description}
6769
</p>
6870
</div>
6971

7072
<div
7173
className={cn(
72-
"flex h-64 w-64 items-center justify-center rounded-full border-4 shadow-lg transition-colors",
74+
'flex h-64 w-64 items-center justify-center rounded-full border-4 shadow-lg transition-colors',
7375
getTimerColor(),
7476
{
75-
"animate-pulse border-destructive": isExpired,
76-
"border-destructive": timeLeft <= 30 && !isExpired,
77-
"border-warning": timeLeft > 30 && timeLeft <= 60,
78-
"border-border": timeLeft > 60,
77+
'animate-pulse border-destructive': isExpired,
78+
'border-destructive': timeLeft <= 30 && !isExpired,
79+
'border-warning': timeLeft > 30 && timeLeft <= 60,
80+
'border-border': timeLeft > 60,
7981
},
8082
)}>
8183
<div className="text-center">
82-
<div className={cn("font-bold text-6xl", getTimerColor())}>
84+
<div className={cn('font-bold text-6xl', getTimerColor())}>
8385
{formattedTime}
8486
</div>
8587
{isExpired && (
86-
<div className="mt-2 font-semibold text-xl">{dict.timer.timeUp}</div>
88+
<div className="mt-2 font-semibold text-xl">
89+
{dict.timer.timeUp}
90+
</div>
8791
)}
8892
</div>
8993
</div>

app/[lang]/game/local/play/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { notFound } from 'next/navigation';
22
import { getDictionary, hasLocale } from '@/dictionaries';
3-
import { Game } from "./_components/game";
3+
import { Game } from './_components/game';
44

55
const Page = async ({ params }: PageProps<'/[lang]/game/local/play'>) => {
6-
const { lang } = await params
6+
const { lang } = await params;
77
if (!hasLocale(lang)) notFound();
88

99
const dict = await getDictionary(lang);

0 commit comments

Comments
 (0)