Skip to content

QuestRoutine/Questine-Frontend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿš€ Questine (ํ€˜์Šคํ‹ด)

Image

Image

๋ชฉ์ฐจ


๐ŸŒŸ ๊ฐœ์š”

Questine์€ ๋ ˆ๋ฒŒ์—…๊ณผ ๊ฐ™์€ RPG ์š”์†Œ๊ฐ€ ์‚ด์ง ๊ฐ€๋ฏธ๋ผ ์žˆ๋Š” ํ•  ์ผ ๊ด€๋ฆฌ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋งค์ผ ํ•  ์ผ์„ ๊ธฐ๋กํ•˜๊ณ , ํ•  ์ผ ๋‹ฌ์„ฑ ์‹œ, ์บ๋ฆญํ„ฐ๊ฐ€ ์„ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ์žฌ๋ฏธ์™€ ๋™๊ธฐ๋ถ€์—ฌ๋ฅผ ๋™์‹œ์— ์ œ๊ณตํ•˜๋Š” ์ƒˆ๋กœ์šด ๊ฒฝํ—˜์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ ๊ธฐ์ˆ  ์Šคํƒ

Expo React Native TypeScript React Query Axios React Hook Form

ERD

Image

๐Ÿงฉ ์„œ๋น„์Šค ์ฃผ์š” ๊ธฐ๋Šฅ

ํ•  ์ผ ๋“ฑ๋ก/์‚ญ์ œ

ํ•  ์ผ ์ถ”๊ฐ€ ๋ฐ ์‚ญ์ œ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์™„๋ฃŒํ•œ ํ•  ์ผ ๊ฐœ์ˆ˜์— ๋”ฐ๋ผ ๊ทธ๋ผ๋ฐ์ด์…˜ ์ƒ‰์ƒ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

์—…์ 

๋‹ค์–‘ํ•œ ์—…์ ์„ ํš๋“ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์บ๋ฆญํ„ฐ

๋ ˆ๋ฒจ - ์™„๋ฃŒํ•œ ํ•  ์ผ ๊ฐœ์ˆ˜์— ๋”ฐ๋ผ ๊ฒฝํ—˜์น˜๋ฅผ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฏธ์ง€ - ๋ ˆ๋ฒจ์— ๋”ฐ๋ผ ๋‹ค์–‘ํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋žญํ‚น

๋žญํ‚น - ์ „์ฒด ์ด์šฉ์ž์˜ ๋žญํ‚น์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ”ง ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

