연습

프론트

atteri 2025. 8. 16. 23:15

로그인 api를 만들었으니 로그인 요청할 화면을 만들어야된다.

react써볼거고 reqct-query랑 zustand, react-bootstrap 써볼 예정

 

npm install @tanstack/react-query

npm install @tanstack/react-query-devtools

npm install zustand

npm install react-bootstrap bootstrap

받고axios도 받고

 

 

main.jsx

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import queryClient from "./queryClient.jsx";
import {QueryClientProvider} from "@tanstack/react-query";
import {ReactQueryDevtools} from "@tanstack/react-query-devtools";

createRoot(document.getElementById('root')).render(
  <StrictMode>
      <QueryClientProvider client={queryClient}>
          <App />
          <div style={{fontSize: '16px'}}>
              <ReactQueryDevtools initialIsOpen={false} />
          </div>
      </QueryClientProvider>
  </StrictMode>,
)

 

queryClient.jsx

import {QueryClient} from "@tanstack/react-query";

const queryClient = new QueryClient( {
    defaultOptions : {
        queries : {
            staleTime : 1000 * 60 * 3,  // 3분.
            retryDelay : 1000 * 2,      // 2초
            gcTime : 1000 * 60 * 5    // 5분
        }
    }
})

export default queryClient

 

App.jsx

import 'bootstrap/dist/css/bootstrap.min.css';

function App() {

  return (
    <>
     <BrowserRouter>
         <Routes>
             <Route element={<Layout/>}>
                <Route index element={<Home/>}/>
             </Route>
         </Routes>
     </BrowserRouter>
    </>
  )
}

export default App

 

layout.jsx

export default function Layout() {

    return (
        <>
            <Header/>
            <main>
                <Outlet />
            </main>
        </>
    )

}

 

Header.jsx

function Header() {
    return(
        <>
            <Navbar collapseOnSelect expand="lg" className="bg-body-tertiary">
                <Container>
                    <Navbar.Brand href="/">Board</Navbar.Brand>
                    <Navbar.Toggle aria-controls="responsive-navbar-nav" />
                    <Navbar.Collapse id="responsive-navbar-nav">
                        <Nav className="me-auto">
                            <Nav.Link href="#features">Features</Nav.Link>
                            <Nav.Link href="#pricing">Pricing</Nav.Link>
                        </Nav>
                        <Nav>
                            <Nav.Link href="#deets">sign in</Nav.Link>
                        </Nav>
                    </Navbar.Collapse>
                </Container>
            </Navbar>
        </>
    )
}

export default Header

 

 

userStore.jsx

export const UserStore = create(
    persist
        // eslint-disable-next-line no-unexpected-multiline
    (
        (set) => ({

            // 로그인 여부
            isLogin : false,
            loginId : '',

            setIsLogin: (state) => set({ isLogin: state }),
            setLoginId: (code) => set({ loginId: code }),
            reset: () => {
                set({
                    isLogin: false,
                    loginId: '',
                });
            },
        }),{
            name: 'userStorage', 
            storage: createJSONStorage(() => {
                return sessionStorage;
            }), 
        }
    )
)

 

헤더 상단에

const {isLogin} = UserStore((state) => state)

추가하고

<Nav>
    {
        isLogin ? <Nav.Link href="#deets">sign out</Nav.Link>
            :
            <Nav.Link href="/login">sign in</Nav.Link>
    }
</Nav>

 

Login.jsx

function Login(){

    const nav = useNavigate();
    const [formData, setFormData] = useState({userId:'',password:''})
    const { isLogin , setIsLogin} = UserStore((state) => state);

    useEffect(() => {
        if(isLogin){
            nav('/')
        }
    })


    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData(prev => ({
            ...prev,
            [name]: value
        }));
    };

    const login = ()  => {

        if(!formData.userId){
            alert('아이디를 입력해주세요')
            return
        }

        if(!formData.password){
            alert('비밀번호를 입력해주세요')
            return;
        }

        loginMutaion.mutate(formData)

    }

    const loginMutaion = useMutation({
        mutationFn: (account) => {
            return loginApi(account)
                .then( result => {
                    console.log(result)
                    if(result.status === 200)
                    {
                        console.log(result.data.token)
                        const accessToken = result.data.token;
                        const refreshToken = result.data.refreshToken;

                        setAccessToken(accessToken)
                        setRefreshToken(refreshToken)

                        setIsLogin(true);
                        nav('/')

                    }else{
                        alert(result.data.message)
                    }
                })
                .catch(
                    reason => {
                        console.log(reason)
                    }
                )
                .finally()
        }
    })


    return(
        <>
            <Form>
                <Form.Group as={Row} className="mb-3" controlId="formPlaintextId">
                    <Form.Label column sm="2">
                        ID
                    </Form.Label>
                    <Col sm="10">
                        <Form.Control type="text" name="userId" onChange={handleChange}/>
                    </Col>
                </Form.Group>

                <Form.Group as={Row} className="mb-3" controlId="formPlaintextPassword">
                    <Form.Label column sm="2">
                        Password
                    </Form.Label>
                    <Col sm="10">
                        <Form.Control type="password" name="password" onChange={handleChange}/>
                    </Col>
                </Form.Group>

                <Row>
                    <Col sm={{ span: 10, offset: 2 }}>
                        <Button type="button" variant="primary" onClick={login}>
                            로그인
                        </Button>
                        <Button type="button" variant="dark" onClick={login} className="ms-2">
                            회원가입
                        </Button>
                    </Col>
                    
                </Row>

            </Form>
        </>
    )
}

 

Axios.interceptor.jsx

axios.defaults.baseURL = import.meta.env.VITE_SERVER_URL
axios.defaults.headers.post['Content-Type'] = 'application/json';

 axios.interceptors.request.use((config) => {

        const accessToken = getAccessToken();

        if (accessToken) {
            config.headers['Authorization'] = `${accessToken}`;
        }
        config.headers['Channel'] = 'WEB';

        config.headers['X-Requested-With'] = 'XMLHttpRequest';
        config.headers['Access-Control-Allow-Credentials'] = true;
        config.headers['Access-Control-Allow-Origin'] = '*';
        config.headers['Access-Control-Allow-Methods'] = 'GET, PUT, POST, DELETE, OPTIONS';
        config.headers['Cache-Control'] = 'no-cache';
        config.headers['Pragma'] = 'no-cache';

        console.log('[ Axios.interceptor ] :: processAwait ' + processAwait);

        return config;
    }, err => {
        console.error(err);
        Promise.reject(err);
    });

일단 요청만 하고

 

잘된다.

 

이제 백이랑 통신시키고 갱신시켜야지

'연습' 카테고리의 다른 글

백+프론트 연결  (0) 2025.08.19
api서버 & 람다 추가  (1) 2025.08.19
람다 3  (3) 2025.08.15
람다 이어서  (3) 2025.08.14
람다  (1) 2025.08.12