Skip to content

Commit

Permalink
Major updates
Browse files Browse the repository at this point in the history
  • Loading branch information
hqasmei committed Jul 20, 2024
1 parent 720e8cf commit 85e7aeb
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 359 deletions.
33 changes: 3 additions & 30 deletions src/app/(unauthenticated)/(landing)/_components/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,17 @@
'use client';

import React from 'react';

import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';
import { ReCaptchaProvider } from 'next-recaptcha-v3';

import NewsletterForm from './newsletter-form';

export default function Hero() {
const portfolios = useQuery(api.portfolios.getAllPortfolios);
const numberOfPortfolios = portfolios?.length;

return (
<>
<div className="flex flex-col items-center sm:pt-8 gap-y-6 sm:gap-y-7">
<h1 className="text-pretty text-neutral-900 dark:text-white lg:text-6xl lg:-tracking-4 lg:leading-[4rem] lg:font-extrabold text-4xl md:text-5xl -tracking-3 font-bold max-w-3xl text-center">
Build your portfolio <span className='text-emerald-500'>without the struggle</span>
Discover portfolios to inspire your creativity
</h1>
</div>
<p className="text-neutral-700 dark:text-neutral-300 mx-auto block text-balance max-w-sm text-center text-base md:max-w-3xl md:text-lg xl:text-xl">
Browse our curated collection of{' '}
{numberOfPortfolios && (
<span className="text-foreground font-semibold">
{numberOfPortfolios}+
</span>
)}{' '}
exceptional designs to help you create your best portfolio yet.
Browse our curated collection of exceptional designs to help you create
your best portfolio yet.
</p>
<div className="flex flex-col gap-2 max-w-md mx-auto w-full">
<ReCaptchaProvider
reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
>
<NewsletterForm />
</ReCaptchaProvider>

<p className="text-sm text-gray-400 mt-2">
Get monthly curated portfolios & career insights.
</p>
</div>
</>
);
}
176 changes: 176 additions & 0 deletions src/app/(unauthenticated)/portfolio/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
'use client';

import React from 'react';

import Image from 'next/image';
import Link from 'next/link';

import SocialIcon from '@/components/social-icon';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { api } from '@/convex/_generated/api';
import { Id } from '@/convex/_generated/dataModel';
import { useFavorites } from '@/hooks/use-favorites';
import { useSession } from '@/lib/client-auth';
import { getImageUrl } from '@/lib/get-image-url';
import { cn } from '@/lib/utils';
import { SignInButton } from '@clerk/nextjs';
import { useMutation, useQuery } from 'convex/react';
import { ChevronLeft, ExternalLink, Heart } from 'lucide-react';

export default function PortfolioPage({ params }: { params: { id: string } }) {
const { id } = params;
const portfolioId = id as Id<'portfolios'>;

const session = useSession();
const favorites = useFavorites();
const portfolio = useQuery(api.portfolios.getPortfolioFromId, {
portfolioId: portfolioId,
});
const addFavorite = useMutation(api.favorites.addFavorite);
const removeFavorite = useMutation(api.favorites.removeFavorite);
const incrementPortfolioFavoriteCount = useMutation(
api.portfolios.incrementPortfolioFavoriteCount,
);
const decrementPortfolioFavoriteCount = useMutation(
api.portfolios.decrementPortfolioFavoriteCount,
);

if (!portfolio) return null;

const imageUrl = getImageUrl(portfolio.image);
const isFavorited = favorites && favorites.has(portfolioId);

const handleFavoriteClick = async () => {
if (favorites) {
const favoriteId = favorites.get(portfolioId);
if (favoriteId) {
// Portfolio is favorited, so remove the favorite
await removeFavorite({ favoriteId: favoriteId });
await decrementPortfolioFavoriteCount({ portfolioId });
} else {
// Portfolio is not favorited, so add a favorite
await addFavorite({ portfolioId });
await incrementPortfolioFavoriteCount({ portfolioId });
}
}
};

return (
<main className="relative flex flex-col-reverse gap-4 lg:flex-row lg:gap-8 p-4">
{/* Center content - main scrollable area */}
<div className="w-full lg:w-2/3">
<div className="space-y-8">
<Image
src={imageUrl}
alt={portfolio.name}
width={1940}
height={1340}
priority
className="object-cover object-top w-full rounded-lg shadow-md"
/>
{/* Add more content here */}
</div>
</div>

{/* Right sidebar - fixed */}
<div className="lg:w-1/3 lg:sticky lg:top-0 lg:h-screen">
<div className="lg:sticky lg:top-10 lg:-mt-12 lg:h-[calc(100vh-3.5rem)] lg:py-12">
<div className="bg-secondary/50 p-4 rounded-lg">
<div className="flex flex-col gap-1">
{/* Name */}
<h2 className="text-2xl font-bold">{portfolio.name}</h2>
{/* Role */}
{portfolio.titles && !portfolio.titles.includes('') && (
<div className="flex flex-wrap gap-2">
{portfolio.titles.map((title, idx) => (
<Badge variant="secondary" key={idx}>
{title}
</Badge>
))}
</div>
)}
</div>

{portfolio.favoritesCount && (
<div className="text-muted-foreground text-sm mt-4">
{portfolio.favoritesCount ?? '0'} likes
</div>
)}

<div className="flex flex-row justify-between items-center mt-4">
{/* Socials */}
{portfolio.socials && portfolio.socials.length > 0 && (
<div className="flex items-center gap-2">
<div className="flex items-center gap-2">
{portfolio.socials && portfolio.socials.length > 0
? portfolio.socials.map((social, idx) => (
<Button
asChild
size="icon"
variant="ghost"
key={idx}
className="h-8 w-8"
>
<SocialIcon
url={social}
className="stroke-muted-foreground"
/>
</Button>
))
: null}
</div>
</div>
)}
{/* Like and Link */}
<div className="flex items-center justify-end gap-2">
<div className="flex items-center gap-2">
{session.isLoggedIn ? (
<Button
size="icon"
variant="ghost"
onClick={handleFavoriteClick}
className="flex items-center gap-2 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200 h-8 w-8"
>
<Heart
size={18}
className={cn(
'stroke-muted-foreground group-hover:stroke-emerald-500 duration-200',
isFavorited && 'fill-emerald-500 stroke-emerald-500',
)}
/>
</Button>
) : (
<button>
<SignInButton mode="modal">
<Heart
size={18}
className={cn(
'stroke-muted-foreground hover:stroke-emerald-500 duration-200',
isFavorited &&
'fill-emerald-500 stroke-emerald-500',
)}
/>
</SignInButton>
</button>
)}
</div>

<Button
size="icon"
variant="ghost"
className="hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200 h-8 w-8"
>
<ExternalLink
size={18}
className="text-emerald-600 dark:text-emerald-400"
/>
</Button>
</div>
</div>
</div>
</div>
</div>
</main>
);
}
2 changes: 1 addition & 1 deletion src/app/_components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function Header() {
},
)}
>
<nav className="w-full h-16 items-center flex sm:px-8 lg:px-16 mx-auto px-4">
<nav className="w-full h-16 items-center flex sm:px-8 lg:px-24 mx-auto px-4">
<div className="w-full items-center flex flex-row justify-between">
<div className="flex flex-row items-center gap-8">
<MainNav />
Expand Down
26 changes: 0 additions & 26 deletions src/components/animated-gradient-text.tsx

