-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: alchemy enhanced api integration
- Loading branch information
Showing
19 changed files
with
632 additions
and
14 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 |
---|---|---|
@@ -0,0 +1 @@ | ||
* @dphilipson @avasisht23 @denniswon |
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,34 @@ | ||
import { Image } from '@/components/images/individual'; | ||
import { ImageRecord, TagRecord, getXataClient } from '@/utils/xata'; | ||
import { JSONData } from '@xata.io/client'; | ||
import { compact } from 'lodash'; | ||
import { notFound } from 'next/navigation'; | ||
|
||
const xata = getXataClient(); | ||
|
||
const getImage = async (id: string) => { | ||
const image = (await xata.db.image.read(id)) as ImageRecord; | ||
if (!image?.image) { | ||
return undefined; | ||
} | ||
return image.toSerializable(); | ||
}; | ||
|
||
export default async function Page({ params: { id } }: { params: { id: string } }) { | ||
const image = await getImage(id); | ||
if (!image) { | ||
notFound(); | ||
} | ||
const tagsFromImage = await xata.db['tag-to-image'] | ||
.filter({ | ||
'image.id': id | ||
}) | ||
.select(['*', 'tag.*']) | ||
.getMany(); | ||
|
||
const tags = compact(tagsFromImage.map((tag) => tag.tag?.toSerializable())) as JSONData<TagRecord>[]; | ||
|
||
const readOnly = process.env.READ_ONLY === 'true'; | ||
|
||
return <Image image={image} tags={tags} readOnly={readOnly} />; | ||
} |
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,77 @@ | ||
import { Images, TagWithImageCount } from '@/components/images'; | ||
import { useAccountContext } from '@/context/account'; | ||
import useNftsForOwner from '@/hooks/assets.ts/useNftsForOwner'; | ||
import { IMAGE_SIZE } from '@/utils/constants'; | ||
import { compact, pick } from 'lodash'; | ||
|
||
export default async function Page({ searchParams }: { searchParams: { page: string } }) { | ||
const pageNumber = parseInt(searchParams.page) || 1; | ||
|
||
const { client } = useAccountContext(); | ||
|
||
// TODO replace with react-query infinite query for pagination | ||
|
||
const { items, count } = useNftsForOwner({ address: client.account }); | ||
|
||
// This page object is needed for building the buttons in the pagination component | ||
const page = { | ||
pageNumber, | ||
hasNextPage: imagesPage.hasNextPage(), | ||
hasPreviousPage: pageNumber > 1, | ||
totalNumberOfPages | ||
}; | ||
|
||
// transform helper to create a thumbnail for each image and apply it to the image object | ||
console.time('Fetching images transforms'); | ||
const images = compact( | ||
await Promise.all( | ||
imagesPage.records.map(async (record) => { | ||
if (!record.image) { | ||
return undefined; | ||
} | ||
|
||
const { url } = record.image.transform({ | ||
width: IMAGE_SIZE, | ||
height: IMAGE_SIZE, | ||
format: 'auto', | ||
fit: 'cover', | ||
gravity: 'top' | ||
}); | ||
|
||
// Since the resulting image will be a square, we don't really need to fetch the metadata in this case. | ||
// The meta data provides both the original and transformed dimensions of the image. | ||
// The metadataUrl you get from the transform() call. | ||
// const metadata = await fetchMetadata(metadataUrl); | ||
|
||
if (!url) { | ||
return undefined; | ||
} | ||
|
||
const thumb = { | ||
url, | ||
attributes: { | ||
width: IMAGE_SIZE, // Post transform width | ||
height: IMAGE_SIZE // Post transform height | ||
} | ||
}; | ||
|
||
return { ...record.toSerializable(), thumb }; | ||
}) | ||
) | ||
); | ||
console.timeEnd('Fetching images transforms'); | ||
|
||
// Find the top 10 tags | ||
const tags = topTags.summaries.map((tagSummary) => { | ||
const tag = tagSummary.tag; | ||
const serializableTag = pick(tag, ['id', 'name', 'slug']); | ||
return { | ||
...serializableTag, | ||
imageCount: tagSummary.imageCount | ||
}; | ||
}) as TagWithImageCount[]; | ||
|
||
const readOnly = process.env.READ_ONLY === 'true'; | ||
|
||
return <Images images={images} tags={tags} page={page} readOnly={readOnly} />; | ||
} |
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
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,144 @@ | ||
'use client'; | ||
|
||
import { ImageRecord, TagRecord } from '@/utils/xata'; | ||
import { Link } from '@chakra-ui/next-js'; | ||
import { Box, Flex, Heading, Select, SimpleGrid, Tag, Text } from '@chakra-ui/react'; | ||
import { JSONData } from '@xata.io/client'; | ||
import { range } from 'lodash'; | ||
import Image from 'next/image'; | ||
import NextLink from 'next/link'; | ||
import { useRouter } from 'next/navigation'; | ||
import { FC } from 'react'; | ||
import { Search } from '../search'; | ||
import { ImageUpload } from './upload'; | ||
|
||
// cast it back to the original type as JSON Data from Serializable | ||
export type ImageRecordWithThumb = JSONData<ImageRecord> & { | ||
thumb: { | ||
url: string; | ||
attributes: { | ||
width: number; | ||
height: number; | ||
}; | ||
}; | ||
}; | ||
|
||
// A similar strategy is used for tags | ||
export type TagWithImageCount = JSONData<TagRecord> & { | ||
imageCount: number; | ||
}; | ||
|
||
export type Page = { | ||
pageNumber: number; | ||
hasNextPage: boolean; | ||
hasPreviousPage: boolean; | ||
totalNumberOfPages: number; | ||
}; | ||
|
||
type ImagesProps = { | ||
images: ImageRecordWithThumb[]; | ||
tags: TagWithImageCount[]; | ||
page: Page; | ||
readOnly: boolean; | ||
}; | ||
|
||
export const Images: FC<ImagesProps> = ({ images, tags, page, readOnly }) => { | ||
const currentPage = page.pageNumber; | ||
const router = useRouter(); | ||
|
||
// render the tags in different ways depending on how many there are | ||
const renderTags = (tags: TagWithImageCount[]) => { | ||
if (tags.length === 0) { | ||
return null; | ||
} | ||
|
||
if (tags.length > 1) { | ||
return ( | ||
<> | ||
<Heading as="h1" size="md" mb={8}> | ||
All images | ||
</Heading> | ||
{tags && ( | ||
<Flex mb={8} gap={2} wrap="wrap"> | ||
{tags.map((tag) => ( | ||
<Tag as={NextLink} key={tag.id} href={`/tags/${tag.id}`} gap={2}> | ||
{tag.name} | ||
<Flex | ||
alignItems="center" | ||
justifyContent="center" | ||
fontSize="xs" | ||
bg="contrastLowest" | ||
boxSize={4} | ||
borderRadius="md" | ||
color="contrastMedium" | ||
> | ||
{tag.imageCount} | ||
</Flex> | ||
</Tag> | ||
))} | ||
</Flex> | ||
)} | ||
</> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<Heading as="h1" size="md" mb={8}> | ||
{tags[0].imageCount} images tagged with <Tag>{tags[0].name}</Tag> | ||
</Heading> | ||
<Flex mb={8} gap={2} wrap="wrap"> | ||
<Link href="/">« Back to all images</Link> | ||
</Flex> | ||
</> | ||
); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Flex alignItems="start" justifyContent="space-between" mb={8}> | ||
<ImageUpload readOnly={readOnly} /> | ||
<Search /> | ||
</Flex> | ||
{renderTags(tags)} | ||
{images.length === 0 && <Text>No images yet added</Text>} | ||
<SimpleGrid columns={{ base: 2, md: 3, lg: 4 }} spacing={4}> | ||
{/* thumbnails generated on server side based for images */} | ||
{images.map(({ id, name, thumb }) => { | ||
return ( | ||
<Box borderRadius="md" overflow="hidden" key={id}> | ||
<NextLink href={`/images/${id}`}> | ||
<Image | ||
src={thumb.url} | ||
width={thumb.attributes.width} | ||
height={thumb.attributes.height} | ||
alt={name ?? 'unknown image'} | ||
/> | ||
</NextLink> | ||
</Box> | ||
); | ||
})} | ||
</SimpleGrid> | ||
{/* | ||
with server side created page object that contains information about the current page, | ||
find the current page from the router query. | ||
*/} | ||
{page.totalNumberOfPages > 1 && ( | ||
<Flex justifyContent="center" mt={8}> | ||
<Flex gap={4} alignItems="center"> | ||
{page.hasPreviousPage && <Link href={`?page=${currentPage - 1}`}>Previous</Link>} | ||
<Select onChange={(event) => router.push(`?page=${event.target.value}`)} value={currentPage}> | ||
{range(1, page.totalNumberOfPages + 1).map((pageNumber) => ( | ||
<option key={pageNumber} value={pageNumber}> | ||
{pageNumber} | ||
</option> | ||
))} | ||
</Select> | ||
|
||
{page.hasNextPage && <Link href={`?page=${currentPage + 1}`}>Next</Link>} | ||
</Flex> | ||
</Flex> | ||
)} | ||
</> | ||
); | ||
}; |
Oops, something went wrong.