Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pages/gallery): fixed gallery page, removed arbitrary pagination #119

Merged
merged 2 commits into from
Feb 18, 2024
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
169 changes: 59 additions & 110 deletions src/app/(pages)/gallery/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
'use client';

import {useEffect, useState, useRef} from 'react';
import {
Skeleton,
Container,
Expand All @@ -11,89 +9,27 @@ import {
SimpleGrid,
Image,
Flex,
Spinner,
Heading,
Icon,
} from '@chakra-ui/react';
import axios from 'axios';
import {FiAlertCircle} from 'react-icons/fi';
import {useQuery} from '@tanstack/react-query';

const Gallery = () => {
const [posts, setPosts] = useState<
Array<{
id: string;
permalink: string;
media_url: string;
caption: string;
}>
>([]);
const [after, setAfter] = useState('unset');
const [loading, setLoading] = useState(false);
const [allLoaded, setAllLoaded] = useState(false);
const loader = useRef(null);

const skeletons = new Array(9)
.fill(null)
.map((_, index) => (
<Skeleton key={index} rounded="lg" height="300px" width="300px" />
));

const fetchPosts = async (afterParam: string) => {
if (loading) return;
if (after === undefined) return;
setLoading(true);
try {
const res = await axios.post(
'/api/instagram/posts',
{after: afterParam},
{
headers: {
'Content-Type': 'application/json',
},
}
);

if (!allLoaded) {
setPosts(prevPosts => [...prevPosts, ...res.data.data]);
setAfter(res.data.paging?.cursors?.after);
}
} catch (error) {
console.error('Error fetching Instagram posts:', error);
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchPosts(after);
}, []);

useEffect(() => {
if (after === undefined) {
setAllLoaded(true);
}

const options = {
root: null,
rootMargin: '0px',
threshold: 0.1,
};

const observer = new IntersectionObserver(entries => {
const [entry] = entries;
if (entry.isIntersecting && !allLoaded && !loading) {
fetchPosts(after);
}
}, options);

if (loader.current) {
observer.observe(loader.current);
}
const getPosts = async () => {
const posts = await axios.get(
`${process.env.NEXT_PUBLIC_HOSTNAME}/api/instagram/posts`
);
return posts.data;
};

return () => {
if (loader.current) {
observer.unobserve(loader.current);
}
};
}, [after, allLoaded, loading]);
const Gallery = () => {
const {isPending, error, data} = useQuery<
Array<{media_url: string; permalink: string; caption: string}>
>({
queryKey: ['instagram-posts'],
queryFn: getPosts,
});

return (
<Container maxW="container.xl">
Expand All @@ -109,39 +45,52 @@ const Gallery = () => {
</Center>
</Box>
<Flex w="100%" justifyContent="center" my="12">
<SimpleGrid columns={3} gap="6" ref={loader}>
{posts.length === 0
? skeletons
: posts.map((post, index) => (
<Box
key={`post-${index}`}
w="72"
h="72"
overflow="hidden"
borderRadius="8"
>
<a
href={post.permalink}
target="_blank"
rel="noopener noreferrer"
>
<Image
src={post.media_url}
alt={post.caption}
objectFit="cover"
width="100%"
height="100%"
/>
</a>
</Box>
<SimpleGrid columns={3} gap="6">
{error && (
<Flex
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
gap="2"
>
<Icon as={FiAlertCircle} color="red.400" fontSize="28" />
<Text maxW="64">An unexpected error has occurred</Text>
</Flex>
)}
{isPending &&
new Array(9)
.fill(null)
.map((_, index) => (
<Skeleton key={index} borderRadius="8" h="72" w="72" />
))}

{data &&
data.map((post, index) => (
<Box
key={`post-${index}`}
w="72"
h="72"
overflow="hidden"
borderRadius="8"
>
<a
href={post.permalink}
target="_blank"
rel="noopener noreferrer"
>
<Image
src={post.media_url}
alt={post.caption}
objectFit="cover"
width="100%"
height="100%"
/>
</a>
</Box>
))}
</SimpleGrid>
</Flex>
{loading && (
<Center>
<Spinner color=":rand.500" />
</Center>
)}
</Container>
);
};
Expand Down
34 changes: 13 additions & 21 deletions src/app/api/instagram/posts/route.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
import axios from 'axios';
import {logger, Cache} from '@lib';
import {ServerResponse} from '@helpers';
import {NextRequest} from 'next/server';

export const POST = async (request: NextRequest) => {
const {after} = await request.json();

const fetchPosts = async () => {
const url = 'https://graph.instagram.com/v18.0/17841417789493733/media';
const params = {
fields: 'id,caption,media_type,media_url,permalink',
access_token: process.env.NEXT_INSTAGRAM_TOKEN,
limit: 9,
after: after === 'unset' ? null : after,
};
const fetchPosts = async () => {
const url = 'https://graph.instagram.com/v18.0/17841417789493733/media';
const params = {
fields: 'caption,media_url,permalink',
access_token: process.env.NEXT_INSTAGRAM_TOKEN,
limit: Number.MAX_SAFE_INTEGER,
};

const response = await axios.get(url, {params});
const response = await axios.get(url, {params});

return response.data;
};
return response.data;
};

export const GET = async () => {
try {
if (!after) {
return ServerResponse.success({});
}

const cachedPosts = await Cache.fetch(
`instagram-posts;after=${after}`,
'instagram-posts',
fetchPosts,
60 * 60
);

return ServerResponse.success(cachedPosts);
return ServerResponse.success(cachedPosts.data);
} catch (error) {
logger.error('Error fetching data from Instagram:', error);
return ServerResponse.serverError('Failed to fetch data from Instagram');
Expand Down
2 changes: 1 addition & 1 deletion src/lib/redis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Redis from 'ioredis';

export const redis = new Redis(`${process.env.REDIS_URL}`);
export const redis = new Redis(`${process.env.NEXT_REDIS_URL}`);

export class Cache {
static async fetch<T>(key: string, fetcher: () => T, expires: number) {
Expand Down
Loading