โš ๏ธย ๋ฌธ์ œ ํ•  ์ผ ์ถ”๊ฐ€ ๋ฐ ์‚ญ์ œ ์‹œ ์ธํ’‹ ๋”œ๋ ˆ์ด ๋ฐœ์ƒ
๐Ÿšง ์›์ธ ์„œ๋ฒ„ ํ†ต์‹  ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์œผ๋กœ ์ธํ•œ ์‚ฌ์šฉ์ž ์ฒด๊ฐ ์„ฑ๋Šฅ ์ €ํ•˜
๐Ÿ’กย ํ•ด๊ฒฐ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ธํ’‹ ๋”œ๋ ˆ์ด ๊ฐœ์„  (React-query์˜ onMutate ํ™œ์šฉ)
// ํ•  ์ผ ์™„๋ฃŒ/๋ฏธ์™„๋ฃŒ ํ† ๊ธ€
export function useToggleTodoComplete(year?: number, month?: number) {
  const queryYear = year ?? now.year();
  const queryMonth = month ?? now.month() + 1;
  const queryKey = getTodosQueryKey(queryYear, queryMonth);
  const storageKey = getTodosStorageKey(queryYear, queryMonth);

  return useMutation({

  ...

    onMutate: async ({ todo_id, completed }) => {
    // ํ•  ์ผ ๋ชฉ๋ก๊ณผ ๊ด€๋ จ๋œ ์š”์ฒญ์ด ์žˆ๋‹ค๋ฉด ์ทจ์†Œ (์„œ๋ฒ„์™€์˜ ๋™๊ธฐํ™” ์ถฉ๋Œ ๋ฐฉ์ง€ ๋ชฉ์ )
      await queryClient.cancelQueries({ queryKey });

    // ํ˜„์žฌ ์ฟผ๋ฆฌ ์บ์‹œ์— ์ €์žฅ๋œ ํ•  ์ผ ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ด (๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค๋ฉด ๋นˆ ๋ฐฐ์—ด ๋ฐ˜ํ™˜)
      const previousTodos = queryClient.getQueryData<Todo[]>(queryKey) ?? [];

	  // ํ•  ์ผ ๋ชฉ๋ก์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ํ† ๊ธ€ํ•œ ๊ฐ’ ์—…๋ฐ์ดํŠธ
      const newTodos = previousTodos.map((todo) => (todo.todo_id === todo_id ?
      { ...todo, completed } : todo));

    // ๋ณ€๊ฒฝ๋œ ํ•  ์ผ ๋ชฉ๋ก ์—…๋ฐ์ดํŠธ (๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ)
      queryClient.setQueryData<Todo[]>(queryKey, newTodos);
      await saveTodosStorage(storageKey, newTodos);


    // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ, ์ด์ „ ํ•  ์ผ ๋ชฉ๋ก ๋ฐ˜ํ™˜ (๋กค๋ฐฑ -> onError์—์„œ ์ด ๋ฐ˜ํ™˜๊ฐ’ ์ด์šฉ)
      return { previousTodos };
    },

	...

  });
}



โš ๏ธย ๋ฌธ์ œ ํ•  ์ผ ์ถ”๊ฐ€ ์‹œ ํƒ€์ž„์กด ์ด์Šˆ ๋ฐœ์ƒ ์˜ˆ) 6์›” 1์ผ์— ๋“ฑ๋กํ•˜๋ฉด 5์›” 30์ผ์— ์ถ”๊ฐ€ ๋จ
๐Ÿšง ์›์ธ new Date๋Š” ๋กœ์ปฌ ์‹œ๊ฐ„๋Œ€ ๊ธฐ์ค€์œผ๋กœ ์ƒ์„ฑํ–ˆ๋Š”๋ฐ ๋ฐฑ์—”๋“œ์—์„œ๋Š” UTC ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑํ•จ
๐Ÿ’กย ํ•ด๊ฒฐ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์˜ ์‹œ๊ฐ„๋Œ€๋ฅผ KST๋กœ ํ†ต์ผํ•˜์—ฌ ํ•ด๊ฒฐ (dayjs ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ)
// before
const startDate = new Date(Date.UTC(year, month - 1, 1, 0, 0, 0, 0));
const endDate = new Date(Date.UTC(year, month, 0, 23, 59, 59, 999));

// after
const startDate = dayjs(`${year}-${month}-01 00:00:00`).toDate();
const endDate = dayjs(`${year}-${month}-01 00:00:00`).endOf('month').toDate();



