MAIN.png

포트폴리오 제작기

잡서칭 과정 중 포트폴리오가 필요함에 따라 작성하게 되었습니다.

구매를 할 지, 직접 작성할 지 고민을 하다가,

웹 프론트를 지원하는데 구매를 하는 건 좀 아닌 듯 하여 만들게 되었습니다.

빠르게 제작하기 위해 React.js로 진행하겠습니다.

완성본은 AWS S3 정적 웹사이트 호스팅으로 배포하였습니다.

👉🏻 포트폴리오 완성본


1. 시작

먼저 CRA로 프로젝트를 생성합니다.

npx create-react-app potfolio

빠른 CSS Design을 위해 Material-ui를 사용하겠습니다.

yarn add @material-ui/core

슬라이드 형식으로 UI를 꾸밀 수 있는 fullPage를 설치합니다.

yarn add @fullpage/react-fullpage

2. Set FullPage

App.js를 열고 기본 설정을 지운 후, FullPage를 불러와서 적용해줍니다.

총 4페이지를 제작하기 위해 옵션의 anchors는 4개로 설정해줍니다. 색은 동일하게 설정하겠습니다.

// FullPage.js
import React from 'react';
import './App.css';
import FullPage from './components/FullPage';

const fullpageOptions = {
  anchors: ['first', 'second', 'third', 'fourth'],
  sectionsColor: ['#171b23', '#171b23', '#171b23', '#171b23'],
  callbacks: ['onLeave'],
  scrollOverflow: false,
  navigation: true,
  navigationTooltips: ['first', 'second', 'third', 'fourth']
};

function App() {
  return <FullPage {...fullpageOptions} />;
}

export default App;

FullPage.js의 파일 내용은 다음과 같습니다.

각 섹션에 포함된 값들은 모두 커스텀 컴포넌트입니다. 이제부터 하나씩 만들어갑니다.

fullpage-wrapper로 감싼 부분의 각 섹션이 각 페이지를 담당합니다.

이후 css파일을 불러와 적용해줍니다.

// FullPage.js
/* eslint-disable jsx-a11y/accessible-emoji */
import React from 'react';
import ReactFullpage from '@fullpage/react-fullpage';
import './FullPage.css';
import Banner from './Banner';
import Footer from './Footer';
import Intro from './Intro';
import SkillContainer from './SkillContainer';
import ProjectContainer from './ProjectContainer';
import ThanksContainer from './ThanksContainer';

const FullPage = fullpageProps => {
  const classes = useStyles();
  return (
    <ReactFullpage
      {...fullpageProps}
      render={({ state, fullpageApi }) => {
        return (
          <div id='fullpage-wrapper'>
            <div className='section'>
              <Banner />
              <Intro />
              <Footer />
            </div>
            <div className='section'>
              <SkillContainer />
            </div>
            <div className='section'>
              <ProjectContainer />
            </div>
            <div className='section'>
              <ThanksContainer />
            </div>
          </div>
        );
      }}
    />
  );
};

export default FullPage;
/* FullPage.css */
body {
  font-family: arial, helvetica;
}

.fp-section {
  text-align: center;
}

#fp-nav ul li a span,
.fp-slidesNav ul li a span {
  background: #fff;
}

3. 배너 작성

첫 페이지의 배너입니다.

배너는 Material-ui의 Paper로 간단하게 만들 수 있습니다.

src값은 계속 변할 수 있도록 랜덤이미지를 가져와줍니다.

// Banner.js
import React from 'react';
import { Paper, Grid, Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';

const Banner = () => {
  const classes = useStyles();
  return (
    <Paper className={classes.mainPost}>
      {
        <img
          style={{ display: 'none' }}
          src='https://source.unsplash.com/user/erondu'
          alt='background'
        />
      }
      <div className={classes.overlay} />
      <Grid container>
        <Grid item md={6}>
          <div className={classes.mainContent}>
            <Typography
              component='h2'
              variant='h3'
              color='inherit'
              gutterBottom
              align='center'
            >
              Web Front Engineer
            </Typography>
          </div>
        </Grid>
      </Grid>
    </Paper>
  );
};

const useStyles = makeStyles(theme => ({
  mainPost: {
    position: 'relative',
    backgroundColor: theme.palette.grey[800],
    color: theme.palette.common.white,
    marginBottom: theme.spacing(4),
    backgroundImage: 'url(https://source.unsplash.com/user/erondu)',
    backgroundSize: 'cover',
    backgroundRepeat: 'no-repeat',
    backgroundPosition: 'center'
  },
  overlay: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    right: 0,
    left: 0,
    backgroundColor: 'rgba(0,0,0,.3)'
  },
  mainContent: {
    position: 'relative',
    padding: theme.spacing(3),
    [theme.breakpoints.up('md')]: {
      padding: theme.spacing(7),
      paddingRight: 0
    }
  }
}));

export default Banner;

image.png


4. 인트로 작성

그리 어려운 내용은 없습니다.

// Intro.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import './FullPage.css';
import { Typography, Grid } from '@material-ui/core';

const Intro = () => {
  const classes = useStyles();
  return (
    <Grid>
      <Typography className={classes.titleName} component='h2' variant='h2'>
        정건휘
      </Typography>
      <Typography className={classes.titleIntro} component='h4' variant='h4'>
        항상 배움의 자세를 가지는 개발자가 되고 싶습니다.
      </Typography>
    </Grid>
  );
};

const useStyles = makeStyles(theme => ({
  titleName: {
    color: '#fff',
    fontWeight: 600,
    marginBottom: theme.spacing(4)
  },
  titleIntro: {
    color: '#eee'
  }
}));

