Table of Contents
- It is a social networking platform for middle-aged adults to help them make neighborhood Friends easily. This app will track the user's location using
KakaoMap API
and will show postings from nearby users or chat with them. It was a part of Web Development challenges from NUMBLE, a side-project platform for developers in Korea, and my team won 2nd prize in this challenge! π
- Service Target Group: the middle aged like parents generation. (40~60)
- Production Period : 2022.10 - 2022.12
- Team Members
Hyejoo Kang Designer |
Joy Lee Frontend Developer |
Jeongjun Jo Frontend Developer |
Juhyeong Ahn Backend Developer |
Sangjin Park Backend Developer |
You are able to start the app by typing the following commands in the command line:
git clone https://github.com/devjoylee/souso.git
npm install
npm start
- Implemented sending verification code by connecting backend API using
React-Query
. - Organized endpoints and queries for Join Page in a separated API folder.
- Created a reusable
Input
component anduseForm
hook. - Code Preview
// api/queries/join.js
import { api } from 'api';
import { JOIN, CHECK_NICKNAME, SEND_CODE, VERIFY_CODE } from 'api/endpoints';
export const join = {
submit: async req => {
const res = await api.post(JOIN, req);
return res.data;
},
nickname: async req => {...},
sendCode: async req => {...},
verifyCode: async req => {...}
};
// components/Join/InputVerified
import React, { useState, useEffect } from 'react';
import { Input } from 'components/Join';
import { useMutation } from 'react-query';
import { join } from 'api/queries/join';
export const InputVerified = ({
values,
onChange,
errors,
isVerified,
setIsVerified
}) => {
const [isSent, setIsSent] = useState(false);
// π response from the verification code API
const { mutate: sendCodeMutate } = useMutation(join.sendCode, {
onSuccess: () => {
errors.phone_number = '';
toast.success('Sent successfully');
setIsSent(true);
setWaiting(true);
},
onError: error => {
if (error.response.data.message === 'Already Auth Code Exist') {
errors.phone_number = 'You can send a code after 3mins.';
} else if (error.response.data.message === 'Already Phone Number Exist') {
errors.phone_number = 'The phone number already exists.';
}
}
});
// π request verification when button is clicked
const sendCode = async e => {
e.preventDefault();
const errorMessage = await validate(values);
errors.phone_number = errorMessage.phone_number || '';
if (!errors.phone_number) {
sendCodeMutate({ phone_number: values.phone_number });
}
setRender(values);
};
useEffect(() => {
setIsSent(false);
}, [setIsSent]);
return (
<Input
name="phone_number"
placeholder="phone number"
onChange={onChange}
values={values}
errors={errors}
>
<button onClick={sendCode}>{isSent ? 'Resend' : 'Send'}</button>
</Input>
// ...
);
};
- When signing up, this API will help you to get the neighborhood name for the user's location.
πΒ Read more about KakaoMap API - Adopted
Recoil
state storage to manage the neighborhood name globally. - Utilized
Geolocation
to get the current latitude and longitude of the user. - Code Preview
// components/TownAuth/KakaoMap
import React, { useEffect, useRef, useState } from 'react';
import { Map, MapMarker } from 'react-kakao-maps-sdk';
import { useSetRecoilState } from 'recoil';
import * as S from './styles';
export const KakaoMap = ({ openModal }) => {
const [currentGeo, setCurrentGeo] = useState({ lat: 0, lng: 0 });
const [pickedGeo, setPickedGeo] = useState(currentGeo);
const [address, setAddress] = useState([]);
const setSaveAddress = useSetRecoilState(addressState);
const { kakao } = window; // π call KakaoAPI from <head>
const mapRef = useRef();
// π get latitude and longitude of picked location on the map
const getPickedGeo = (_, mouseEvent) => {
setPickedGeo({
lat: mouseEvent.latLng.getLat(),
lng: mouseEvent.latLng.getLng()
});
};
// π move the pin to the current location on the map.
const moveToCurrent = () => {
const center = new kakao.maps.LatLng(currentGeo.lat, currentGeo.lng);
if (mapRef.current) {
mapRef.current.panTo(center);
setPickedGeo(currentGeo);
}
};
useEffect(() => {
// π get the current location using GeoLocation
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(({ coords }) => {
setCurrentGeo({
lat: coords.latitude,
lng: coords.longitude
});
setPickedGeo({
lat: coords.latitude,
lng: coords.longitude
});
});
}
}, []);
useEffect(() => {
const geocoder = new kakao.maps.services.Geocoder();
// π request the detailed address of coordinates
const getAddressFromGeo = (coords, callback) => {
geocoder.coord2Address(coords.lng, coords.lat, callback);
};
// π return the address when the specific point is clicked on the map.
getAddressFromGeo(pickedGeo, function (result, status) {
if (status === kakao.maps.services.Status.OK) {
setAddress([
result[0].address.region_1depth_name,
result[0].address.region_2depth_name,
result[0].address.region_3depth_name
]);
}
});
}, [kakao, pickedGeo, setAddress]);
useEffect(() => {
setSaveAddress(address); // save the address
}, [setSaveAddress, address]);
return (
<S.MapContainer>
<Map
center={currentGeo}
onClick={getPickedGeo}
className="kakao_map"
ref={mapRef}
>
<MapMarker position={pickedGeo} />
</Map>
<SearchSection openModal={openModal} moveToCurrent={moveToCurrent} />
</S.MapContainer>
);
};
- Implemented the infinite scroll when the feed list is loaded
- If the scroll is down to the
FetchObserver
, it will send request to fetch the next page. - Code Preview
// pages/FeedPage
import { useInfiniteQuery } from 'react-query';
import { feed } from 'api/queries/feed';
export const FeedPage = () => {
const params = pageParam => {
return {
cursorId: isLatest ? pageParam : 0,
pageId: !isLatest ? pageParam : 0,
sortType: isLatest ? 'LATEST' : 'POPULAR'
};
};
// request fetching more feed data if the next page exists.
const infiniteResponse = useInfiniteQuery(
['feed'],
({ pageParam = 0 }) => feed.list(params(pageParam)),
{
getNextPageParam: lastPage =>
isLatest
? lastPage.feed_list.length > 0 &&
lastPage.feed_list.slice(-1)[0].feed_id
: lastPage.page_id + 1
}
);
return (
<PostList
active={active}
setActive={setActive}
infiniteResponse={infiniteResponse}
/>
);
};
// components/Feed/PostList
export const PostList = ({ infiniteResponse, active, setActive }) => {
const { data, isLoading, isFetching, fetchNextPage, refetch } =
infiniteResponse;
const { pathname } = useLocation();
const isEmpty =
!isLoading &&
('feed_list' in data.pages[0]
? !data.pages[0].feed_list.length
: !data.pages[0].category_feed_list.length);
if (isEmpty) return <EmptyList message="Empty Feed" />;
return (
<S.PostListContainer>
{active === 'popluar' &&
(isTabLoading ? (
<SkeletonThRight />
) : (
<S.PostLists>
{data.pages.map(page =>
(page.feed_list || page.category_feed_list).map(post => (
<ThumbRight key={post.feed_id} postData={post} />
))
)}
</S.PostLists>
))}
{/* Latest feed or categorized feed */}
{active !== 'popluar' &&
(isTabLoading ? (
<SkeletonThBottom />
) : (
<S.PostLists>
{data.pages.map(page =>
(page.feed_list || page.category_feed_list).map(post => (
<ThumbBottom
key={post.feed_id}
postData={post}
refetch={refetch}
/>
))
)}
</S.PostLists>
))}
{/* If the scrollbar scroll down to this, fetch next page */}
<FetchObserver
data={data}
fetchNextPage={fetchNextPage}
isFetching={isFetching}
/>
</S.PostListContainer>
);
};
- Created a skeleton component as placeholders when texts and images are loading.
- Code Preview
// components/Common/Skeleton
import React from 'react';
import { Skeleton } from './Skeleton';
import styled from 'styled-components';
export const SkeletonCategory = () => {
return (
<Container>
{[...Array(8)].map((_, i) => (
<div key={i}>
<Skeleton type="circle" size={40} />
<Skeleton type="text" width={40} />
</div>
))}
</Container>
);
};
export const Skeleton = ({ type, height, width, size, line }) => {
return [...Array(line)].map((_, i) => (
<SkeletonItem
key={i}
className={type}
height={height}
width={width}
size={size}
>
<HighLight />
</SkeletonItem>
));
};
The commit message is written with the GITMOJI icons in order to make commit messages more intuitive.
Gitmoji | Meaning |
---|---|
π | Init or begin a project. |
π | Move or rename resources |
β¨ | Introduce new features |
π | Add the UI and style files |
β»οΈ | Refactor code |
π | Add or update documentation |
β | Add a dependency |
π | Fix a bug |
π | Deploy stuff |
REFERENCE : Gitmoji (http://gitmoji.dev/)