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: 1 addition & 1 deletion .github/workflows/vercel-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v20
- uses: amondnet/vercel-action@v25
id: vercel-deploy
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
Expand Down
266 changes: 208 additions & 58 deletions src/app/personalize/page.tsx

Large diffs are not rendered by default.

301 changes: 301 additions & 0 deletions src/app/posts/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
"use client";
import { usePosts, Post, Platform, PostStatus } from "@/hooks/data/use-posts";
import React, { useState, useMemo } from "react";
import { Card, CardBody, CardHeader } from "@heroui/card";
import { Button } from "@heroui/button";
import { Input } from "@heroui/input";
import { Skeleton } from "@heroui/skeleton";
import { Pagination } from "@heroui/pagination";
import { Select, SelectItem } from "@heroui/select";
import { Chip } from "@heroui/chip";
import { Search, AlertCircle, FileText } from "lucide-react";
import PostCard from "@/components/posts/PostCard";
import PostActivityTracker from "@/components/posts/PostActivityTracker";

function PostGridSkeleton() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{Array.from({ length: 8 }).map((_, index) => (
<Card key={index} className="w-full animate-pulse">
<CardHeader className="flex gap-4">
<Skeleton className="w-12 h-12 rounded-full" />
<div className="flex-1 space-y-2">
<Skeleton className="w-3/4 h-4 rounded" />
<Skeleton className="w-1/2 h-3 rounded" />
</div>
</CardHeader>
<CardBody>
<div className="space-y-3">
<Skeleton className="w-full h-20 rounded-lg" />
<div className="flex gap-2">
<Skeleton className="w-16 h-16 rounded-lg" />
<Skeleton className="w-16 h-16 rounded-lg" />
</div>
<div className="flex gap-2">
<Skeleton className="flex-1 h-8 rounded" />
<Skeleton className="flex-1 h-8 rounded" />
<Skeleton className="w-8 h-8 rounded" />
</div>
</div>
</CardBody>
</Card>
))}
</div>
);
}

