[회고] 2022 SW 마이스터고 연합해커톤 회고록
10일 전 SW 마이스터고 연합 해커톤을 하러 광주에 다녀왔다. 다녀와서 바로 적으려고 했지만, 바쁜 현생 때문에 잠시 잊고있었다. 그치만 내일의 나보다 오늘의 내가 적는게 기억력이 더 좋을 것 같아 지금이라도 쓰려고 한다.
해커톤 참가 & 팀 빌딩
학교마다 다른 것 같았지만 우리학교에서는 마역량이라는 제도를 활용해서 해커톤에 참가할 학생을 뽑았다. 20명까지만 가능했는데, 그동안 마역량 점수를 열심히 채워온 보람이 있었다.
그리고 일전에 토크 콘서트에서 인연이 있던 친구가 함께하자고 제안해준 덕분에 나는 지금의 우리팀과 합류할 수 있게 되었다.
주제 선정
위와 같이 주제는 총 4가지로 이뤄져있었고, 세분화되어있었다. 우리팀은 의논을 통해 지속 가능한 기술을 선택했고, 그 중 선배-동기-후배들이 기술 노하우나 팀/테크 뿐만 아니라 취업 및 IT 뉴스 등 정보를 공유하는 매거진을 중심으로 잡고 시작했다.
1일차 - 아이디에이션 & 세팅
주제는 정했는데, 어떤 형식으로 플랫폼을 만들지에 대한 고민이 많았다. 처음 본 친구들도 있었기에 아이디어를 공유하는 과정에서 조금 어려운 부분들도 있었지만, 한두 개씩 서로 의견을 내면서 보완해나갔다.
내가 가진 아이디어 고민 - 나 같은 경우는 현재 재학 중인 학교에 1기생이기 때문에 선배가 없다. 주변에 물어볼 수 있는 커뮤니티가 활성화되어있지 않아 배움에 있어서 어려운 부분이 많고, 이를 도와줄 수 있는 플랫폼이 있었으면 좋겠다.
다른 친구들도 비슷한 고민을 하고 있었다. 선배들의 도움을 받아서 더욱 빠르게 성장하고 싶다던지, 좋은 인연을 맺어 다양한 팁을 얻고싶다던지, 모르는 것을 물어볼 사람이 필요하다거나 같은 관심사를 가지고 있는 사람들과 소통하고 싶다는 등.. 여러 고민들이 있었지만 맥락은 비슷했다.
그래서 우리는 학생들이 간편하게 다른 학교나 선배들에게 질문과 커피챗을 통해 지속적으로 성장할 수 있는 플랫폼인 "마스크"라는 서비스를 개발하기로 하였다.
2일차 - 개발 ..
기본적인 세팅은 함께 프론트를 담당했던 친구가 해주었고, 나는 클론을 받아 브랜치를 따오는 방식으로 진행헀다.
우선 기능은 크게 분류해 총 3가지 정도 있었다.
- 사용자 기능(회원가입, 로그인, 로그 아웃)
- 에스크 기능(에스크 작성, 에스크 조회, 에스크 댓글, 에스크 좋아요)
- 커피챗 기능 (커피챗 메세지 보내기, 커피챗 사람 조회, 검색)
이렇게 있었는데, 나는 그 중 사용자 기능을 담당했다.
styled-components, 타입스크립트를 처음 사용했기에 어떻게 해야하는 지 모르는 부분이 많았다.
로그인 조차도 한 번도 구현해본 경험이 없어서 정말 어려운 부분이 많았다. 그래서 내가 구현한 과정을 복기하면서 정리를 해보려고 한다.
우선 퍼블리싱을 해주었다. styled-components는 css style을 컴포넌트 단위로 사용할 수 있도록 돕는 라이브러리다. 기존에 사용하던 css와 다를게 거의 없어서 쉽게 사용할 수 있었다.
import {
LoginAlertText,
LoginContainer,
LoginInput,
LoginInputTitle,
LoginLogo,
LoginSubmitButton,
LoginWrap,
} from "./style";
const Login = () => {
return (
<LoginContainer>
<LoginWrap>
<LoginInputTitle>아이디</LoginInputTitle>
<LoginInput />
<LoginInputTitle>비밀번호</LoginInputTitle>
<LoginInput />
<LoginSubmitButton>로그인</LoginSubmitButton>
<LoginAlertText>
마스크가 처음이신가요? <strong>회원가입</strong>하러가기
</LoginAlertText>
</LoginWrap>
</LoginContainer>
);
};
export default Login;
그리고 이제 기능을 넣어주어야했다. 로그인 기능을 기준으로 이야기 해보자.
- 사용자는 input에 이메일과 패스워드를 입력한다.
- "로그인" 버튼을 클릭하면 api에 post요청을 날려 데이터베이스에 저장된 정보가 옳은지 확인한다.
- 확인된 데이터는 토큰과 함께 쿠키에 저장하고, 메인 페이지로 라우팅한다.
위 세 단계를 거쳐서 구현할 수 있다.
api 통신 경험이 없던 나는 이 부분에서 친구에게 가장 많은 도움을 받았다. 쿠키가 무엇인지는 알고 있었지만, 쿠키를 사용하는 방법은 몰랐던 부분이기에 이번 기회를 통해 공부할 수 있었다.
- 사용자는 input에 이메일과 패스워드를 입력한다.
여기서 값을 가지고 있어야하기 때문에 상태를 관리해 줄 필요가 있다.
const Login = () => {
const [loginData, setLoginData] = useState<{
email: string;
password: string;
}>({
email: "",
password: "",
});
return (
<LoginContainer>
<LoginWrap>
<LoginLogo src={Logo} />
<LoginInputTitle>아이디</LoginInputTitle>
<LoginInput
value={loginData.email}
name="email"
placeholder="s21032@gsm.hs.kr"
/>
<LoginInputTitle>비밀번호</LoginInputTitle>
<LoginInput
value={loginData.password}
name="password"
type="password"
placeholder="비밀번호를 입력해주세요."
/>
<LoginSubmitButton>로그인</LoginSubmitButton>
<LoginAlertText>
마스크가 처음이신가요? <strong>회원가입</strong>하러가기
</LoginAlertText>
</LoginWrap>
</LoginContainer>
);
};
export default Login;
loginData라는 상태를 하나 만들고 useState로 관리를 해주기로했다. 그리고 onChange 이벤트를 통해 setLoginData에 값을 업데이트해주기로 하였다. 이전 값들을 가진 상태에서 더해져 나가는 것이기 때문에 prev를 활용했다.
const Login = () => {
const [loginData, setLoginData] = useState<{
email: string;
password: string;
}>({
email: "",
password: "",
});
const onChangeLoginData = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value, name } = e.target;
setLoginData((prev) => ({ ...prev, [name]: value }));
};
return (
<LoginContainer>
<LoginWrap>
<LoginLogo src={Logo} />
<LoginInputTitle>아이디</LoginInputTitle>
<LoginInput
value={loginData.email}
name="email"
onChange={onChangeLoginData}
placeholder="s21032@gsm.hs.kr"
/>
<LoginInputTitle>비밀번호</LoginInputTitle>
<LoginInput
value={loginData.password}
name="password"
type="password"
onChange={onChangeLoginData}
placeholder="비밀번호를 입력해주세요."
/>
<LoginSubmitButton>로그인</LoginSubmitButton>
<LoginAlertText>
마스크가 처음이신가요? <strong>회원가입</strong>하러가기
</LoginAlertText>
</LoginWrap>
</LoginContainer>
);
};
export default Login;
그리고 다음 단계!
2. "로그인" 버튼을 클릭하면 api에 post요청을 날려 데이터베이스에 저장된 정보가 옳은지 확인한다.
3. 확인된 데이터는 토큰과 함께 쿠키에 저장하고, 메인 페이지로 라우팅한다.
const Login = () => {
const navigate = useNavigate();
const [loginData, setLoginData] = useState<{
email: string;
password: string;
}>({
email: "",
password: "",
});
const onChangeLoginData = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value, name } = e.target;
setLoginData((prev) => ({ ...prev, [name]: value }));
};
const onSubmitLogin = async () => {
try {
const { data } = await axios.post(
"[API URL 주소]/users/tokens",
{
email: loginData.email,
password: loginData.password,
}
);
cookie.setCookie(ACCESS_TOKEN_KEY, data.access_token);
navigate("/");
} catch (error) {}
};
return (
<LoginContainer>
<LoginWrap>
<LoginLogo src={Logo} />
<LoginInputTitle>아이디</LoginInputTitle>
<LoginInput
value={loginData.email}
name="email"
onChange={onChangeLoginData}
placeholder="s21032@gsm.hs.kr"
/>
<LoginInputTitle>비밀번호</LoginInputTitle>
<LoginInput
value={loginData.password}
name="password"
type="password"
onChange={onChangeLoginData}
placeholder="비밀번호를 입력해주세요."
/>
<LoginSubmitButton>로그인</LoginSubmitButton>
<LoginAlertText>
마스크가 처음이신가요? <strong>회원가입</strong>하러가기
</LoginAlertText>
</LoginWrap>
</LoginContainer>
);
};
export default Login;
코드가 너무 길어져서 로그인 로직만 모아두는 커스텀 훅으로 분리하였다. 이후에는 급해서 네이밍은 못바꿨지만 회원가입에도 사용해서 재사용가능하도록 만들었다.
const useLogin = () => {
const [loginData, setLoginData] = useState<{
email: string;
password: string;
}>({
email: "",
password: "",
});
const navigate = useNavigate();
const onChangeLoginData = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value, name } = e.target;
setLoginData((prev) => ({ ...prev, [name]: value }));
};
const onSubmitLogin = async () => {
try {
const { data } = await axios.post(
"[API URL 주소]",
{
email: loginData.email,
password: loginData.password,
}
);
cookie.setCookie(ACCESS_TOKEN_KEY, data.access_token);
navigate("/");
} catch (error) {}
};
return { loginData, onChangeLoginData, onSubmitLogin };
};
export default useLogin;
const Login = () => {
const { loginData, onChangeLoginData, onSubmitLogin } = useLogin();
return (
<LoginContainer>
<LoginWrap>
<LoginLogo src={Logo} />
<LoginInputTitle>아이디</LoginInputTitle>
<LoginInput
value={loginData.email}
name="email"
onChange={onChangeLoginData}
placeholder="s21032@gsm.hs.kr"
/>
<LoginInputTitle>비밀번호</LoginInputTitle>
<LoginInput
value={loginData.password}
name="password"
type="password"
onChange={onChangeLoginData}
placeholder="비밀번호를 입력해주세요."
/>
<LoginSubmitButton onClick={onSubmitLogin}>로그인</LoginSubmitButton>
<LoginAlertText>
마스크가 처음이신가요? <strong>회원가입</strong>하러가기
</LoginAlertText>
</LoginWrap>
</LoginContainer>
);
};
export default Login;
3일차 - 발표 & 결과
디자이너 친구가 발표자료를 잘 만들어준 덕분에 보여지는 자료를 토대로 잘 발표 할 수 있었다. 발표 담당은 나였는데, 아쉽게도 영상은 남아있지 않다. 대신 아래 시연 영상과 발표자료는 남겨 두었으니 궁금하신 분들은 확인해보셔도 좋다.
정말 좋게 봐주신 덕분에 우리는 카카오 기업상을 수상할 수 있었다. 할 수 있다는 마음가짐으로 끝까지 해낸 덕분에 받은 상이라 그런지 더 뿌듯했다.
마지막 소감 & 앞으로
나는 지난 10개월 간 공기업에 들어가기 위해 준비하는 과정 속에 있었다. 개발자에 대한 허들이 너무 높다고 생각해서, 뛰어난 친구들이 너무 많다고 생각해서 또 나는 그걸 넘어설수 없으리라 확신해서 포기하면서 공기업쪽으로 발길을 돌렸다. 그래도 마지막 기회라 생각하고 해커톤에 나왔던건데 이 기회가 나에게는 터닝포인트가 된것 같다. 그동안 맨땅에 해딩하면서 삽질한 것보다 2박 3일간 같은 프론트 친구에게 배운 부분이 훨씬 많았다.
도와주는 친구가 있으니 같이 하려고 하는 의지도 생기고, 못한다고 해서 뭐라하는게 아닌 오히려 독려하면서 같이 해쳐나가보자는 모습을 보면서 정말 많이 배운 것 같다. 그 속에서 협업은 이렇게 하는 거구나를 제대로 경험한 것 같다. 개발하면서 이번에 타입스크립트를 처음 사용해봤는데 생각했던 것보다 쉬웠다. 그동안 내가 어렵다 생각해서 보지도 않고 포기해버린 것 뿐, 충분히 해낼 수 있을만한 것들이었다.
그래서 마지막으로 다시 한 번 공부해보려고 한다. 프론트엔드 개발자로서 성장하는 나의 모습이 너무 궁금해졌고, 내가 앞으로 마주할 어려움을 다 이겨낼 수 있을 거라는 자신감이 생겼다. 꼭 멋있는 개발자가 되어서 그 친구들과 다시 한 번 개발하는 순간이 오기를!