โš ๏ธย ๋ฌธ์ œ ์บ˜๋ฆฐ๋” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ปค์Šคํ…€ ์ค‘, ํƒ€์ž… ์—๋Ÿฌ ๋ฐœ์ƒ
๐Ÿšง ์›์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” props์˜ ํƒ€์ž…์ด any๋กœ ์ •์˜๋˜์–ด์žˆ์–ด ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์˜ค๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Œ (๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ด์Šˆ)
๐Ÿ’กย ํ•ด๊ฒฐ ๋”ฐ๋กœ ์ปค์Šคํ…€ํ—ค๋” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ import

  const customHeader = (props: CalendarHeaderProps) => {
    const year = props.month?.getFullYear();
    const month = props.month?.getMonth() + 1;
    const today = dayjs();
    const isCurrentMonth =
      props.month && props.month.getFullYear() === today.year() && props.month.getMonth() === today.month();

    // ์›” ๋ณ€๊ฒฝ ์‹œ ํ˜ธ์ถœํ•  ๊ณตํ†ต ํ•จ์ˆ˜
    const handleMonthChange = (monthOffset: number) => {
      if (props.month && props.addMonth) {
        const newDate = new Date(props.month);
        newDate.setMonth(newDate.getMonth() + monthOffset);
        props.addMonth(monthOffset);

        // onMonthChange ์™ธ๋ถ€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ (๋™๊ธฐํ™”)
        onMonthChange({
          year: newDate.getFullYear(),
          month: newDate.getMonth() + 1,
          day: 1,
          timestamp: newDate.getTime(),
          dateString: `${newDate.getFullYear()}-${String(newDate.getMonth() + 1).padStart(2, '0')}-01`,
        });
      }
    };

          <CalendarList
          ...
          customHeader={customHeader}
          ...
          />



โš ๏ธย ๋ฌธ์ œ refresh token์ด ์œ ํšจํ•จ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  access token ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๋กœ๊ทธ์•„์›ƒ๋˜๋Š” ํ˜„์ƒ
๐Ÿšง ์›์ธ access token ๋งŒ๋ฃŒ ์‹œ, ์„œ๋ฒ„์—๊ฒŒ ์žฌ๋ฐœ๊ธ‰ ํ•˜๋Š” ๊ณผ์ •์„ ์ƒ๋žตํ•จ
๐Ÿ’กย ํ•ด๊ฒฐ axios interceptor๋ฅผ ์ด์šฉํ•˜์—ฌ 401 (unauthorized) ์ผ ๊ฒฝ์šฐ token์„ ์žฌ๋ฐœ๊ธ‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณ€๊ฒฝ
axiosInstance.interceptors.request.use(async (config) => {
  const accessToken = await getSecureStore('accessToken');
  if (accessToken) {
    if (config.headers) config.headers['Authorization'] = `Bearer ${accessToken}`;
  }
  return config;
});

axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    try {
      if (error.response?.status === 401) {
        await getAccessToken();
      }
    } catch (error) {}
  }
);

๐Ÿ“ ํด๋” ๊ตฌ์กฐ

app/ : ๋ผ์šฐํŒ… ๋ฐ ์ฃผ์š” ํ™”๋ฉด ์ปดํฌ๋„ŒํŠธ (file-based routing)

tabs/ : ๋ฉ”์ธ ํƒญ ํ™”๋ฉด (Achievement, Character, Profile, Rank ๋“ฑ)

auth/ : ์ธ์ฆ ๊ด€๋ จ ํ™”๋ฉด (Login, Register ๋“ฑ)

settings/ : ์„ค์ • ๊ด€๋ จ ํ™”๋ฉด

components/ : ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ UI ์ปดํฌ๋„ŒํŠธ ๋ฐ ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ

constants/ : ์ƒ์ˆ˜ ๋ฐ ๊ณตํ†ต ๊ฐ’ ์ •์˜ (์˜ˆ: Colors, Calendars)

hooks/ : ์ปค์Šคํ…€ ํ›… (์˜ˆ: useAuth, useTodo)

api/ : API ํ†ต์‹  ๋ฐ ์ฟผ๋ฆฌ ํด๋ผ์ด์–ธํŠธ

utils/ : ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜

assets/ : ์ด๋ฏธ์ง€, ํฐํŠธ ๋“ฑ ์ •์  ๋ฆฌ์†Œ์Šค

types/ : ํƒ€์ž… ์ •์˜


๐Ÿ“„ ์ฐธ๊ณ  ์ž๋ฃŒ

About

๐Ÿ’Ž ํ€˜์Šคํ‹ด ํ”„๋ก ํŠธ์—”๋“œ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published