This file was deleted.

13 changes: 5 additions & 8 deletions src/components/main-content-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@ export default function MainContentSkeleton() {
<div className="flex flex-col gap-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
{Array.from({ length: 6 }).map((_, idx) => (
<Card
key={idx}
className="w-full border-none shadow-sm relative bg-transparent"
>
<Card key={idx} className="w-full relative">
<div>
<div className="overflow-hidden rounded-xl">
<Skeleton className="h-80 object-top" />
<div className="overflow-hidden border-b rounded-t-lg">
<Skeleton className="h-80 object-top rounded-none" />
</div>
</div>

<CardContent className="py-3 flex px-0">
<Skeleton className="h-6 w-36 rounded-xl" />
<CardContent className="py-3 flex px-4">
<Skeleton className="h-6 w-36 " />
</CardContent>
</Card>
))}
Expand Down
36 changes: 14 additions & 22 deletions src/components/main-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { useInView } from 'react-intersection-observer';

import MainContentSkeleton from './main-content-skeleton';
import PortfolioCard from './portfolio-card';
import PortfolioDetails from './portfolio-details';
import { Id } from '@/convex/_generated/dataModel';

export default function MainContent({
selectedSort,
Expand All @@ -22,7 +20,6 @@ export default function MainContent({
selectedFilter: string | null;
searchValue: string;
}) {
const searchParams = useSearchParams();
const [visibleCount, setVisibleCount] = useState(6);
const [loading, setLoading] = useState(false);
const [hasMoreData, setHasMoreData] = useState(true);
Expand Down Expand Up @@ -81,26 +78,21 @@ export default function MainContent({
}

return (
<>
<div className="flex flex-col gap-2 pb-4 md:pb-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
{filteredData
?.slice(0, visibleCount)
.map((item, idx) => <PortfolioCard key={idx} item={item} />)}
</div>
{filteredData && hasMoreData && (
<div className="flex justify-center pt-4">
<button
ref={scrollTrigger}
onClick={handleLoadMore}
disabled={loading}
></button>
</div>
)}
<div className="flex flex-col gap-2 pb-4 md:pb-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
{filteredData
?.slice(0, visibleCount)
.map((item, idx) => <PortfolioCard key={idx} item={item} />)}
</div>
{!!searchParams.get('id') && (
<PortfolioDetails portfolioId={searchParams.get('id') as Id<'portfolios'>} />
{filteredData && hasMoreData && (
<div className="flex justify-center pt-4">
<button
ref={scrollTrigger}
onClick={handleLoadMore}
disabled={loading}
></button>
</div>
)}
</>
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/max-width-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function MaxWidthWrapper({
return (
<div
className={cn(
'flex flex-col pt-4 pb-8 sm:px-8 lg:px-16 mx-auto px-4 w-full',
'flex flex-col pt-4 pb-8 sm:px-8 lg:px-24 mx-auto px-4 w-full',
className,
)}
>
Expand Down
Loading

0 comments on commit 85e7aeb

Please sign in to comment.