Good Eats Across The Globe!
Good Global Eats is a full stack restaurant-sharing app inspired by Airbnb's split map UI design. Find your next good eats anywhere in the world or put other users on to your own favorite spots.
- Next.js
- TypeScript
- GraphQL
- PostgreSQL
- Tailwind CSS
- Cloudinary
- Firebase Auth
- Mapbox
- Google Places
- Heroku
- Prisma for data modeling, migrations, and data access to the PostgreSQL database.
- Apollo Client for GraphQL querires and mutations.
- Apollo Server for establishing self-documenting GraphQL server.
- geolib for calculating coordinates bounds to find nearby restaurants.
- React Hook Form for upload post form validations.
- react-map-gl for Mapbox markers and pop-ups.
- USER AUTH
- Login, Create Account
- or explore as guest / demo-user
- MAP
- Search for an area, a city, or country
- Open a preview of the restaurants
- Zoom in/out or move the map to show posted restaurants within the visible map
- RESTAURANT POSTS
- Search for restaurants by name or address
- Upload recommended menu items with an image
- Delete or edit your own posts
When Apollo is performing GraphQL queries while a user is interacting with the map (zooming in/out, dragging), it returns undefined
before it returns the desired data. While Apollo is in the loading state, it doesn't give me access to the previous data, even if the new data being returned is the same as before the loading state.
To prevent this, I implemented a custom hook that takes in data from Apollo and returns when it's not undefined
or null
.
// src/utils/useLastData.ts
export function useLastData<S>(data: S) {
const ref = useRef(data);
if (data !== null && data !== undefined) {
ref.current = data;
}
return ref.current;
}
useRef
instead of useState
to prevent unnecessary rerenders.
// pages/index.tsx
export default function Spot() {
const [dataBounds, setDataBounds] = useLocalState<string>(
"bounds",
"[[0,0],[0,0]]"
);
// reduce the amount of queries called to the apollo server when zooming in/out of map
const [debouncedDataBounds] = useDebounce(dataBounds, 200);
const { data, error } = useQuery<SpotsQuery, SpotsQueryVariables>(
SPOTS_QUERY,
{
variables: { bounds: parseBounds(debouncedDataBounds) },
}
);
// custom hook to prevent data flickering (undefined) when querying SPOTS_QUERY
const lastData = useLastData(data);
return (
<>
....
<Map
setDataBounds={setDataBounds}
spots={lastData ? lastData.spots : []}
....
/>
</>
)
}
Visiting protected routes such as edit/putOn pages re-routes unauthorized users to the login/signup page on the server side before the requested page is served to the front-end.
getServerSideProps
method provided by Next.js is used to check if the user is authenticated on the server side to re-route them accordingly.
// pages/spots/putOn.tsx
export default function PutOn() {
return <Layout main={<SpotForm />} />;
}
// intercept putOn route access
// redirect to "/auth" if not logged in
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const uid = await loadIdToken(req as NextApiRequest);
if (!uid) {
res.setHeader("location", "/auth");
res.statusCode = 302;
res.end();
}
return { props: {} };
};
Unauthorized users are sent to /auth
page to login/signup with the status code of 302 (redirect
)
- Map clusters for when markers overlap each other
- Like/Dislike for posts
- User page with their posts
- Ryan Naing - Portfolio