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
2 changes: 2 additions & 0 deletions apps/api/app/api/v1/markets/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export async function GET(req: Request): Promise<SchemaResponse<typeof schema.ge
}
}

export const maxDuration = 60 // Extend max duration for creating lists with lots of markets.

export async function POST(req: Request): Promise<SchemaResponse<typeof schema.post.responses>> {
try {
const userId = await getAuthUser(req)
Expand Down
7 changes: 7 additions & 0 deletions packages/api-helpers/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ export async function updateMarket({ marketId, body }: { marketId: string; body:
})
}

export async function updateList({ listId, body }: { listId: string; body: Record<string, unknown> }) {
return apiHandler<{ data: Market }>(`${process.env.NEXT_PUBLIC_API_URL}/v1/lists/${listId}`, {
method: 'PATCH',
body: body,
})
}

export async function updateMarketOption({
marketId,
optionId,
Expand Down
154 changes: 154 additions & 0 deletions packages/lists/components/EditListDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import React from 'react'
import { useForm } from 'react-hook-form'
import z from 'zod'
import { updateList } from '@play-money/api-helpers/client'
import { List, ListSchema } from '@play-money/database'
import { Button } from '@play-money/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@play-money/ui/dialog'
import { Editor } from '@play-money/ui/editor'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@play-money/ui/form'
import { Input } from '@play-money/ui/input'
import { MultiSelect } from '@play-money/ui/multi-select'
import { toast } from '@play-money/ui/use-toast'

const FormSchema = ListSchema.pick({ title: true, description: true, tags: true })

type FormData = z.infer<typeof FormSchema>

// Copied from https://github.com/orgs/react-hook-form/discussions/1991
function getDirtyValues<DirtyFields extends Record<string, unknown>, Values extends Record<keyof DirtyFields, unknown>>(
dirtyFields: DirtyFields,
values: Values
): Partial<typeof values> {
const dirtyValues = Object.keys(dirtyFields).reduce((prev, key) => {
if (!dirtyFields[key]) return prev

return {
...prev,
[key]:
typeof dirtyFields[key] === 'object'
? getDirtyValues(dirtyFields[key] as DirtyFields, values[key] as Values)
: values[key],
}
}, {})

return dirtyValues
}

export const EditListDialog = ({
open,
onClose,
onSuccess,
list,
}: {
open: boolean
onClose: () => void
onSuccess?: () => void
list: List
}) => {
const form = useForm<FormData>({
resolver: zodResolver(FormSchema),
defaultValues: {
title: list.title,
description: list.description ?? '<p></p>',
tags: list.tags,
},
})

const {
formState: { isSubmitting, isDirty, isValid },
} = form

const onSubmit = async (data: FormData) => {
try {
const changedData = getDirtyValues(form.formState.dirtyFields, data)
await updateList({ listId: list.id, body: changedData })
toast({ title: 'List edited successfully' })
form.reset()
onSuccess?.()
onClose()
} catch (error: any) {
console.error('Failed to edit list:', error)
toast({
title: 'There was an issue editing the list',
description: error.message ?? 'Please try again later',
variant: 'destructive',
})
}
}

return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit List</DialogTitle>
</DialogHeader>

<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
{/* <FormLabel>Question</FormLabel> */}
<FormControl>
<Input placeholder="Title" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Resolution criteria</FormLabel>
<FormControl>
<div className="min-h-[80px]">
<Editor
inputClassName="border text-sm p-3 min-h-[80px]"
placeholder="Description..."
{...field}
value={field.value ?? ''}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="tags"
render={({ field: { value, onChange, ...field } }) => (
<FormItem>
<FormLabel>Tags</FormLabel>
<FormControl>
<MultiSelect
value={value?.map((v) => ({ value: v, label: v }))}
onChange={(values) => onChange(values?.map((v) => v.value))}
hideClearAllButton
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button disabled={!isDirty || !isValid} loading={isSubmitting} type="submit">
Publish list edits
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
)
}
18 changes: 16 additions & 2 deletions packages/lists/components/ListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import { UserLink } from '@play-money/users/components/UserLink'
import { useUser } from '@play-money/users/context/UserContext'
import { useSelectedItems } from '../../ui/src/contexts/SelectedItemContext'
import { useSearchParam } from '../../ui/src/hooks/useSearchParam'
import { canAddToList } from '../rules'
import { canAddToList, canModifyList } from '../rules'
import { ExtendedList } from '../types'
import { AddMoreListDialog } from './AddMoreListDialog'
import { EditListDialog } from './EditListDialog'
import { ListMarketRow } from './ListMarketRow'
import { ListToolbar } from './ListToolbar'

Expand All @@ -36,6 +37,7 @@ export function ListPage({
const { triggerEffect } = useSidebar()
const { selected, setSelected } = useSelectedItems()
const [isAddMore, setIsAddMore] = useSearchParam('addMore')
const [isEditing, setIsEditing] = useSearchParam('edit')

const handleRevalidateBalance = async () => {
void onRevalidate?.()
Expand All @@ -45,7 +47,11 @@ export function ListPage({

return (
<Card className="flex-1">
<ListToolbar list={list} />
<ListToolbar
list={list}
canEdit={user ? canModifyList({ list, user }) : false}
onInitiateEdit={() => setIsEditing('true')}
/>

<CardHeader className="pt-0 md:pt-0">
<CardTitle className="leading-relaxed">{list.title}</CardTitle>
Expand Down Expand Up @@ -125,6 +131,14 @@ export function ListPage({
<div className="px-6 text-lg font-semibold">Comments</div>
{renderComments}

<EditListDialog
key={list.updatedAt.toString()} // reset form when list updates
list={list}
open={isEditing === 'true'}
onClose={() => setIsEditing(undefined)}
onSuccess={onRevalidate}
/>

<AddMoreListDialog
list={list}
open={isAddMore != null}
Expand Down
36 changes: 33 additions & 3 deletions packages/lists/components/ListToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
'use client'

import { MoreVertical, Link } from 'lucide-react'
import { MoreVertical, Link, Pencil } from 'lucide-react'
import React from 'react'
import { Button } from '@play-money/ui/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@play-money/ui/dropdown-menu'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@play-money/ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipTrigger } from '@play-money/ui/tooltip'
import { toast } from '@play-money/ui/use-toast'
import { useUser } from '@play-money/users/context/UserContext'
import { ExtendedList } from '../types'

export function ListToolbar({ list }: { list: ExtendedList }) {
export function ListToolbar({
list,
canEdit,
onInitiateEdit,
}: {
list: ExtendedList
canEdit?: boolean
onInitiateEdit: () => void
}) {
const { user } = useUser()

const handleCopyLink = async () => {
Expand All @@ -32,6 +46,12 @@ export function ListToolbar({ list }: { list: ExtendedList }) {

return (
<div className="flex items-center justify-end">
{canEdit ? (
<Button variant="ghost" size="sm" onClick={onInitiateEdit}>
<Pencil className="h-4 w-4" />
<span>Edit</span>
</Button>
) : null}
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" onClick={handleCopyLink}>
Expand All @@ -52,6 +72,16 @@ export function ListToolbar({ list }: { list: ExtendedList }) {
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleCopyLink}>Copy link</DropdownMenuItem>
{user ? <DropdownMenuItem onClick={handleCopyReferralLink}>Copy referral link</DropdownMenuItem> : null}

{canEdit ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onInitiateEdit}>
<Pencil className="mr-2 h-4 w-4" />
Edit list
</DropdownMenuItem>
</>
) : null}
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down
Loading
Loading