이번 주는 github 협업에 적응하고, 개발을 슬슬 시작해보는 시간이었다.


prettier 설정도 잘못돼있었고, tailwind css 설정 파일의 css 적용 범위가 잘못되어있는 바람에 헤더 만들때 간단한 작업임에도 시간이 많이 걸렸다.

 

https://stackoverflow.com/questions/74584091/how-to-get-the-current-pathname-in-the-app-directory-of-next-js

 

How to get the current pathname in the app directory of Next.js?

I'm using the experimental app folder in Next.js 13, where they have replaced next/router with next/navigation, so I imported the useRouter hook accordingly. I do not see the property pathname in ...

stackoverflow.com

 

 

https://uiverse.io/vinodjangid07/good-donkey-28

 

Input by vinodjangid07 made with CSS | Uiverse.io

This Input was posted by vinodjangid07. Tagged with: input, message, send, image, upload, file. You can create your own elements by signing up.

uiverse.io

 

 

React input태그에서 Enter키로 이벤트 활성화하기

 

[JavaScript] 값 입력 후 엔터(Enter)키 눌렀을 때 이벤트 실행

 

React input태그에서 Enter키로 이벤트 활성화하기

 

위 문서들을 참고하여

서치데이터 콘솔에 띄워보기까지 완료

useParams 쓰려고 시도해보다가 서치 다시 해보니 id 말고 문자열로 된 링크는 usePathName 쓰면 되는 것을 알게 되었다.

함수도 onKeyPress로 적었는데 onKeyDown으로 수정해야한다.

이건 다음에 새로운 브랜치에서 작업하기로 한다.

지금에서야 드는 생각인데 useEffect로 바꿔도 될거같다.


이후 props활용해 거래 페이지를 위한 카드 컴포넌트도 직접 작성해봤는데 css가 어려웠고, css적용을 위해 컴포넌트를 어떻게 나눌지, props로 어떻게 css 설정을 가져올지 찾아보고 하느라 어려웠지만 일단 레이아웃은 의도대로 나왔고, 나쁘지 않아 보였다.

css는 다음에 한번 갈아 엎긴 해야할 것 같다..


테이블 작업은 따로 문서 작성하였다.

https://developmiran.tistory.com/11

 

[Next.js] shadcn/ui 로 데이터테이블 만들기

시작하기에 앞서, shadcn/ui로 진행하게 된 이유shadcn/ui 가 mui보다 기능이 적어보여 고민하던 중 아래 글을 보게 되었다. 테이블을 편하게, Tanstack-table 사용하기 테이블을 편하게, Tanstack-table 사용

developmiran.tistory.com

 




강의 한번 더 보고 리팩토링 해봐야지.



풀리퀘스트 잘못보내고 그랬는데 경험자이신 팀장님께서 열심히 알려주셔서 감사했다.

우리 조는 제대로 된 협업 경험이 많이 없어서 시작이 좀 느리지만 열심히 해서 잘 마무리하고싶다.

나만 느린 것일수도 ...

 

 

 

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다. #유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

 

이번 주 수업 내용

 

이번 주는 supabase 코드를 hook으로 빼고 로그인, 로그아웃 등의 기능을 구현하였다.

 

npm install jotai

 

 

import { atom } from "jotai";
import { Task } from "@/types";

/** Supabase에 저장되어 있는 'todos' (tasks) 테이블 내에 있는 모든 데이터 조회 */
/** 전체 tasks 목록 조회 */
export const tasksAtom = atom<Task[]>([]);

/** 단일(개별) task 상태 */
export const taskAtom = atom<Task | null>(null);

 


jotai라는 상태 관리 라이브러리를 이용해 task data를 tasksAtom이라는 전역상태에 할당하게 만들었다.

useState와 비슷한 구조를 가지고 있다.

 

https://velog.io/@ggong/%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-jotai

 

상태 관리를 위한 라이브러리 jotai 훑어보기

이번에 신규 프로젝트 개발을 하면서 React-query와 함께 jotai를 도입하기로 했다.전에 작업하면서는 거의 리덕스로 모든 데이터를 전역에 저장하고 썼었는데, 전역 상태를 관리하고 업데이트 하기

velog.io

 

더보기

1. atom

const priceAtom = atom(10000);

atom 을 생성하고 괄호 안에 초기값을 넣을 수 있다.

 

