-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
245 additions
and
359 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.