feat: can update publish status

This commit is contained in:
0xzio
2025-02-10 23:22:34 +08:00
parent 28eb6c2f9e
commit e4232c82d2
3 changed files with 170 additions and 28 deletions

View File

@@ -1,16 +1,25 @@
'use client'
import { useState } from 'react'
import { format } from 'date-fns'
import { Archive, CalendarIcon, Edit3Icon, Trash2 } from 'lucide-react'
import Link from 'next/link'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import { Switch } from '@/components/ui/switch'
import { PostStatus } from '@/lib/constants'
import { extractErrorMessage } from '@/lib/extractErrorMessage'
import { Post } from '@/lib/hooks/usePost'
import { usePosts } from '@/lib/hooks/usePosts'
import { api } from '@/lib/trpc'
import { cn } from '@/lib/utils'
import { format } from 'date-fns'
import { Archive, Edit3Icon, Trash2 } from 'lucide-react'
import Link from 'next/link'
import { toast } from 'sonner'
import { Calendar } from './ui/calendar'
interface PostItemProps {
status: PostStatus
@@ -20,6 +29,8 @@ interface PostItemProps {
export function PostItem({ post, status }: PostItemProps) {
const { refetch } = usePosts()
const isPublished = post.postStatus === PostStatus.PUBLISHED
const [date, setDate] = useState<Date>(post.publishedAt || new Date())
const [open, setOpen] = useState(false)
return (
<div className={cn('flex flex-col gap-2 py-[6px]')}>
@@ -39,10 +50,12 @@ export function PostItem({ post, status }: PostItemProps) {
</Badge>
))}
</div>
<div className="flex items-center gap-2">
<div className="text-sm text-zinc-500">
<div>{format(new Date(post.updatedAt), 'yyyy-MM-dd')}</div>
</div>
<div className="flex items-center gap-2 flex-wrap">
{post.postStatus !== PostStatus.PUBLISHED && (
<div className="text-sm text-foreground/50">
<div>{format(new Date(post.updatedAt), 'yyyy-MM-dd')}</div>
</div>
)}
<Link href={`/~/post?id=${post.id}`}>
<Button
size="xs"
@@ -105,6 +118,90 @@ export function PostItem({ post, status }: PostItemProps) {
<div>Delete</div>
</Button>
)}
{status === PostStatus.PUBLISHED && (
<div className="text-xs text-foreground/50 flex gap-6">
<div className="flex items-center gap-1">
<Switch
size="sm"
defaultChecked={!!post.featured}
onCheckedChange={async (value) => {
try {
await api.post.updatePublishedPost.mutate({
postId: post.id,
featured: value,
})
toast.success('Update successfully!')
} catch (error) {
toast.error(extractErrorMessage(error))
}
}}
/>
<div>Featured</div>
</div>
<div className="flex items-center gap-1">
<Switch
size="sm"
defaultChecked={!!post.isPopular}
onCheckedChange={async (value) => {
try {
await api.post.updatePublishedPost.mutate({
postId: post.id,
isPopular: value,
})
toast.success('Update successfully!')
} catch (error) {
toast.error(extractErrorMessage(error))
}
}}
/>
<div>Popular</div>
</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
className={cn(
'h-8 justify-start text-xs px-2 rounded-md flex gap-1',
!date && 'text-muted-foreground',
)}
onClick={() => setOpen(!open)}
>
<CalendarIcon size={14} />
{date ? (
<span>{format(date, 'PPP')}</span>
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={async (d) => {
setOpen(false)
if (d) {
setDate(d!)
try {
await api.post.updatePublishedPost.mutate({
postId: post.id,
publishedAt: d.toString(),
})
toast.success('Update publish date successfully!')
} catch (error) {
toast.error(extractErrorMessage(error))
}
}
}}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
)}
</div>
</div>
)

View File

@@ -1,29 +1,44 @@
"use client"
'use client'
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
import * as React from 'react'
import { cn } from '@/lib/utils'
import * as SwitchPrimitives from '@radix-ui/react-switch'
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> & {
size?: 'sm' | 'md' | 'lg'
}
>(({ className, size = 'md', ...props }, ref) => {
const rootClassName = React.useMemo(() => {
if (size === 'sm') return 'h-4 w-6'
return 'h-6 w-11 '
}, [size])
const thumbClassName = React.useMemo(() => {
if (size === 'sm') {
return 'h-3 w-3 data-[state=checked]:translate-x-2'
}
return 'h-5 w-5 data-[state=checked]:translate-x-5'
}, [size])
return (
<SwitchPrimitives.Root
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
rootClassName,
className,
)}
/>
</SwitchPrimitives.Root>
))
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
'pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=unchecked]:translate-x-0',
thumbClassName,
)}
/>
</SwitchPrimitives.Root>
)
})
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

View File

@@ -181,6 +181,36 @@ export const postRouter = router({
return newPost
}),
updatePublishedPost: protectedProcedure
.input(
z.object({
postId: z.string(),
featured: z.boolean().optional(),
isPopular: z.boolean().optional(),
publishedAt: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const { postId, publishedAt, ...data } = input
await db
.update(posts)
.set({
...data,
...(publishedAt ? { publishedAt: new Date(publishedAt) } : {}),
})
.where(eq(posts.id, input.postId))
try {
revalidatePath('/', 'layout')
// revalidatePath('/(blog)/(home)', 'page')
revalidatePath('/(blog)/posts', 'page')
revalidatePath('/(blog)/posts/[...slug]', 'page')
revalidatePath('/(blog)/posts/page/[page]', 'page')
} catch (error) {}
return true
}),
archive: protectedProcedure
.input(z.string())
.mutation(async ({ ctx, input }) => {