Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions app/components/BuyClassCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,35 @@ const BuyClassCard = ({ cards, xp, name, label, owned, formApi }: Props) => {
))}
<div className="divider m-0"></div>
{buyable.map((card) => {
const disabled = !field.value().some((v: string) => +v === card.id) && card.cost > balance
const disabled =
!field.value().some((v: string) => +v === card.id) &&
card.cost > balance
return (
<label
className={clsx(
// card.tagline && 'tooltip before:whitespace-break-spaces',
'label gap-1 flex py-1',
disabled ? 'cursor-not-allowed!' : 'cursor-pointer'
)}
key={card.id}
data-tip={card.tagline}
>
<input
{...field.getInputProps({
type: 'checkbox',
className:
'checkbox checkbox-sm checkbox-primary border-neutral hover:border-neutral',
value: card.id,
disabled
})}
data-cost={card.cost}
/>
<span className={clsx(!disabled && 'text-base-content')}>
{card.cost} XP - {card.name}
</span>
</label>
)})}
<label
className={clsx(
// card.tagline && 'tooltip before:whitespace-break-spaces',
'label gap-1 flex py-1',
disabled ? 'cursor-not-allowed!' : 'cursor-pointer'
)}
key={card.id}
data-tip={card.tagline}
>
<input
{...field.getInputProps({
type: 'checkbox',
className:
'checkbox checkbox-sm checkbox-primary border-neutral hover:border-neutral',
value: card.id,
disabled
})}
data-cost={card.cost}
/>
<span className={clsx(!disabled && 'text-base-content')}>
{card.cost} XP - {card.name}
</span>
</label>
)
})}
<div className="label">
{error && <span className="text-error">{error}</span>}
</div>
Expand Down
5 changes: 4 additions & 1 deletion app/components/BuyItemCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ const BuyItemCard = ({
const balance =
credits -
boughtItems.reduce((acc, cur) => acc + cur.cost, 0) +
soldItems.reduce((acc, cur) => acc + (cur.cost ? getSellPrice(cur.cost) : 50), 0)
soldItems.reduce(
(acc, cur) => acc + (cur.cost ? getSellPrice(cur.cost) : 50),
0
)

return (
<div className="flex flex-col">
Expand Down
4 changes: 2 additions & 2 deletions app/components/EditButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { ElementRef, MouseEventHandler } from 'react'
import type { ComponentRef, MouseEventHandler } from 'react'
import clsx from 'clsx'
import { PencilIcon, XMarkIcon } from '@heroicons/react/24/solid'

type EditButtonProps = {
onClick: MouseEventHandler<ElementRef<'button'>>
onClick: MouseEventHandler<ComponentRef<'button'>>
active: boolean
hideLabel?: boolean
disabled?: boolean
Expand Down
19 changes: 9 additions & 10 deletions app/components/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import clsx from 'clsx'
import {
useRef,
type ElementRef,
type HTMLProps,
type PropsWithChildren,
type ReactEventHandler,
useEffect
import type {
PropsWithChildren,
HTMLProps,
ComponentRef,
ReactEventHandler
} from 'react'
import { useRef, useEffect } from 'react'

type Props = PropsWithChildren<
{
open?: boolean
onClose?: ReactEventHandler<ElementRef<'dialog'>>
} & HTMLProps<ElementRef<'dialog'>>
onClose?: ReactEventHandler<ComponentRef<'dialog'>>
} & HTMLProps<ComponentRef<'dialog'>>
>

const Modal = ({ children, open, onClose, ...props }: Props) => {
const ref = useRef<ElementRef<'dialog'>>(null)
const ref = useRef<ComponentRef<'dialog'>>(null)

useEffect(() => {
if (open) {
Expand Down
5 changes: 3 additions & 2 deletions app/components/PlaceholderInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { FormApi } from '@rvf/react-router'
import RequiredIndicator from '~/components/RequiredIndicator'
import clsx from 'clsx'
import { useState, type ChangeEventHandler, type ElementRef } from 'react'
import { useState } from 'react'
import type { ChangeEventHandler, ComponentRef } from 'react'
import type { loader as resolveLoader } from '~/routes/_app.games.$game.resolve.$mission._index'
import type { useLoaderData } from 'react-router'
import type { JsonObject } from '@prisma/client/runtime/library'
Expand All @@ -13,7 +14,7 @@ type Placeholder = ReturnType<
type Props = {
index: number
placeholder: Placeholder
onChange?: ChangeEventHandler<ElementRef<'input'>>
onChange?: ChangeEventHandler<ComponentRef<'input'>>
formApi: FormApi<any>
}

Expand Down
8 changes: 3 additions & 5 deletions app/components/RebelRewardManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,9 @@ const RebelRewardManager = ({ rebel, allRewards, formAction }: Props) => {
</button>
</div>
<input {...form.getHiddenInputProps('id')} />
{form
.value('rewards')
?.map((_, i) => (
<input {...form.getHiddenInputProps(`rewards[${i}]`)} />
))}
{form.value('rewards')?.map((_, i) => (
<input {...form.getHiddenInputProps(`rewards[${i}]`)} />
))}
</form>
) : !rebel.rewards.length ? (
<p className="m-0">No Rewards</p>
Expand Down
5 changes: 1 addition & 4 deletions app/components/SelectInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ const SelectInput = forwardRef(
<span>{labelRight}</span>
</label>
<select
className={clsx(
'select w-full',
error && 'select-error'
)}
className={clsx('select w-full', error && 'select-error')}
{...formApi.getInputProps(name, {
id: name,
value,
Expand Down
14 changes: 7 additions & 7 deletions app/components/SideMissionsInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
type ChangeEvent,
type ElementRef,
type ComponentProps,
type PropsWithChildren,
useId
import { useId } from 'react'
import type {
ChangeEvent,
ComponentRef,
ComponentProps,
PropsWithChildren
} from 'react'
import RequiredIndicator from '../RequiredIndicator'
import type { FormApi } from '@rvf/react-router'
Expand All @@ -22,7 +22,7 @@ const SideMissionsInput = ({ name, count = 1, children, formApi }: Props) => {
const error = field.error()
const random = field.value() === 'RANDOM'

const onChange = (e: ChangeEvent<ElementRef<'input'>>) => {
const onChange = (e: ChangeEvent<ComponentRef<'input'>>) => {
field.setValue(e.target.checked ? 'RANDOM' : [])
field.clearError()
}
Expand Down
9 changes: 6 additions & 3 deletions app/components/TextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ const TextInput = forwardRef(
type === 'number' && 'sm:w-fit'
)}
>
<label className={clsx('label text-base-content', inline && 'py-0')} htmlFor={id}>
{required && <RequiredIndicator />}
{label}
<label
className={clsx('label text-base-content', inline && 'py-0')}
htmlFor={id}
>
{required && <RequiredIndicator />}
{label}
</label>
<input
className={clsx(
Expand Down
12 changes: 9 additions & 3 deletions app/routes/_app.games.$game._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ const Game = () => {
schema,
method: 'POST',
defaultValues: {
slot: -1,
mission: -1
slot: -1 as number,
mission: -1 as number
}
})

Expand Down Expand Up @@ -401,13 +401,19 @@ const Game = () => {
</tbody>
</table>
</Link>
<div className="flex flex-col px-2">
<div className="flex flex-col px-2 h-full">
<Link
to={`/games/${params.game}/missions`}
className="no-underline hover:underline"
>
Side Mission Deck
</Link>
<Link
to={`/games/${params.game}/settings`}
className="no-underline hover:underline"
>
Settings
</Link>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_app.games.$game.rebels.rewards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {

export const rewardSchema = z.object({
id: z.coerce.number().int().positive(),
rewards: z.array(z.coerce.number().int().positive()).default([]),
rewards: z.array(z.coerce.number().int().positive()).default([])
})

export const action = async (args: ActionFunctionArgs) => {
Expand Down
4 changes: 2 additions & 2 deletions app/routes/_app.games.$game.resolve.$mission._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { redirect, useLoaderData, useOutletContext } from 'react-router'
import type { LoaderData } from './_app.games.$game'
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'
import { prisma } from '~/services/db.server'
import type { ChangeEvent, ElementRef } from 'react'
import type { ChangeEvent, ComponentRef } from 'react'
import { useEffect, useId, useReducer, useState } from 'react'
import {
MissionRewardType,
Expand Down Expand Up @@ -698,7 +698,7 @@ const Resolve = () => {
action: {
name: string
type: string
event: ChangeEvent<ElementRef<'input'>>
event: ChangeEvent<ComponentRef<'input'>>
}
) => ({
...state,
Expand Down
99 changes: 99 additions & 0 deletions app/routes/_app.games.$game.settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { parseFormData, useForm } from '@rvf/react-router'
import { useState } from 'react'
import { ActionFunction, redirect, useFetcher } from 'react-router'
import z from 'zod'
import Modal from '~/components/Modal'
import { requireAuth } from '~/utils/requireAuth.server'
import { prisma } from '~/services/db.server'
import SubmitButton from '~/components/SubmitButton'

const schema = z.object({
action: z.enum(['delete'])
})

export const action: ActionFunction = async (args) => {
const { data } = await parseFormData(await args.request.formData(), schema)

const { userId } = await requireAuth(args)
const gameId = parseInt(args.params.game!, 10)
Comment thread
727021 marked this conversation as resolved.

switch (data?.action) {
case 'delete': {
await prisma.game.delete({
where: {
id: gameId,
userId: userId
}
})

return redirect('/games')
}
}

throw new Response('Invalid action', { status: 400 })
Comment thread
727021 marked this conversation as resolved.
}

const GameSettings = () => {
const [deleting, setDeleting] = useState(false)

const deleteFetcher = useFetcher()
const deleteForm = useForm({
schema,
defaultValues: { action: 'delete' },
fetcher: deleteFetcher,
method: 'POST'
})

return (
<>
<div className="flex flex-col flex-1 gap-2">
<h2 className="m-0">Settings</h2>
<div className="w-2xl max-w-full no-underline border rounded-xs p-2">
<div className="flex flex-col min-[400px]:flex-row gap-4 justify-between">
<div className="flex flex-col">
<h4 className="m-0 text-error">Delete Game</h4>
<p className="text-sm text-error m-0 whitespace-pre-wrap">
This will permanently delete the game and all associated data.
This action cannot be undone.
</p>
</div>
<button
className="btn btn-outline btn-error btn-sm self-end"
onClick={() => setDeleting(true)}
>
Delete
</button>
</div>
</div>
</div>
<Modal open={deleting} onClose={() => setDeleting(false)}>
<form {...deleteForm.getFormProps()} className="flex flex-col gap-4">
<input {...deleteForm.getHiddenInputProps('action')} />
<h3 className="m-0">Confirm Deletion</h3>
<p className="text-sm">
Are you sure you want to delete this game? This action cannot be
undone.
</p>
<div className="flex justify-end gap-2">
<button
className="btn btn-ghost"
type="button"
onClick={() => setDeleting(false)}
>
Cancel
</button>
<SubmitButton
className="btn btn-outline btn-error"
formApi={deleteForm}
fetcher={deleteFetcher}
>
Delete Game
</SubmitButton>
</div>
</form>
</Modal>
</>
)
}

export default GameSettings
5 changes: 3 additions & 2 deletions app/routes/_app.games.new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -741,8 +741,9 @@ const NewGame = () => {
label="Imperial Class"
required
hintRight={
imperialClasses.find((c) => c.id === +form.value('imperialClass'))?.cards?.[0]
?.name
imperialClasses.find(
(c) => c.id === +form.value('imperialClass')
)?.cards?.[0]?.name
}
>
<option value={-1}>Choose a Class</option>
Expand Down