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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

An online campaign manager for [Star Wars: Imperial Assault](https://www.fantasyflightgames.com/en/products/star-wars-imperial-assault/)

<br><br><br><br><br>
![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
[![Remix](https://img.shields.io/badge/remix-%23000.svg?style=for-the-badge&logo=remix&logoColor=white)](https://remix.run)
[![Vite](https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white)](https://vite.dev)
[![Prisma](https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white)](https://www.prisma.io)
[![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white)](https://tailwindcss.com)
[![DaisyUI](https://img.shields.io/badge/daisyui-5A0EF8?style=for-the-badge&logo=daisyui&logoColor=white)](https://daisyui.com)
[![Clerk](https://img.shields.io/badge/Clerk-6C47FF?style=for-the-badge&logo=clerk&logoColor=white)](https://clerk.com/)
[![Vercel](https://img.shields.io/badge/vercel-%23000000.svg?style=for-the-badge&logo=vercel&logoColor=white)](https://vercel.com)
[![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?style=for-the-badge&logo=Cloudflare&logoColor=white)](https://cloudflare.com)

---

Expand Down
33 changes: 18 additions & 15 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# MVP

- [X] empire page
- [X] view only
- [X] manage owned agendas (discard and reshuffle)
- [X] arbitrary updates (agendas, villains, class cards, rewards, xp, name, influence)
- [X] BUG - fix styling for class card edit mode
- [X] BUG - fix styling for villains manager edit mode
- [x] empire page
- [x] view only
- [x] manage owned agendas (discard and reshuffle)
- [x] arbitrary updates (agendas, villains, class cards, rewards, xp, name, influence)
- [x] BUG - fix styling for class card edit mode
- [x] BUG - fix styling for villains manager edit mode
- [ ] move save buttons to match rebels page
- [X] rebels page
- [X] view only
- [X] update player name and xp
- [X] update player class cards
- [X] update player rewards
- [X] update allies
- [X] update items and credits
- [X] Move drawing side missions to resolve mission page, with mission rewards
- [x] rebels page
- [x] view only
- [x] update player name and xp
- [x] update player class cards
- [x] update player rewards
- [x] update allies
- [x] update items and credits
- [x] Move drawing side missions to resolve mission page, with mission rewards
- [ ] Edit side mission deck
- [ ] Delete games
- [ ] mobile view
Expand All @@ -26,8 +26,11 @@
- [ ] imperial buy
- [ ] empire
- [ ] rebel
- [X] home page (maybe just replace with games page?)
- [x] home page (maybe just replace with games page?)
- [ ] landing page (before login)
- [ ] Migrate from remix to react-router@7
- [x] Clerk auth
- [ ] Default theme picker to "os"

# Stretch

Expand Down
85 changes: 39 additions & 46 deletions app/components/AppNav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,51 @@
import { Link, NavLink } from '@remix-run/react'
import {
SignedIn,
SignedOut,
SignInButton,
UserButton,
useUser
} from '@clerk/remix'
import { NavLink } from '@remix-run/react'
import ThemePicker from '~/components/ThemePicker'
import type { getUser } from '~/services/auth.server'
import { getAvatarUrls } from '~/utils/avatar'

type Props = {
user: Awaited<ReturnType<typeof getUser>>
}

const AppNav = ({ user }: Props) => {
const urls = getAvatarUrls(user, 32)
const AppNav = ({ minimal = false }) => {
const { isSignedIn } = useUser()

return (
<div className="navbar bg-base-300 text-base-content mb-4">
<div className="navbar bg-base-300 text-base-content mb-4 pr-4 gap-2">
<div className="flex-1">
<NavLink to="/games" className="btn btn-ghost text-xl">
<NavLink
to={isSignedIn ? '/games' : '/'}
className="btn btn-ghost text-xl"
>
Unquestable
</NavLink>
</div>
<div className="flex-none">
<ul className="menu menu-horizontal px-1">
<li>
<NavLink to="/collection">Collection</NavLink>
</li>
<li>
<NavLink to="/games" end>
Games
</NavLink>
</li>
</ul>
</div>
<div className="flex-none">
<ThemePicker />
</div>
<div className="flex-none dropdown dropdown-end">
<button className="btn btn-ghost btn-circle avatar">
<div className="w-8 rounded-full">
<picture>
{urls.webp && <source src={urls.webp} />}
<img src={urls.gif ?? urls.png} alt="" />
</picture>
{!minimal && (
<>
<SignedIn>
<div className="flex-none">
<ul className="menu menu-horizontal px-1">
<li>
<NavLink to="/collection">Collection</NavLink>
</li>
<li>
<NavLink to="/games" end>
Games
</NavLink>
</li>
</ul>
</div>
</SignedIn>
<div className="flex-none">
<ThemePicker />
</div>
</button>
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-300 text-base-content rounded-box w-52"
>
<li>
<NavLink to="/profile">Profile</NavLink>
</li>
<li>
<Link to="/logout">Log Out</Link>
</li>
</ul>
</div>
<UserButton userProfileMode="navigation" userProfileUrl="/user" />
<SignedOut>
<SignInButton />
</SignedOut>
</>
)}
</div>
)
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Link } from '@remix-run/react'

const Footer = () => {
return (
<footer className="footer footer-center p-10 bg-base-300 text-base-content">
<footer className="footer footer-center p-10 bg-base-300 text-base-content mt-4">
<nav className="grid grid-flow-col gap-4">
<Link to="#" className="link link-hover">
About Us
Expand All @@ -14,7 +14,7 @@ const Footer = () => {
Privacy Policy
</Link>
</nav>
<div className="text-left">
<div className="text-left max-w-screen-lg">
{/* Disclaimer copied from https://cards.boardwars.eu/ */}
This website is not produced, endorsed, supported, or affiliated with
Fantasy Flight Games. The copyrightable portions of Star Wars: Imperial
Expand Down
16 changes: 4 additions & 12 deletions app/components/ItemManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,11 @@ import type { LoaderData as GameLoaderData } from '~/routes/_app.games.$game'
import type { LoaderData } from '~/routes/_app.games.$game.rebels'
import EditButton from '../EditButton'
import { sortItems } from '~/utils/sortItems'
import {
Reducer,
useCallback,
useEffect,
useId,
useMemo,
useReducer
} from 'react'
import type { Reducer } from 'react'
import { useCallback, useEffect, useId, useMemo, useReducer } from 'react'
import { useFetcher } from 'react-router-dom'
import {
ActionData,
itemValidator
} from '~/routes/_app.games.$game.rebels.items'
import type { ActionData } from '~/routes/_app.games.$game.rebels.items'
import { itemValidator } from '~/routes/_app.games.$game.rebels.items'
import SubmitButton from '../SubmitButton'
import TextInput from '../TextInput'
import { ValidatedForm } from 'remix-validated-form'
Expand Down
16 changes: 13 additions & 3 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import type { LinksFunction } from '@vercel/remix'
import type { LinksFunction, LoaderFunction } from '@vercel/remix'
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration
} from '@remix-run/react'
import { rootAuthLoader } from '@clerk/remix/ssr.server'

import stylesheet from '~/tailwind.css?url'
import { useTheme } from './context/theme-context'
import { ClerkApp } from '@clerk/remix'

export const loader: LoaderFunction = (args) => rootAuthLoader(args)

export const links: LinksFunction = () => [
{ rel: 'stylesheet', href: stylesheet }
]

export default function App() {
export function Layout({ children }: { children: React.ReactNode }) {
const { theme } = useTheme()

return (
Expand All @@ -26,10 +30,16 @@ export default function App() {
<Links />
</head>
<body>
<Outlet />
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
)
}

function App() {
return <Outlet />
}

export default ClerkApp(App)
15 changes: 15 additions & 0 deletions app/routes/_account.sign-in.$.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SignIn } from '@clerk/remix'

export default function Page() {
return (
<SignIn
appearance={{
elements: {
rootBox: {
margin: '0 auto'
}
}
}}
/>
)
}
15 changes: 15 additions & 0 deletions app/routes/_account.sign-up.$.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SignUp } from '@clerk/remix'

export default function Page() {
return (
<SignUp
appearance={{
elements: {
rootBox: {
margin: '0 auto'
}
}
}}
/>
)
}
15 changes: 15 additions & 0 deletions app/routes/_account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Outlet } from '@remix-run/react'
import AppNav from '~/components/AppNav'
import Footer from '~/components/Footer'

export default function Page() {
return (
<div className="min-h-screen flex flex-col">
<AppNav minimal />
<main className="grow max-w-screen-xl w-full mx-auto">
<Outlet />
</main>
<Footer />
</div>
)
}
16 changes: 8 additions & 8 deletions app/routes/_app.collection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { useLoaderData } from '@remix-run/react'
import { z } from 'zod'
import { zfd } from 'zod-form-data'
import CollectionItem from '~/components/CollectionItem'
import { getUser } from '~/services/auth.server'
import { prisma } from '~/services/db.server'
import { requireAuth } from '~/utils/requireAuth.server'

export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await getUser(request)
export const loader = async (args: LoaderFunctionArgs) => {
const { userId } = await requireAuth(args)

const allExpansions = await prisma.expansion.findMany({
include: { boxArt: true }
Expand All @@ -18,7 +18,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
where: {
owners: {
some: {
id: user.id
id: userId
}
}
},
Expand All @@ -36,12 +36,12 @@ const addRemoveSchema = zfd.formData({
action: zfd.text(z.enum(['add', 'remove']))
})

export const action = async ({ request }: ActionFunctionArgs) => {
export const action = async (args: ActionFunctionArgs) => {
const { action, expansionId } = addRemoveSchema.parse(
await request.formData()
await args.request.formData()
)

const user = await getUser(request)
const { userId } = await requireAuth(args)

const expansion = await prisma.expansion.findUnique({
where: {
Expand All @@ -59,7 +59,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {

await prisma.user.update({
where: {
id: user.id
id: userId
},
data: {
collection: {
Expand Down
12 changes: 6 additions & 6 deletions app/routes/_app.games.$game.empire.agendas.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { json, redirect } from '@vercel/remix'
import type { LoaderFunctionArgs, ActionFunctionArgs } from '@vercel/remix'
import { prisma } from '~/services/db.server'
import { getUser } from '~/services/auth.server'
import { withZod } from '@remix-validated-form/with-zod'
import { zfd } from 'zod-form-data'
import { z } from 'zod'
import { requireAuth } from '~/utils/requireAuth.server'

export type ActionData = { success?: number }

Expand All @@ -29,17 +29,17 @@ export const agendaValidator = withZod(
})
)

export const action = async ({ request, params }: ActionFunctionArgs) => {
const { data } = await agendaValidator.validate(await request.formData())
export const action = async (args: ActionFunctionArgs) => {
const { data } = await agendaValidator.validate(await args.request.formData())

const user = await getUser(request)
const gameId = parseInt(params.game!, 10)
const { userId } = await requireAuth(args)
const gameId = parseInt(args.params.game!, 10)

const player = await prisma.imperialPlayer.findFirst({
where: {
game: {
id: gameId,
userId: user.id
userId
}
},
select: {
Expand Down
Loading