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
10 changes: 5 additions & 5 deletions app/(dashboard)/_components/board-card/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ export const Footer = ({
onClick();
};
return (
<div className="group relative bg-white p-2">
<p className="max-w-[calc(100%-20px)] truncate text-[13px]">{title}</p>
<p className="truncate text-[11px] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100">
<div className="group relative bg-white p-2 md:p-3">
<p className="max-w-[calc(100%-20px)] truncate text-xs md:text-[13px] font-medium">{title}</p>
<p className="truncate text-[10px] md:text-[11px] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100">
{authorLabel}, {createdAtLabel}
</p>
<button
disabled={disabled}
onClick={handleClick}
aria-label="Favorite"
className={cn(
"absolute right-3 top-3 text-muted-foreground opacity-0 transition-opacity hover:text-blue-600 group-hover:opacity-100",
"absolute right-2 top-2 md:right-3 md:top-3 text-muted-foreground opacity-0 transition-opacity hover:text-blue-600 group-hover:opacity-100 touch-manipulation",
disabled && "cursor-not-allowed opacity-75"
)}
>
<Star
className="h-4 w-4"
className="h-3 w-3 md:h-4 md:w-4"
fill={isFavorite ? "currentColor" : "none"}
stroke="currentColor"
/>
Expand Down
16 changes: 11 additions & 5 deletions app/(dashboard)/_components/board-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const BoardCard = ({
orgId,
isFavorite,
}: BoardCardProps) => {
console.log(id);
// console.log(id);
const { userId } = useAuth();
const authorLabel = userId === authorId ? "You" : authorName;
const createdAtLabel = formatDistanceToNow(createdAt, {
Expand All @@ -59,13 +59,19 @@ export const BoardCard = ({
};
return (
<Link href={`/board/${id}`}>
<div className="group flex aspect-[100/127] flex-col justify-between overflow-hidden rounded-lg border">
<div className="group flex aspect-[100/127] flex-col justify-between overflow-hidden rounded-lg border hover:shadow-lg transition-shadow duration-200">
<div className="relative flex-1 bg-amber-50">
<Image src={imageUrl} alt={title} fill className="object-fit" />
<Image src={imageUrl} alt={title} fill className="object-cover" />
<Overlay />
<Actions id={id} title={title} side="right">
<button className="absolute right-1 top-1 px-3 py-2 opacity-0 outline-none transition-opacity group-hover:opacity-100">
<MoreHorizontal className="text-white opacity-75 transition-opacity hover:opacity-100" />
<button
className="absolute right-1 top-1 px-2 py-1 md:px-3 md:py-2 opacity-0 outline-none transition-opacity group-hover:opacity-100 touch-manipulation"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<MoreHorizontal className="h-4 w-4 md:h-5 md:w-5 text-white opacity-75 transition-opacity hover:opacity-100" />
</button>
</Actions>
</div>
Expand Down
67 changes: 57 additions & 10 deletions app/(dashboard)/_components/board-list.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
"use client";

import { useSearchParams } from "next/navigation";
import { useMemo, useEffect } from "react";
import { useMemo, useEffect, useState } from "react";
import { EmptySearch } from "./empty-search";
import { EmptyFavorites } from "./empty-favorites";
import { EmptyBoards } from "./empty-boards";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
import { BoardCard } from "./board-card";
import { NewBoardButton } from "./new-board-button";
import { useOrganization } from "@clerk/nextjs";

interface BoardListProps {
orgId: string;
}

export const BoardList = ({ orgId }: BoardListProps) => {
const searchParams = useSearchParams();
const { organization } = useOrganization();
const [isOrgSwitching, setIsOrgSwitching] = useState(false);

// Inject keyframes dynamically since no global CSS is used
useEffect(() => {
Expand All @@ -38,6 +41,17 @@ export const BoardList = ({ orgId }: BoardListProps) => {
};
}, []);

// Handle organization switching
useEffect(() => {
if (organization?.id !== orgId) {
setIsOrgSwitching(true);
const timer = setTimeout(() => {
setIsOrgSwitching(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [organization?.id, orgId]);

const search = useMemo(
() => searchParams.get("search") || undefined,
[searchParams]
Expand All @@ -49,7 +63,11 @@ export const BoardList = ({ orgId }: BoardListProps) => {

const data = useQuery(api.boards.get, { orgId, search, favorites });

if (data === undefined) {
// Debug logging
// console.log("BoardList - Search:", search, "Favorites:", favorites, "Data:", data);

// Show loading state when organization is switching
if (isOrgSwitching || data === undefined) {
return (
<div className="relative">
{/* Beautiful Background */}
Expand All @@ -74,8 +92,11 @@ export const BoardList = ({ orgId }: BoardListProps) => {
</div>

<div className="relative z-10">
<h2 className="text-3xl">
<h2 className="text-3xl flex items-center gap-3">
{favorites ? "Favorite Boards" : "Team Boards"}
{isOrgSwitching && (
<div className="animate-spin h-5 w-5 border-2 border-blue-500 border-t-transparent rounded-full"></div>
)}
</h2>
<div className="mt-8 grid grid-cols-1 gap-5 pb-10 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6">
<NewBoardButton orgId={orgId} disabled />
Expand All @@ -102,7 +123,7 @@ export const BoardList = ({ orgId }: BoardListProps) => {
}

return (
<div className="relative">
<div className="relative h-full overflow-y-auto">

<div className="absolute inset-0 overflow-hidden z-0">
<div className="absolute inset-0 bg-gradient-to-br from-blue-50 via-white to-purple-100 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900 opacity-50" />
Expand All @@ -124,12 +145,38 @@ export const BoardList = ({ orgId }: BoardListProps) => {
/>
</div>

<div className="relative z-10">
<h2 className="text-3xl">
{favorites ? "Favorite Boards" : "Team Boards"}
</h2>
<div className="mt-8 grid grid-cols-1 gap-5 pb-10 sm:grid-cols-2 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6">
<NewBoardButton orgId={orgId} />
<div className="relative z-10 p-4 md:p-6">
{/* Header with search context */}
<div className="mb-6">
<h2 className="text-2xl md:text-3xl font-bold text-gray-900 mb-2">
{search ? `Search Results` : favorites ? "Favorite Boards" : "Team Boards"}
</h2>

{/* Search query display */}
{search && (
<p className="text-sm text-gray-600 mb-2">
Found {data.length} {data.length === 1 ? 'board' : 'boards'} matching &ldquo;{search}&rdquo;
</p>
)}

{/* Mobile-friendly board count for non-search views */}
{!search && (
<p className="text-sm text-gray-600 md:hidden">
{data.length} {data.length === 1 ? 'board' : 'boards'}
</p>
)}
</div>

{/* Responsive Grid */}
<div className="grid grid-cols-1 gap-4 pb-10
xs:grid-cols-2
sm:grid-cols-2
md:grid-cols-3
lg:grid-cols-4
xl:grid-cols-5
2xl:grid-cols-6">
{/* Only show NewBoardButton when not searching */}
{!search && <NewBoardButton orgId={orgId} />}
{data.map((board) => (
<BoardCard
key={board._id}
Expand Down
17 changes: 8 additions & 9 deletions app/(dashboard)/_components/empty-boards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import { useApiMutation } from "@/hooks/use-api-mutation";
import { api } from "@/convex/_generated/api";
import { useOrganization } from "@clerk/nextjs";
import { toast } from "sonner";
import { useRouter } from "next/navigation";

export const EmptyBoards = () => {
const router = useRouter();
const { organization } = useOrganization();
const { mutate, pending } = useApiMutation(api.board.create);

Expand All @@ -24,21 +22,22 @@ export const EmptyBoards = () => {
})
.then((id) => {
toast.success("Board Created");
router.push(`/board/${id}`);
// Use window.location.href to ensure a full page load with proper auth sync
window.location.href = `/board/${id}`;
})
.catch(() => toast.error("Failed to create board"));
};

return (
<div className="flex h-full flex-col items-center justify-center">
<div className="flex min-h-full flex-col items-center justify-center p-6 md:p-8">
<Image src="/note.svg" height={110} width={110} alt="Empty" />
<h2 className="mt-6 text-2xl font-semibold">Create your first board!</h2>
<p className="mt-2 text-sm text-muted-foreground">
Start by creating a board for your organization.
<h2 className="mt-6 text-xl md:text-2xl font-semibold text-center">Create your first board!</h2>
<p className="mt-2 text-sm text-muted-foreground text-center max-w-md">
Start by creating a board for your organization to begin collaborating.
</p>
<div className="mt-6">
<Button disabled={pending} onClick={onClick} size="lg">
Create Board
<Button disabled={pending} onClick={onClick} size="lg" className="touch-manipulation">
{pending ? "Creating..." : "Create Board"}
</Button>
</div>
</div>
Expand Down
45 changes: 40 additions & 5 deletions app/(dashboard)/_components/empty-favorites.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
import Image from "next/image";
import { Heart, Star } from "lucide-react";

export const EmptyFavorites = () => {
const handleViewAllBoards = () => {
window.location.href = "/";
};

return (
<div className="flex h-full flex-col items-center justify-center">
<Image src="/empty-favourites.svg" height={140} width={140} alt="Empty" />
<h2 className="mt-6 text-2xl font-semibold">No favorite Boards !</h2>
<p className="textg-sm mt-2 text-muted-foreground">
Try favoriting a board .
<div className="flex min-h-full flex-col items-center justify-center p-6 md:p-8">
<div className="relative mb-6">
<Image src="/empty-favourites.svg" height={140} width={140} alt="No favorite boards" />
<div className="absolute -top-2 -right-2 bg-yellow-100 rounded-full p-2">
<Heart className="h-5 w-5 text-yellow-600" />
</div>
</div>

<h2 className="text-xl md:text-2xl font-semibold text-center text-gray-900 mb-2">
No favorite boards yet!
</h2>

<p className="text-sm text-gray-600 text-center max-w-md mb-6">
Mark boards as favorites by clicking the star icon on any board.
Your favorite boards will appear here for quick access.
</p>

<button
onClick={handleViewAllBoards}
className="flex items-center justify-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
<Star className="h-4 w-4" />
Browse All Boards
</button>

<div className="mt-8 text-center">
<p className="text-xs text-gray-500 mb-3">How to favorite a board:</p>
<div className="flex flex-wrap justify-center gap-2 text-xs">
<span className="bg-yellow-100 text-yellow-700 px-2 py-1 rounded flex items-center gap-1">
<Star className="h-3 w-3" />
Click the star icon
</span>
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded">On any board card</span>
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded">Quick access</span>
</div>
</div>
</div>
);
};
54 changes: 49 additions & 5 deletions app/(dashboard)/_components/empty-search.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,57 @@
import Image from "next/image";
import { useSearchParams } from "next/navigation";
import { Search, RefreshCw } from "lucide-react";

export const EmptySearch = () => {
const searchParams = useSearchParams();
const search = searchParams.get("search") || "";

const handleClearSearch = () => {
window.location.href = "/";
};

return (
<div className="flex h-full flex-col items-center justify-center">
<Image src="/empty-search.svg" height={140} width={140} alt="Empty" />
<h2 className="mt-6 text-2xl font-semibold">No results Found !</h2>
<p className="textg-sm mt-2 text-muted-foreground">
Try Searching for something else .
<div className="flex min-h-full flex-col items-center justify-center p-6 md:p-8">
<div className="relative mb-6">
<Image src="/empty-search.svg" height={140} width={140} alt="No search results" />
<div className="absolute -top-2 -right-2 bg-blue-100 rounded-full p-2">
<Search className="h-5 w-5 text-blue-600" />
</div>
</div>

<h2 className="text-xl md:text-2xl font-semibold text-center text-gray-900 mb-2">
No results found
</h2>

<p className="text-sm text-gray-600 text-center max-w-md mb-6">
{search ? (
<>
No boards found matching <span className="font-medium">&ldquo;{search}&rdquo;</span>.
Try a different search term or check your spelling.
</>
) : (
"Try searching for something else or browse all boards."
)}
</p>

<div className="flex flex-col sm:flex-row gap-3 w-full max-w-sm">
<button
onClick={handleClearSearch}
className="flex items-center justify-center gap-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
<RefreshCw className="h-4 w-4" />
View All Boards
</button>
</div>

<div className="mt-8 text-center">
<p className="text-xs text-gray-500 mb-3">Search tips:</p>
<div className="flex flex-wrap justify-center gap-2 text-xs">
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded">Try board names</span>
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded">Check spelling</span>
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded">Use fewer words</span>
</div>
</div>
</div>
);
};
Loading
Loading