-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #229 from nyomansunima/project
🤖 chore: add list of project
- Loading branch information
Showing
12 changed files
with
380 additions
and
1 deletion.
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Metadata, ResolvingMetadata } from 'next' | ||
import { notFound } from 'next/navigation' | ||
import { workService } from '~/services/work-service' | ||
import { | ||
defaultOpenGraphMetadata, | ||
defaultTwitterMetadata, | ||
} from '~/lib/shared-metadata' | ||
|
||
type Props = { | ||
params: { | ||
slug: string | ||
} | ||
} | ||
|
||
export async function generateMetadata( | ||
{ params }: Props, | ||
parent: ResolvingMetadata, | ||
): Promise<Metadata> { | ||
const slug = params.slug | ||
const meta = await workService.getMetadata(slug) | ||
|
||
if (!slug || !meta) { | ||
return notFound() | ||
} | ||
|
||
return { | ||
title: `${meta.title} | Nyoman Sunima`, | ||
description: meta.desc, | ||
openGraph: { | ||
...defaultOpenGraphMetadata, | ||
title: `${meta.title} | Nyoman Sunima`, | ||
description: meta.desc, | ||
}, | ||
twitter: { | ||
...defaultTwitterMetadata, | ||
title: `${meta.title} | Nyoman Sunima`, | ||
description: meta.desc, | ||
}, | ||
} | ||
} | ||
|
||
export default function WorkDetailPage({ params }: Props) { | ||
return <main></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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import Image from 'next/image' | ||
import Link from 'next/link' | ||
import * as React from 'react' | ||
import { Work } from '~/types' | ||
import { mergeClass, parseReadableDate } from '~/utils/helpers' | ||
|
||
type ItemPillProps = { | ||
children: React.ReactNode | ||
className?: string | ||
} | ||
|
||
type Props = { | ||
work: Work | ||
} | ||
|
||
function ItemPill({ children, className }: ItemPillProps) { | ||
return ( | ||
<span | ||
className={mergeClass( | ||
'bg-background border border-border rounded-full px-3 py-2', | ||
className, | ||
)} | ||
> | ||
{children} | ||
</span> | ||
) | ||
} | ||
|
||
export function WorkListItemCard({ work }: Props) { | ||
const workURL = `/works/${work.slug}` | ||
const parsedDate = parseReadableDate(work.createdAt) | ||
const tag = work.tags[0] || '' | ||
|
||
const Comp = work.status === 'DONE' ? 'a' : 'div' | ||
|
||
return ( | ||
<Comp href={workURL} className="col-span-1 flex flex-col"> | ||
<picture className="relative w-full laptop:h-[560px] h-[330px] overflow-hidden rounded-2xl"> | ||
<Image src={work.thumbnail} alt="Hello" fill className="object-cover" /> | ||
|
||
{work.status === 'IN_PROGRESS' && ( | ||
<ItemPill className="absolute top-5 right-5">On Going</ItemPill> | ||
)} | ||
|
||
<div className="flex absolute bottom-5 inset-x-5 items-center flex-wrap gap-3"> | ||
<ItemPill>{tag}</ItemPill> | ||
<ItemPill>{parsedDate}</ItemPill> | ||
</div> | ||
</picture> | ||
|
||
<h3 className="text-2xl !leading-tight mt-6 line-clamp-2 laptop:w-10/12"> | ||
{work.title} | ||
</h3> | ||
</Comp> | ||
) | ||
} |
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,15 @@ | ||
export default function WorksIntroSection() { | ||
return ( | ||
<section className="container mx-auto px-5 laptop:px-20"> | ||
<h2 | ||
className="text-5xl laptop:text-8xl !leading-tight" | ||
data-cursor-size="200" | ||
data-cursor-exclusion | ||
data-animation="text-char-slide-up" | ||
> | ||
Design, Development & Apps crafting works. Built using heart and mind to | ||
solve problems. | ||
</h2> | ||
</section> | ||
) | ||
} |
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,95 @@ | ||
'use client' | ||
|
||
import { Button } from '~/app/components/ui/button' | ||
import { WorkListItemCard } from './work-list-item' | ||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
import { workService } from '~/services/work-service' | ||
import * as React from 'react' | ||
import { useRouter, useSearchParams } from 'next/navigation' | ||
|
||
const types = ['Project', 'Exploration', 'Playground'] | ||
|
||
function WorksFilter() { | ||
const router = useRouter() | ||
const searchParams = useSearchParams() | ||
const typeParam = searchParams.get('type') | ||
|
||
function filteringByType(type: string) { | ||
const newParams = new URLSearchParams(searchParams.toString()) | ||
if (newParams.get('type') === type) { | ||
newParams.delete('type') | ||
} else { | ||
newParams.set('type', type) | ||
} | ||
|
||
const newURL = `/works?${newParams.toString()}` | ||
|
||
router.push(newURL) | ||
} | ||
|
||
return ( | ||
<div className="flex flex-col mt-28"> | ||
<div className="flex items-center gap-3"> | ||
{types.map((type, i) => ( | ||
<Button | ||
size={'md'} | ||
variant={typeParam === type ? 'secondary' : 'outline'} | ||
key={i} | ||
className="trall duration-700 hover:scale-95" | ||
onClick={() => filteringByType(type)} | ||
> | ||
{type} | ||
</Button> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
function WorksList() { | ||
const searchParams = useSearchParams() | ||
const { data, isSuccess, isFetchingNextPage, fetchNextPage, hasNextPage } = | ||
useInfiniteQuery({ | ||
queryKey: ['works', 'list', { ...searchParams }], | ||
initialPageParam: null, | ||
queryFn: workService.getAllWorks, | ||
getNextPageParam: (lastPage, allPages) => lastPage.pageInfo.endCursor, | ||
}) | ||
|
||
return ( | ||
<div className="flex flex-col mt-28"> | ||
<div className="grid w-full laptop:grid-cols-2 grid-cols-1 gap-x-20 laptop:gap-y-24 gap-14"> | ||
{isSuccess && | ||
data.pages.map((group, i) => ( | ||
<React.Fragment key={i}> | ||
{group.works.map((work, i) => ( | ||
<WorkListItemCard work={work} key={i} /> | ||
))} | ||
</React.Fragment> | ||
))} | ||
</div> | ||
|
||
<div className="flex justify-center mt-32"> | ||
{hasNextPage && data && ( | ||
<Button | ||
disabled={isFetchingNextPage || !hasNextPage} | ||
onClick={() => fetchNextPage()} | ||
> | ||
{isFetchingNextPage ? <>Load More</> : <>Loading more</>} | ||
</Button> | ||
)} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default function WorksListSection() { | ||
return ( | ||
<section className="flex container mx-auto"> | ||
<div className="flex flex-col laptop:w-9/12 mx-auto px-5"> | ||
<WorksFilter /> | ||
<WorksList /> | ||
</div> | ||
</section> | ||
) | ||
} |
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,31 @@ | ||
import { Metadata } from 'next' | ||
import { | ||
defaultOpenGraphMetadata, | ||
defaultTwitterMetadata, | ||
} from '~/lib/shared-metadata' | ||
import WorksIntroSection from './components/works-intro-section' | ||
import WorksListSection from './components/works-list-section' | ||
|
||
export const metadata: Metadata = { | ||
title: 'Design, development & indie hacking works | Nyoman Sunima', | ||
description: 'My works and all of the playground', | ||
openGraph: { | ||
...defaultOpenGraphMetadata, | ||
title: 'Design, development & indie hacking works | Nyoman Sunima', | ||
description: 'My works and all of the playground', | ||
}, | ||
twitter: { | ||
...defaultTwitterMetadata, | ||
title: 'Design, development & indie hacking works | Nyoman Sunima', | ||
description: 'My works and all of the playground', | ||
}, | ||
} | ||
|
||
export default function WorksPage() { | ||
return ( | ||
<main className="flex flex-col laptop:py-28 py-20"> | ||
<WorksIntroSection /> | ||
<WorksListSection /> | ||
</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 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,88 @@ | ||
import { hygraphConnection } from '~/config/hygraph' | ||
import { PaginatedWork, Work, WorkMeta } from '~/types' | ||
|
||
class WorkService { | ||
async getAllWorks({ pageParam, queryKey }): Promise<PaginatedWork> { | ||
const searchParams = queryKey[2] as URLSearchParams | ||
let types = ['Project', 'Playground', 'Exploration'] | ||
const typeParam = searchParams.get('type') | ||
if (typeParam) { | ||
types = [typeParam] | ||
} | ||
|
||
const query = ` | ||
query WorksQuery($limit: Int!, $nextCursor: String, $types: [WorkType]) { | ||
worksConnection(first: $limit, after: $nextCursor, where: {type_in: $types}) { | ||
edges { | ||
node { | ||
slug | ||
tags | ||
title | ||
type | ||
workStatus | ||
createdAt | ||
thumbnail { | ||
url | ||
} | ||
} | ||
} | ||
pageInfo { | ||
hasNextPage | ||
pageSize | ||
endCursor | ||
} | ||
} | ||
} | ||
` | ||
|
||
const res = await hygraphConnection.request<any>(query, { | ||
limit: 6, | ||
nextCursor: pageParam, | ||
types, | ||
}) | ||
|
||
const payload = { | ||
works: res.worksConnection.edges | ||
.map((edge) => edge.node) | ||
.map( | ||
(node) => | ||
({ | ||
...node, | ||
status: node.workStatus, | ||
thumbnail: node.thumbnail.url, | ||
} as Work), | ||
), | ||
pageInfo: res.worksConnection.pageInfo, | ||
} | ||
|
||
return payload | ||
} | ||
|
||
async getMetadata(slug: string): Promise<WorkMeta> { | ||
const query = ` | ||
query WorkMetadata($slug: String!) { | ||
work(where: {slug: $slug}) { | ||
title | ||
description | ||
thumbnail { | ||
url | ||
} | ||
} | ||
} | ||
` | ||
|
||
const res = await hygraphConnection.request<any>(query, { | ||
slug, | ||
}) | ||
|
||
const payload: WorkMeta = { | ||
title: res.work.title, | ||
desc: res.work.description, | ||
image: res.work.thumbnail.url, | ||
} | ||
|
||
return payload | ||
} | ||
} | ||
|
||
export const workService = new WorkService() |
Oops, something went wrong.