2. useAtom(read/write)

const [price, setPrice] = useAtom(priceAtom);

생성된 atom 을 불러와서 useState 와 동일하게 사용 가능하다.

 

3. useSetAtom(write)

const setPrice = useSetAtom(priceAtom);

생성된 atom 의 값을 update 만 할 때 사용한다.

 

4. useAtomValue(read)

const price = useAtomValue(priceAtom);

생성된 atom 의 값을 read 만 할 때 사용한다.

 

useSetAtom, useAtomValue 는 각각 읽기, 쓰기만 가져와서 사용하기 때문에 useAtom 과 다르게 재랜더링 하지 않는 장점이 있다.

 

출처 https://liebe97.tistory.com/49

 

React Jotai 사용법 총 정리(1) - Jotai는 무엇이고 어떻게 사용하나요?

React Jotai 사용법 총 정리(2) - atom 이해 및 기본 유용한 기능 ( https://liebe97.tistory.com/51 ) React Jotai 사용법 총 정리(3) - useAtom & provider 사용법 ( https://liebe97.tistory.com/52 ) 앞으로 프로젝트 리팩토링을

liebe97.tistory.com

 

 

use~로 시작하는 파일은 리액트가 hook으로 자동 인식한다.

 

 

"use client";
import { useRouter } from "next/navigation";
import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { useAtom } from "jotai";
import { tasksAtom } from "@/stores/atoms";

function useCreateTask() {
    const router = useRouter();
    const { toast } = useToast();
    const [  , setTasks] = useAtom(tasksAtom); // task 안쓰니까 없앰

    const createTask = async () => {
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .insert([
                    {
                        title: null,
                        start_date: null,
                        end_date: null,
                        boards: [],
                    },
                ])
                .select();
            if (data && status === 201) {
                /** 올바르게 tasks 테이블에 ROW 데이터 한 줄이 올바르게 생성되면 tasksAtom에 할당한다. */
                setTasks((prevTasks) => [...prevTasks, data[0]]); // Jotai의 tasksAtom 상태 업데이트
                toast({
                    title: "새로운 TASK가 생성되었습니다.",
                    description: "나만의 TODO-BOARD를 생성해보세요!",
                });
                router.push(`/board/${data[0].id}`);
            }

            if (error) {
                toast({
                    variant: "destructive",
                    title: "에러가 발생했습니다.",
                    description: `Supabase 오류: ${error.message || "알 수 없는 오류"}`,
                });
            }

        } catch (error) {
            /** 네트워크 오류나 예기치 않은 에러를 잡기 위해 catch 구문 사용 */
            console.error(error);
            toast({
                variant: "destructive",
                title: "네트워크 오류",
                description: "서버와 연결할 수 없습니다. 다시 시도해주세요!",
            });
        }
    };

    return createTask;
}

export {useCreateTask};

 

 

"use client";

import { useToast } from "@/hooks/use-toast";
import { supabase } from "@/lib/supabase";
import { useAtom } from "jotai";
import { tasksAtom } from "@/stores/atoms";