export default function PostsPage() {
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [selectedPlatform, setSelectedPlatform] = useState<Platform | "ALL">(
"ALL"
);
const [selectedStatus, setSelectedStatus] = useState<PostStatus | "ALL">(
"ALL"
);

const pageSize = 12;

const { data, isLoading, error } = usePosts({
page: currentPage,
pageSize,
});

// Filter posts based on search and filters
const filteredPosts = useMemo(() => {
if (!data?.posts) return [];

return data.posts.filter((post) => {
const matchesSearch =
post.text.toLowerCase().includes(searchQuery.toLowerCase()) ||
post.platform.toLowerCase().includes(searchQuery.toLowerCase());
const matchesPlatform =
selectedPlatform === "ALL" || post.platform === selectedPlatform;
const matchesStatus =
selectedStatus === "ALL" || post.status === selectedStatus;

return matchesSearch && matchesPlatform && matchesStatus;
});
}, [data?.posts, searchQuery, selectedPlatform, selectedStatus]);

const handlePostView = (post: Post) => {
console.log("View post:", post);
};

if (error) {
return (
<div className="container max-w-7xl mx-auto px-4 py-8">
<Card className="border-danger-200 bg-danger-50">
<CardBody className="text-center py-12">
<AlertCircle size={64} className="mx-auto mb-4 text-danger" />
<h2 className="text-2xl font-semibold text-danger mb-2">
Unable to Load Posts
</h2>
<p className="text-danger-600 mb-4">
There was an error loading your posts. Please check your
connection and try again.
</p>
<Button
color="danger"
variant="flat"
onPress={() => window.location.reload()}
>
Retry
</Button>
</CardBody>
</Card>
</div>
);
}

return (
<div className="min-h-screen">
<div className="container max-w-4xl mx-auto px-4 py-6">
<div className="mb-6">
<h1 className="mb-1 text-2xl font-bold text-foreground">
Recent Posts
</h1>
<p className="text-sm text-default-600">
Your social media posts in one place
</p>
</div>

<div className="mb-8">
<PostActivityTracker posts={data?.posts || []} />
</div>

<div className="mb-6">
<div className="flex flex-col sm:flex-row gap-3 mb-4">
<Input
placeholder="Search posts..."
startContent={<Search size={16} />}
value={searchQuery}
onValueChange={setSearchQuery}
variant="flat"
size="sm"
className="flex-1"
/>

<div className="flex gap-2">
<Select
placeholder="Platform"
selectedKeys={
selectedPlatform !== "ALL" ? [selectedPlatform] : []
}
onSelectionChange={(keys) => {
const selected = Array.from(keys)[0] as Platform | undefined;
setSelectedPlatform(selected || "ALL");
}}
variant="flat"
size="sm"
className="min-w-[120px]"
>
<SelectItem key="ALL">All</SelectItem>
<SelectItem key="LINKEDIN">LinkedIn</SelectItem>
<SelectItem key="TWITTER">Twitter</SelectItem>
<SelectItem key="X">X</SelectItem>
<SelectItem key="INSTAGRAM">Instagram</SelectItem>
<SelectItem key="DEVTO">Dev.to</SelectItem>
<SelectItem key="MEDIUM">Medium</SelectItem>
<SelectItem key="FACEBOOK">Facebook</SelectItem>
</Select>

<Select
placeholder="Status"
selectedKeys={selectedStatus !== "ALL" ? [selectedStatus] : []}
onSelectionChange={(keys) => {
const selected = Array.from(keys)[0] as
| PostStatus
| undefined;
setSelectedStatus(selected || "ALL");
}}
variant="flat"
size="sm"
className="min-w-[100px]"
>
<SelectItem key="ALL">All</SelectItem>
<SelectItem key="DRAFT">Draft</SelectItem>
<SelectItem key="READY">Ready</SelectItem>
<SelectItem key="SCHEDULED">Scheduled</SelectItem>
<SelectItem key="PUBLISHED">Published</SelectItem>
<SelectItem key="FAILED">Failed</SelectItem>
</Select>
</div>
</div>

{/* Active Filters */}
{(searchQuery ||
selectedPlatform !== "ALL" ||
selectedStatus !== "ALL") && (
<div className="flex items-center gap-2">
{searchQuery && (
<Chip
size="sm"
variant="flat"
onClose={() => setSearchQuery("")}
className="text-xs"
>
"{searchQuery}"
</Chip>
)}
{selectedPlatform !== "ALL" && (
<Chip
size="sm"
variant="flat"
onClose={() => setSelectedPlatform("ALL")}
className="text-xs"
>
{selectedPlatform}
</Chip>
)}
{selectedStatus !== "ALL" && (
<Chip
size="sm"
variant="flat"
onClose={() => setSelectedStatus("ALL")}
className="text-xs"
>
{selectedStatus}
</Chip>
)}
</div>
)}
</div>

<div className="mb-8">
{isLoading ? (
<PostGridSkeleton />
) : filteredPosts.length > 0 ? (
<div
className={"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"}
>
{filteredPosts.map((post) => (
<PostCard key={post.id} post={post} onView={handlePostView} />
))}
</div>
) : (
<Card
className="border-dashed border-2 border-default-300"
shadow="none"
>
<CardBody className="text-center py-20">
<div className="flex flex-col items-center space-y-4">
<div className="p-4 bg-default-100 rounded-full">
<FileText size={48} className="text-default-400" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold text-default-600">
{searchQuery ||
selectedPlatform !== "ALL" ||
selectedStatus !== "ALL"
? "No posts match your filters"
: "No posts created yet"}
</h3>
<p className="text-default-500 max-w-md">
{searchQuery ||
selectedPlatform !== "ALL" ||
selectedStatus !== "ALL"
? "Try adjusting your search criteria or clear the filters to see more posts."
: "Get started by creating your first social media post. Share your content across multiple platforms effortlessly."}
</p>
</div>
<div className="flex gap-3">
{(searchQuery ||
selectedPlatform !== "ALL" ||
selectedStatus !== "ALL") && (
<Button
variant="flat"
onPress={() => {
setSearchQuery("");
setSelectedPlatform("ALL");
setSelectedStatus("ALL");
}}
>
Clear Filters
</Button>
)}
</div>
</div>
</CardBody>
</Card>
)}
</div>

{data && data.totalPages > 1 && (
<div className="flex justify-center">
<Pagination
total={data.totalPages}
page={currentPage}
onChange={setCurrentPage}
showControls
showShadow
color="primary"
size="lg"
className="gap-2"
/>
</div>
)}
</div>
</div>
);
}
Loading