Skip to content

Commit

Permalink
feat: controller & routes in server
Browse files Browse the repository at this point in the history
  • Loading branch information
warmachine028 committed Oct 25, 2024
1 parent 3f8fe43 commit 0356fa3
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 63 deletions.
45 changes: 10 additions & 35 deletions client/src/api/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,23 @@ import axios from 'axios'
import { PostsResponse, Post } from '@/types'
import { sleep } from '@/lib/utils'

const baseURL = import.meta.env.VITE_API_URL

const api = axios.create({ baseURL })
const api = axios.create({ baseURL: import.meta.env.VITE_API_URL })

export const getPosts = async (skip: number = 0, limit: number = 10): Promise<PostsResponse> => {
try {
const { data } = await api.get<PostsResponse>(`/posts?limit=${limit}&skip=${skip}`)

// Add imageUrl and transform reactions to the required format for each post
const postsWithImages = data.posts.map((post) => ({
...post,
imageUrl: `https://picsum.photos/seed/${post.id}/800/600`
}))

return {
...data,
posts: postsWithImages
}
const { data } = await api.get<PostsResponse>('/posts', {
params: { skip, limit }
})
return data
} catch (error) {
throw handleApiError(error)
}
}

// Other API functions remain similar but need to be updated with the new Post interface
export const createPost = async (post: Omit<Post, 'id' | 'imageUrl' | 'views'>): Promise<Post> => {
export const createPost = async (post: Omit<Post, 'id' | 'views'>): Promise<Post> => {
try {
await sleep(5000)
const { data } = await api.post(`/posts/add`, post)
return {
...data,
imageUrl: `https://picsum.photos/seed/${data.id}/800/600`,
views: 0,
reactions: { likes: 0, dislikes: 0 }
}
const { data } = await api.post<Post>('/posts', post)
return data
} catch (error) {
throw handleApiError(error)
}
Expand All @@ -44,18 +27,12 @@ export const createPost = async (post: Omit<Post, 'id' | 'imageUrl' | 'views'>):
export const updatePost = async (post: Partial<Post> & { id: number }): Promise<Post> => {
try {
const { data } = await api.put(`/posts/${post.id}`, post)
return {
...data,
imageUrl: `https://picsum.photos/seed/${data.id}/800/600`,
reactions: post.reactions || { likes: 0, dislikes: 0 },
views: post.views || 0
}
return data
} catch (error) {
throw handleApiError(error)
}
}

// Delete a post
export const deletePost = async (id: number): Promise<void> => {
try {
await api.delete(`/posts/${id}`)
Expand Down Expand Up @@ -102,9 +79,7 @@ export const getPostsByUser = async (userId: number): Promise<PostsResponse> =>
export const updateReaction = async (postId: number, type: 'like' | 'dislike') => {
try {
await sleep(3000)
const { data } = await api.put(`/posts/${postId}`, {
body: JSON.stringify({ reactions: { [type]: +1 } })
})
const { data } = await api.put(`/posts/${postId}`, { reactions: { [type]: +1 } })

return data
} catch (error) {
Expand Down
26 changes: 12 additions & 14 deletions client/src/components/CreatePost.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useRef, useState } from 'react'
import { Button, Input, Textarea } from '@/components/ui'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Loader2, Plus } from 'lucide-react'
import { ListRestart, Loader2, Plus } from 'lucide-react'
import { useCreatePost } from '@/hooks'

const CreatePost = () => {
Expand Down Expand Up @@ -41,6 +41,7 @@ const CreatePost = () => {
title: post.title.trim(),
body: post.body.trim(),
userId: 1,
imageUrl: URL.createObjectURL(post.image as File),
tags: post.tags
.split(',')
.map((tag) => tag.trim())
Expand All @@ -50,7 +51,9 @@ const CreatePost = () => {
dislikes: 0
}
})

handleReset()
}
const handleReset = () => {
setPost(initialData)
if (fileInputRef.current) {
fileInputRef.current.value = ''
Expand All @@ -63,7 +66,7 @@ const CreatePost = () => {
<CardTitle className="text-2xl font-bold">Create New Post</CardTitle>
</CardHeader>
<CardContent className="p-6">
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4" onReset={handleReset}>
<Input
type="text"
name="title"
Expand Down Expand Up @@ -97,19 +100,14 @@ const CreatePost = () => {
placeholder="eg: radio.png"
ref={fileInputRef}
/>
<Button
type="submit"
disabled={addMutation.isPending}
className="bg-primary text-primary-foreground"
>
{addMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Plus className="h-4 w-4 mr-2" />
)}
Add Post
<Button type="reset" className="*:size-4" size="icon" title='reset'>
<ListRestart />
</Button>
</div>
<Button type="submit" disabled={addMutation.isPending} className="w-full *:size-4 *:mr-2">
{addMutation.isPending ? <Loader2 className="animate-spin" /> : <Plus />}
Add Post
</Button>
</form>
</CardContent>
</Card>
Expand Down
13 changes: 9 additions & 4 deletions client/src/components/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
DialogTrigger
} from '@/components/ui/dialog'
import { useDeletePost, useUpdatePost, useUpdateReaction } from '@/hooks'
import { type Post as PostType } from '@/types'
import type { Post as PostType } from '@/types'

const tagColors = [
'bg-red-100 text-red-800',
Expand Down Expand Up @@ -42,6 +42,7 @@ const Post = ({ post }: { post: PostType }) => {
const handleSave = () => {
handleUpdatePost(editedPost)
setIsEditing(false)
setEditedPost(post)
}

return (
Expand All @@ -52,7 +53,7 @@ const Post = ({ post }: { post: PostType }) => {
exit={{ opacity: 0, y: -50 }}
transition={{ duration: 0.3 }}
>
<Card className="h-full flex flex-col overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
<Card className="h-full flex flex-col overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300" title={post.title}>
<CardHeader className="bg-secondary flex-shrink-0">
<div className="relative h-48 w-full mb-4">
<img
Expand Down Expand Up @@ -110,6 +111,7 @@ const Post = ({ post }: { post: PostType }) => {
variant="secondary"
className="flex items-center gap-1"
onClick={() => handleReaction('like')}
title='Like'
>
<ThumbsUp className="h-4 w-4" />
<span className="hidden xl:block">{post.reactions.likes}</span>
Expand All @@ -118,18 +120,19 @@ const Post = ({ post }: { post: PostType }) => {
variant="secondary"
className="flex items-center gap-1"
onClick={() => handleReaction('dislike')}
title="Dislike"
>
<ThumbsDown className="h-4 w-4" />
<span className="hidden xl:block">{post.reactions.dislikes}</span>
</Button>
<Badge variant="secondary" className="flex items-center gap-1">
<Badge variant="secondary" className="flex items-center gap-1" title="Views">
<Eye className="h-4 w-4" />
<span className="hidden xl:block">{post.views}</span>
</Badge>
</div>
<div className="flex gap-2">
{isEditing ? (
<Button onClick={handleSave} className="bg-primary text-primary-foreground">
<Button onClick={handleSave} className="bg-primary text-primary-foreground" title="Save">
<Save className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:block">Save</span>
</Button>
Expand All @@ -138,6 +141,7 @@ const Post = ({ post }: { post: PostType }) => {
variant="ghost"
onClick={() => setIsEditing(true)}
className="text-primary hover:text-primary hover:bg-primary/10"
title="Edit"
>
<Edit2 className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:block">Edit</span>
Expand All @@ -149,6 +153,7 @@ const Post = ({ post }: { post: PostType }) => {
<Button
variant="ghost"
className="text-destructive hover:text-destructive hover:bg-destructive/10"
title="Delete"
>
<Trash2 className="size-4" />
</Button>
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/ui/mode-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { Button } from './button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './dropdown-menu'
import { useTheme } from '@/hooks'

export const ModeToggle = () => {
export const ModeToggle = ({ variant, className }: { variant?: 'default' | 'outline'; className?: string }) => {
const { setTheme } = useTheme()

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Button variant={variant} size="icon" className={className} title="Toggle theme">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
Expand Down
29 changes: 21 additions & 8 deletions client/src/pages/Posts.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useEffect } from 'react'
import { useInView } from 'react-intersection-observer'
import { Button, ScrollArea, Badge, Search } from '@/components/ui'
import { Button, ScrollArea, Badge, Search, ModeToggle } from '@/components/ui'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Loader2, RefreshCcw } from 'lucide-react'
import { House, Loader2, RefreshCcw } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion'
import { useGetPosts, useRefresh, useSearch } from '@/hooks'
import { Post } from '@/components'
import { useStore } from '@/store'
import { CreatePost } from '@/components'
import { Link } from 'react-router-dom'

const PostsCard = () => {
const { ref, inView } = useInView()
Expand All @@ -23,18 +24,30 @@ const PostsCard = () => {
fetchNextPage()
}
}, [inView, hasNextPage, query, fetchNextPage])

return (
<Card className="shadow-lg">
<CardHeader className="bg-primary text-primary-foreground sticky top-0 z-10">
<div className="flex items-center justify-between">
<CardTitle className="text-2xl font-bold">Posts</CardTitle>
<Button variant="outline" onClick={refresh} className="bg-primary" disabled={refreshing}>
<RefreshCcw className={`${refreshing && 'animate-spin'} mr-2`} />
{refreshing ? 'Refreshing' : 'Refresh'}
</Button>
<div className="space-x-2 *:bg-primary">
<Button asChild variant="outline" size="icon" title="Home">
<Link to="/" className="text-inherit">
<House />
</Link>
</Button>
<ModeToggle variant="outline" />
<Button
variant="outline"
onClick={refresh}
disabled={refreshing}
title={refreshing ? 'Refreshing' : 'Refresh'}
size="icon"
>
<RefreshCcw className={`${refreshing && 'animate-spin'}`} />
</Button>
</div>
</div>
<Badge variant="secondary" className="max-w-fit">
<Badge variant="secondary" className="max-w-fit" title="Posts loaded">
{allPosts.length} posts
</Badge>
</CardHeader>
Expand Down

0 comments on commit 0356fa3

Please sign in to comment.