Add pre-commit hooks for linting and formatting
All checks were successful
CI / backend-lint (push) Successful in 9s
CI / frontend-lint (push) Successful in 33s

Set up pre-commit framework with ruff (backend) and ESLint/Prettier/tsc
(frontend) hooks to catch issues locally before CI. Auto-format all
frontend files with Prettier to comply with the new check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 16:41:24 +01:00
parent b05a75f7f2
commit 2963f16aa4
67 changed files with 1905 additions and 792 deletions

View File

@@ -20,7 +20,7 @@ type PresetType = 'true' | 'normal' | 'custom' | null
function buildLegsFromPreset(
regions: Region[],
preset: 'true' | 'normal',
preset: 'true' | 'normal'
): LegEntry[] {
const legs: LegEntry[] = []
for (const region of regions) {
@@ -45,8 +45,11 @@ export function NewGenlocke() {
const [name, setName] = useState('')
const [legs, setLegs] = useState<LegEntry[]>([])
const [preset, setPreset] = useState<PresetType>(null)
const [nuzlockeRules, setNuzlockeRules] = useState<NuzlockeRules>(DEFAULT_RULES)
const [genlockeRules, setGenlockeRules] = useState<GenlockeRules>({ retireHoF: false })
const [nuzlockeRules, setNuzlockeRules] =
useState<NuzlockeRules>(DEFAULT_RULES)
const [genlockeRules, setGenlockeRules] = useState<GenlockeRules>({
retireHoF: false,
})
const [namingScheme, setNamingScheme] = useState<string | null>(null)
const { data: namingCategories } = useNamingCategories()
@@ -61,7 +64,9 @@ export function NewGenlocke() {
}
const handleGameChange = (index: number, game: Game) => {
setLegs((prev) => prev.map((leg, i) => (i === index ? { ...leg, game } : leg)))
setLegs((prev) =>
prev.map((leg, i) => (i === index ? { ...leg, game } : leg))
)
}
const handleRemoveLeg = (index: number) => {
@@ -70,7 +75,8 @@ export function NewGenlocke() {
const handleAddLeg = (region: Region) => {
const defaultSlug = region.genlockeDefaults.normalGenlocke
const game = region.games.find((g) => g.slug === defaultSlug) ?? region.games[0]
const game =
region.games.find((g) => g.slug === defaultSlug) ?? region.games[0]
if (game) {
setLegs((prev) => [...prev, { region: region.name, game }])
}
@@ -105,17 +111,18 @@ export function NewGenlocke() {
navigate('/runs')
}
},
},
}
)
}
const enabledRuleCount = RULE_DEFINITIONS.filter((r) => nuzlockeRules[r.key]).length
const enabledRuleCount = RULE_DEFINITIONS.filter(
(r) => nuzlockeRules[r.key]
).length
const totalRuleCount = RULE_DEFINITIONS.length
// Regions not yet used in legs (for "add leg" picker)
const availableRegions = regions?.filter(
(r) => !legs.some((l) => l.region === r.name),
) ?? []
const availableRegions =
regions?.filter((r) => !legs.some((l) => l.region === r.name)) ?? []
return (
<div className="max-w-4xl mx-auto p-8">
@@ -198,7 +205,9 @@ export function NewGenlocke() {
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
>
<div className={`font-medium ${isActive ? 'text-blue-600 dark:text-blue-400' : 'text-gray-900 dark:text-gray-100'}`}>
<div
className={`font-medium ${isActive ? 'text-blue-600 dark:text-blue-400' : 'text-gray-900 dark:text-gray-100'}`}
>
{labels[type]}
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mt-1">
@@ -241,11 +250,17 @@ export function NewGenlocke() {
)}
{/* Also allow adding extra regions for presets */}
{preset && preset !== 'custom' && availableRegions.length > 0 && legs.length > 0 && (
<div className="mt-4">
<AddLegDropdown regions={availableRegions} onAdd={handleAddLeg} />
</div>
)}
{preset &&
preset !== 'custom' &&
availableRegions.length > 0 &&
legs.length > 0 && (
<div className="mt-4">
<AddLegDropdown
regions={availableRegions}
onAdd={handleAddLeg}
/>
</div>
)}
<div className="mt-6 flex justify-between">
<button
@@ -270,7 +285,10 @@ export function NewGenlocke() {
{/* Step 3: Rules */}
{step === 3 && (
<div>
<RulesConfiguration rules={nuzlockeRules} onChange={setNuzlockeRules} />
<RulesConfiguration
rules={nuzlockeRules}
onChange={setNuzlockeRules}
/>
{/* Genlocke-specific rules */}
<div className="mt-6 bg-white dark:bg-gray-800 rounded-lg shadow">
@@ -301,7 +319,8 @@ export function NewGenlocke() {
Keep Hall of Fame
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Pokemon that beat the Elite Four can continue to the next leg
Pokemon that beat the Elite Four can continue to the
next leg
</div>
</div>
</label>
@@ -318,7 +337,8 @@ export function NewGenlocke() {
Retire Hall of Fame
</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Pokemon that beat the Elite Four are retired and cannot be used in the next leg
Pokemon that beat the Elite Four are retired and cannot
be used in the next leg
</div>
</div>
</label>
@@ -334,7 +354,8 @@ export function NewGenlocke() {
Naming Scheme
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
Get nickname suggestions from a themed word list when catching Pokemon. Applied to all legs.
Get nickname suggestions from a themed word list when catching
Pokemon. Applied to all legs.
</p>
</div>
<div className="px-4 py-4">
@@ -384,7 +405,9 @@ export function NewGenlocke() {
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">
Name
</h3>
<p className="text-gray-900 dark:text-gray-100 font-medium">{name}</p>
<p className="text-gray-900 dark:text-gray-100 font-medium">
{name}
</p>
</div>
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
@@ -403,7 +426,8 @@ export function NewGenlocke() {
{leg.game.name}
</span>
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">
{leg.region.charAt(0).toUpperCase() + leg.region.slice(1)}
{leg.region.charAt(0).toUpperCase() +
leg.region.slice(1)}
</span>
</div>
</li>
@@ -417,22 +441,29 @@ export function NewGenlocke() {
</h3>
<dl className="space-y-1 text-sm">
<div className="flex justify-between">
<dt className="text-gray-600 dark:text-gray-400">Nuzlocke Rules</dt>
<dt className="text-gray-600 dark:text-gray-400">
Nuzlocke Rules
</dt>
<dd className="text-gray-900 dark:text-gray-100 font-medium">
{enabledRuleCount} of {totalRuleCount} enabled
</dd>
</div>
<div className="flex justify-between">
<dt className="text-gray-600 dark:text-gray-400">Hall of Fame</dt>
<dt className="text-gray-600 dark:text-gray-400">
Hall of Fame
</dt>
<dd className="text-gray-900 dark:text-gray-100 font-medium">
{genlockeRules.retireHoF ? 'Retire' : 'Keep'}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-gray-600 dark:text-gray-400">Naming Scheme</dt>
<dt className="text-gray-600 dark:text-gray-400">
Naming Scheme
</dt>
<dd className="text-gray-900 dark:text-gray-100 font-medium">
{namingScheme
? namingScheme.charAt(0).toUpperCase() + namingScheme.slice(1)
? namingScheme.charAt(0).toUpperCase() +
namingScheme.slice(1)
: 'None'}
</dd>
</div>
@@ -530,8 +561,18 @@ function LegRow({
className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:cursor-not-allowed"
title="Move up"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" />
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5 15l7-7 7 7"
/>
</svg>
</button>
<button
@@ -541,8 +582,18 @@ function LegRow({
className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:cursor-not-allowed"
title="Move down"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
<button
@@ -551,8 +602,18 @@ function LegRow({
className="p-1 text-red-400 hover:text-red-600 dark:hover:text-red-300"
title="Remove leg"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
@@ -576,8 +637,18 @@ function AddLegDropdown({
onClick={() => setOpen(true)}
className="flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 4v16m8-8H4"
/>
</svg>
Add Region
</button>