mirror of
https://github.com/penxio/penx.git
synced 2026-04-19 03:03:06 -04:00
feat: can update publish status
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
Reference in New Issue
Block a user