function useGetTasks() {
    const { toast } = useToast();
    const [tasks, setTasks] = useAtom(tasksAtom);

    /** 하단의 코드에서 Supabase에서 error를 반환함에도 불구하고 try-catch 구문을 사용하는 이유
     * async-await 구문에서 비동기 로직을 처리할 경우, try-catch는 주로 비동기 함수에서 발생할 수 있는 예외를 처리하기 위해 사용됩니다.
     * 만약, getTasks 함수 내에서 await한 API 호출이나 네트워크 요청에서 에러가 발생한다면, 그 오류는 자동으로 예외를 발생할 수 있습니다.
     * 그럴 경우, 예외를 잡아내지 않으면 프로그램이 중단되거나 예상치 못한 오류가 발생할 수 있습니다.
     */

    const getTasks = async () => {
        try {
            const { data, status, error } = await supabase
                .from("todos")
                .select("*");

            if (data && status === 200) {
                setTasks(data);
                toast({
                    title: "task loaded",
                    description: "새로운 TASK가 생기시면 언제든 추가해주세요!",
                });
            }

            if (error) {
                toast({
                    variant: "destructive",
                    title: "에러가 발생했습니다.",
                    description: `Supabase 오류: ${error.message || "알 수 없는 오류"}`,
                });
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    return { tasks, getTasks };
}

export { useGetTasks };

 

"use client";

import { useRouter } from "next/navigation";
import { supabase } from "@/lib/supabase";
import { useToast } from "@/hooks/use-toast";

function useDeleteTask() {
    const router = useRouter();
    const { toast } = useToast();

    const deleteTask = async (taskId: number) => {
        try {
            const { status, error } = await supabase
                .from("todos")
                .delete()
                .eq("id", taskId);

            if (status === 204) {
                toast({
                    title: "해당 task를 삭제했습니다",
                    description: "새로운 task를 생성해보세요",
                });
                router.push("/");
            }

            if (error) {
                /** 네트워크 오류나 예기치 않은 에러를 잡기 위해 catch 구문 사용 */
                toast({
                    variant: "destructive",
                    title: "에러가 발생했습니다.",
                    description: `Supabase 오류: ${error.message || "알 수 없는 오류"}`,
                });
            }
        } catch (error) {
            console.error(error);
            toast({
                variant: "destructive",
                title: "에러",
                description: "에러발생",
            });
        }
    };

    return deleteTask;
}

export { useDeleteTask };

 

"use client";

import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui";
import { useDeleteTask } from "@/hooks/api";
import { useParams } from "next/navigation";

interface Props {
    children: React.ReactNode;
}

function AlertPopup({ children }: Props) {
    const { id } = useParams();

    const handleDeleteTask = useDeleteTask();

    return (
        <AlertDialog>
            <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
            <AlertDialogContent>
                <AlertDialogHeader>
                    <AlertDialogTitle>해당 TASK를 정말로 삭제하시겠습니까?</AlertDialogTitle>
                    <AlertDialogDescription>
                        이 작업이 실행되면 다시 취소할 수 없습니다.
                        <br /> 삭제가 진행되면 귀하의 게시물은 영구적으로 삭제됩니다.
                    </AlertDialogDescription>
                </AlertDialogHeader>
                <AlertDialogFooter>
                    <AlertDialogCancel>취소</AlertDialogCancel>
                    <AlertDialogAction onClick={() => handleDeleteTask(Number(id))}  className="bg-red-600 hover:bg-rose-600">삭제</AlertDialogAction>
                </AlertDialogFooter>
            </AlertDialogContent>
        </AlertDialog>
    );
}

export { AlertPopup };

 

onClick={() => handleDeleteTask(Number(id))}

 

https://ej-development-note.tistory.com/80

 

[JavaScript] 이벤트 - 이벤트 객체, this 키워드

이벤트 이벤트(event)는 웹 브라우저와 사용자 사이에 상호작용이 발생하는 특정 시점을 의미한다. 자바스크립트로 이벤트 종류에 따른 제어 작업을 할 수 있다. 1. 이벤트 종류 구분 이벤트 설명

ej-development-note.tistory.com

 

상태코드

200 ok

204 no content

 

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code)

 

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code)

REST API 관점에서 바라보는 HTTP 상태 코드(HTTP status code) TOC Introduction HTTP 와 REST HTTP Status Code 2XX Success 4.1. 200 OK 4.2. 201 Created 4.3. 202 Accepted 4.4. 204 No Content 4XX Client errors 5.1. 400 Bad Request 5.2. 401 Unauthori

sanghaklee.tistory.com

 

hook 분리하고 데이터 저장 기능이나 로그인 기능도 만들었다.

로그인 기능은 단순히 로그인 기능을 만드는 것 뿐만 아니라 페이지 접근 권한 또한 관리해주어야 해서 꽤나 복잡한 기능이었다.

아직 정리가 덜되어 자료 링크만 첨부하고 다음주 내로 다시 정리해야겠다.

 

 

https://lopunko.notion.site/Part-3-Supabase-Auth-1ba2621a1b5345f3b02d2059d47ac7a3

 

Part 3. Supabase Auth 소개 및 인증방식 기획 | Notion

Supabase Auth

lopunko.notion.site

 

 

https://velog.io/@sanghyeon/Supabase%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

Supabase를 이용하여 로그인 인증 구현하기

[풀스택 완성] Supabase로 웹사이트 3개 클론하기 (Next.js 14)

velog.io

 

 

https://www.youtube.com/watch?v=7FemRsuDdGg

 

https://supabase.com/docs/reference/javascript/auth-admin-deleteuser

 

JavaScript: Delete a user | Supabase Docs

shouldSoftDelete (Required) If true, then the user will be soft-deleted (setting `deleted_at` to the current timestamp and disabling their account while preserving their data) from the auth schema. Defaults to false for backward compatibility. This functio

supabase.com

 

 

 

프로젝트 조 배정 & 활동 준비

 

팀 배정이 발표되어서 팀 서류 작성 등등 팀활동을 했다!

어쩌다보니 내가 총무를 하게 되어서 카뱅 계좌도 하나 팠다..

잘 할수 있을지 자신은 없지만 다음주부터 활동 시작이니 화이팅.

 

 

——————————————————————————

본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.

#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

비수도권 거주자라 온라인 zep으로 참여했다.

 

일주일 학습 요약

1주차에는 자바스크립트를 간단히 배우고 리액트 프로젝트 2개를 같이 만들었다.

 

cd desktop
npm create vite@latest
Project name: » vite-project (프로젝트 이름은 본인이 원하는 이름으로 생성)
Select a framework: » React
Select a variant: » TypeScript
cd 생성한 프로젝트명
npm install
code . -r

 

 

https://ui.shadcn.com/docs/installation/vite

 

Vite

Install and configure Vite.

ui.shadcn.com

shadcn UI를 이용할 것이라서 위 문서를 보고 테일윈드 깔아주었다.

npm install -D tailwindcss postcss autoprefixer

npx tailwindcss init -p

VSCode 오류 - npx : 이 시스템에서 스크립트를 실행할 수 없으므로 ...

npx오류로 구글링하여 해결하였다.

 

axios와 jotai도 활용하였다

npm install react-router-dom
npm install localforage match-sorter sort-by
npm install sass --save-dev
npm install react-kakao-maps-sdk
npm install axios
npm install jotai

 

지도 생성하기 | react-kakao-maps-sdk docs

 

지도 생성하기 | react-kakao-maps-sdk docs

지도를 생성하는 가장 기본적인 예제입니다.

react-kakao-maps-sdk.jaeseokim.dev

 

 

import { Map } from "react-kakao-maps-sdk";
import useKakaoLoader from "@/hooks/useKakaoLoader";
import { Card } from "@/components";

function GetKakaoMapWidget() {
    useKakaoLoader()

    return (
        <Card className="w-1/4 min-w-[25%] h-full">
            {/* 지도를 표시할 컨테이너 */}
            <Map
                id="map"
                center={{
                    /** 지도의 중심좌표 */
                    lat: 37.5683,
                    lng: 126.9778,
                }}
                style={{
                    /** 지도의 크기 */
                    width: "100%",
                    height: "100%",
                    borderRadius: "8px",
                }}
                /** 지도의 확대 레벨 */
                level={13}
            />
        </Card>
    );
}

export { GetKakaoMapWidget };

 

use state와 props를 이용한 데이터 관리, 날씨api연결

function HomePage() {
    const [cityName, setCityName] = useAtom(cityNameAtom);


    const [weatherData,setWeatherData] = useState(defaultWeatherData);
    const [tideData, setTideData] = useState<ForecastTideDay>(defaultTideData);
    const [oneWeekWeatherSummary, setOneWeekWeatherSummary] = useState([]);

    const APP_KEY = "/** 앱키 */";
    const BASE_URL = "http://api.weatherapi.com/v1";

    const fetchApi= async ()=>{
        
        try{
            const res = await axios.get(`${BASE_URL}/forecast.json?q=${cityName}&days=7&key=${APP_KEY}`) ;
            console.log(res);

            if (res.status === 200){
                setWeatherData(res.data);
            }

        } catch(error){
            console.error(error);
        }finally{
            console.log("fetchApi 호출은 되었습니다.");
        }

    };
    
    const fetchTideApi= async ()=>{
//길어져서 생략
    };
    
    const getOneWeekWeather = async ()=>{
        
        try{
            /** Promise 인스턴스 방법을 사용했을 땐, resolve에 해당 */  
            const res = await axios.get(`${BASE_URL}/forecast.json?q=${cityName}&days=7&key=${APP_KEY}`) ;
            console.log(res);

            if (res.status === 200 && res.data ){
                const newData=res.data.forecast.forecastday.map((item: ForecastDay)=>
                {
                    return{
                        maxTemp: Math.round(item.day.maxtemp_c),
                        minTemp: Math.round(item.day.mintemp_c),
                        date: item.date_epoch,
                        iconCode: item.day.condition.code,
                        isDay: item.day.condition.icon.includes("day"),
                    };
            });
            setOneWeekWeatherSummary(newData);
        }
        } catch(error){
            /** Promise 인스턴스 방법을 사용했을 땐, resolve에 해당 */  
            console.error(error);
        }

    };
    

    useEffect(()=>{
        fetchApi();
        fetchTideApi();
        getOneWeekWeather();
    }, [cityName])

 

다음 프로젝트도 비슷하게 진행했다. 한 번 해보고 나니 더 수월하게 진행할 수 있었다.

 

과제

 

ui 이미지만 보고 혼자서 해 봤던 코드!

https://github.com/nrkcode/React-hw

 

GitHub - nrkcode/React-hw

Contribute to nrkcode/React-hw development by creating an account on GitHub.

github.com

 

 

깃허브 연결 과제는 이미 깃허브를 많이 사용해보아서 쉽게 해결했다.

프롭스에서 html 특수문자를 string {""}형식으로 전송하니 문자코드 그대로 출력되었는데, 중괄호를 벗기니까 원래 의도한 ℃로 잘 출력되었다.

props 받는 코드를 수정하거나 조건문을 달거나 하려고 여러 시도를 해 보았는데 괄호 제거라는.. 생각보다 간단한 방법으로 해결되었다.

https://ko.legacy.reactjs.org/docs/jsx-in-depth.html

 

JSX 이해하기 – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

prop에 대해 알아보자!! 하고 찾아본 문서에서 힌트를 얻었다. 역시 공식문서를 보는 습관이 들어야겠다.

 

 

스크롤바 없애기는 알려주신 방법 말고 다른 방법을 구글링해서 찾아보았다.

 

 

npm: tailwind-scrollbar-hide

 

tailwind-scrollbar-hide

tailwindcss plugin for hide scrollbar. Latest version: 1.1.7, last published: 3 years ago. Start using tailwind-scrollbar-hide in your project by running `npm i tailwind-scrollbar-hide`. There are 95 other projects in the npm registry using tailwind-scroll

www.npmjs.com

 

npm install tailwind-scrollbar-hide

 

// tailwind.config.js
module.exports = {
  theme: {
    // ...
  },
  plugins: [
    require('tailwind-scrollbar-hide')
    // ...
  ]
}

 

 

어려웠던 점 & 더 찾아본 정보들

 

동기/비동기함수&화살표함수

function a() {
    console.log("1");

    setTimeout( () => {
        console.log("2");
    }, 1000);

    console.log("3");
}

a();

화살표 함수로 적어도 된다

const a = () => {
    console.log("1");

    setTimeout( () => console.log("2"), 1000);

    console.log("3");
}

a();

이렇게도 된다

자바스크립트 동기/비동기함수 할 때 좀 놓쳐서 자습을 더 했다

 

코드 템플릿 단축키가 정말 유용해서 더 찾아봤다!

https://velog.io/@rgfdds98/React-snippets-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%A2%85%EB%A5%98%EB%B3%84-%EB%8B%A8%EC%B6%95%ED%82%A4

 

 

 

일주일 해 보고 느낀 점은,

하루에 8시간 수업이다 보니 한 번 놓치면 따라가기 쉽지 않고 제때 복습을 해야 잊어버리지 않겠다는 점이다.

주말에 문서를 정리하다 보니 상세한 부분이 잘 기억이 나지 않아서 다음 주 부터는 가능하다면 하루에 한 번 일지를 써봐도 좋을 것 같다.

노션에라도 정리해봐야겠다.

그리고 자바스크립트, css 지식이 너무 부족하다는 것을 느꼈다.

알려주시는 부분이라도 열심히 습득하고 주말에 추가적인 공부를 꼭 더 해야겠다..

주말에 추가 공부 하기 위해서는 진도 나간 부분의 공부는 평일에 꼭 끝내야겠다.

 

——————————————————————————

본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프론트엔드 프로젝트 캠프 과정(B-log) 리뷰로 작성 되었습니다.

#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #Next.js #프론트엔드개발자양성과정 #개발자교육과정

+ Recent posts