export default Intro;

image.png


5. 푸터 작성

메인페이지 하단에 이메일, 깃허브, 전화번호를 적겠습니다.

// Footer.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import './FullPage.css';
import { Typography, Container, Link, Grid } from '@material-ui/core';
import Copyright from './Copyright';

const Footer = () => {
  const classes = useStyles();
  return (
    <Grid className={classes.footer}>
      <Container maxWidth='lg'>
        <Copyright />
        <Typography variant='body2' color='primary' align='center'>
          {'Belog 👉🏻 '}
          <Link color='secondary' href='https://velog.io/@geonhwi'>
            https://velog.io/@geonhwi
          </Link>
        </Typography>
        <Typography variant='body2' color='primary' align='center'>
          {'Phone 👉🏻'}
          <Typography
            display='inline'
            variant='body2'
            color='secondary'
            align='center'
          >
            010-6396-2671
          </Typography>
        </Typography>
      </Container>
    </Grid>
  );
};

const useStyles = makeStyles(theme => ({
  footer: {
    backgroundColor: 'inherit',
    marginTop: theme.spacing(4),
    padding: theme.spacing(3, 0)
  }
}));

export default Footer;

image.png


6. Skill Container

기술 스택을 적을 컨테이너를 만들겠습니다.

// SkillContainer.js
/* eslint-disable jsx-a11y/accessible-emoji */
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Typography, Grid } from '@material-ui/core';

const SkillContainer = () => {
  const classes = useStyles();
  return (
    <Grid md='lg'>
      <Typography className={classes.skillTitle} component='h2' variant='h2'>
        <b
          style={{
            borderTop: '1px solid lightGray',
            borderBottom: '1px solid lightGray'
          }}
        >
          Skill
        </b>
      </Typography>
      <Typography className={classes.skillSubTitle} component='h4' variant='h4'>
        <b>💪🏻 Strong</b>
      </Typography>
      <Typography className={classes.skillItem1} component='h6' variant='h6'>
        JavaScript, React, Material-ui, Node, express, MySQL, AWS EC2 / RDS
      </Typography>
      <Typography className={classes.skillSubTitle} component='h4' variant='h4'>
        <b>👀 Experience</b>
      </Typography>
      <Typography
        className={classes.skillItem2}
        color='secondary'
        component='h6'
        variant='h6'
      >
        TypeScript, Redux, AWS S3, passport, JWT, koa, MongoDB
      </Typography>
      <Typography className={classes.skillSubTitle} component='h4' variant='h4'>
        <b>⚙️ Tools</b>
      </Typography>
      <Typography className={classes.skillItem3} component='h6' variant='h6'>
        Github, Slack, Notion, VSCode, draw.io (Schema design)
      </Typography>
    </Grid>
  );
};

const useStyles = makeStyles(theme => ({
  skillTitle: {
    color: '#fff',
    marginBottom: theme.spacing(8)
  },
  skillSubTitle: {
    color: '#fff',
    marginBottom: theme.spacing(4)
  },
  skillItem1: {
    color: '#5588ff',
    fontWeight: 600,
    marginBottom: theme.spacing(4)
  },
  skillItem2: {
    fontWeight: 600,
    marginBottom: theme.spacing(4)
  },
  skillItem3: {
    color: '#55dd55',
    fontWeight: 600,
    marginBottom: theme.spacing(4)
  }
}));

export default SkillContainer;

image.png


7. Project Container

2가지 진행했던 프로젝트를 카드 형식으로 담아보겠습니다.

Material-ui의 Card를 이용하여 map으로 펼쳐주겠습니다.

// ProjectContinaer.js
/* eslint-disable jsx-a11y/accessible-emoji */
import React from 'react';
import ReactFullpage from '@fullpage/react-fullpage';
import { makeStyles } from '@material-ui/core/styles';
import './FullPage.css';
import {
  Typography,
  Link,
  Container,
  Toolbar,
  Button,
  IconButton,
  Paper,
  Grid
} from '@material-ui/core';

import Banner from './Banner';
import Footer from './Footer';
import Intro from './Intro';
import SkillContainer from './SkillContainer';
import ProjectContainer from './ProjectContainer';
import ThanksContainer from './ThanksContainer';

const FullPage = fullpageProps => {
  const classes = useStyles();
  return (
    <ReactFullpage
      {...fullpageProps}
      render={({ state, fullpageApi }) => {
        return (
          <div id='fullpage-wrapper'>
            <div className='section'>
              <Banner />
              <Intro />
              <Footer />
            </div>
            <div className='section'>
              <SkillContainer />
            </div>
            <div className='section'>
              <ProjectContainer />
            </div>
            <div className='section'>
              <ThanksContainer />
            </div>
          </div>
        );
      }}
    />
  );
};

const useStyles = makeStyles(theme => ({
  //
}));

export default FullPage;

image.png

8. Thanks Container

마지막 감사인사를 적어놓겠습니다. 사실 이름은 좀 애매했습니다 ㅎ

// ThanksContainer.js
/* eslint-disable jsx-a11y/accessible-emoji */
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import './FullPage.css';
import { Typography } from '@material-ui/core';

const ThanksContainer = () => {
  const classes = useStyles();
  return (
    <>
      <Typography className={classes.thanks} variant='h2' align='center'>
        <b>🙇🏻‍♂️ 감사합니다 🙇🏻‍♂️</b>
      </Typography>
    </>
  );
};

const useStyles = makeStyles(theme => ({
  thanks: {
    color: '#fff'
  }
}));

export default ThanksContainer;

image.png