├── .babelrc
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.tsx
├── assets
│ ├── fonts
│ │ ├── Pretendard-Black.woff2
│ │ ├── Pretendard-Bold.woff2
│ │ ├── Pretendard-ExtraBold.woff2
│ │ ├── Pretendard-ExtraLight.woff2
│ │ ├── Pretendard-Light.woff2
│ │ ├── Pretendard-Medium.woff2
│ │ ├── Pretendard-Regular.woff2
│ │ ├── Pretendard-SemiBold.woff2
│ │ └── Pretendard-Thin.woff2
│ ├── icons
│ │ ├── Company
│ │ │ ├── bag.svg
│ │ │ ├── bolt.svg
│ │ │ ├── tool.svg
│ │ │ └── write.png
│ │ ├── Home
│ │ │ ├── developer.svg
│ │ │ ├── emptyFace.svg
│ │ │ ├── graph.svg
│ │ │ ├── smileFace.svg
│ │ │ ├── web.svg
│ │ │ └── wonderFace.svg
│ │ ├── Logo
│ │ │ ├── bee.svg
│ │ │ └── logo.svg
│ │ ├── Rank
│ │ │ ├── balance.svg
│ │ │ ├── career.svg
│ │ │ ├── company.svg
│ │ │ ├── leftArrow.png
│ │ │ ├── money.svg
│ │ │ ├── rightArrow.png
│ │ │ └── total.svg
│ │ ├── Search
│ │ │ ├── readingGlasses.svg
│ │ │ ├── search1.svg
│ │ │ ├── search2.svg
│ │ │ └── search3.svg
│ │ └── Story
│ │ │ ├── blueHeart.svg
│ │ │ ├── box.svg
│ │ │ ├── check.svg
│ │ │ ├── cloud.svg
│ │ │ ├── idCard.svg
│ │ │ ├── paper.svg
│ │ │ ├── printer.svg
│ │ │ └── redHeart.png
│ └── images
│ │ ├── Auth
│ │ ├── close.svg
│ │ ├── github.svg
│ │ └── wave.svg
│ │ ├── Register
│ │ ├── bigBannerIcon1.svg
│ │ ├── bigBannerIcon2.svg
│ │ ├── middleBannerIcon.svg
│ │ ├── photo.png
│ │ ├── smallBannerIcon1.svg
│ │ ├── smallBannerIcon2.svg
│ │ └── upload.png
│ │ ├── Search
│ │ ├── semicircle1.svg
│ │ ├── semicircle2.svg
│ │ └── wave.svg
│ │ ├── Story
│ │ ├── close.svg
│ │ └── wave.svg
│ │ └── User
│ │ ├── cancel.svg
│ │ ├── del.svg
│ │ └── edit.svg
├── components
│ ├── Alumni
│ │ └── Certify
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ ├── Common
│ │ ├── Auth
│ │ │ └── SignIn
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ ├── ErrorBoundary
│ │ │ └── index.tsx
│ │ ├── Footer
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Header
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Modal
│ │ │ ├── CompanyAddress
│ │ │ │ └── index.tsx
│ │ │ ├── MyInfo
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── Search
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ ├── NotFound
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Provider
│ │ │ └── index.tsx
│ │ ├── Skeleton
│ │ │ ├── Common
│ │ │ │ └── style.ts
│ │ │ ├── CompanyDetail
│ │ │ │ ├── Review
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── Home
│ │ │ │ ├── Main
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── Recommand
│ │ │ │ │ └── index.tsx
│ │ │ │ └── UserInfo
│ │ │ │ │ └── index.tsx
│ │ │ ├── User
│ │ │ │ ├── Nav
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── StoryStatus
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ └── ViewAll
│ │ │ │ ├── All
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ │ └── Rank
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ ├── Star
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ └── StorySetUp
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ ├── Company
│ │ └── CompanyDetail
│ │ │ ├── CompanyDetailItem
│ │ │ ├── CompanyDetailInfo
│ │ │ │ ├── Content
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── Story
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── UserProfile
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── CompanyStarGrades
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ ├── Home
│ │ ├── Main
│ │ │ ├── Rank
│ │ │ │ ├── RankItem
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── Search
│ │ │ │ ├── SearchItem
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ └── index.tsx
│ │ │ └── style.ts
│ │ ├── Nav
│ │ │ ├── ExternalSite
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── UserInfo
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── index.tsx
│ │ ├── Recommand
│ │ │ ├── RecommandItem
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── index.tsx
│ │ └── style.ts
│ ├── Story
│ │ ├── Company
│ │ │ ├── Modify
│ │ │ │ ├── ModifyItem
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Register
│ │ │ │ ├── RegisterItem
│ │ │ │ │ └── index.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── Search
│ │ │ │ ├── SearchCompanyModal
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── style.ts
│ │ ├── StoryRegister
│ │ │ ├── RegisterItem
│ │ │ │ ├── PositionList
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── StarGrade
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── stye.ts
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── index.tsx
│ │ └── style.ts
│ ├── User
│ │ ├── Nav
│ │ │ ├── NavFooter
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Profile
│ │ │ ├── MyInfo
│ │ │ │ ├── NickName
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── index.tsx
│ │ ├── Story
│ │ │ ├── StoryItem
│ │ │ │ ├── CompanyContent
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── CompanyInfo
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── style.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── style.ts
│ │ │ └── index.tsx
│ │ ├── StoryStatus
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── index.tsx
│ │ └── style.ts
│ └── ViewAll
│ │ ├── All
│ │ ├── AllList
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ └── index.tsx
│ │ ├── Rank
│ │ ├── RankList
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ └── index.tsx
│ │ └── style.ts
├── constants
│ ├── Auth
│ │ └── auth.constant.ts
│ ├── Company
│ │ └── company.constant.ts
│ ├── ExternalSite
│ │ └── externalSite.constant.ts
│ ├── Footer
│ │ └── footer.constant.ts
│ ├── Header
│ │ └── header.constant.ts
│ ├── Position
│ │ └── position.constant.ts
│ ├── Router
│ │ └── router.constant.ts
│ ├── Story
│ │ └── story.constant.ts
│ └── User
│ │ └── user.constants.ts
├── hooks
│ ├── Alumni
│ │ ├── useAlumniCheck.ts
│ │ └── useCertify.ts
│ ├── Auth
│ │ ├── useLogin.ts
│ │ ├── useLogout.ts
│ │ ├── useSocialLogin.ts
│ │ └── useTokenCheck.ts
│ ├── Company
│ │ ├── useCompanyModify.ts
│ │ ├── useCompanyRegister.ts
│ │ ├── useDeleteCompany.ts
│ │ └── useSearchCompany.ts
│ ├── Header
│ │ └── useHideHeader.ts
│ ├── Invalidates
│ │ └── useQueryInvalidates.ts
│ ├── Logging
│ │ └── useLogging.ts
│ ├── NickName
│ │ └── useSetUpNickName.ts
│ └── Story
│ │ ├── index.tsx
│ │ ├── useSetUpStory.ts
│ │ ├── useStoryModify.ts
│ │ └── useStoryRegister.ts
├── index.tsx
├── libs
│ ├── Axios
│ │ ├── RollingAxios.ts
│ │ ├── requestHandler.ts
│ │ └── responseHandler.ts
│ ├── Cookie
│ │ └── cookie.ts
│ └── Token
│ │ └── Token.ts
├── pages
│ ├── AllPage
│ │ └── index.tsx
│ ├── AlumniPage
│ │ └── CertifyPage
│ │ │ └── index.tsx
│ ├── AuthPage
│ │ └── AuthLoadingPage
│ │ │ └── index.tsx
│ ├── CompanyPage
│ │ └── CompanyDetailPage
│ │ │ └── index.tsx
│ ├── HomePage
│ │ └── index.tsx
│ ├── RankPage
│ │ └── index.tsx
│ ├── StoryPage
│ │ ├── ModifyPage
│ │ │ └── Company
│ │ │ │ └── index.tsx
│ │ └── index.tsx
│ └── UserPage
│ │ └── index.tsx
├── react-app-env.d.ts
├── reportWebVitals.ts
├── router
│ └── index.tsx
├── services
│ ├── Auth
│ │ └── api.ts
│ ├── Company
│ │ ├── api.ts
│ │ ├── mutation.ts
│ │ └── queries.ts
│ ├── Employment
│ │ ├── api.ts
│ │ └── queries.ts
│ ├── File
│ │ ├── api.ts
│ │ └── mutations.ts
│ ├── Graduate
│ │ ├── api.ts
│ │ └── mutations.ts
│ ├── Logging
│ │ ├── api.ts
│ │ └── mutations.ts
│ ├── Member
│ │ ├── api.ts
│ │ ├── mutations.ts
│ │ └── queries.ts
│ ├── News
│ │ ├── api.ts
│ │ └── queries.ts
│ ├── Story
│ │ ├── api.ts
│ │ ├── mutations.ts
│ │ └── queries.ts
│ └── queryKey.ts
├── setupTests.ts
├── stores
│ ├── auth
│ │ └── auth.store.ts
│ ├── common
│ │ └── common.store.ts
│ ├── company
│ │ └── company.store.ts
│ ├── member
│ │ └── member.store.ts
│ └── story
│ │ └── story.store.ts
├── styles
│ ├── GlobalStyles.ts
│ ├── common.style.ts
│ ├── flex.ts
│ └── font.css
├── types
│ ├── Auth
│ │ └── auth.type.ts
│ ├── Company
│ │ └── company.type.ts
│ ├── Employment
│ │ └── employment.type.ts
│ ├── Member
│ │ └── member.type.ts
│ ├── News
│ │ └── news.type.ts
│ ├── StarGrade
│ │ └── starGrade.type.ts
│ ├── Story
│ │ └── story.type.ts
│ ├── common
│ │ └── commont.type.ts
│ └── logging
│ │ └── logging.type.ts
└── utils
│ ├── Auth
│ └── tokenDecode.ts
│ ├── Error
│ ├── Auth
│ │ └── authErrorHandler.ts
│ ├── Company
│ │ └── companyErrorHanlder.ts
│ ├── Employment
│ │ └── employmentErrorHanlder.ts
│ ├── File
│ │ └── fileErrorHanlder.ts
│ ├── Member
│ │ └── memberErrorHandler.ts
│ └── Story
│ │ └── storyErrorHanlder.ts
│ ├── Logging
│ └── changeUrlToLoggingText.ts
│ ├── Modal
│ └── turnOnOffModal.ts
│ ├── Position
│ └── searchPosition.ts
│ ├── Rank
│ ├── getCompanyRankIntroduce.ts
│ ├── getRankIcon.ts
│ └── getSlideRankBoxWidth.ts
│ ├── Rgb
│ └── getRgb.ts
│ ├── StarRating
│ ├── convertRankingObject.ts
│ └── tieStarGradeToObject.ts
│ ├── Story
│ └── storyItemsObject.ts
│ └── github
│ └── convertToGithubLink.ts
├── tsconfig.json
├── webpack.common.js
├── webpack.dev.js
└── webpack.prod.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | { "targets": { "browsers": ["last 2 versions", ">= 5% in KR"] } }
6 | ],
7 | "@babel/react"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 | config.json
11 |
12 | # production
13 | /build
14 | dist
15 | # misc
16 | .DS_Store
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
학생들을 위한 기업정보 공유 서비스, Rolling
4 |
5 |
6 |
7 |
8 |
9 | # [Rolling](https://rolling.stubee.kr)에 대해 정확하게 알려줄게요!
10 |
11 | 학생 구직자들이 취업을 하는데에 있어서 가장 힘든점은 기업정보인데요. 어떤 정보를 모아야할지 몰라서, 물어볼 선배가 없어서 등등의 이유와 입학, 학생관리는 동아리 내에서 운영 중이지만 가장 중요한 취업은 외부 서비스에 의존하고 있었어요. 또한 학생들을 위한 취업플랫폼 서비스는 존재하고 있지 않았기에 이 문제를 해소하고자 롤링을 만들게 되었어요.
12 |
13 | > 현재는 대소고 학생들(재학생, 졸업생) 한정으로만 사용할 수 있어요.
14 |
15 |
16 |
17 |
18 | # 롤링에서 이러한 기능들이 있어요!
19 |
20 | - Github 소셜 로그인을 지원하고 있어요.
21 | - 사감 선생님 이름으로 DGSW 동문인증하여 사용이 가능해요.
22 | - 학생들이 궁금해하는 정보(연봉, 워라벨, 조직문화, 커리어 향상, 총합)를 랭킹으로 보여줘요.
23 | - 졸업생들이 재직 중이거나 퇴직한 회사를 직접 등록하고 리뷰를 달 수 있어요.
24 | - 등록되어있는 회사들을 전체 조회 가능하고 단일 조회 또한 가능해요.
25 |
26 |
27 |
28 |
29 |
30 | # 기술 스택
31 | `React.js`, `TypeScript`, `React-Query`, `webpack`,
32 | `styled-components`, `Recoil`, `Axios`
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 | 롤링 Rolling
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { QueryClient, QueryClientProvider } from "react-query";
2 | import { BrowserRouter } from "react-router-dom";
3 | import { RecoilRoot } from "recoil";
4 | import Provider from "./components/Common/Provider";
5 | import Router from "./router";
6 | import { RollingToastProvider } from "@stubee2/stubee2-rolling-toastify";
7 |
8 | function App() {
9 | const queryClient: QueryClient = new QueryClient({
10 | defaultOptions: {
11 | queries: {
12 | useErrorBoundary: true,
13 | },
14 | },
15 | });
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-Black.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-Bold.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-ExtraBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-ExtraBold.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-ExtraLight.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-Light.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-Medium.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-Regular.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-SemiBold.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Pretendard-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/fonts/Pretendard-Thin.woff2
--------------------------------------------------------------------------------
/src/assets/icons/Company/write.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/icons/Company/write.png
--------------------------------------------------------------------------------
/src/assets/icons/Rank/leftArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/icons/Rank/leftArrow.png
--------------------------------------------------------------------------------
/src/assets/icons/Rank/rightArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/icons/Rank/rightArrow.png
--------------------------------------------------------------------------------
/src/assets/icons/Search/search1.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/Search/search2.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/Search/search3.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/Story/redHeart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/icons/Story/redHeart.png
--------------------------------------------------------------------------------
/src/assets/images/Auth/close.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/Auth/wave.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/Register/bigBannerIcon1.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/images/Register/bigBannerIcon2.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/images/Register/middleBannerIcon.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/images/Register/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/images/Register/photo.png
--------------------------------------------------------------------------------
/src/assets/images/Register/smallBannerIcon1.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/images/Register/smallBannerIcon2.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/assets/images/Register/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StuBee2/Rolling_Frontend/4d6690ac8011407da9e802188c478b309e543797/src/assets/images/Register/upload.png
--------------------------------------------------------------------------------
/src/assets/images/Search/semicircle1.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/assets/images/Search/semicircle2.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/images/Search/wave.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/Story/close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/Story/wave.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/User/cancel.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/assets/images/User/del.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/assets/images/User/edit.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/Alumni/Certify/index.tsx:
--------------------------------------------------------------------------------
1 | import { useCertify } from "@src/hooks/Alumni/useCertify";
2 | import * as S from "./style";
3 | import { Button } from "@stubee2/stubee2-rolling-ui";
4 | import { tokenDecode } from "@src/utils/Auth/tokenDecode";
5 | import { useEffect } from "react";
6 | import { Column } from "@src/styles/flex";
7 |
8 | const Certify = () => {
9 | const { ...attr } = useCertify();
10 | const memberRole = tokenDecode("access", "authority");
11 |
12 | useEffect(() => {
13 | if (memberRole === "MEMBER") {
14 | attr.rollingToast("동문 인증이 이미 되어있습니다.", "info");
15 | attr.navigate("/");
16 | }
17 | }, []);
18 |
19 | return (
20 |
21 |
27 |
28 | That's 동문인증
29 |
30 | 동문인증을 하여 더 다양한 서비스를 즐겨보세요!
31 |
32 |
33 |
34 |
35 |
36 | DGSW 동문 인증
37 |
38 |
39 | 해당 절차는 DGSW 동문 인증하여 서비스를 제공하기 위해
40 | 이루어집니다.
41 |
42 | 동문 인증으로 롤링의 더 많은 기능을 활성화하세요.
43 |
44 |
45 | attr.handleGraduateCertified(e)}
47 | >
48 |
49 |
50 | Q. DGSW 사감선생님 중, 한 분의 성함을 입력해주세요.
51 |
52 |
53 |
58 | 선생님
59 |
60 |
61 |
62 |
65 |
66 |
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default Certify;
74 |
--------------------------------------------------------------------------------
/src/components/Common/Auth/SignIn/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import { useEscCloseModal } from "@stubee2/stubee2-rolling-util";
3 | import { useSetRecoilState } from "recoil";
4 | import { SignInModalAtom } from "@src/stores/auth/auth.store";
5 | import wave from "@src/assets/images/Auth/wave.svg";
6 | import close from "@src/assets/images/Auth/close.svg";
7 | import github from "@src/assets/images/Auth/github.svg";
8 | import { gitSignInUrl } from "@src/constants/Auth/auth.constant";
9 | import { turnOnOffModal } from "@src/utils/Modal/turnOnOffModal";
10 |
11 | const SignIn = () => {
12 | const setSignInModal = useSetRecoilState(SignInModalAtom);
13 | useEscCloseModal(setSignInModal);
14 | return (
15 | turnOnOffModal(setSignInModal, "off")}>
16 | e.stopPropagation()}>
17 | turnOnOffModal(setSignInModal, "off")}
20 | alt="이미지 없음"
21 | />
22 |
23 | 시작하기
24 |
25 |
26 | 지금 로그인하고 맞춤 롤링 콘텐츠를 활성화하세요.
27 | 수 많은 선배들의 이야기가 롤링에서 펼쳐집니다.
28 |
29 |
30 | (window.location.href = gitSignInUrl)}>
31 |
32 | Github 간편 로그인
33 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default SignIn;
42 |
--------------------------------------------------------------------------------
/src/components/Common/ErrorBoundary/index.tsx:
--------------------------------------------------------------------------------
1 | import { Component, ErrorInfo, ReactNode } from "react";
2 |
3 | interface Props {
4 | children: ReactNode;
5 | fallback: ReactNode;
6 | }
7 |
8 | interface State {
9 | hasError: boolean;
10 | }
11 |
12 | class ErrorBoundary extends Component {
13 | public state: State = {
14 | hasError: false,
15 | };
16 |
17 | public static getDerivedStateFromError(_: Error): State {
18 | return { hasError: true };
19 | }
20 |
21 | public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
22 | console.error("Uncaught error:", error, errorInfo);
23 | }
24 |
25 | public render() {
26 | if (this.state.hasError) {
27 | return this.props.fallback;
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/src/components/Common/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import logo from "@src/assets/icons/Logo/logo.svg";
3 | import {
4 | FOOTER_ITEMS,
5 | FOOTER_MEMBERS_ITEMS,
6 | } from "@src/constants/Footer/footer.constant";
7 | import { Row } from "@src/styles/flex";
8 |
9 | export const Footer = () => {
10 | return (
11 |
12 |
13 |
14 |
15 | (window.location.href = "/")}>
16 |
17 | Rolling
18 |
19 |
20 | Rolling은 직업계고 학생들을 돕기 위한 졸업생 취업 정보 리뷰
21 | 서비스입니다.
22 |
23 |
24 |
25 |
26 | {FOOTER_ITEMS.map((item) => (
27 | window.open(item.link, "_blank")}
30 | >
31 | {item.name}
32 |
33 | ))}
34 |
35 |
36 |
37 |
38 |
39 | Team StuBee
40 |
41 | {FOOTER_MEMBERS_ITEMS.map((item) => (
42 | window.open(item.link, "_blank")}
45 | >
46 | {item.name}
47 |
48 | ))}
49 |
50 |
51 |
52 | 주소: 대구 달성군 구지면 창리로11길 93
53 | (대구소프트웨어마이스터고등학교)
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default Footer;
62 |
--------------------------------------------------------------------------------
/src/components/Common/Modal/CompanyAddress/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEscCloseModal } from "@stubee2/stubee2-rolling-util";
2 | import { Dispatch, SetStateAction } from "react";
3 | import DaumPostcodeEmbed from "react-daum-postcode";
4 | import * as S from "../../../Story/Company/style";
5 | import { turnOnOffModal } from "@src/utils/Modal/turnOnOffModal";
6 | import { CompanyParam } from "@src/services/Company/api";
7 |
8 | interface Props {
9 | setIsOpenModal: Dispatch>;
10 | companyInfo: string;
11 | setCompanyInfo: Dispatch>;
12 | }
13 |
14 | const CompanyAddressModal = ({ ...attr }: Props) => {
15 | useEscCloseModal(attr.setIsOpenModal);
16 |
17 | const handleSelectAddress = (data: { address: string }) => {
18 | attr.setCompanyInfo((prev) => ({
19 | ...prev,
20 | address: data.address,
21 | }));
22 | turnOnOffModal(attr.setIsOpenModal, "off");
23 | };
24 |
25 | return (
26 | turnOnOffModal(attr.setIsOpenModal, "off")}
28 | >
29 |
36 |
37 | );
38 | };
39 |
40 | export default CompanyAddressModal;
41 |
--------------------------------------------------------------------------------
/src/components/Common/Modal/MyInfo/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const Container = styled.div`
6 | width: 100%;
7 | height: 100vh;
8 |
9 | ${Flex({ alignItems: "center", justifyContent: "center" })}
10 |
11 | position: fixed;
12 | top: 0;
13 | left: 0;
14 | z-index: 10;
15 | `;
16 |
17 | export const Wrapper = styled.div`
18 | width: 1090px;
19 | height: calc(100% - 75px);
20 |
21 | position: absolute;
22 | bottom: 0;
23 |
24 | @media screen and (max-width: 1105px) {
25 | width: 100%;
26 | }
27 | `;
28 |
29 | export const MyInfoBox = styled.div`
30 | width: 272px;
31 | height: 181px;
32 | background-color: ${RollingPalette.text.Dark};
33 | z-index: 3;
34 | border-radius: 15px;
35 | position: absolute;
36 | top: 0;
37 | right: 5px;
38 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
39 | `;
40 |
41 | export const ProfileContainer = styled.div`
42 | width: 100%;
43 | height: 52%;
44 |
45 | border-radius: 15px 15px 0 0;
46 | border-bottom: 2px solid rgba(222, 224, 230, 0.3);
47 |
48 | padding-left: 20px;
49 |
50 | img {
51 | width: 51px;
52 | height: 51px;
53 | border-radius: 10px;
54 | }
55 |
56 | ${Flex({ alignItems: "center", columnGap: "10px" })}
57 | `;
58 |
59 | export const Name = styled.p`
60 | color: #ffffff;
61 | font-size: 14px;
62 | font-weight: bold;
63 | `;
64 |
65 | export const GitInfo = styled.div`
66 | color: ${RollingPalette.main.Base};
67 | cursor: pointer;
68 |
69 | ${Flex({ columnGap: "5px", alignItems: "center" })}
70 |
71 | font-size: 14px;
72 | img {
73 | width: 15px;
74 | height: 15px;
75 | }
76 | `;
77 |
78 | export const MyPageLogout = styled.div`
79 | width: 100%;
80 | height: 48%;
81 |
82 | border-radius: 0 0 15px 15px;
83 | color: #ffffff;
84 | font-size: 14px;
85 | padding-left: 20px;
86 |
87 | ${Flex({ flexDirection: "column", justifyContent: "center", rowGap: "13px" })}
88 |
89 | div {
90 | ${Flex({ columnGap: "7px", alignItems: "center" })}
91 | cursor: pointer;
92 |
93 | img {
94 | width: 17px;
95 | height: 17px;
96 | }
97 | }
98 | `;
99 |
--------------------------------------------------------------------------------
/src/components/Common/Modal/Search/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import search1 from "@src/assets/icons/Search/search1.svg";
3 | import wave from "@src/assets/images/Search/wave.svg";
4 | import { useSearchCompany } from "@src/hooks/Company/useSearchCompany";
5 | import { turnOnOffModal } from "@src/utils/Modal/turnOnOffModal";
6 | import { useEscCloseModal } from "@stubee2/stubee2-rolling-util";
7 |
8 | const Search = () => {
9 | const { handleKeywordChange, handleKeywordSubmit, keyword, setSearchModal } =
10 | useSearchCompany();
11 | useEscCloseModal(setSearchModal);
12 |
13 | return (
14 | turnOnOffModal(setSearchModal, "off")}>
15 | e.stopPropagation()}>
16 |
17 |
20 |
21 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default Search;
36 |
--------------------------------------------------------------------------------
/src/components/Common/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigate } from "react-router-dom";
2 | import useHideHeader from "@src/hooks/Header/useHideHeader";
3 | import * as S from "./style";
4 | import { Row } from "@src/styles/flex";
5 |
6 | const NotFound = () => {
7 | const navigate = useNavigate();
8 | useHideHeader();
9 | return (
10 |
16 |
17 | 404
18 | 페이지를 찾을 수 없습니다.
19 | navigate("/")}>홈으로
20 |
21 |
22 | );
23 | };
24 |
25 | export default NotFound;
26 |
--------------------------------------------------------------------------------
/src/components/Common/NotFound/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const ErrorBox = styled.div`
6 | width: 50vh;
7 |
8 | margin: 0 10px 0 10px;
9 | font-weight: bold;
10 |
11 | ${Flex({
12 | flexDirection: "column",
13 | alignItems: "center",
14 | justifyContent: "center",
15 | rowGap: "15px",
16 | })}
17 | `;
18 |
19 | export const ErrorText = styled.p`
20 | font-size: 100px;
21 | `;
22 |
23 | export const GoHomeBtn = styled.button`
24 | width: 100%;
25 | height: 50px;
26 | color: #ffffff;
27 | background-color: ${RollingPalette.main.Dark};
28 | border-radius: 10px;
29 | outline: none;
30 | border: none;
31 | cursor: pointer;
32 |
33 | transform: scale(1);
34 | transition: all 0.1s ease-in-out;
35 | &:hover {
36 | background-color: rgba(28, 31, 91, 0.91);
37 | transform: scale(0.98);
38 | }
39 | &:active {
40 | background-color: rgba(28, 31, 91, 0.93);
41 | }
42 | `;
43 |
--------------------------------------------------------------------------------
/src/components/Common/Provider/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 | import GlobalStyle from "@src/styles/GlobalStyles";
3 | import Header from "../Header/index";
4 | import { useRecoilValue } from "recoil";
5 | import { HideHeader } from "@src/stores/common/common.store";
6 |
7 | interface Props {
8 | children: ReactNode;
9 | }
10 |
11 | const Provider = ({ children }: Props) => {
12 | const hideHeader = useRecoilValue(HideHeader);
13 | return (
14 | <>
15 |
16 | {!hideHeader && }
17 | <>{children}>
18 | >
19 | );
20 | };
21 |
22 | export default Provider;
23 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/Common/style.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { skeletonAnimation } from "@stubee2/stubee2-rolling-styled-components-util";
3 |
4 | export const SkeletonBox = styled.div<{
5 | width: string;
6 | height: string;
7 | }>`
8 | width: ${({ width }) => width};
9 | height: ${({ height }) => height};
10 | border-radius: 5px;
11 | ${skeletonAnimation}
12 | `;
13 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/CompanyDetail/Review/index.tsx:
--------------------------------------------------------------------------------
1 | import { SkeletonBox } from "../../Common/style";
2 | import { Column } from "@src/styles/flex";
3 |
4 | const StorySkeleton = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | {Array.from({ length: 10 }).map((_, idx) => (
12 |
13 | ))}
14 |
15 | );
16 | };
17 |
18 | export default StorySkeleton;
19 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/CompanyDetail/index.tsx:
--------------------------------------------------------------------------------
1 | import { Column, Row } from "@src/styles/flex";
2 | import { SkeletonBox } from "../Common/style";
3 | import * as S from "./style";
4 |
5 | const CompanyDetailSkeleton = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default CompanyDetailSkeleton;
33 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/CompanyDetail/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { skeletonAnimation } from "@stubee2/stubee2-rolling-styled-components-util";
3 | import styled from "styled-components";
4 |
5 | export const CompanySkeletonContainer = styled.div`
6 | width: 100%;
7 | height: 100%;
8 | display: flex;
9 |
10 | @media screen and (max-width: 1005px) {
11 | flex-direction: column;
12 | row-gap: 2rem;
13 | }
14 | `;
15 |
16 | export const CompanySkeletonStarGrade = styled.div`
17 | width: 250px;
18 | height: 100%;
19 |
20 | ${Flex({ flexDirection: "column", rowGap: "2rem", alignItems: "center" })}
21 |
22 | @media screen and (max-width: 1005px) {
23 | width: 100%;
24 | height: 200px;
25 | flex-direction: row;
26 | row-gap: 0;
27 | column-gap: 3rem;
28 | }
29 | `;
30 |
31 | export const CompanyStarGradeSkeleton = styled.div`
32 | width: 100%;
33 | height: 500px;
34 | border-radius: 10px;
35 | ${skeletonAnimation};
36 | @media screen and (max-width: 1005px) {
37 | height: 160px;
38 | }
39 | `;
40 |
41 | export const CompanySkeletonContent = styled.div`
42 | width: calc(100% - 250px);
43 | height: 100%;
44 |
45 | ${Flex({ flexDirection: "column", rowGap: "3rem" })}
46 | padding-left: 5rem;
47 | @media screen and (max-width: 1005px) {
48 | width: 100%;
49 | padding-left: 0;
50 | }
51 | `;
52 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/Home/Main/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { SkeletonBox } from "../../Common/style";
3 | import * as S from "./style";
4 | import { Column, Flex } from "@src/styles/flex";
5 |
6 | const MainSkeleton = () => {
7 | return (
8 |
9 |
10 |
11 |
12 | {Array.from({ length: 9 }).map((_, idx) => (
13 |
14 | ))}
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default MainSkeleton;
22 |
23 | const Row = styled.div`
24 | width: 100%;
25 | height: 100%;
26 | ${Flex({ flexWrap: "wrap", gap: "1.5rem" })}
27 | `;
28 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/Home/Main/style.ts:
--------------------------------------------------------------------------------
1 | import { skeletonAnimation } from "@stubee2/stubee2-rolling-styled-components-util";
2 | import styled from "styled-components";
3 |
4 | export const MainItemSkeletonBox = styled.div`
5 | width: 330px;
6 | height: 300px;
7 | border: 1px solid #ddd;
8 |
9 | border-radius: 10px;
10 | overflow: hidden;
11 |
12 | @media screen and (max-width: 1385px) {
13 | flex-basis: 47%;
14 | height: 340px;
15 | }
16 | @media screen and (max-width: 1105px) {
17 | flex-basis: 31%;
18 | height: 330px;
19 | }
20 | @media screen and (max-width: 900px) {
21 | flex-basis: 48%;
22 | }
23 | @media screen and (max-width: 533px) {
24 | flex-basis: auto;
25 | width: 100%;
26 | height: 380px;
27 | }
28 |
29 | ${skeletonAnimation};
30 | `;
31 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/Home/Recommand/index.tsx:
--------------------------------------------------------------------------------
1 | import { RecommandItemContainer } from "@src/components/Home/Recommand/RecommandItem/style";
2 | import { SkeletonBox } from "../../Common/style";
3 | import { Row } from "@src/styles/flex";
4 |
5 | const RecommandSkeleton = () => {
6 | return (
7 |
8 |
13 | {Array.from({ length: 10 }).map((_, idx) => (
14 |
15 | ))}
16 |
17 |
18 | );
19 | };
20 |
21 | export default RecommandSkeleton;
22 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/Home/UserInfo/index.tsx:
--------------------------------------------------------------------------------
1 | import { Column } from "@src/styles/flex";
2 | import { SkeletonBox } from "../../Common/style";
3 |
4 | const UserInfoSkeleton = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | export default UserInfoSkeleton;
17 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/User/Nav/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | PageSelectContainer,
3 | PageSelectWrapper,
4 | UserNavBar,
5 | } from "@src/components/User/Nav/style";
6 | import { SkeletonBox } from "../../Common/style";
7 | import * as S from "./style";
8 | import { Column } from "@src/styles/flex";
9 |
10 | const NavSkeleton = () => {
11 | return (
12 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {Array.from({ length: 2 }).map((item, idx) => (
29 |
30 | ))}
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default NavSkeleton;
39 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/User/Nav/style.ts:
--------------------------------------------------------------------------------
1 | import { skeletonAnimation } from "@stubee2/stubee2-rolling-styled-components-util";
2 | import styled from "styled-components";
3 |
4 | export const NavProfileLogoSkeleton = styled.div`
5 | width: 115px;
6 | height: 115px;
7 | border-radius: 100%;
8 | ${skeletonAnimation}
9 | `;
10 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/User/StoryStatus/index.tsx:
--------------------------------------------------------------------------------
1 | import { SkeletonBox } from "../../Common/style";
2 |
3 | const StoryStatusSkeleton = () => {
4 | return ;
5 | };
6 |
7 | export default StoryStatusSkeleton;
8 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/User/index.tsx:
--------------------------------------------------------------------------------
1 | import { Column } from "@src/styles/flex";
2 | import { SkeletonBox } from "../Common/style";
3 |
4 | const UserSkeleton = () => {
5 | return (
6 |
7 | {Array.from({
8 | length: 5,
9 | }).map((item, idx) => (
10 |
11 | ))}
12 |
13 | );
14 | };
15 |
16 | export default UserSkeleton;
17 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/ViewAll/All/index.tsx:
--------------------------------------------------------------------------------
1 | import { Wrapper } from "@src/components/ViewAll/All/AllList/style";
2 | import { CompanyContent } from "@src/components/ViewAll/style";
3 | import { SkeletonBox } from "../../Common/style";
4 | import * as S from "./style";
5 | import { Column } from "@src/styles/flex";
6 |
7 | const AllListSkeleton = () => {
8 | return (
9 |
10 |
11 | {Array.from({ length: 20 }).map((_, idx) => (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ))}
22 |
23 |
24 | );
25 | };
26 |
27 | export default AllListSkeleton;
28 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/ViewAll/All/style.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const AllSkeletonBox = styled.div`
4 | width: 282px;
5 | height: 320px;
6 |
7 | @media screen and (max-width: 985px) {
8 | flex-basis: 31.5%;
9 | }
10 | @media screen and (max-width: 645px) {
11 | flex-basis: 48%;
12 | height: 300px;
13 | }
14 | @media screen and (max-width: 500px) {
15 | flex-basis: 47.6%;
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/ViewAll/Rank/index.tsx:
--------------------------------------------------------------------------------
1 | import { Wrapper } from "@src/components/ViewAll/Rank/RankList/style";
2 | import { CompanyContent } from "@src/components/ViewAll/style";
3 | import { SkeletonBox } from "../../Common/style";
4 | import * as S from "./style";
5 | import { Column, Row } from "@src/styles/flex";
6 |
7 | const RankSkeleton = () => {
8 | return (
9 | <>
10 | {Array.from({ length: 5 }).map((_, idx) => (
11 |
12 |
13 |
14 |
15 | {Array.from({ length: 10 }).map((_, idx) => (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ))}
26 |
27 |
28 |
29 | ))}
30 | >
31 | );
32 | };
33 |
34 | export default RankSkeleton;
35 |
--------------------------------------------------------------------------------
/src/components/Common/Skeleton/ViewAll/Rank/style.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const RankSkeletonBox = styled.div`
4 | width: 285px;
5 | height: 320px;
6 |
7 | @media screen and (max-width: 985px) {
8 | flex-basis: 31.5%;
9 | }
10 | @media screen and (max-width: 645px) {
11 | flex-basis: 48%;
12 | width: 267px;
13 | height: 300px;
14 | }
15 | @media screen and (max-width: 500px) {
16 | flex-basis: 47.6%;
17 | width: 223px;
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/src/components/Common/Star/index.tsx:
--------------------------------------------------------------------------------
1 | import { StarGradeProps } from "@src/types/StarGrade/starGrade.type";
2 | import { StarRating } from "@stubee2/stubee2-rolling-ui";
3 | import * as S from "./style";
4 | import { Column, Row } from "@src/styles/flex";
5 |
6 | const Star = ({ rankStatus, fontSize, ...attr }: StarGradeProps) => {
7 | return (
8 |
9 | {rankStatus.map((item) => (
10 |
11 |
12 | {item.title}
13 | · {item.star}점
14 |
15 |
16 |
17 |
18 |
19 |
20 | ))}
21 |
22 | );
23 | };
24 |
25 | export default Star;
26 |
--------------------------------------------------------------------------------
/src/components/Common/Star/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import styled from "styled-components";
3 |
4 | export const StarContainer = styled.div<{ fontSize?: string }>`
5 | width: 100%;
6 | height: 140px;
7 |
8 | background-color: rgba(242, 244, 245, 1);
9 | border-radius: 10px;
10 | font-size: ${({ fontSize }) => fontSize};
11 |
12 | ${Flex({
13 | alignItems: "center",
14 | justifyContent: "space-between",
15 | columnGap: "1.5rem",
16 | })}
17 |
18 | font-size: 16px;
19 |
20 | padding: 0 3rem 0 3rem;
21 | white-space: nowrap;
22 | background-color: red;
23 |
24 | overflow-x: auto;
25 | overflow-y: hidden;
26 | background-color: #f2f4f5;
27 | `;
28 |
29 | export const StarGradeText = styled.p`
30 | font-weight: 800;
31 | `;
32 |
33 | export const StarRatingContainer = styled.p`
34 | text-align: center;
35 | `;
36 |
--------------------------------------------------------------------------------
/src/components/Common/StorySetUp/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import { BiDotsVerticalRounded } from "@react-icons/all-files/bi/BiDotsVerticalRounded";
3 | import { USER_STORY_SETUP_ITEMS } from "@src/constants/User/user.constants";
4 | import { useSetUpStory } from "@src/hooks/Story/useSetUpStory";
5 |
6 | interface Props {
7 | storyId: string;
8 | companyId: string;
9 | isCoincideStoryId: boolean;
10 | }
11 |
12 | const StorySetUp = ({ ...attr }: Props) => {
13 | const { ...hooks } = useSetUpStory();
14 | const { storyId, companyId, isCoincideStoryId } = attr;
15 |
16 | return (
17 | <>
18 | {hooks.isClickDots && isCoincideStoryId ? (
19 | <>
20 | {USER_STORY_SETUP_ITEMS.map((item) => (
21 |
25 | hooks.hanldeStorySetUpClick(item.id, storyId, companyId)
26 | }
27 | alt="이미지 없음"
28 | />
29 | ))}
30 | >
31 | ) : (
32 |
33 | {
37 | hooks.setIsClickDots(true);
38 | hooks.setModifyStoryId(storyId);
39 | }}
40 | />
41 |
42 | )}
43 | >
44 | );
45 | };
46 |
47 | export default StorySetUp;
48 |
--------------------------------------------------------------------------------
/src/components/Common/StorySetUp/style.ts:
--------------------------------------------------------------------------------
1 | import { HoverAnimation } from "@src/styles/common.style";
2 | import styled from "styled-components";
3 |
4 | export const Icon = styled.img`
5 | cursor: pointer;
6 | width: 30px;
7 | height: 30px;
8 | `;
9 |
10 | export const SetUpIconContainer = styled.div`
11 | width: 30px;
12 | height: 30px;
13 | ${HoverAnimation};
14 | `;
15 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyDetailInfo/Content/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import bag from "@src/assets/icons/Company/bag.svg";
3 | import tool from "@src/assets/icons/Company/tool.svg";
4 | import { CompanyInfoType } from "@src/types/Company/company.type";
5 | import logo from "@src/assets/icons/Logo/logo.svg";
6 | import { getDateText } from "@stubee2/stubee2-rolling-util";
7 | import { CompanyDetailRegistAt } from "../style";
8 | import { getRgb } from "@src/utils/Rgb/getRgb";
9 | import { Column, Row } from "@src/styles/flex";
10 |
11 | const CompanyDetailContent = ({ ...attr }: CompanyInfoType) => {
12 | return (
13 |
14 |
15 |
16 |
17 | 기본정보
18 |
19 |
20 | 이 정보는 졸업생들이 작성한 데이터를 기반으로 만들어지고 있어요!
21 |
22 |
23 |
24 |
25 |
26 | {getDateText(new Date(attr.companyCreatedAt))} 작성
27 |
28 |
29 |
30 |
31 |
32 | {attr.companyName}
33 |
34 | {attr.companyAddress + (" " + (attr.companyAddressEtc || ""))}
35 |
36 |
37 |
38 |
39 |
40 |
41 | {attr.companyDescription}
42 |
43 |
44 |
45 | {/* 아래는 추후에 추가될 기능 */}
46 | {/*
47 | {Array.from({ length: 10 }).map((_, idx) => (
48 |
53 | ))}
54 | */}
55 |
56 | );
57 | };
58 |
59 | export default CompanyDetailContent;
60 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyDetailInfo/Content/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const Image = styled.img`
6 | width: 30px;
7 | height: 30px;
8 | `;
9 |
10 | export const BasicInfo = styled.p`
11 | margin-top: 5px;
12 | font-size: 25px;
13 | font-weight: 700;
14 | `;
15 |
16 | export const SubTitle = styled.p`
17 | color: ${RollingPalette.unEmphasize.Dark};
18 | font-size: 15px;
19 | font-weight: 600;
20 | `;
21 |
22 | export const InfoContainer = styled.div`
23 | width: 100%;
24 | padding-bottom: 20px;
25 | ${Flex({ flexDirection: "column", rowGap: "15px" })}
26 | border-bottom: 3px solid ${RollingPalette.unEmphasize.Light};
27 | `;
28 |
29 | export const Info = styled.div<{ backgroundColor: string }>`
30 | width: 100%;
31 | ${Flex({ alignItems: "center", columnGap: "15px" })}
32 | img {
33 | width: 75px;
34 | height: 75px;
35 | border-radius: 5px;
36 | object-fit: contain;
37 | border: 1px solid #dddddd;
38 | background-color: ${({ backgroundColor }) => backgroundColor || "#fff"};
39 | }
40 | `;
41 |
42 | export const CompanyName = styled.p`
43 | color: #090a0a;
44 | font-size: 25px;
45 | font-weight: 700;
46 | `;
47 |
48 | export const CompanyAddress = styled.p`
49 | color: ${RollingPalette.unEmphasize.Dark};
50 | font-size: 15px;
51 | font-weight: 500;
52 | `;
53 |
54 | export const Description = styled.div`
55 | width: 100%;
56 | line-height: 25px;
57 | font-size: 16px;
58 | font-weight: 600;
59 |
60 | ${Flex({ columnGap: "5px" })}
61 |
62 | img {
63 | width: 22px;
64 | height: 22px;
65 | }
66 | p {
67 | width: 100%;
68 | font-size: 18px;
69 | max-height: 200px;
70 | white-space: pre-wrap;
71 | overflow-y: auto;
72 | padding-left: 10px;
73 | }
74 | `;
75 |
76 | export const CompanyImgContainer = styled.div`
77 | overflow-x: auto;
78 | padding-bottom: 15px;
79 | ${Flex({ columnGap: "10px" })}
80 | img {
81 | width: 350px;
82 | height: 200px;
83 | object-fit: cover;
84 | border-radius: 5px;
85 | border: 1px solid #dddddd;
86 | }
87 | `;
88 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyDetailInfo/Story/index.tsx:
--------------------------------------------------------------------------------
1 | import StoryItem from "@src/components/User/Story/StoryItem";
2 | import { useEffect } from "react";
3 | import { useInView } from "react-intersection-observer";
4 | import * as S from "./style";
5 | import { useGetStoryListCompanyIdQuery } from "@src/services/Story/queries";
6 | import { Column } from "@src/styles/flex";
7 |
8 | interface Props {
9 | companyId: string;
10 | }
11 |
12 | const CompanyDetailStory = ({ companyId }: Props) => {
13 | const { data: storyList, fetchNextPage } = useGetStoryListCompanyIdQuery(
14 | {
15 | id: companyId,
16 | },
17 | { suspense: true }
18 | );
19 | const [ref, inView] = useInView();
20 | const storyListData = storyList?.pages[0].data;
21 |
22 | useEffect(() => {
23 | if (inView) {
24 | fetchNextPage();
25 | }
26 | }, [inView]);
27 |
28 | return (
29 |
30 |
31 | 졸업생들의 롤링 Story ·
32 | {storyListData?.length}
33 |
34 |
35 | {storyListData?.length!! > 0 ? (
36 | storyList?.pages.map((list) =>
37 | list.data.map((item) => (
38 |
39 | ))
40 | )
41 | ) : (
42 | 등록된 스토리가 없습니다.
43 | )}
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default CompanyDetailStory;
51 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyDetailInfo/Story/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const StoryTitle = styled.div`
6 | font-size: 30px;
7 | font-weight: 700;
8 | ${Flex({ alignItems: "center", columnGap: "5px" })}
9 | `;
10 |
11 | export const StoryCount = styled.p`
12 | color: ${RollingPalette.main.Base};
13 | `;
14 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyDetailInfo/UserProfile/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import github from "@src/assets/images/Auth/github.svg";
3 | import { getDateText } from "@stubee2/stubee2-rolling-util";
4 | import { convertToGithubLink } from "@src/utils/github/convertToGithubLink";
5 | import { CompanyDetailRegistAt } from "../style";
6 |
7 | interface Props {
8 | memberImageUrl: string;
9 | memberNickName: string;
10 | memberSocialLoginId: string;
11 | companyCreatedAt: string;
12 | }
13 |
14 | const CompanyDetailUserProfile = ({ ...attr }: Props) => {
15 | const userName = attr.memberNickName || attr.memberSocialLoginId;
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {userName}
25 |
27 | window.open(
28 | convertToGithubLink(attr.memberSocialLoginId),
29 | "_blank"
30 | )
31 | }
32 | >
33 |
34 | {attr.memberSocialLoginId}
35 |
36 |
37 |
38 |
39 |
40 | {getDateText(new Date(attr.companyCreatedAt))} 작성
41 |
42 |
43 | );
44 | };
45 |
46 | export default CompanyDetailUserProfile;
47 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyDetailInfo/UserProfile/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const Container = styled.div`
6 | width: 100%;
7 | height: 50px;
8 | position: relative;
9 | ${Flex({ alignItems: "center", justifyContent: "flex-end" })}
10 | `;
11 |
12 | export const Wrapper = styled.div`
13 | position: absolute;
14 | top: 0;
15 | left: 0;
16 | ${Flex({ alignItems: "center" })}
17 | `;
18 |
19 | export const ImgContainer = styled.div`
20 | width: 130px;
21 | height: 130px;
22 | border-radius: 100%;
23 |
24 | ${Flex({ alignItems: "center", justifyContent: "center" })}
25 |
26 | background-color: #fff;
27 | z-index: 1;
28 |
29 | position: absolute;
30 | left: 0;
31 | border: 1px solid #d9d9d9;
32 |
33 | img {
34 | width: 105px;
35 | height: 105px;
36 | border-radius: 100%;
37 | object-fit: cover;
38 | }
39 | `;
40 |
41 | export const Content = styled.div<{
42 | nameLength: number;
43 | }>`
44 | width: ${({ nameLength }) => (nameLength >= 13 ? "335px" : "300px")};
45 | height: 105px;
46 |
47 | padding-right: ${({ nameLength }) => nameLength >= 13 && "15px"};
48 | background-color: ${RollingPalette.main.Base};
49 | border-radius: 5px;
50 |
51 | position: absolute;
52 | left: 3.5rem;
53 |
54 | ${Flex({ flexDirection: "column", rowGap: "12px", justifyContent: "center" })}
55 |
56 | padding-left: 5.5rem;
57 | white-space: nowrap;
58 | border: 1px solid #d9d9d9;
59 | `;
60 |
61 | export const NickName = styled.p`
62 | color: #f9fafb;
63 | font-size: 18px;
64 | font-weight: 700;
65 | `;
66 |
67 | export const GithubId = styled.div`
68 | color: ${RollingPalette.unEmphasize.Base};
69 | font-size: 15px;
70 | font-weight: 500;
71 |
72 | ${Flex({ columnGap: "5px" })}
73 | cursor: pointer;
74 |
75 | img {
76 | width: 20px;
77 | height: 20px;
78 | }
79 | p {
80 | margin-top: 2px;
81 | overflow: hidden;
82 | text-overflow: ellipsis;
83 | }
84 | `;
85 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/CompanyStarGrades/index.tsx:
--------------------------------------------------------------------------------
1 | import bolt from "@src/assets/icons/Company/bolt.svg";
2 | import { convertStarRatingObject } from "@src/utils/StarRating/convertRankingObject";
3 | import { StarRating } from "@stubee2/stubee2-rolling-ui";
4 | import * as S from "./style";
5 | import logo from "@src/assets/icons/Logo/logo.svg";
6 | import { CompanyStarGradeInfo } from "@src/types/Company/company.type";
7 | import { getRgb } from "@src/utils/Rgb/getRgb";
8 | import { Row } from "@src/styles/flex";
9 |
10 | interface Props {
11 | starGradeInfo: CompanyStarGradeInfo;
12 | }
13 |
14 | const CompanyStarGrades = ({ starGradeInfo }: Props) => {
15 | const { ...attr } = starGradeInfo;
16 | return (
17 |
18 |
19 |
22 |
23 | {attr.companyName}
24 |
25 |
26 |
27 |
28 | 평균 평점
29 |
30 |
31 | {convertStarRatingObject(attr).map((item) => (
32 |
33 |
34 | {item.title}
35 | {item.star}점
36 |
37 |
38 |
43 |
44 |
45 | ))}
46 |
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default CompanyStarGrades;
54 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/CompanyDetailItem/index.tsx:
--------------------------------------------------------------------------------
1 | import { tieStarGradeToObject } from "@src/utils/StarRating/tieStarGradeToObject";
2 | import CompanyDetailInfo from "./CompanyDetailInfo";
3 | import CompanyStarGrades from "./CompanyStarGrades";
4 | import { useGetCompanyInfoIdQuery } from "@src/services/Company/queries";
5 |
6 | interface Props {
7 | id: string;
8 | }
9 |
10 | const CompanyDetailItem = ({ id }: Props) => {
11 | const { data: companyInfo } = useGetCompanyInfoIdQuery(
12 | { id },
13 | { suspense: true }
14 | );
15 |
16 | return (
17 | <>
18 |
19 |
20 | >
21 | );
22 | };
23 |
24 | export default CompanyDetailItem;
25 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/index.tsx:
--------------------------------------------------------------------------------
1 | import { useAuthTopScroll } from "@stubee2/stubee2-rolling-util";
2 | import { Suspense } from "react";
3 | import ErrorBoundary from "../../Common/ErrorBoundary";
4 | import CompanyDetailSkeleton from "../../Common/Skeleton/CompanyDetail";
5 | import CompanyDetailItem from "./CompanyDetailItem";
6 | import * as S from "./style";
7 |
8 | interface Props {
9 | id: string;
10 | }
11 |
12 | const CompanyDetail = ({ id }: Props) => {
13 | useAuthTopScroll();
14 | return (
15 |
16 |
17 | 회사정보를 갖고오지 못했습니다.>}>
18 | }>
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default CompanyDetail;
28 |
--------------------------------------------------------------------------------
/src/components/Company/CompanyDetail/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import styled from "styled-components";
3 |
4 | export const Container = styled.div`
5 | width: 100%;
6 | height: 100%;
7 |
8 | ${Flex({ alignItems: "center", justifyContent: "center" })}
9 | zoom: 0.8;
10 |
11 | padding: 90px 15px 15px 15px;
12 | min-width: 500px;
13 | `;
14 |
15 | export const Wrapper = styled.div`
16 | width: 1370px;
17 | height: 100%;
18 | padding-top: 2rem;
19 | display: flex;
20 |
21 | @media screen and (max-width: 1005px) {
22 | ${Flex({ flexDirection: "column", alignItems: "center", rowGap: "3rem" })}
23 | }
24 |
25 | @media screen and (max-width: 500px) {
26 | row-gap: 0rem;
27 | }
28 | overflow-y: auto;
29 | ::-webkit-scrollbar {
30 | display: none;
31 | }
32 | `;
33 |
--------------------------------------------------------------------------------
/src/components/Home/Main/Rank/RankItem/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const RankCategoryTitle = styled.div`
6 | font-size: 17px;
7 | font-weight: bold;
8 | color: ${RollingPalette.unEmphasize.Dark};
9 |
10 | ${Flex({ columnGap: "5px", alignItems: "center" })}
11 |
12 | img {
13 | width: 24px;
14 | height: 24px;
15 | }
16 | `;
17 |
18 | export const MainItemWrap = styled.div`
19 | width: 100%;
20 | height: 100%;
21 | ${Flex({ flexWrap: "wrap", gap: "1.5rem" })}
22 | `;
23 |
24 | export const RankNumber = styled.div`
25 | width: 40px;
26 | height: 40px;
27 |
28 | background-color: ${RollingPalette.main.Dark};
29 | border-radius: 10px 0 10px 0;
30 | position: absolute;
31 | top: 0;
32 | left: 0;
33 |
34 | ${Flex({ alignItems: "center", justifyContent: "center" })}
35 |
36 | font-size: 20px;
37 | color: #fff;
38 | z-index: 2;
39 | `;
40 |
--------------------------------------------------------------------------------
/src/components/Home/Main/Rank/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import graph from "@src/assets/icons/Home/graph.svg";
3 | import { Suspense, useState } from "react";
4 | import ErrorBoundary from "@src/components/Common/ErrorBoundary";
5 | import RankItem from "./RankItem";
6 | import { COMPANY_RANK_ITMES } from "@src/constants/Company/company.constant";
7 | import MainSkeleton from "@src/components/Common/Skeleton/Home/Main";
8 | import { MainContainer, MainTitle, MainWrapper } from "../style";
9 | import { ViewAll } from "../../style";
10 | import { useNavigate } from "react-router-dom";
11 | import { Column, Row } from "@src/styles/flex";
12 |
13 | const Rank = () => {
14 | const [rankCategorySelect, setRankCategorySelect] = useState("total");
15 | const navigate = useNavigate();
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | BEST 기업 랭킹
26 |
27 |
28 |
29 | navigate("/ranking")}>전체보기
30 |
31 |
32 |
33 | {COMPANY_RANK_ITMES.map((item) => (
34 | setRankCategorySelect(item.categoryName!!)}
37 | isSelect={rankCategorySelect === item.categoryName}
38 | >
39 | {item.name}
40 |
41 | ))}
42 |
43 |
44 |
45 | 회사 랭킹을 갖고오지 못했습니다.>}>
46 | }>
47 |
48 |
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default Rank;
56 |
--------------------------------------------------------------------------------
/src/components/Home/Main/Rank/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const RankCategoriesUl = styled.ul`
6 | ${Flex({ alignItems: "center", columnGap: "15px" })}
7 |
8 | overflow-x: hidden;
9 | overflow-y: hidden;
10 |
11 | padding: 20px 0 40px 0;
12 | width: 100%;
13 | height: 40px;
14 |
15 | @media screen and (max-width: 595px) {
16 | overflow-x: auto;
17 | }
18 | `;
19 |
20 | export const RankCategoryLi = styled.li<{ isSelect: boolean }>`
21 | height: 40px;
22 | padding: 0.5rem;
23 | font-size: 17px;
24 |
25 | font-weight: ${({ isSelect }) => isSelect && "800"};
26 | color: ${({ isSelect }) => isSelect && "#fff"};
27 | background-color: ${({ isSelect }) => isSelect && RollingPalette.main.Base};
28 | cursor: pointer;
29 |
30 | ${Flex({ alignItems: "center", justifyContent: "center" })}
31 | border-radius: 10px;
32 |
33 | transform: scale(1);
34 | transition: all 0.1s ease-in-out;
35 | &:hover {
36 | background-color: ${({ isSelect }) =>
37 | isSelect ? "rgba(72, 105, 246, 1)" : "#eeeeee"};
38 | transform: scale(0.98);
39 | }
40 | &:active {
41 | background-color: ${({ isSelect }) => (isSelect ? "#2b3f94" : "#dddddd")};
42 | }
43 | `;
44 |
--------------------------------------------------------------------------------
/src/components/Home/Main/Search/SearchItem/style.ts:
--------------------------------------------------------------------------------
1 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
2 | import styled from "styled-components";
3 |
4 | export const NoneData = styled.div`
5 | font-size: 18px;
6 |
7 | p {
8 | span {
9 | transition: all 0.1s ease-in-out;
10 | color: ${RollingPalette.main.Base};
11 | font-weight: 800;
12 | cursor: pointer;
13 |
14 | &:hover {
15 | color: rgba(72, 105, 246, 0.81);
16 | }
17 | }
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/src/components/Home/Main/Search/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "../style";
2 | import readingGlasses from "@src/assets/icons/Search/readingGlasses.svg";
3 | import React, { Suspense } from "react";
4 | import ErrorBoundary from "@src/components/Common/ErrorBoundary";
5 | import SearchItem from "./SearchItem";
6 | import MainSkeleton from "@src/components/Common/Skeleton/Home/Main";
7 |
8 | interface Props {
9 | company: string;
10 | }
11 |
12 | function Search({ company }: Props) {
13 | return (
14 |
15 |
16 |
17 |
18 | 내가 검색한 기업 · {company}
19 |
20 |
21 | 검색한 회사를 갖고오지 못했습니다.>}>
22 | }>
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default React.memo(Search);
32 |
--------------------------------------------------------------------------------
/src/components/Home/Nav/ExternalSite/index.tsx:
--------------------------------------------------------------------------------
1 | import { EXTERNALSITE_ITEMS } from "@src/constants/ExternalSite/externalSite.constant";
2 | import * as S from "./style";
3 | import web from "@src/assets/icons/Home/web.svg";
4 |
5 | const ExternalSite = () => {
6 | return (
7 |
8 |
9 |
10 | 더 많은 정보를 알고 싶다면?
11 |
12 |
13 |
14 |
15 | {EXTERNALSITE_ITEMS.map((item) => (
16 | window.open(item.link, "_blank")}
19 | >
20 |
21 | {item.name} 바로가기
22 |
23 | ))}
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default ExternalSite;
31 |
--------------------------------------------------------------------------------
/src/components/Home/Nav/ExternalSite/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import styled from "styled-components";
3 |
4 | export const ExternalSiteContainer = styled.div`
5 | margin-top: 10px;
6 | width: 100%;
7 | ${Flex({ flexDirection: "column", rowGap: "1rem" })}
8 | `;
9 |
10 | export const ExternalSiteTitle = styled.div`
11 | font-size: 17px;
12 | font-weight: 600;
13 |
14 | ${Flex({ alignItems: "center", columnGap: "5px" })}
15 |
16 | img {
17 | width: 20px;
18 | height: 20px;
19 | }
20 | `;
21 |
22 | export const ExternalSiteItemContainer = styled.div`
23 | width: 100%;
24 | padding: 0 1rem 1rem 1rem;
25 | border: 1px solid #dddddd;
26 | border-radius: 10px;
27 | background-color: #fff;
28 | `;
29 |
30 | export const ExternalSiteItemWrapper = styled.ul`
31 | margin-top: 15px;
32 | ${Flex({ flexDirection: "column", rowGap: "1rem" })}
33 | `;
34 |
35 | export const ExternalSiteItem = styled.li`
36 | width: 100%;
37 | cursor: pointer;
38 | border-radius: 10px;
39 |
40 | ${Flex({ alignItems: "center", columnGap: "0.7rem" })}
41 |
42 | img {
43 | width: 50px;
44 | height: 50px;
45 | border-radius: 10px;
46 | border: 1px solid #dddddd;
47 | object-fit: cover;
48 | }
49 |
50 | p {
51 | font-size: 16px;
52 | }
53 |
54 | transform: scale(1);
55 | transition: all 0.1s ease-in-out;
56 | cursor: pointer;
57 | &:hover {
58 | background-color: #eeeeee;
59 | transform: scale(0.97);
60 | }
61 | &:active {
62 | background-color: #dddddd;
63 | color: #4869f6;
64 | }
65 | `;
66 |
--------------------------------------------------------------------------------
/src/components/Home/Nav/UserInfo/index.tsx:
--------------------------------------------------------------------------------
1 | import ErrorBoundary from "@src/components/Common/ErrorBoundary";
2 | import * as S from "./style";
3 | import wonderFace from "@src/assets/icons/Home/wonderFace.svg";
4 | import { Suspense } from "react";
5 | import { stringEllipsis } from "@stubee2/stubee2-rolling-util";
6 | import { useNavigate } from "react-router-dom";
7 | import UserInfoSkeleton from "@src/components/Common/Skeleton/Home/UserInfo";
8 | import { tokenDecode } from "@src/utils/Auth/tokenDecode";
9 | import { useGetMyInfoQuery } from "@src/services/Member/queries";
10 | import { Column } from "@src/styles/flex";
11 |
12 | const UserInfo = () => {
13 | return (
14 |
15 | 내 정보를 갖고오지 못했습니다.>}>
16 | }>
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | function UserInfoItem() {
25 | const navigate = useNavigate();
26 | const { data: myInfo } = useGetMyInfoQuery({ suspense: true });
27 | const isNickName = !myInfo?.details.nickName;
28 | const isMember = tokenDecode("access", "authority") === "MEMBER";
29 |
30 | return (
31 | <>
32 |
33 |
34 | navigate("/mypage/profile")}>
35 |
36 | {isNickName ? myInfo?.details.name : myInfo?.details.nickName}
37 |
38 |
39 | {stringEllipsis(
40 | myInfo?.details.email!! || "이메일을 설정해주세요",
41 | 22
42 | )}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {isMember
52 | ? "내 기업 스토리가 등록 되어있나요?"
53 | : "동문 인증이 되어 있지 않나요?"}
54 |
55 |
56 |
57 | navigate(isMember ? "/story" : "/alumni/certify")}
59 | >
60 | {isMember ? "스토리 등록하러 가기" : "동문인증 하러가기"}
61 |
62 |
63 | >
64 | );
65 | }
66 |
67 | export default UserInfo;
68 |
--------------------------------------------------------------------------------
/src/components/Home/Nav/UserInfo/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const UserInfoBox = styled.div`
6 | width: 100%;
7 | white-space: nowrap;
8 | ${Flex({ alignItems: "center", columnGap: "10px" })};
9 |
10 | img {
11 | width: 75px;
12 | height: 75px;
13 | border: 1px solid #d9d9d9;
14 | object-fit: cover;
15 | border-radius: 4rem;
16 | }
17 |
18 | div {
19 | width: calc(100% - 85px);
20 | cursor: pointer;
21 | ${Flex({ flexDirection: "column", rowGap: "8px" })}
22 | }
23 | `;
24 |
25 | export const UserInfoNickName = styled.p`
26 | height: 20px;
27 | font-size: 18px;
28 |
29 | font-weight: 600;
30 | overflow-x: hidden;
31 | text-overflow: ellipsis;
32 |
33 | transition: all 0.1s ease-in-out;
34 | &:hover {
35 | color: ${RollingPalette.main.Base};
36 | }
37 | `;
38 |
39 | export const UserInfoEmail = styled.p`
40 | font-size: 15px;
41 | `;
42 |
43 | export const RegistTextContainer = styled.div`
44 | width: 100%;
45 |
46 | margin-bottom: 15px;
47 | font-weight: 600;
48 |
49 | ${Flex({ alignItems: "center", columnGap: "5px" })}
50 |
51 | img {
52 | width: 20px;
53 | height: 20px;
54 | }
55 | `;
56 |
57 | export const CompanyRegistBtn = styled.button`
58 | width: 100%;
59 | height: 57px;
60 |
61 | outline: none;
62 | background-color: #fff;
63 | border-radius: 5px;
64 | font-size: 15px;
65 |
66 | color: ${RollingPalette.main.Base};
67 | border: 2px solid ${RollingPalette.main.Base};
68 | cursor: pointer;
69 |
70 | transform: scale(1);
71 | transition: all 0.1s ease-in-out;
72 | &:hover {
73 | background-color: rgba(247, 249, 250, 1);
74 | transform: scale(0.985);
75 | }
76 | `;
77 |
--------------------------------------------------------------------------------
/src/components/Home/Nav/index.tsx:
--------------------------------------------------------------------------------
1 | import { ACCESS_TOKEN_KEY } from "@src/constants/Auth/auth.constant";
2 | import Token from "@src/libs/Token/Token";
3 | import styled from "styled-components";
4 | import ExternalSite from "./ExternalSite";
5 | import UserInfo from "./UserInfo";
6 | import { Flex } from "@src/styles/flex";
7 |
8 | const Nav = () => {
9 | return (
10 |
11 | {Token.getToken(ACCESS_TOKEN_KEY) && }
12 |
13 |
14 | );
15 | };
16 |
17 | export const NavContainer = styled.div`
18 | width: 290px;
19 | height: 100%;
20 |
21 | ${Flex({ flexDirection: "column" })}
22 | row-gap: calc(2rem - 10px);
23 |
24 | position: sticky;
25 | top: calc(90px + 2rem);
26 |
27 | @media screen and (max-width: 1105px) {
28 | display: none;
29 | }
30 | `;
31 |
32 | export default Nav;
33 |
--------------------------------------------------------------------------------
/src/components/Home/Recommand/RecommandItem/index.tsx:
--------------------------------------------------------------------------------
1 | import { useInView } from "react-intersection-observer";
2 | import logo from "@src/assets/icons/Logo/logo.svg";
3 | import * as S from "./style";
4 | import { useNavigate } from "react-router-dom";
5 | import { useEffect } from "react";
6 | import { getRgb } from "@src/utils/Rgb/getRgb";
7 | import { useGetAllCompanyListQuery } from "@src/services/Company/queries";
8 | import { Row } from "@src/styles/flex";
9 |
10 | const RecommandItem = () => {
11 | const { data, fetchNextPage } = useGetAllCompanyListQuery({ suspense: true });
12 | const companyList = data?.pages;
13 | const [ref, inView] = useInView();
14 | const navigate = useNavigate();
15 |
16 | useEffect(() => {
17 | if (inView) {
18 | fetchNextPage();
19 | }
20 | }, [inView]);
21 |
22 | return (
23 | <>
24 | {companyList!![0].data.length!! > 0 ? (
25 |
26 |
31 | {companyList?.map((data) =>
32 | data.data.map((item) => (
33 | navigate(`/company/${item.companyId.id}`)}
36 | >
37 |
38 |
42 |
43 | {item.companyDetails.name}
44 |
45 | ))
46 | )}
47 |
48 |
49 |
50 | ) : (
51 | 졸업생들이 추천하는 회사가 없습니다.
52 | )}
53 | >
54 | );
55 | };
56 |
57 | export default RecommandItem;
58 |
--------------------------------------------------------------------------------
/src/components/Home/Recommand/RecommandItem/style.ts:
--------------------------------------------------------------------------------
1 | import { StopDrag } from "@src/styles/common.style";
2 | import { Flex } from "@src/styles/flex";
3 | import styled from "styled-components";
4 |
5 | export const RecommandItemContainer = styled.div`
6 | width: 100%;
7 | height: 100%;
8 |
9 | display: flex;
10 | overflow-y: hidden;
11 | overflow-x: auto;
12 | `;
13 |
14 | export const RecommandItemBox = styled.div`
15 | width: 250px;
16 | height: 200px;
17 |
18 | ${Flex({ flexDirection: "column", justifyContent: "center", rowGap: "5px" })}
19 |
20 | p {
21 | text-align: center;
22 | font-size: 17px;
23 | font-weight: bold;
24 | margin-top: 5px;
25 | }
26 |
27 | @media screen and (max-width: 1010px) {
28 | width: 220px;
29 | }
30 | `;
31 |
32 | export const ImageContainer = styled.div<{ rgb: string }>`
33 | width: 100%;
34 | height: 167px;
35 |
36 | border-radius: 10px;
37 | border: 1px solid #ddd;
38 | cursor: pointer;
39 |
40 | ${Flex({ justifyContent: "center" })}
41 | overflow: hidden;
42 | background-color: ${({ rgb }) => rgb || "#fff"};
43 | zoom: 0.85;
44 |
45 | img {
46 | max-width: 100%;
47 | max-height: 100%;
48 | }
49 |
50 | ${StopDrag}
51 | `;
52 |
--------------------------------------------------------------------------------
/src/components/Home/Recommand/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import developer from "@src/assets/icons/Home/developer.svg";
3 | import RecommandItem from "./RecommandItem";
4 | import ErrorBoundary from "@src/components/Common/ErrorBoundary";
5 | import { Suspense } from "react";
6 | import RecommandSkeleton from "@src/components/Common/Skeleton/Home/Recommand";
7 | import { useNavigate } from "react-router-dom";
8 | import { ViewAll } from "../style";
9 | import { Row } from "@src/styles/flex";
10 |
11 | const Recommand = () => {
12 | const navigate = useNavigate();
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | 졸업생들이 추천해요!
20 |
21 |
22 | navigate("/all")}>전체보기
23 |
24 |
25 |
26 | 회사 정보를 가지고 오지 못했습니다.>}>
27 | }>
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Recommand;
37 |
--------------------------------------------------------------------------------
/src/components/Home/Recommand/style.ts:
--------------------------------------------------------------------------------
1 | import { FadeInAnimation } from "@src/styles/common.style";
2 | import { Flex } from "@src/styles/flex";
3 | import styled from "styled-components";
4 |
5 | export const RecommandContainer = styled.div`
6 | width: 100%;
7 | ${Flex({ flexDirection: "column" })}
8 |
9 | @media screen and (max-width: 710px) {
10 | width: 99%;
11 | }
12 | `;
13 |
14 | export const SeniorRecommand = styled.div`
15 | font-size: 22px;
16 | font-weight: bold;
17 |
18 | ${Flex({ alignItems: "center", columnGap: "5px" })}
19 |
20 | img {
21 | width: 27px;
22 | height: 27px;
23 | }
24 | `;
25 |
26 | export const RecommandWrapper = styled.div`
27 | width: 100%;
28 | height: 240px;
29 |
30 | display: flex;
31 | ${FadeInAnimation};
32 | `;
33 |
--------------------------------------------------------------------------------
/src/components/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import Rank from "./Main/Rank";
3 | import Nav from "./Nav";
4 | import Footer from "../Common/Footer";
5 | import Recommand from "./Recommand";
6 | import { useRecoilValue } from "recoil";
7 | import { SearchCompanyAtom } from "@src/stores/company/company.store";
8 | import Search from "./Main/Search";
9 | import { useAuthTopScroll } from "@stubee2/stubee2-rolling-util";
10 | import { Row } from "@src/styles/flex";
11 |
12 | const Home = () => {
13 | const searchCompany = useRecoilValue(SearchCompanyAtom);
14 | useAuthTopScroll();
15 | return (
16 |
17 |
18 |
19 | {searchCompany ? : }
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default Home;
30 |
--------------------------------------------------------------------------------
/src/components/Home/style.ts:
--------------------------------------------------------------------------------
1 | import { HoverAnimation } from "@src/styles/common.style";
2 | import styled from "styled-components";
3 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
4 | import { Flex } from "@src/styles/flex";
5 |
6 | export const HomeContainer = styled.div`
7 | width: 100%;
8 | height: 100%;
9 | padding-top: 90px;
10 |
11 | zoom: 0.8;
12 |
13 | ${Flex({
14 | flexDirection: "column",
15 | alignItems: "center",
16 | justifyContent: "center",
17 | rowGap: "10rem",
18 | })}
19 |
20 | background-color: #f9fafb;
21 | white-space: nowrap;
22 | min-width: 500px;
23 | `;
24 |
25 | export const HomeWrapper = styled.div`
26 | width: 1370px;
27 | height: 100%;
28 | padding-top: 2rem;
29 |
30 | ${Flex({ flexDirection: "column", rowGap: "8rem" })}
31 |
32 | @media screen and (max-width: 1165px) {
33 | width: 90%;
34 | padding-left: 0px;
35 | padding-right: 0px;
36 | }
37 | `;
38 |
39 | export const ViewAll = styled.p`
40 | cursor: pointer;
41 | padding: 10px;
42 |
43 | margin-top: 3px;
44 | font-weight: bold;
45 | color: ${RollingPalette.unEmphasize.Dark};
46 |
47 | ${HoverAnimation};
48 | `;
49 |
--------------------------------------------------------------------------------
/src/components/Story/Company/Modify/index.tsx:
--------------------------------------------------------------------------------
1 | import useAlumniCheck from "@src/hooks/Alumni/useAlumniCheck";
2 | import useTokenCheck from "@src/hooks/Auth/useTokenCheck";
3 | import { useState } from "react";
4 | import { Portal } from "@stubee2/stubee2-rolling-ui";
5 | import CompanyAddressModal from "@src/components/Common/Modal/CompanyAddress";
6 | import ModifyItem from "./ModifyItem";
7 | import * as S from "../../style";
8 | import { useCompanyModify } from "@src/hooks/Company/useCompanyModify";
9 | import { useAuthTopScroll } from "@stubee2/stubee2-rolling-util";
10 |
11 | const CompanyModify = () => {
12 | useTokenCheck();
13 | useAlumniCheck();
14 | useAuthTopScroll();
15 |
16 | const { companyModifyInfo, setCompanyModifyInfo } = useCompanyModify();
17 |
18 | const [isModalOpen, setIsModalOpen] = useState(false);
19 | return (
20 | <>
21 |
22 |
23 |
24 | My Company 수정
25 |
26 | 해당 기업의 정보를 수정하여 후배들에게 더욱 더 좋은 정보를
27 | 알려주세요!
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {isModalOpen && (
36 |
37 |
42 |
43 | )}
44 | >
45 | );
46 | };
47 |
48 | export default CompanyModify;
49 |
--------------------------------------------------------------------------------
/src/components/Story/Company/Register/index.tsx:
--------------------------------------------------------------------------------
1 | import { AddText, SubTitle, Title, TitleContainer } from "../../style";
2 | import React from "react";
3 | import CompanyRegisterItem from "./RegisterItem";
4 | import { Column } from "@src/styles/flex";
5 |
6 | interface Props {
7 | storySearchCompany: {
8 | companyName: string;
9 | isExistSearchList: boolean;
10 | };
11 | storyCompanyId: string;
12 | }
13 |
14 | function CompanyRegister({ storySearchCompany, storyCompanyId }: Props) {
15 | const { companyName, isExistSearchList } = storySearchCompany;
16 |
17 | return (
18 |
19 |
20 | My Company 등록
21 |
22 | 내 기업이 없다면, 내 기업 등록으로 더 다양한 서비스를 즐길 수 있어요
23 |
24 |
25 |
26 | {companyName === "" && 기업을 먼저 찾아주세요!}
27 | {storyCompanyId !== "" && (
28 | 기업이 등록되어 있습니다. 스토리를 작성해주세요!
29 | )}
30 |
31 | {!isExistSearchList && companyName !== "" && storyCompanyId === "" && (
32 |
33 | )}
34 |
35 | );
36 | }
37 | export default React.memo(CompanyRegister);
38 |
--------------------------------------------------------------------------------
/src/components/Story/Company/Search/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import readingGlasses from "@src/assets/icons/Search/readingGlasses.svg";
3 | import search3 from "@src/assets/icons/Search/search3.svg";
4 | import { useRecoilValue } from "recoil";
5 | import { StorySearchCompanyAtom } from "@src/stores/story/story.store";
6 | import { SubTitle, Title, TitleContainer } from "../../style";
7 | import { Portal } from "@stubee2/stubee2-rolling-ui";
8 | import SearchCompanyModal from "./SearchCompanyModal";
9 | import { useState } from "react";
10 | import { turnOnOffModal } from "@src/utils/Modal/turnOnOffModal";
11 | import { Column, Row } from "@src/styles/flex";
12 |
13 | const SearchCompany = () => {
14 | const [searchCompanyModal, setSearchCompanyModal] = useState(false);
15 | const storySearchCompany = useRecoilValue(StorySearchCompanyAtom);
16 | const companyName = storySearchCompany.companyName;
17 |
18 | return (
19 | <>
20 |
21 |
22 | My Company 찾기
23 |
24 | 잠깐! 기업을 등록하기 전에 해당회사가 존재하는지 찾아주세요!
25 |
26 |
27 |
28 |
29 |
30 |
31 | 내 기업 찾기
32 |
33 |
34 |
35 | turnOnOffModal(setSearchCompanyModal, "on")}
37 | >
38 |
39 | {companyName || "기업명"}
40 |
41 |
42 |
43 |
44 |
45 |
46 | {searchCompanyModal && (
47 |
48 |
49 |
50 | )}
51 | >
52 | );
53 | };
54 |
55 | export default SearchCompany;
56 |
--------------------------------------------------------------------------------
/src/components/Story/Company/Search/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const SearchContainer = styled.div`
6 | width: 100%;
7 | height: 170px;
8 | background-color: ${RollingPalette.main.Base};
9 |
10 | border-radius: 10px;
11 | padding: 1.5rem 1.5rem 2rem 1.5rem;
12 |
13 | ${Flex({ flexDirection: "column", justifyContent: "space-between" })}
14 | `;
15 |
16 | export const Image = styled.img`
17 | width: 30px;
18 | height: 30px;
19 | transform: scaleX(-1);
20 | `;
21 |
22 | export const FindMyCompany = styled.p`
23 | font-size: 20px;
24 | font-family: "Pretendard-Bold" !important;
25 | color: #fff;
26 | `;
27 |
28 | export const InputContainer = styled.div`
29 | width: 100%;
30 | height: 52px;
31 | border-bottom: 3px solid ${RollingPalette.unEmphasize.Base};
32 |
33 | ${Flex({ alignItems: "center", justifyContent: "space-between" })}
34 | cursor: pointer;
35 |
36 | img {
37 | width: 30px;
38 | height: 30px;
39 | margin-right: 20px;
40 | }
41 | `;
42 |
43 | export const Input = styled.div<{ isCompanyName: string }>`
44 | width: 100%;
45 | height: 100%;
46 |
47 | background-color: transparent;
48 | outline: none;
49 | border: none;
50 |
51 | font-size: 28px;
52 | padding-left: 20px;
53 | color: ${({ isCompanyName }) =>
54 | isCompanyName !== "" ? "#fff" : RollingPalette.unEmphasize.Base};
55 |
56 | font-weight: bold;
57 | ${Flex({ alignItems: "center" })}
58 | `;
59 |
--------------------------------------------------------------------------------
/src/components/Story/StoryRegister/RegisterItem/PositionList/index.tsx:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction } from "react";
2 | import * as S from "./style";
3 |
4 | interface Props {
5 | positionList: string[];
6 | setShowPositionList: Dispatch>;
7 | setStoryRequiredElement: Dispatch>>;
8 | positionTop: string;
9 | }
10 |
11 | const StoryPositionList = ({
12 | positionList,
13 | setShowPositionList,
14 | setStoryRequiredElement,
15 | positionTop,
16 | }: Props) => {
17 | return (
18 | <>
19 | {positionList.length > 0 && (
20 |
21 |
22 | {positionList.map((item, idx) => (
23 | {
26 | setStoryRequiredElement((prev) => ({
27 | ...prev,
28 | position: item,
29 | }));
30 | setShowPositionList(false);
31 | }}
32 | >
33 | {item}
34 |
35 | ))}
36 |
37 |
38 | )}
39 | >
40 | );
41 | };
42 |
43 | export default StoryPositionList;
44 |
--------------------------------------------------------------------------------
/src/components/Story/StoryRegister/RegisterItem/PositionList/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import styled from "styled-components";
3 |
4 | export const Container = styled.div<{ positionTop: string }>`
5 | position: absolute;
6 |
7 | width: 100%;
8 | max-height: 225px;
9 |
10 | border: 2px solid #d9d9d9;
11 | background-color: #fff;
12 | border-radius: 5px;
13 |
14 | top: ${({ positionTop }) => positionTop};
15 |
16 | z-index: 3;
17 | overflow-y: auto;
18 | `;
19 |
20 | export const Ul = styled.ul`
21 | width: 100%;
22 | height: 100%;
23 |
24 | ${Flex({ flexDirection: "column" })}
25 |
26 | li {
27 | width: 100%;
28 | height: 55px;
29 |
30 | padding-left: 20px;
31 | line-height: 25px;
32 | font-size: 17px;
33 |
34 | ${Flex({ alignItems: "center" })}
35 | cursor: pointer;
36 |
37 | &:hover {
38 | background-color: rgba(242, 244, 245, 1);
39 | }
40 | }
41 | `;
42 |
--------------------------------------------------------------------------------
/src/components/Story/StoryRegister/RegisterItem/stye.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const CompanyStoryText = styled.p`
6 | font-size: 24px;
7 | font-family: "Pretendard-Bold" !important;
8 |
9 | span {
10 | color: ${RollingPalette.main.Base};
11 | font-family: "Pretendard-Bold" !important;
12 | }
13 | `;
14 |
15 | export const Wrapper = styled.div`
16 | width: 100%;
17 |
18 | background-color: #fff;
19 | border-radius: 15px;
20 |
21 | border: 1px solid #ddd;
22 | overflow: hidden;
23 |
24 | padding: 4.5rem 4rem 3rem 4rem;
25 | `;
26 |
27 | export const Form = styled.form`
28 | width: 100%;
29 | height: 100%;
30 | ${Flex({ flexDirection: "column", rowGap: "3rem" })}
31 | `;
32 |
33 | export const InputContainer = styled.div`
34 | width: 50%;
35 | ${Flex({ flexDirection: "column", rowGap: "10px" })}
36 | position: relative;
37 | `;
38 |
39 | export const InputStyle = {
40 | width: "100%",
41 | height: "60px",
42 | paddingLeft: "20px",
43 | fontSize: "17px",
44 | backgroundColor: `${RollingPalette.unEmphasize.Lightest}`,
45 | };
46 |
47 | export const StarGradeContainer = styled.div`
48 | width: 100%;
49 | background-color: rgba(247, 249, 250, 1);
50 | border: 1px solid #bdc2d0;
51 | border-radius: 5px;
52 | `;
53 |
54 | export const TextAreaStyle = {
55 | width: "100%",
56 | height: "230px",
57 | padding: "20px",
58 | fontSize: "17px",
59 | backgroundColor: `${RollingPalette.unEmphasize.Lightest}`,
60 | lineHeight: "25px",
61 | };
62 |
--------------------------------------------------------------------------------
/src/components/Story/StoryRegister/index.tsx:
--------------------------------------------------------------------------------
1 | import { Column } from "@src/styles/flex";
2 | import { AddText, SubTitle, Title, TitleContainer } from "../style";
3 | import StoryRegisterItem from "./RegisterItem";
4 | import * as S from "./style";
5 |
6 | interface Props {
7 | storySearchCompany: {
8 | companyName: string;
9 | isExistSearchList: boolean;
10 | };
11 | storyCompanyId: string;
12 | }
13 |
14 | function StoryRegister({ storySearchCompany, storyCompanyId }: Props) {
15 | const { companyName, isExistSearchList } = storySearchCompany;
16 |
17 | return (
18 |
19 |
20 | My Company 스토리
21 |
22 | 내 기업의 스토리를 작성하고 후배들에게 롤링해 주세요
23 |
24 | {isExistSearchList && companyName !== "" && (
25 |
26 | 이미 기업이 등록 되어있습니다. {companyName}의 스토리를 남겨주세요!
27 |
28 | )}
29 |
30 |
31 |
32 | {storyCompanyId && companyName ? (
33 |
37 | ) : (
38 |
39 | {companyName
40 | ? `${companyName} 기업의 정보를 먼저 입력해주세요!`
41 | : "기업을 먼저 찾아주세요!"}
42 |
43 | )}
44 |
45 |
46 | );
47 | }
48 |
49 | export default StoryRegister;
50 |
--------------------------------------------------------------------------------
/src/components/Story/StoryRegister/style.ts:
--------------------------------------------------------------------------------
1 | import { FadeInAnimation } from "@src/styles/common.style";
2 | import { Flex } from "@src/styles/flex";
3 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
4 | import styled from "styled-components";
5 |
6 | export const GuideText = styled.p`
7 | font-size: 15px;
8 | color: ${RollingPalette.main.Base};
9 |
10 | ${Flex({ alignItems: "center" })}
11 | padding-left: 10px;
12 |
13 | width: 100%;
14 | height: 40px;
15 | background-color: ${RollingPalette.unEmphasize.Light};
16 |
17 | ${FadeInAnimation};
18 | `;
19 |
--------------------------------------------------------------------------------
/src/components/Story/index.tsx:
--------------------------------------------------------------------------------
1 | import useAlumniCheck from "@src/hooks/Alumni/useAlumniCheck";
2 | import useTokenCheck from "@src/hooks/Auth/useTokenCheck";
3 | import {
4 | StoryCompanyIdAtom,
5 | StorySearchCompanyAtom,
6 | } from "@src/stores/story/story.store";
7 | import { useEffect } from "react";
8 | import { useRecoilState } from "recoil";
9 | import CompanyRegister from "./Company/Register";
10 | import SearchCompany from "./Company/Search";
11 | import StoryRegister from "./StoryRegister";
12 | import * as S from "./style";
13 |
14 | const Story = () => {
15 | useTokenCheck();
16 | useAlumniCheck();
17 |
18 | const [storySearchCompany, setStorySearchCompany] = useRecoilState(
19 | StorySearchCompanyAtom
20 | );
21 | const { companyName } = storySearchCompany;
22 |
23 | const [storyCompanyId, setStoryCompanyId] =
24 | useRecoilState(StoryCompanyIdAtom);
25 |
26 | useEffect(() => {
27 | // 기업정보 박스로 이동
28 | if (storyCompanyId === "" && companyName !== "") {
29 | window.scrollTo({
30 | top: 370,
31 | behavior: "smooth",
32 | });
33 | }
34 |
35 | // 스토리 남기기 박스로 이동
36 | if (storyCompanyId !== "" && companyName !== "") {
37 | window.scrollTo({
38 | top: 600,
39 | behavior: "smooth",
40 | });
41 | }
42 | }, [storyCompanyId, companyName]);
43 |
44 | useEffect(() => {
45 | // 전역 상태관리이므로 다른 페이지에 나갔다가 들어오면 데이터가 유지되는 것을 막기 위해 초기화
46 | return () => {
47 | setStorySearchCompany((prev) => ({
48 | ...prev,
49 | companyName: "",
50 | isExistSearchList: false,
51 | }));
52 | setStoryCompanyId("");
53 | };
54 | }, []);
55 |
56 | return (
57 |
58 |
59 |
60 |
64 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default Story;
74 |
--------------------------------------------------------------------------------
/src/components/Story/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled, { css } from "styled-components";
4 |
5 | export const Container = styled.div`
6 | width: 100%;
7 | height: 100%;
8 |
9 | ${Flex({ alignItems: "center", justifyContent: "center" })}
10 | padding: 90px 20px 0 20px;
11 |
12 | min-width: 830px;
13 |
14 | zoom: 0.8;
15 | `;
16 |
17 | export const Wrapper = styled.div<{ rowGap: string }>`
18 | width: 950px;
19 | height: 100%;
20 | padding-top: 4rem;
21 | padding-bottom: 3rem;
22 |
23 | ${({ rowGap }) => Flex({ flexDirection: "column", rowGap })}
24 | `;
25 |
26 | export const TitleContainer = styled.div`
27 | ${Flex({ flexDirection: "column", rowGap: "15px" })}
28 | padding-bottom: 1.5rem;
29 | border-bottom: 1px solid #d9d9d9;
30 | `;
31 |
32 | export const Title = styled.p`
33 | font-size: 26px;
34 | font-family: "Pretendard-Bold" !important;
35 | `;
36 |
37 | export const SubTitle = styled.p`
38 | font-size: 15px;
39 | color: ${RollingPalette.unEmphasize.Dark};
40 | font-weight: bold;
41 | `;
42 |
43 | export const AddText = styled.p`
44 | font-size: 20px;
45 | font-weight: 700;
46 | color: rgba(72, 105, 246, 1);
47 | `;
48 |
49 | export const InputEmphasizeText = styled.div`
50 | font-size: 18px;
51 | font-family: "Pretendard-Bold" !important;
52 | color: ${RollingPalette.unEmphasize.Dark};
53 |
54 | span {
55 | color: ${RollingPalette.check.Light};
56 | }
57 | `;
58 |
59 | export const RegistButton = styled.div<{ isRequired: boolean }>`
60 | width: 100%;
61 | ${Flex({ alignItems: "center", justifyContent: "flex-end" })}
62 | margin-top: 15px;
63 |
64 | button {
65 | width: 153px;
66 | height: 52px;
67 |
68 | outline: none;
69 | border: none;
70 | font-size: 17px;
71 | font-weight: 800;
72 |
73 | border-radius: 7px;
74 | cursor: pointer;
75 | background-color: ${({ isRequired }) =>
76 | isRequired ? RollingPalette.main.Base : "rgba(72, 105, 246, 0.58)"};
77 | color: #fff;
78 |
79 | transition: all 0.2s ease-in-out;
80 | ${({ isRequired }) =>
81 | isRequired &&
82 | css`
83 | transform: scale(1);
84 | &:active {
85 | background-color: rgba(72, 105, 246, 0.81);
86 | transform: scale(0.99);
87 | }
88 | `}
89 | }
90 | `;
91 |
--------------------------------------------------------------------------------
/src/components/User/Nav/NavFooter/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import { USER_ITEMS } from "@src/constants/User/user.constants";
3 | import { useLocation, useNavigate } from "react-router-dom";
4 |
5 | const NavFooter = () => {
6 | const { pathname } = useLocation();
7 | const navigate = useNavigate();
8 |
9 | return (
10 |
11 |
12 | {USER_ITEMS.map((user) => (
13 | navigate(user.link)}
17 | >
18 | {user.title}
19 |
20 | ))}
21 |
22 |
23 | );
24 | };
25 |
26 | export default NavFooter;
27 |
--------------------------------------------------------------------------------
/src/components/User/Nav/NavFooter/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const NavFooterContainer = styled.div`
6 | display: none;
7 | @media screen and (max-width: 1040px) {
8 | width: 100%;
9 | height: 60px;
10 |
11 | background-color: #ffffff;
12 | border-top: 1px solid #d9d9d9;
13 | border-bottom: 1px solid #d9d9d9;
14 |
15 | ${Flex({ justifyContent: "center" })}
16 |
17 | position: fixed;
18 | bottom: 0;
19 | left: 0;
20 | }
21 | `;
22 |
23 | export const NavFooterWrapper = styled.ul`
24 | width: 100%;
25 | height: 100%;
26 |
27 | ${Flex({ alignItems: "center", justifyContent: "space-evenly" })}
28 |
29 | font-size: 18px;
30 | overflow-x: auto;
31 | ::-webkit-scrollbar {
32 | display: none;
33 | }
34 | `;
35 |
36 | export const NavFooterCategoryItem = styled.li<{ isSelect: boolean }>`
37 | color: ${({ isSelect }) =>
38 | isSelect ? RollingPalette.main.Dark : RollingPalette.unEmphasize.Dark};
39 | white-space: nowrap;
40 | font-weight: ${({ isSelect }) => isSelect && "800"};
41 | padding-bottom: 10px;
42 | cursor: pointer;
43 | `;
44 |
--------------------------------------------------------------------------------
/src/components/User/Profile/MyInfo/NickName/index.tsx:
--------------------------------------------------------------------------------
1 | import { useSetUpNickName } from "@src/hooks/NickName/useSetUpNickName";
2 | import * as S from "./style";
3 |
4 | interface Props {
5 | nickName: string;
6 | }
7 |
8 | const NickName = ({ nickName }: Props) => {
9 | const { ...attr } = useSetUpNickName(nickName);
10 | const isEditing =
11 | nickName !== attr.setUpNickName && attr.setUpNickName !== "";
12 |
13 | return (
14 |
15 |
21 |
22 |
23 | {nickName === null ? "등록" : "수정"}
24 |
25 |
26 | );
27 | };
28 |
29 | export default NickName;
30 |
--------------------------------------------------------------------------------
/src/components/User/Profile/MyInfo/NickName/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const Form = styled.form<{ isEditing: boolean }>`
6 | width: 295px;
7 | height: 55px;
8 |
9 | border-radius: 5px;
10 | border: 2px solid
11 | ${({ isEditing }) => (isEditing ? RollingPalette.main.Base : "#c3c9d9")};
12 | display: flex;
13 | overflow: hidden;
14 |
15 | transition: all 0.25s ease-in-out;
16 | `;
17 |
18 | export const Input = styled.input`
19 | width: calc(100% - 60px);
20 | height: 100%;
21 | border: none;
22 | outline: none;
23 | background-color: rgb(243, 244, 245);
24 |
25 | padding-left: 15px;
26 | font-size: 18px;
27 | color: ${RollingPalette.text.Darkest};
28 | `;
29 |
30 | export const SubmitButton = styled.button<{ isEditing: boolean }>`
31 | width: 60px;
32 | height: 100%;
33 |
34 | ${Flex({ alignItems: "center", justifyContent: "center" })}
35 |
36 | outline: none;
37 | border: none;
38 | cursor: pointer;
39 | background-color: ${({ isEditing }) =>
40 | isEditing ? RollingPalette.main.Base : "#c3c9d9"};
41 | font-size: 18px;
42 | color: #fff;
43 | font-weight: 700;
44 |
45 | transition: all 0.25s ease-in-out;
46 | `;
47 |
--------------------------------------------------------------------------------
/src/components/User/Profile/MyInfo/index.tsx:
--------------------------------------------------------------------------------
1 | import { AiFillGithub } from "@react-icons/all-files/ai/AiFillGithub";
2 | import * as S from "./style";
3 | import NickName from "./NickName";
4 | import { Explain, Title } from "../../style";
5 | import { convertToGithubLink } from "@src/utils/github/convertToGithubLink";
6 | import { useRecoilValue } from "recoil";
7 | import { MyMemberInfo } from "@src/stores/member/member.store";
8 | import { Row } from "@src/styles/flex";
9 |
10 | const MyInfo = () => {
11 | const myInfo = useRecoilValue(MyMemberInfo);
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | 기본 정보
19 | {!myInfo?.details.nickName && (
20 | 닉네임을 설정하세요!
21 | )}
22 |
23 |
24 | 롤링에서 제공되는 맞춤 콘텐츠의 기본 데이터로 활용됩니다.
25 |
26 |
27 |
28 |
29 |
30 | {myInfo && (
31 |
32 |
33 |
34 | )}
35 |
36 |
37 |
38 |
40 | window.open(
41 | convertToGithubLink(myInfo?.socialDetails.socialLoginId!!),
42 | "_blank"
43 | )
44 | }
45 | >
46 |
47 |
{myInfo?.socialDetails.socialLoginId}
48 |
49 |
50 |
51 | {myInfo?.socialDetails.loginType}으로 로그인 중
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default MyInfo;
60 |
--------------------------------------------------------------------------------
/src/components/User/Profile/MyInfo/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const Container = styled.div`
6 | width: 100%;
7 | height: 260px;
8 |
9 | background-color: #f3f4f5;
10 | border-radius: 15px;
11 | border: 1px solid #dddddd;
12 | padding: 20px;
13 |
14 | ${Flex({ flexDirection: "column", rowGap: "10px" })}
15 |
16 | margin-bottom: 20px;
17 | `;
18 |
19 | export const Wrapper = styled.div`
20 | width: 100%;
21 | height: 100%;
22 | font-weight: bold;
23 |
24 | ${Flex({ flexDirection: "column", rowGap: "15px" })}
25 | `;
26 |
27 | export const TextContainer = styled.div`
28 | font-size: 22px;
29 | width: 100%;
30 | height: 20px;
31 | margin-bottom: 18px;
32 | `;
33 |
34 | export const SetUpNickNameText = styled.p`
35 | font-size: 13px;
36 | margin-bottom: 2px;
37 | color: ${RollingPalette.error.Base};
38 | `;
39 |
40 | export const UserInfoContainer = styled.div`
41 | width: 100%;
42 | height: 200px;
43 | border-bottom: 2.5px solid rgba(189, 194, 208, 1);
44 |
45 | ${Flex({ alignItems: "center", columnGap: "20px" })}
46 | white-space: nowrap;
47 | overflow-x: auto;
48 |
49 | img {
50 | width: 85px;
51 | height: 85px;
52 | border-radius: 4rem;
53 | object-fit: cover;
54 | border: 1px solid #ddd;
55 | }
56 | `;
57 |
58 | export const MyGitInfoLoginTypeText = styled.p`
59 | color: ${RollingPalette.unEmphasize.Dark};
60 | margin-top: 5px;
61 | font-size: 14px;
62 | `;
63 |
64 | export const MyGitInfoContainer = styled.div`
65 | width: 100%;
66 | height: 45px;
67 | div {
68 | ${Flex({ alignItems: "center", columnGap: "5px" })}
69 | font-weight: 400;
70 | cursor: pointer;
71 | }
72 | `;
73 |
--------------------------------------------------------------------------------
/src/components/User/Profile/index.tsx:
--------------------------------------------------------------------------------
1 | import MyInfo from "./MyInfo";
2 |
3 | function Profile() {
4 | return ;
5 | }
6 | export default Profile;
7 |
--------------------------------------------------------------------------------
/src/components/User/Story/StoryItem/CompanyInfo/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const CompanyLogo = styled.div<{ isHaveSocialId: string }>`
6 | width: 115px;
7 | height: 115px;
8 | cursor: pointer;
9 |
10 | img {
11 | width: 100%;
12 | height: 100%;
13 | border-radius: ${({ isHaveSocialId }) => (isHaveSocialId ? "4rem" : "5px")};
14 | object-fit: cover;
15 | border: 1px solid #ddd;
16 | }
17 | `;
18 |
19 | export const Wrapper = styled.div`
20 | width: calc(100% - 115px);
21 | height: 115px;
22 | padding: 0.5rem 0 10px 2rem;
23 | ${Flex({ flexDirection: "column", justifyContent: "center", rowGap: "20px" })}
24 | `;
25 |
26 | export const Title = styled.p`
27 | font-size: 25px;
28 | font-family: "Pretendard-Bold" !important;
29 | cursor: pointer;
30 | `;
31 |
32 | export const Info = styled.div`
33 | ${Flex({ alignItems: "center", columnGap: "5px" })}
34 | color: ${RollingPalette.main.Dark};
35 |
36 | font-family: "Pretendard-Bold" !important;
37 | font-weight: 600;
38 | font-size: 19px;
39 |
40 | p {
41 | font-family: "Pretendard-Bold" !important;
42 | font-weight: 600;
43 | font-size: 18px;
44 | white-space: nowrap;
45 | }
46 |
47 | span {
48 | color: ${RollingPalette.text.Darkest};
49 | line-height: 25px;
50 | }
51 | `;
52 |
53 | export const PositionContainer = styled.div`
54 | position: relative;
55 | `;
56 |
57 | export const ModifyInput = styled.input`
58 | width: 100%;
59 | height: 130%;
60 | border: 1.5px solid #ddd;
61 | border-radius: 2px;
62 | background-color: #fff;
63 | outline-color: blue;
64 | color: #737b98;
65 | padding: 5px;
66 | `;
67 |
68 | export const PositionListUl = styled.ul`
69 | width: 100%;
70 |
71 | position: absolute;
72 | top: 95px;
73 |
74 | background-color: #fff;
75 | border-radius: 5px;
76 | border: 1px solid rgba(189, 194, 208, 1);
77 |
78 | color: rgba(29, 30, 90, 1);
79 | max-height: 300px;
80 | z-index: 3;
81 | overflow-y: auto;
82 | padding: 1rem;
83 |
84 | ${Flex({ flexDirection: "column", rowGap: "20px" })}
85 |
86 | li {
87 | cursor: pointer;
88 | }
89 | `;
90 |
--------------------------------------------------------------------------------
/src/components/User/Story/index.tsx:
--------------------------------------------------------------------------------
1 | import StoryItem from "@src/components/User/Story/StoryItem";
2 | import { useEffect } from "react";
3 | import { useInView } from "react-intersection-observer";
4 | import { useGetMyStoryQuery } from "@src/services/Story/queries";
5 | import { Row } from "@src/styles/flex";
6 |
7 | const Story = () => {
8 | const { data: storyList, fetchNextPage } = useGetMyStoryQuery({
9 | suspense: true,
10 | });
11 | const [ref, inView] = useInView();
12 | const storyListData = storyList?.pages[0].data!!;
13 | useEffect(() => {
14 | if (inView) {
15 | fetchNextPage();
16 | }
17 | }, [inView]);
18 |
19 | return (
20 | <>
21 | {storyListData?.length!! > 0 ? (
22 | <>
23 |
29 | {storyList?.pages.map((data) =>
30 | data.data.map((story) => (
31 |
32 | ))
33 | )}
34 |
35 |
36 | >
37 | ) : (
38 | 지금 당장 스토리를 등록하세요!
39 | )}
40 | >
41 | );
42 | };
43 |
44 | export default Story;
45 |
--------------------------------------------------------------------------------
/src/components/User/StoryStatus/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import printer from "@src/assets/icons/Story/printer.svg";
3 | import { getTimeAgo } from "@stubee2/stubee2-rolling-util";
4 | import write from "@src/assets/icons/Company/write.png";
5 | import { useNavigate } from "react-router-dom";
6 | import { useGetStoryMyStatusQuery } from "@src/services/Story/queries";
7 | import { Row } from "@src/styles/flex";
8 |
9 | const StoryStatus = () => {
10 | const { data: storyStatus, isError } = useGetStoryMyStatusQuery({
11 | suspense: true,
12 | });
13 | const { count, lastModifiedDate } = storyStatus!!;
14 | const navigate = useNavigate();
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 스토리를 등록하여
26 | 후배들에게 더 많은 이야기를 들려주세요!
27 |
28 |
29 |
30 |
31 |
32 |
33 | 내가 작성한 롤링 Story · {isError ? 0 : count}개
34 |
35 |
36 | {count > 0
37 | ? "최근 업데이트 " + getTimeAgo(new Date(lastModifiedDate))
38 | : "롤링한 회사가 없습니다."}
39 |
40 |
41 |
42 | navigate("/story")}>
43 | 스토리 등록
44 |
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default StoryStatus;
53 |
--------------------------------------------------------------------------------
/src/components/User/StoryStatus/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const Container = styled.div`
6 | width: 100%;
7 | height: 183px;
8 | background-color: ${RollingPalette.main.Base};
9 | border-radius: 10px;
10 | padding: 0 1.5rem 0 2rem;
11 | ${Flex({ flexDirection: "column", justifyContent: "center", rowGap: "10px" })}
12 | `;
13 |
14 | export const RegistStoryText = styled.div`
15 | ${Flex({ flexDirection: "column", alignItems: "flex-end", rowGap: "7px" })}
16 |
17 | font-size: 18px;
18 | color: #fff;
19 |
20 | @media screen and (max-width: 585px) {
21 | font-size: 15px;
22 | }
23 | `;
24 |
25 | export const IconCotainer = styled.div`
26 | margin-bottom: 20px;
27 | img {
28 | width: 53px;
29 | height: 53px;
30 | }
31 | `;
32 |
33 | export const StoryNumber = styled.div`
34 | background-color: #ffffff;
35 | border-radius: 4rem;
36 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
37 | color: #4869f6;
38 | font-size: 22px;
39 | ${Flex({ alignItems: "center", justifyContent: "center" })}
40 | `;
41 |
42 | export const WroteStoryCount = styled.p`
43 | color: #f9fafb;
44 | font-size: 18px;
45 | font-weight: bold;
46 | `;
47 |
48 | export const StatusText = styled.p`
49 | color: ${RollingPalette.unEmphasize.Light};
50 | font-size: 12px;
51 | margin-top: 10px;
52 | `;
53 |
54 | export const StoryButton = styled.button`
55 | width: 135px;
56 | height: 52px;
57 |
58 | background-color: ${RollingPalette.main.Dark};
59 | border-radius: 7px;
60 | outline: none;
61 | border: none;
62 |
63 | font-size: 17px;
64 | font-weight: bold;
65 | color: #fff;
66 | cursor: pointer;
67 |
68 | ${Flex({ alignItems: "center", justifyContent: "center", columnGap: "5px" })}
69 |
70 | img {
71 | width: 20px;
72 | height: 20px;
73 | }
74 |
75 | transform: scale(1);
76 | transition: all 0.1s ease-in-out;
77 | &:hover {
78 | background-color: rgba(29, 30, 90, 0.87);
79 | transform: scale(0.985);
80 | }
81 | `;
82 |
--------------------------------------------------------------------------------
/src/components/User/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "./style";
2 | import Nav from "./Nav";
3 | import { ReactNode, Suspense } from "react";
4 | import ErrorBoundary from "../Common/ErrorBoundary";
5 | import useTokenCheck from "@src/hooks/Auth/useTokenCheck";
6 | import NavFooter from "./Nav/NavFooter";
7 | import UserSkeleton from "../Common/Skeleton/User";
8 | import NavSkeleton from "../Common/Skeleton/User/Nav";
9 | import { useLocation } from "react-router";
10 | import StoryStatus from "./StoryStatus";
11 | import StoryStatusSkeleton from "../Common/Skeleton/User/StoryStatus";
12 |
13 | interface Props {
14 | children: ReactNode;
15 | }
16 |
17 | const User = ({ children }: Props) => {
18 | useTokenCheck();
19 | const { pathname } = useLocation();
20 | const isStoryPage = pathname === "/mypage/story";
21 |
22 | return (
23 |
24 |
25 | 내 정보 갖고오지 못했습니다.>}>
26 | }>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {isStoryPage ? "내 롤링 Story" : "롤링 Profile"}
36 |
37 |
38 | {isStoryPage
39 | ? "자신이 직접 롤링한 회사를 보여줘요"
40 | : "기본 정보와 서비스에서 이용되는 프로필을 설정할 수 있어요"}
41 |
42 |
43 |
44 | 롤링한 회사의 수를 갖고오지 못했습니다.>}
46 | >
47 | }>
48 |
49 |
50 |
51 | }>{children}
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default User;
63 |
--------------------------------------------------------------------------------
/src/components/User/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled, { css } from "styled-components";
4 |
5 | export const UserContainer = styled.div`
6 | width: 100%;
7 | height: 100%;
8 | background-color: #f9fafb;
9 | zoom: 0.8;
10 |
11 | ${Flex({
12 | flexDirection: "column",
13 | alignItems: "center",
14 | justifyContent: "center",
15 | })}
16 |
17 | padding-bottom: 2rem;
18 | min-width: 500px;
19 | padding: 90px 10px 0 10px;
20 |
21 | @media screen and (max-width: 500px) {
22 | height: 100%;
23 | }
24 | `;
25 |
26 | export const UserWrapper = styled.div`
27 | width: 1370px;
28 | height: 100%;
29 | display: flex;
30 |
31 | @media screen and (max-width: 1130px) {
32 | width: 100%;
33 | }
34 |
35 | @media screen and (max-width: 1040px) {
36 | flex-direction: column;
37 | height: calc(100% - 60px);
38 | }
39 | `;
40 |
41 | export const UserListContainer = styled.div<{ page: boolean }>`
42 | width: calc(100% - 420px);
43 | height: 100%;
44 | padding-top: 40px;
45 |
46 | ${Flex({ flexDirection: "column", alignItems: "center" })}
47 |
48 | ${({ page }) =>
49 | page &&
50 | css`
51 | overflow-y: auto;
52 | ::-webkit-scrollbar {
53 | display: none;
54 | }
55 | `}
56 |
57 | @media screen and (max-width: 1040px) {
58 | width: 100%;
59 | }
60 | @media screen and (max-width: 500px) {
61 | padding-bottom: 3rem;
62 | }
63 | `;
64 |
65 | export const Container = styled.div`
66 | width: 90%;
67 | height: 100%;
68 | ${Flex({ flexDirection: "column", rowGap: "30px" })}
69 | `;
70 |
71 | export const Title = styled.div`
72 | font-weight: bold;
73 | `;
74 |
75 | export const Explain = styled.div`
76 | font-size: 14.7px;
77 | color: ${RollingPalette.unEmphasize.Dark};
78 | margin-top: 10px;
79 | `;
80 |
81 | export const FontSize = styled.p<{ fontSize: string }>`
82 | font-size: ${({ fontSize }) => fontSize};
83 | `;
84 |
85 | export const StoryStatusCommonHover = css`
86 | transform: scale(1);
87 | transition: all 0.1s ease-in-out;
88 | cursor: pointer;
89 | &:hover {
90 | background-color: #5a78f7;
91 | transform: scale(0.99);
92 | }
93 | `;
94 |
--------------------------------------------------------------------------------
/src/components/ViewAll/All/AllList/style.ts:
--------------------------------------------------------------------------------
1 | import { FadeInAnimation } from "@src/styles/common.style";
2 | import { Flex } from "@src/styles/flex";
3 | import styled from "styled-components";
4 |
5 | export const Wrapper = styled.div`
6 | width: 100%;
7 | height: 100%;
8 |
9 | ${Flex({ flexWrap: "wrap", columnGap: "20px", rowGap: "6rem" })}
10 |
11 | @media screen and (max-width: 1031px) {
12 | row-gap: 2.5rem;
13 | }
14 |
15 | ${FadeInAnimation};
16 | `;
17 |
18 | export const Title = styled.div`
19 | padding-bottom: 10px;
20 | border-bottom: 1px solid #ddd;
21 | ${Flex({ alignItems: "center", columnGap: "5px" })}
22 | `;
23 |
24 | export const Image = styled.img`
25 | width: 25px;
26 | height: 25px;
27 | `;
28 |
29 | export const Text = styled.p`
30 | font-size: 22px;
31 | font-weight: bold;
32 | `;
33 |
34 | export const CompanyBox = styled.div`
35 | width: 282px;
36 | height: 320px;
37 |
38 | overflow: hidden;
39 | cursor: pointer;
40 |
41 | @media screen and (max-width: 985px) {
42 | flex-basis: 31.5%;
43 | }
44 | @media screen and (max-width: 645px) {
45 | flex-basis: 48%;
46 | height: 300px;
47 | }
48 | @media screen and (max-width: 500px) {
49 | flex-basis: 47.6%;
50 | }
51 | `;
52 |
53 | export const NoneData = styled.div`
54 | width: 100%;
55 | height: 600px;
56 | `;
57 |
--------------------------------------------------------------------------------
/src/components/ViewAll/All/index.tsx:
--------------------------------------------------------------------------------
1 | import * as S from "../style";
2 | import ErrorBoundary from "../../Common/ErrorBoundary";
3 | import { Suspense } from "react";
4 | import AllList from "./AllList";
5 | import { useAuthTopScroll } from "@stubee2/stubee2-rolling-util";
6 | import Footer from "../../Common/Footer";
7 | import AllListSkeleton from "../../Common/Skeleton/ViewAll/All";
8 | import { Column } from "@src/styles/flex";
9 |
10 | const All = () => {
11 | useAuthTopScroll();
12 | return (
13 |
14 |
15 |
16 | That's 모든 기업
17 |
18 | 졸업생들이 후배들을 위해 롤링한 기업들을 모두 보여줘요!
19 |
20 |
21 |
22 | 졸업생 추천 회사들을 가지고 오지 못했습니다.>}
24 | >
25 | }>
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default All;
36 |
--------------------------------------------------------------------------------
/src/components/ViewAll/Rank/RankList/style.ts:
--------------------------------------------------------------------------------
1 | import { FadeInAnimation } from "@src/styles/common.style";
2 | import { Flex } from "@src/styles/flex";
3 | import styled from "styled-components";
4 |
5 | export const TitleContainer = styled.div`
6 | border-bottom: 1px solid #ddd;
7 | ${Flex({ alignItems: "center", justifyContent: "space-between" })}
8 | `;
9 |
10 | export const Title = styled.div`
11 | padding-bottom: 10px;
12 | ${Flex({ alignItems: "center", columnGap: "5px" })}
13 |
14 | img {
15 | width: 25px;
16 | height: 25px;
17 | }
18 |
19 | p {
20 | font-size: 22px;
21 | font-weight: bold;
22 | }
23 | `;
24 |
25 | export const SliderButton = styled.button`
26 | width: 25px;
27 | height: 25px;
28 |
29 | ${Flex({ alignItems: "center", justifyContent: "center" })}
30 |
31 | border: 1px solid #ddd;
32 | cursor: pointer;
33 | border-radius: 5px;
34 |
35 | transition: all 0.2s ease-in-out;
36 | transform: scale(1);
37 |
38 | img {
39 | width: 10px;
40 | height: 10px;
41 | }
42 |
43 | &:active {
44 | transform: scale(0.99);
45 | background-color: #ddd;
46 | }
47 | `;
48 |
49 | export const Wrapper = styled.div`
50 | width: 100%;
51 | display: flex;
52 |
53 | overflow-x: auto;
54 | scroll-behavior: smooth;
55 |
56 | @media screen and (min-width: 650px) {
57 | ::-webkit-scrollbar {
58 | display: none;
59 | }
60 | }
61 |
62 | ${FadeInAnimation};
63 | `;
64 |
65 | export const RankBox = styled.div`
66 | width: 273px;
67 | height: 320px;
68 |
69 | overflow: hidden;
70 | cursor: pointer;
71 |
72 | @media screen and (max-width: 985px) {
73 | flex-basis: 31.5%;
74 | }
75 | @media screen and (max-width: 645px) {
76 | flex-basis: 48%;
77 | width: 267px;
78 | height: 300px;
79 | }
80 | @media screen and (max-width: 500px) {
81 | flex-basis: 47.6%;
82 | width: 223px;
83 | }
84 | `;
85 |
86 | export const NoneData = styled.div`
87 | width: 100%;
88 | height: 250px;
89 | `;
90 |
--------------------------------------------------------------------------------
/src/components/ViewAll/Rank/index.tsx:
--------------------------------------------------------------------------------
1 | import { COMPANY_RANK_ITMES } from "@src/constants/Company/company.constant";
2 | import { Suspense } from "react";
3 | import ErrorBoundary from "../../Common/ErrorBoundary";
4 | import Footer from "../../Common/Footer";
5 | import RankingSkeleton from "../../Common/Skeleton/ViewAll/Rank";
6 | import RankList from "./RankList";
7 | import * as S from "../style";
8 | import { Column } from "@src/styles/flex";
9 |
10 | const Rank = () => {
11 | return (
12 |
13 |
14 |
15 |
16 | That's 기업 랭킹
17 | 매일 오전 3:00시 업데이트
18 |
19 |
20 | 졸업생들이 직접 매긴 회사만족도를 기반으로 카테고리별{" "}
21 | TOP 10 기업을 보여줘요!
22 |
23 |
24 |
25 | 데이터를 가지고 오지 못했습니다.>}>
26 | }>
27 | {COMPANY_RANK_ITMES.map((item) => (
28 |
33 | ))}
34 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default Rank;
43 |
--------------------------------------------------------------------------------
/src/components/ViewAll/style.ts:
--------------------------------------------------------------------------------
1 | import { Flex } from "@src/styles/flex";
2 | import { RollingPalette } from "@stubee2/stubee2-rolling-design-token";
3 | import styled from "styled-components";
4 |
5 | export const ViewAllContainer = styled.div<{ rowGap: string }>`
6 | width: 100%;
7 | height: 100%;
8 | zoom: 0.8;
9 | padding-top: 90px;
10 |
11 | ${({ rowGap }) =>
12 | Flex({
13 | alignItems: "center",
14 | flexDirection: "column",
15 | justifyContent: "center",
16 | rowGap,
17 | })}
18 |
19 | background-color: #f9fafb;
20 | min-width: 500px;
21 | `;
22 |
23 | export const ViewAllWrapper = styled.div`
24 | width: 1200px;
25 | height: 100%;
26 | padding: 3rem 5px 2rem 5px;
27 |
28 | ${Flex({ flexDirection: "column", rowGap: "5rem" })}
29 |
30 | @media screen and (max-width: 985px) {
31 | width: 785px;
32 | }
33 | @media screen and (max-width: 645px) {
34 | width: 580px;
35 | }
36 | @media screen and (max-width: 500px) {
37 | width: 490px;
38 | }
39 | `;
40 |
41 | export const Title = styled.p`
42 | font-size: 30px;
43 | font-weight: 700;
44 |
45 | span {
46 | font-size: 13px;
47 | margin-left: 10px;
48 | color: ${RollingPalette.unEmphasize.Dark};
49 | }
50 | `;
51 |
52 | export const SubTitle = styled.p`
53 | color: ${RollingPalette.unEmphasize.Dark};
54 | font-size: 17px;
55 | font-weight: 600;
56 |
57 | line-height: 25px;
58 |
59 | span {
60 | color: ${RollingPalette.main.Base};
61 | }
62 | `;
63 |
64 | export const CompanyLogoContainer = styled.div<{ rgb: string }>`
65 | width: 100%;
66 | height: 60%;
67 | border-radius: 10px;
68 |
69 | //회사 랭킹 숫자 보여주기 위해서 사용함
70 | position: relative;
71 |
72 | ${Flex({ justifyContent: "center" })}
73 | border-bottom: 1px solid #ddd;
74 |
75 | background-color: ${({ rgb }) => rgb || "#fff"};
76 | overflow: hidden;
77 |
78 | border: 1px solid #ddd;
79 | `;
80 |
81 | export const LogoImg = styled.img`
82 | max-width: 100%;
83 | max-height: 100%;
84 |
85 | transform: scale(1);
86 | transition: all 0.27s ease-in-out;
87 | &:hover {
88 | transform: scale(1.08);
89 | }
90 | `;
91 |
92 | export const CompanyContent = styled.div`
93 | width: 100%;
94 | height: 40%;
95 |
96 | ${Flex({ flexDirection: "column", rowGap: "1rem" })}
97 | padding: 1rem 1rem 0.7rem 0.3rem;
98 | `;
99 |
--------------------------------------------------------------------------------
/src/constants/Auth/auth.constant.ts:
--------------------------------------------------------------------------------
1 | export const ACCESS_TOKEN_KEY = "accessToken" as const;
2 | export const REFRESH_TOKEN_KEY = "refreshToken" as const;
3 | export const REQUEST_TOKEN_KEY = "Authorization" as const;
4 | export const REQUEST_REFRESH_TOKEN_KEY = "Refresh-Token" as const;
5 |
6 | export const SIGNIN_OPTIONS_ITEMS = [
7 | "회원가입",
8 | "아이디 찾기",
9 | "비밀번호 찾기",
10 | ] as const;
11 |
12 | export const gitSignInUrl = `${process.env.REACT_APP_ROLLING_API_KEY}/oauth2/authorization/github`;
13 |
--------------------------------------------------------------------------------
/src/constants/Company/company.constant.ts:
--------------------------------------------------------------------------------
1 | export const COMPANY_RANK_ITMES = [
2 | {
3 | id: 0,
4 | name: "총합순위",
5 | categoryName: "total",
6 | },
7 | {
8 | id: 1,
9 | name: "연봉순위",
10 | categoryName: "salary-benefits",
11 | },
12 | {
13 | id: 2,
14 | name: "워라벨순위",
15 | categoryName: "balance",
16 | },
17 | {
18 | id: 3,
19 | name: "조직문화순위",
20 | categoryName: "culture",
21 | },
22 | {
23 | id: 4,
24 | name: "커리어 향상순위",
25 | categoryName: "career",
26 | },
27 | ];
28 |
--------------------------------------------------------------------------------
/src/constants/ExternalSite/externalSite.constant.ts:
--------------------------------------------------------------------------------
1 | interface Type {
2 | id: number;
3 | name: string;
4 | logo: string;
5 | link: string;
6 | }
7 |
8 | export const EXTERNALSITE_ITEMS: Type[] = [
9 | {
10 | id: 0,
11 | name: "원티드",
12 | logo: "https://rollingbucket.s3.ap-northeast-2.amazonaws.com/rollingbucket/4e77e6ce-e511-48ac-ae6b-5828101d9adafile",
13 | link: "https://www.wanted.co.kr/wdlist/518?country=kr&job_sort=job.latest_order&years=0&locations=seoul.all",
14 | },
15 | {
16 | id: 1,
17 | name: "Blind",
18 | logo: "https://upload.wikimedia.org/wikipedia/commons/e/e4/B_symbol_white-red.png",
19 | link: "https://www.teamblind.com/kr/topics/%EA%B0%9C%EB%B0%9C",
20 | },
21 | {
22 | id: 2,
23 | name: "사람인",
24 | logo: "https://yt3.googleusercontent.com/ytc/AGIKgqOVZ_qx_7hVUXX-0z6kyiRn7a5V1zofWRdOQZRVDQ=s900-c-k-c0x00ffffff-no-rj",
25 | link: "https://www.saramin.co.kr/zf_user/search?search_area=main&search_done=y&search_optional_item=n&searchType=search&searchword=it",
26 | },
27 | {
28 | id: 3,
29 | name: "잡플래닛",
30 | logo: "https://image.rocketpunch.com/company/4268/jobplanet_logo_1513155867.png?s=400x400&t=inside",
31 | link: "https://www.jobplanet.co.kr/job",
32 | },
33 | {
34 | id: 4,
35 | name: "잡코리아",
36 | logo: "https://image.rocketpunch.com/company/12850/jobkorea_logo.png?s=400x400&t=inside",
37 | link: "https://www.jobkorea.co.kr/OnePick/JobList?Ord=GcmCodeAmtDesc&DutyCtgr=10031",
38 | },
39 | ];
40 |
--------------------------------------------------------------------------------
/src/constants/Footer/footer.constant.ts:
--------------------------------------------------------------------------------
1 | interface Type {
2 | id: number;
3 | name: string;
4 | link: string;
5 | }
6 |
7 | export const FOOTER_ITEMS: Type[] = [
8 | {
9 | id: 0,
10 | name: "GitHub",
11 | link: "https://github.com/StuBee2",
12 | },
13 | {
14 | id: 1,
15 | name: "Notion",
16 | link: "https://chldkdudsally.notion.site/Rolling-d7c0ffe961b7498bab3a5af2e112198c?pvs=4",
17 | },
18 | {
19 | id: 2,
20 | name: "개인정보 처리방침",
21 | link: "",
22 | },
23 | {
24 | id: 3,
25 | name: "이용약관",
26 | link: "",
27 | },
28 | ];
29 |
30 | export const FOOTER_MEMBERS_ITEMS: Type[] = [
31 | {
32 | id: 0,
33 | name: "전해림",
34 | link: "https://github.com/HAERIM0",
35 | },
36 | {
37 | id: 1,
38 | name: "최아영",
39 | link: "https://github.com/chldkduds2",
40 | },
41 | {
42 | id: 2,
43 | name: "최수원",
44 | link: "https://github.com/suw0n",
45 | },
46 | {
47 | id: 3,
48 | name: "문정환",
49 | link: "https://github.com/JeongWhan06",
50 | },
51 | {
52 | id: 4,
53 | name: "박상현",
54 | link: "https://github.com/Sanghyun0505",
55 | },
56 | ];
57 |
--------------------------------------------------------------------------------
/src/constants/Header/header.constant.ts:
--------------------------------------------------------------------------------
1 | interface Type {
2 | id: number;
3 | name: string;
4 | link: string;
5 | }
6 |
7 | export const HEADER_ITEMS: Type[] = [
8 | {
9 | id: 0,
10 | name: "홈 피드",
11 | link: "/",
12 | },
13 | // {
14 | // id: 1,
15 | // name: "취업 맵",
16 | // link: "/map",
17 | // },
18 | {
19 | id: 1,
20 | name: "스토리 등록",
21 | link: "/story",
22 | },
23 | ];
24 |
--------------------------------------------------------------------------------
/src/constants/Position/position.constant.ts:
--------------------------------------------------------------------------------
1 | export const POSITION_ITEMS: string[] = [
2 | "소프트웨어 엔지니어",
3 | "프론트엔드 개발자",
4 | "백엔드 개발자",
5 | "임베디드 개발자",
6 | "자바 개발자",
7 | "C,C++ 개발자",
8 | "파이썬 개발자",
9 | "Node.js 개발자",
10 | "머신러닝 엔지니어",
11 | "데이터 엔지니어",
12 | "안드로이드 개발자",
13 | "DevOps 개발자",
14 | "시스템 관리자",
15 | "기술이사",
16 | "네트워크 관리자",
17 | "iOS 개발자",
18 | "개발 매니저",
19 | "데이터 사이언티스트",
20 | "기술지원",
21 | "QA,테스트 엔지니어",
22 | "보안 엔지니어",
23 | "빅데이터 엔지니어",
24 | "하드웨어 엔지니어",
25 | "프로덕트 매니저",
26 | "프로젝트 매니저",
27 | "크로스플랫폼 앱 개발자",
28 | "블록체인 플랫폼 엔지니어",
29 | "CEO",
30 | "데이터 분석가",
31 | "DBA",
32 | "웹 퍼블리셔",
33 | "PHP 개발자",
34 | "ERP전문가",
35 | ".NET 개발자",
36 | "영상,음성 엔지니어",
37 | "그래픽스 엔지니어",
38 | "CTO,Chief Technology Officer",
39 | "VR 엔지니어",
40 | "루비온레일즈 개발자",
41 | "CIO,Chief Information Officer",
42 | "UI/UX 엔지니어",
43 | "UI 엔지니어",
44 | "UX 엔지니어",
45 | "UI/UX 디자이너",
46 | "UI 디자이너",
47 | "UX 디자이너",
48 | "GUI 디자이너",
49 | "웹 디자이너",
50 | "그래픽 디자이너",
51 | "모바일 디자이너",
52 | "3D 개발자",
53 | "BI/BX 엔지니어",
54 | "BI 엔지니어",
55 | "BX 엔지니어",
56 | "BI/BX 디자이너",
57 | "BI 디자이너",
58 | "BX 디자이너",
59 | ];
60 |
--------------------------------------------------------------------------------
/src/constants/Router/router.constant.ts:
--------------------------------------------------------------------------------
1 | interface Type {
2 | path: string;
3 | page: number;
4 | }
5 |
6 | export const ROUTE_ITEMS: Type[] = [
7 | { path: "/mypage/profile", page: 0 },
8 | { path: "/mypage/story", page: 1 },
9 | ];
10 |
--------------------------------------------------------------------------------
/src/constants/Story/story.constant.ts:
--------------------------------------------------------------------------------
1 | interface Type {
2 | id: number;
3 | title: string;
4 | name: string;
5 | }
6 |
7 | export const STORY_STARGRAGE_ITEMS: Type[] = [
8 | {
9 | id: 0,
10 | title: "워라벨",
11 | name: "workLifeBalance",
12 | },
13 | {
14 | id: 1,
15 | title: "연봉",
16 | name: "salaryAndBenefits",
17 | },
18 | {
19 | id: 2,
20 | title: "커리어 향상",
21 | name: "careerAdvancement",
22 | },
23 | {
24 | id: 3,
25 | title: "조직문화",
26 | name: "organizationalCulture",
27 | },
28 | ];
29 |
--------------------------------------------------------------------------------
/src/constants/User/user.constants.ts:
--------------------------------------------------------------------------------
1 | import idCard from "@src/assets/icons/Story/idCard.svg";
2 | import smileFace from "@src/assets/icons/Home/smileFace.svg";
3 | import edit from "@src/assets/images/User/edit.svg";
4 | import del from "@src/assets/images/User/del.svg";
5 | import cancel from "@src/assets/images/User/cancel.svg";
6 |
7 | interface Type {
8 | id: number;
9 | link: string;
10 | image: string;
11 | title: string;
12 | }
13 |
14 | export const USER_ITEMS: Type[] = [
15 | {
16 | id: 1,
17 | link: "/mypage/profile",
18 | image: smileFace,
19 | title: "마이 프로필",
20 | },
21 | {
22 | id: 2,
23 | link: "/mypage/story",
24 | image: idCard,
25 | title: "롤링한 기업",
26 | },
27 | ];
28 |
29 | export const USER_STORY_SETUP_ITEMS = [
30 | {
31 | id: 0,
32 | image: edit,
33 | },
34 | {
35 | id: 1,
36 | image: del,
37 | },
38 | {
39 | id: 2,
40 | image: cancel,
41 | },
42 | ];
43 |
--------------------------------------------------------------------------------
/src/hooks/Alumni/useAlumniCheck.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { ACCESS_TOKEN_KEY } from "@src/constants/Auth/auth.constant";
3 | import Token from "@src/libs/Token/Token";
4 | import { useNavigate } from "react-router-dom";
5 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
6 | import { tokenDecode } from "@src/utils/Auth/tokenDecode";
7 |
8 | const useAlumniCheck = () => {
9 | const navigate = useNavigate();
10 | const { rollingToast } = useRollingToast();
11 | const memberRole = tokenDecode("access", "authority");
12 |
13 | useEffect(() => {
14 | const checkAlumni = () => {
15 | if (Token.getToken(ACCESS_TOKEN_KEY) && memberRole === "TEMP") {
16 | rollingToast("동문 인증이 필요합니다.", "warning");
17 | navigate(-1);
18 | }
19 | };
20 | checkAlumni();
21 | }, [navigate]);
22 | };
23 |
24 | export default useAlumniCheck;
25 |
--------------------------------------------------------------------------------
/src/hooks/Alumni/useCertify.ts:
--------------------------------------------------------------------------------
1 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
2 | import React, { ChangeEvent, useState } from "react";
3 | import Token from "@src/libs/Token/Token";
4 | import {
5 | ACCESS_TOKEN_KEY,
6 | REFRESH_TOKEN_KEY,
7 | } from "@src/constants/Auth/auth.constant";
8 | import { useNavigate } from "react-router-dom";
9 | import { usePostCertifyMutation } from "@src/services/Graduate/mutations";
10 | import authApi from "@src/services/Auth/api";
11 |
12 | export const useCertify = () => {
13 | const [housemaster, setHousemaster] = useState("");
14 |
15 | const { rollingToast } = useRollingToast();
16 | const postCertify = usePostCertifyMutation();
17 | const navigate = useNavigate();
18 |
19 | const handleQuestionChange = (e: ChangeEvent) => {
20 | setHousemaster(e.target.value);
21 | };
22 |
23 | const tokenRefresh = async () => {
24 | const refresh_token = Token.getToken(REFRESH_TOKEN_KEY);
25 | if (refresh_token) {
26 | try {
27 | const { accessToken: newAccessToken } = await authApi.postRefreshToken({
28 | refreshToken: refresh_token,
29 | });
30 |
31 | Token.setToken(ACCESS_TOKEN_KEY, newAccessToken);
32 | rollingToast("동문 인증에 성공하였습니다.", "success");
33 | navigate("/mypage/profile");
34 | } catch (e) {
35 | console.log(e);
36 | }
37 | }
38 | };
39 |
40 | const handleGraduateCertified = (e: React.FormEvent) => {
41 | e.preventDefault();
42 |
43 | if (housemaster.trim() === "") {
44 | return rollingToast("사감선생님 성함을 작성해주세요", "warning");
45 | }
46 | postCertify.mutate(housemaster, {
47 | onSuccess: () => {
48 | tokenRefresh();
49 | },
50 | onError: () => {
51 | rollingToast("졸업생 인증 실패", "error");
52 | },
53 | });
54 | };
55 |
56 | return {
57 | housemaster,
58 | handleQuestionChange,
59 | handleGraduateCertified,
60 | navigate,
61 | rollingToast,
62 | };
63 | };
64 |
--------------------------------------------------------------------------------
/src/hooks/Auth/useLogin.ts:
--------------------------------------------------------------------------------
1 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
2 | import { useState } from "react";
3 |
4 | export function useLogin() {
5 | const { rollingToast } = useRollingToast();
6 | const [credentials, setCredentials] = useState<{ id: string; pw: string }>({
7 | id: "",
8 | pw: "",
9 | });
10 |
11 | const handleChange = (
12 | e: React.ChangeEvent
13 | ) => {
14 | const { name, value } = e.target;
15 | setCredentials((prevCredentials) => ({
16 | ...prevCredentials,
17 | [name]: value,
18 | }));
19 | };
20 |
21 | const handleUseGithubAlert = () => {
22 | rollingToast("깃허브 로그인을 이용해주세요", "info");
23 | };
24 |
25 | const handleSubmit = (e: React.FormEvent) => {
26 | e.preventDefault();
27 | const { id, pw } = credentials;
28 | if (id.trim() === "") {
29 | return rollingToast("아이디를 입력해주세요", "info");
30 | }
31 | if (pw.trim() === "") {
32 | return rollingToast("비밀번호를 입력해주세요", "info");
33 | }
34 | return rollingToast("깃허브 로그인을 이용해주세요", "info");
35 | };
36 |
37 | return {
38 | handleChange,
39 | handleUseGithubAlert,
40 | handleSubmit,
41 | credentials,
42 | setCredentials,
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/src/hooks/Auth/useLogout.ts:
--------------------------------------------------------------------------------
1 | import Token from "@src/libs/Token/Token";
2 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
3 |
4 | export const useLogout = () => {
5 | const { rollingToast } = useRollingToast();
6 | const handleLogout = () => {
7 | const answer = window.confirm("로그아웃 하시겠습니까?");
8 | if (answer) {
9 | rollingToast("로그아웃 하셨습니다", "info");
10 | Token.clearToken();
11 | window.location.replace("/");
12 | }
13 | };
14 |
15 | return { handleLogout };
16 | };
17 |
--------------------------------------------------------------------------------
/src/hooks/Auth/useSocialLogin.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import {
3 | ACCESS_TOKEN_KEY,
4 | REFRESH_TOKEN_KEY,
5 | } from "@src/constants/Auth/auth.constant";
6 | import Token from "@src/libs/Token/Token";
7 | import { useLocation, useNavigate } from "react-router-dom";
8 | import queryString from "query-string";
9 | import { tokenDecode } from "@src/utils/Auth/tokenDecode";
10 |
11 | export function useSocialLogin() {
12 | const navigate = useNavigate();
13 | const { search } = useLocation();
14 | const { accessToken, refreshToken } = queryString.parse(search);
15 |
16 | useEffect(() => {
17 | if (accessToken && refreshToken) {
18 | Token.setToken(ACCESS_TOKEN_KEY, accessToken.toString());
19 | Token.setToken(REFRESH_TOKEN_KEY, refreshToken.toString());
20 | const jwtDecode = tokenDecode("access", "authority");
21 | if (jwtDecode === "MEMBER") {
22 | navigate("/");
23 | } else {
24 | navigate("/alumni/certify");
25 | }
26 | }
27 | }, [accessToken, refreshToken, navigate]);
28 | }
29 |
--------------------------------------------------------------------------------
/src/hooks/Auth/useTokenCheck.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import {
3 | ACCESS_TOKEN_KEY,
4 | REFRESH_TOKEN_KEY,
5 | } from "@src/constants/Auth/auth.constant";
6 | import Token from "@src/libs/Token/Token";
7 | import { useNavigate } from "react-router-dom";
8 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
9 |
10 | const useTokenCheck = () => {
11 | const navigate = useNavigate();
12 | const { rollingToast } = useRollingToast();
13 | useEffect(() => {
14 | const checkTokens = () => {
15 | if (
16 | !Token.getToken(ACCESS_TOKEN_KEY) ||
17 | !Token.getToken(REFRESH_TOKEN_KEY)
18 | ) {
19 | rollingToast("로그인이 필요한 기능입니다.", "warning");
20 | // 어세스토큰만 있거나 리프레쉬 토큰만 있을 경우를 대비해 토큰을 비워줌
21 | Token.clearToken();
22 | navigate("/");
23 | }
24 | };
25 | checkTokens();
26 | }, [navigate]);
27 | };
28 |
29 | export default useTokenCheck;
30 |
--------------------------------------------------------------------------------
/src/hooks/Company/useDeleteCompany.ts:
--------------------------------------------------------------------------------
1 | import { useQueryClient } from "react-query";
2 | import { QUERY_KEYS } from "@src/services/queryKey";
3 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
4 | import { useDeleteCompanyMutation } from "@src/services/Company/mutation";
5 |
6 | export const useDeleteCompany = () => {
7 | const deleteCompany = useDeleteCompanyMutation();
8 | const queryClient = useQueryClient();
9 | const { rollingToast } = useRollingToast();
10 |
11 | const handleCompanyDeleteClick = async (companyId: string) => {
12 | const answer = window.confirm("회사를 삭제하시겠습니까?");
13 | if (answer) {
14 | deleteCompany.mutateAsync(companyId, {
15 | onSuccess: () => {
16 | rollingToast("회사를 삭제하였습니다.", "success");
17 | queryClient.invalidateQueries(QUERY_KEYS.company.getMyListCompany);
18 | },
19 | onError: (e) => {
20 | rollingToast("회사 삭제에 실패했습니다.", "error");
21 | },
22 | });
23 | }
24 | };
25 |
26 | return { handleCompanyDeleteClick };
27 | };
28 |
--------------------------------------------------------------------------------
/src/hooks/Company/useSearchCompany.ts:
--------------------------------------------------------------------------------
1 | import { SearchModalAtom } from "@src/stores/common/common.store";
2 | import { SearchCompanyAtom } from "@src/stores/company/company.store";
3 | import { useRollingToast } from "@stubee2/stubee2-rolling-toastify";
4 | import { useState } from "react";
5 | import { useNavigate } from "react-router-dom";
6 | import { useSetRecoilState } from "recoil";
7 |
8 | export const useSearchCompany = () => {
9 | const [keyword, setKeyword] = useState("");
10 | const setSearchModal = useSetRecoilState(SearchModalAtom);
11 | const setSearchCompany = useSetRecoilState(SearchCompanyAtom);
12 |
13 | const [isTyping, setIsTyping] = useState(false);
14 | const navigate = useNavigate();
15 | const { rollingToast } = useRollingToast();
16 |
17 | // 메인 회사검색 onChange
18 | const handleKeywordChange = (e: React.ChangeEvent) => {
19 | setKeyword(e.target.value);
20 | };
21 |
22 | const handleKeywordSubmit = (e: React.FormEvent) => {
23 | e.preventDefault();
24 | if (keyword.trim() === "") {
25 | return rollingToast("검색 키워드를 입력해주세요!", "warning");
26 | }
27 |
28 | navigate("/");
29 | window.scrollTo(0, 0);
30 | setSearchCompany(keyword);
31 | setSearchModal(false);
32 | setKeyword("");
33 | };
34 |
35 | // 스토리 등록 회사검색 onChange
36 | const handleCompanyChange = (
37 | e: React.ChangeEvent
38 | ) => {
39 | const inputValue = e.target.value;
40 | setIsTyping(!!inputValue);
41 | setKeyword(inputValue);
42 | };
43 |
44 | return {
45 | handleKeywordChange,
46 | handleKeywordSubmit,
47 | keyword,
48 | setKeyword,
49 | setSearchModal,
50 | handleCompanyChange,
51 | isTyping,
52 | setIsTyping,
53 | };
54 | };
55 |
--------------------------------------------------------------------------------
/src/hooks/Header/useHideHeader.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useSetRecoilState } from "recoil";
3 | import { HideHeader } from "@src/stores/common/common.store";
4 |
5 | const useHideHeader = () => {
6 | const setHideHeader = useSetRecoilState(HideHeader);
7 | useEffect(() => {
8 | setHideHeader(true);
9 | return () => setHideHeader(false);
10 | }, [setHideHeader]);
11 | };
12 |
13 | export default useHideHeader;
14 |
--------------------------------------------------------------------------------
/src/hooks/Invalidates/useQueryInvalidates.ts:
--------------------------------------------------------------------------------
1 | import { useQueryClient } from "react-query";
2 |
3 | export const useQueryInvalidates = () => {
4 | const queryClient = useQueryClient();
5 | const queryInvalidates = (queryKeys: (string | string[])[]) => {
6 | return queryKeys.map((item) =>
7 | queryClient.invalidateQueries(item, { refetchInactive: true })
8 | );
9 | };
10 | return { queryInvalidates };
11 | };
12 |
--------------------------------------------------------------------------------
/src/hooks/Logging/useLogging.ts:
--------------------------------------------------------------------------------
1 | import { usePostLoggingMutation } from "@src/services/Logging/mutations";
2 | import { changeUrlToLoggingText } from "@src/utils/Logging/changeUrlToLoggingText";
3 |
4 | export const useLogging = () => {
5 | const loggingMutataion = usePostLoggingMutation();
6 |
7 | const handleLogging = (link: string) => {
8 | const loggingText = changeUrlToLoggingText(link);
9 | const loggingData = {
10 | description: loggingText,
11 | module: loggingText,
12 | };
13 | loggingMutataion.mutate(loggingData, {
14 | onSuccess: () => {
15 | console.log(loggingData);
16 | },
17 | onError: (e) => {
18 | console.log(e);
19 | },
20 | });
21 | };
22 |
23 | return { handleLogging };
24 | };
25 |
--------------------------------------------------------------------------------
/src/hooks/Story/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const modifyCustomInput = () => {
4 | return index
;
5 | };
6 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createRoot } from "react-dom/client";
3 | import App from "./App";
4 |
5 | const Index = () => {
6 | return ;
7 | };
8 |
9 | const root = document.getElementById("root");
10 | root && createRoot(root).render();
11 |
--------------------------------------------------------------------------------
/src/libs/Axios/RollingAxios.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {
3 | ACCESS_TOKEN_KEY,
4 | REQUEST_TOKEN_KEY,
5 | } from "@src/constants/Auth/auth.constant";
6 | import Token from "../Token/Token";
7 | import { responseHandler } from "./responseHandler";
8 | import { requestHandler } from "./requestHandler";
9 |
10 | export const customAxios = axios.create({
11 | baseURL: process.env.REACT_APP_ROLLING_API_KEY,
12 | });
13 |
14 | export const rollingAxios = axios.create({
15 | baseURL: process.env.REACT_APP_ROLLING_API_KEY,
16 | headers: {
17 | [REQUEST_TOKEN_KEY]: `Bearer ${Token.getToken(ACCESS_TOKEN_KEY)}`,
18 | },
19 | });
20 |
21 | rollingAxios.interceptors.request.use(requestHandler, (response) => response);
22 | rollingAxios.interceptors.response.use((response) => response, responseHandler);
23 |
--------------------------------------------------------------------------------
/src/libs/Axios/requestHandler.ts:
--------------------------------------------------------------------------------
1 | import { AxiosRequestConfig } from "axios";
2 | import {
3 | ACCESS_TOKEN_KEY,
4 | REFRESH_TOKEN_KEY,
5 | REQUEST_TOKEN_KEY,
6 | } from "@src/constants/Auth/auth.constant";
7 | import Token from "../Token/Token";
8 |
9 | export const requestHandler = (config: AxiosRequestConfig) => {
10 | const access_token = Token.getToken(ACCESS_TOKEN_KEY);
11 | const refresh_token = Token.getToken(REFRESH_TOKEN_KEY);
12 |
13 | if (access_token || refresh_token) {
14 | config.headers = {
15 | "Content-Type": "application/json",
16 | [REQUEST_TOKEN_KEY]: `Bearer ${Token.getToken(ACCESS_TOKEN_KEY)}`,
17 | };
18 | }
19 | return config;
20 | };
21 |
--------------------------------------------------------------------------------
/src/libs/Axios/responseHandler.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError } from "axios";
2 | import {
3 | ACCESS_TOKEN_KEY,
4 | REFRESH_TOKEN_KEY,
5 | REQUEST_TOKEN_KEY,
6 | } from "@src/constants/Auth/auth.constant";
7 | import Token from "../Token/Token";
8 | import authApi from "@src/services/Auth/api";
9 | import { rollingAxios } from "./RollingAxios";
10 |
11 | export const responseHandler = async (error: AxiosError) => {
12 | const access_token = Token.getToken(ACCESS_TOKEN_KEY);
13 | const refresh_token = Token.getToken(REFRESH_TOKEN_KEY);
14 | if (error.response) {
15 | const {
16 | config: originalRequest,
17 | response: { status },
18 | } = error;
19 |
20 | if (access_token && refresh_token && status === 401) {
21 | try {
22 | const { accessToken: newAccessToken } = await authApi.postRefreshToken({
23 | refreshToken: refresh_token,
24 | });
25 |
26 | Token.setToken(ACCESS_TOKEN_KEY, newAccessToken);
27 |
28 | rollingAxios.defaults.headers.common[
29 | REQUEST_TOKEN_KEY
30 | ] = `Bearer ${newAccessToken}`;
31 |
32 | return rollingAxios(originalRequest);
33 | } catch (e) {
34 | Token.clearToken();
35 | window.alert("토큰이 만료되었습니다!");
36 | window.location.href = "/";
37 | }
38 | }
39 | }
40 | return Promise.reject(error);
41 | };
42 |
--------------------------------------------------------------------------------
/src/libs/Cookie/cookie.ts:
--------------------------------------------------------------------------------
1 | import cookie from "js-cookie";
2 |
3 | class Cookie {
4 | public getCookie(key: string): string | undefined {
5 | let item = undefined;
6 | if (cookie.get(key) !== undefined) {
7 | item = cookie.get(key);
8 | }
9 | return item;
10 | }
11 |
12 | public setCookie(key: string, value: string, expire?: Date): void {
13 | cookie.set(key, value, { expires: expire });
14 | }
15 |
16 | public removeCookie(key: string): void {
17 | cookie.remove(key);
18 | }
19 | }
20 |
21 | export default new Cookie();
22 |
--------------------------------------------------------------------------------
/src/libs/Token/Token.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ACCESS_TOKEN_KEY,
3 | REFRESH_TOKEN_KEY,
4 | } from "@src/constants/Auth/auth.constant";
5 | import cookie from "../Cookie/cookie";
6 |
7 | interface Storage {
8 | getToken(key: string): string | undefined;
9 | setToken(key: string, value: string, expire?: Date): void;
10 | clearToken(): void;
11 | removeToken(key: string): void;
12 | }
13 |
14 | class Token implements Storage {
15 | public getToken(key: string): string | undefined {
16 | return cookie.getCookie(key);
17 | }
18 |
19 | public setToken(key: string, token: string, expire?: Date): void {
20 | cookie.setCookie(key, token, expire);
21 | }
22 |
23 | public clearToken() {
24 | cookie.removeCookie(ACCESS_TOKEN_KEY);
25 | cookie.removeCookie(REFRESH_TOKEN_KEY);
26 | }
27 |
28 | public removeToken(key: string) {
29 | cookie.removeCookie(key);
30 | }
31 | }
32 |
33 | export default new Token();
34 |
--------------------------------------------------------------------------------
/src/pages/AllPage/index.tsx:
--------------------------------------------------------------------------------
1 | import All from "@src/components/ViewAll/All";
2 |
3 | const AllPage = () => {
4 | return ;
5 | };
6 |
7 | export default AllPage;
8 |
--------------------------------------------------------------------------------
/src/pages/AlumniPage/CertifyPage/index.tsx:
--------------------------------------------------------------------------------
1 | import Certifiy from "@src/components/Alumni/Certify";
2 |
3 | const CertifyPage = () => {
4 | return ;
5 | };
6 |
7 | export default CertifyPage;
8 |
--------------------------------------------------------------------------------
/src/pages/AuthPage/AuthLoadingPage/index.tsx:
--------------------------------------------------------------------------------
1 | import { useSocialLogin } from "@src/hooks/Auth/useSocialLogin";
2 | import useHideHeader from "@src/hooks/Header/useHideHeader";
3 |
4 | const AuthLoadingPage = () => {
5 | useHideHeader();
6 | useSocialLogin();
7 | return 로딩중
;
8 | };
9 |
10 | export default AuthLoadingPage;
11 |
--------------------------------------------------------------------------------
/src/pages/CompanyPage/CompanyDetailPage/index.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from "react-router-dom";
2 | import CompanyDetail from "@src/components/Company/CompanyDetail";
3 |
4 | const CompanyDetailPage = () => {
5 | const { id } = useParams();
6 |
7 | return ;
8 | };
9 |
10 | export default CompanyDetailPage;
11 |
--------------------------------------------------------------------------------
/src/pages/HomePage/index.tsx:
--------------------------------------------------------------------------------
1 | import Home from "@src/components/Home";
2 |
3 | const HomePage = () => {
4 | return ;
5 | };
6 |
7 | export default HomePage;
8 |
--------------------------------------------------------------------------------
/src/pages/RankPage/index.tsx:
--------------------------------------------------------------------------------
1 | import Rank from "@src/components/ViewAll/Rank";
2 |
3 | const RankPage = () => {
4 | return ;
5 | };
6 |
7 | export default RankPage;
8 |
--------------------------------------------------------------------------------
/src/pages/StoryPage/ModifyPage/Company/index.tsx:
--------------------------------------------------------------------------------
1 | import CompanyModify from "@src/components/Story/Company/Modify";
2 |
3 | const CompanyModifyPage = () => {
4 | return ;
5 | };
6 |
7 | export default CompanyModifyPage;
8 |
--------------------------------------------------------------------------------
/src/pages/StoryPage/index.tsx:
--------------------------------------------------------------------------------
1 | import Story from "@src/components/Story";
2 |
3 | const StoryPage = () => {
4 | return ;
5 | };
6 |
7 | export default StoryPage;
8 |
--------------------------------------------------------------------------------
/src/pages/UserPage/index.tsx:
--------------------------------------------------------------------------------
1 | import User from "@src/components/User";
2 | import Story from "@src/components/User/Story";
3 | import Profile from "@src/components/User/Profile";
4 | import { tokenDecode } from "@src/utils/Auth/tokenDecode";
5 | import styled from "styled-components";
6 | import { useNavigate } from "react-router-dom";
7 | import { Flex } from "@src/styles/flex";
8 |
9 | interface Props {
10 | page: number;
11 | }
12 |
13 | const UserPage = ({ page }: Props) => {
14 | const memberRole = tokenDecode("access", "authority");
15 | const navigate = useNavigate();
16 | return (
17 |
18 | {page === 0 && }
19 | {page === 1 &&
20 | (memberRole === "MEMBER" ? (
21 |
22 | ) : (
23 |
24 | 동문인증이 필요한 기능입니다.
25 | navigate("/alumni/certify")}>
26 | 인증하러 가기
27 |
28 |
29 | ))}
30 |
31 | );
32 | };
33 |
34 | const AlumniCheck = styled.div`
35 | font-size: 18px;
36 | ${Flex({ justifyContent: "center", columnGap: "10px" })}
37 | `;
38 |
39 | const Authenticate = styled.p`
40 | color: rgba(72, 105, 246, 1);
41 | cursor: pointer;
42 | &:active {
43 | color: rgba(86, 103, 193, 1);
44 | }
45 | `;
46 |
47 | export default UserPage;
48 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/router/index.tsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import HomePage from "../pages/HomePage";
3 | import AuthLoadingPage from "@src/pages/AuthPage/AuthLoadingPage";
4 | import UserPage from "../pages/UserPage";
5 | import NotFound from "../components/Common/NotFound";
6 | import { ROUTE_ITEMS } from "../constants/Router/router.constant";
7 | import CompanyDetailPage from "@src/pages/CompanyPage/CompanyDetailPage";
8 | import CompanyModifyPage from "@src/pages/StoryPage/ModifyPage/Company";
9 | import CertifiedPage from "@src/pages/AlumniPage/CertifyPage";
10 | import StoryPage from "@src/pages/StoryPage";
11 | import AllPage from "@src/pages/AllPage";
12 | import RankingPage from "@src/pages/RankPage";
13 |
14 | const Router = () => {
15 | return (
16 |
17 | } />
18 | } />
19 | {ROUTE_ITEMS.map((item) => (
20 | }
24 | />
25 | ))}
26 | } />
27 | } />
28 | } />
29 | } />
30 | } />
31 | } />
32 | } />
33 |
34 | );
35 | };
36 |
37 | export default Router;
38 |
--------------------------------------------------------------------------------
/src/services/Auth/api.ts:
--------------------------------------------------------------------------------
1 | import { customAxios } from "@src/libs/Axios/RollingAxios";
2 |
3 | export interface newAccessTokenResponse {
4 | accessToken: string;
5 | }
6 |
7 | export interface RefreshTokenParam {
8 | refreshToken: string;
9 | }
10 |
11 | class AuthApi {
12 | public async postRefreshToken(
13 | refreshToken: RefreshTokenParam
14 | ): Promise {
15 | const { data } = await customAxios.post(
16 | "/auth/refresh",
17 | refreshToken
18 | );
19 | return data;
20 | }
21 | }
22 |
23 | export default new AuthApi();
24 |
--------------------------------------------------------------------------------
/src/services/Company/mutation.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-query";
2 | import companyApi, { CompanyParam } from "./api";
3 |
4 | export const usePostCompanyRegisterMutation = () => {
5 | const registermutation = useMutation((data: CompanyParam) =>
6 | companyApi.postRegister(data)
7 | );
8 | return registermutation;
9 | };
10 |
11 | export const useDeleteCompanyMutation = () => {
12 | const mutation = useMutation((param: string) =>
13 | companyApi.deleteCompany({ companyId: param })
14 | );
15 | return mutation;
16 | };
17 |
18 | export const usePatchCompanyMutation = () => {
19 | const mutation = useMutation(
20 | (input: { companyId: string; companyData: CompanyParam }) =>
21 | companyApi.patchCompany(input)
22 | );
23 | return mutation;
24 | };
25 |
--------------------------------------------------------------------------------
/src/services/Employment/api.ts:
--------------------------------------------------------------------------------
1 | import { EmploymentResponse } from "@src/types/Employment/employment.type";
2 | import { rollingAxios } from "@src/libs/Axios/RollingAxios";
3 |
4 | class EmployApi {
5 | public async getMyEmployList(): Promise {
6 | const { data } = await rollingAxios.post("/employment/my");
7 | return data;
8 | }
9 | }
10 | export default new EmployApi();
11 |
--------------------------------------------------------------------------------
/src/services/Employment/queries.ts:
--------------------------------------------------------------------------------
1 | import { UseQueryOptions, useQuery } from "react-query";
2 | import { QUERY_KEYS } from "../../services/queryKey";
3 | import emplotmentApi from "./api";
4 | import { EmploymentResponse } from "@src/types/Employment/employment.type";
5 | import { AxiosError } from "axios";
6 |
7 | export const useGetMyEmploymenyListQuery = (
8 | options?: UseQueryOptions<
9 | EmploymentResponse[],
10 | AxiosError,
11 | EmploymentResponse[],
12 | string
13 | >
14 | ) =>
15 | useQuery(
16 | QUERY_KEYS.employment.getMyEmploymentList,
17 | () => emplotmentApi.getMyEmployList(),
18 | { staleTime: 1000 * 60 * 60, cacheTime: 1000 * 60 * 60, ...options }
19 | );
20 |
--------------------------------------------------------------------------------
/src/services/File/api.ts:
--------------------------------------------------------------------------------
1 | import { rollingAxios } from "@src/libs/Axios/RollingAxios";
2 |
3 | export interface FileParam {
4 | file: string;
5 | }
6 |
7 | export interface FileResponse {
8 | url: string;
9 | rgb: number;
10 | }
11 |
12 | class FileApi {
13 | public async uploadFile(file: FileParam): Promise {
14 | const { data } = await rollingAxios.post("/file", file);
15 | return data;
16 | }
17 | }
18 |
19 | export default new FileApi();
20 |
--------------------------------------------------------------------------------
/src/services/File/mutations.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-query";
2 | import fileApi, { FileParam } from "./api";
3 |
4 | export const useUploadFileMutation = () => {
5 | const mutation = useMutation((file: FileParam) => fileApi.uploadFile(file));
6 | return mutation;
7 | };
8 |
--------------------------------------------------------------------------------
/src/services/Graduate/api.ts:
--------------------------------------------------------------------------------
1 | import { rollingAxios } from "@src/libs/Axios/RollingAxios";
2 |
3 | class GraduateApi {
4 | public async certifyGraduate(housemaster: string): Promise {
5 | await rollingAxios.patch("/auth/certify", { housemaster });
6 | }
7 | }
8 |
9 | export default new GraduateApi();
10 |
--------------------------------------------------------------------------------
/src/services/Graduate/mutations.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-query";
2 | import graduateApi from "./api";
3 |
4 | export const usePostCertifyMutation = () => {
5 | const mutation = useMutation((housemaster: string) =>
6 | graduateApi.certifyGraduate(housemaster)
7 | );
8 | return mutation;
9 | };
10 |
--------------------------------------------------------------------------------
/src/services/Logging/api.ts:
--------------------------------------------------------------------------------
1 | import { customAxios } from "@src/libs/Axios/RollingAxios";
2 |
3 | export interface LoggingParam {
4 | description: string;
5 | module: string;
6 | }
7 |
8 | class LoggingApi {
9 | public async postLogging({
10 | description,
11 | module,
12 | }: LoggingParam): Promise {
13 | await customAxios.post("/logging", {
14 | description,
15 | module,
16 | });
17 | }
18 | }
19 |
20 | export default new LoggingApi();
21 |
--------------------------------------------------------------------------------
/src/services/Logging/mutations.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-query";
2 | import loggingApi, { LoggingParam } from "./api";
3 |
4 | export const usePostLoggingMutation = () => {
5 | const mutate = useMutation(({ description, module }: LoggingParam) =>
6 | loggingApi.postLogging({ description, module })
7 | );
8 | return mutate;
9 | };
10 |
--------------------------------------------------------------------------------
/src/services/Member/api.ts:
--------------------------------------------------------------------------------
1 | import { rollingAxios } from "@src/libs/Axios/RollingAxios";
2 | import { MemberType } from "@src/types/Member/member.type";
3 |
4 | export interface MemberNickNameParam {
5 | nickName: string;
6 | }
7 |
8 | class MemberApi {
9 | public async getMyInfo(): Promise {
10 | const { data } = await rollingAxios.get("/member/my");
11 | return data;
12 | }
13 |
14 | public async getMemberInfoId(id: string): Promise {
15 | const { data } = await rollingAxios.get(`/member/${id}`);
16 | return data;
17 | }
18 |
19 | public async patchMyNickName(nickName: MemberNickNameParam): Promise {
20 | await rollingAxios.patch("/member/nickName", nickName);
21 | }
22 | }
23 |
24 | export default new MemberApi();
25 |
--------------------------------------------------------------------------------
/src/services/Member/mutations.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "react-query";
2 | import { MemberNickNameParam } from "./api";
3 | import memberApi from "./api";
4 |
5 | export const usePatchMyNickNameMutation = () => {
6 | const mutation = useMutation((nickName: MemberNickNameParam) =>
7 | memberApi.patchMyNickName(nickName)
8 | );
9 | return mutation;
10 | };
11 |
--------------------------------------------------------------------------------
/src/services/Member/queries.ts:
--------------------------------------------------------------------------------
1 | import { MemberType } from "@src/types/Member/member.type";
2 | import { AxiosError } from "axios";
3 | import { UseQueryOptions, useQuery } from "react-query";
4 | import { QUERY_KEYS } from "../queryKey";
5 | import memberApi from "./api";
6 |
7 | export const useGetMyInfoQuery = (
8 | options?: UseQueryOptions
9 | ) =>
10 | useQuery(QUERY_KEYS.member.getMyMember, () => memberApi.getMyInfo(), {
11 | staleTime: 1000 * 60 * 60,
12 | cacheTime: 1000 * 60 * 60,
13 | ...options,
14 | });
15 |
--------------------------------------------------------------------------------
/src/services/News/api.ts:
--------------------------------------------------------------------------------
1 | import { customAxios } from "@src/libs/Axios/RollingAxios";
2 | import { NewsInfiniteScrollType } from "@src/types/News/news.type";
3 | import { CommonPageParam } from "@src/types/common/commont.type";
4 |
5 | class NewsApi {
6 | public async getNews(
7 | { companyName }: { companyName: string },
8 | { page }: CommonPageParam
9 | ): Promise {
10 | const { data } = await customAxios.get(
11 | `/news/${companyName}?page=${page}&size=10`
12 | );
13 | return data;
14 | }
15 | }
16 |
17 | export default new NewsApi();
18 |
--------------------------------------------------------------------------------
/src/services/News/queries.ts:
--------------------------------------------------------------------------------
1 | import {
2 | UseInfiniteQueryOptions,
3 | UseQueryOptions,
4 | useInfiniteQuery,
5 | } from "react-query";
6 | import { AxiosError } from "axios";
7 | import { QUERY_KEYS } from "../../services/queryKey";
8 | import newsApi from "./api";
9 | import { NewsInfiniteScrollType } from "@src/types/News/news.type";
10 |
11 | export const useGetNewsQuery = (
12 | {
13 | companyName,
14 | }: {
15 | companyName: string;
16 | },
17 | options?: UseInfiniteQueryOptions<
18 | NewsInfiniteScrollType,
19 | AxiosError,
20 | NewsInfiniteScrollType,
21 | NewsInfiniteScrollType,
22 | string[]
23 | >
24 | ) =>
25 | useInfiniteQuery(
26 | QUERY_KEYS.news.getNews(companyName),
27 | ({ pageParam = 1 }) =>
28 | newsApi.getNews({ companyName }, { page: pageParam }),
29 | {
30 | staleTime: 1000 * 60 * 60,
31 | cacheTime: 1000 * 60 * 60,
32 | getNextPageParam: (nextPage) => nextPage.nextPage,
33 | ...options,
34 | }
35 | );
36 |
--------------------------------------------------------------------------------
/src/services/Story/api.ts:
--------------------------------------------------------------------------------
1 | import { customAxios, rollingAxios } from "@src/libs/Axios/RollingAxios";
2 | import {
3 | StoryCompanyContentsType,
4 | StoryInfiniteScrollListType,
5 | StoryInfoIdInfiniteScrollListType,
6 | StoryInfoIdType,
7 | StoryMyStatusResponse,
8 | StoryPostType,
9 | } from "@src/types/Story/story.type";
10 | import { CommonPageParam, CommonIdParam } from "@src/types/common/commont.type";
11 |
12 | class StoryApi {
13 | public async getMyStoryList({
14 | page,
15 | }: CommonPageParam): Promise {
16 | const { data } = await rollingAxios.get(`/story/my?page=${page}&size=10`);
17 | return { ...data, nextPage: page + 1 };
18 | }
19 |
20 | public async getListStoryMemberId(
21 | { id }: CommonIdParam,
22 | { page }: CommonPageParam
23 | ): Promise {
24 | const { data } = await rollingAxios.get(
25 | `/story/list/member/${id}?page=${page}&size=10`
26 | );
27 | return { ...data, nextPage: page + 1 };
28 | }
29 |
30 | public async getListStoryCompanyId(
31 | { id }: CommonIdParam,
32 | { page }: CommonPageParam
33 | ): Promise {
34 | const { data } = await customAxios.get(
35 | `/story/list/company/${id}?page=${page}&size=10`
36 | );
37 | return { ...data, nextPage: page + 1 };
38 | }
39 |
40 | public async getStoryInfoId({ id }: CommonIdParam): Promise {
41 | const { data } = await customAxios.get(`/story/info/${id}`);
42 | return data;
43 | }
44 |
45 | public async getStoryMyStatus(): Promise {
46 | const { data } = await rollingAxios.get("/story/my/status");
47 | return data;
48 | }
49 |
50 | public async postStory(storyInfo: StoryPostType): Promise {
51 | const { data } = await rollingAxios.post("/story", storyInfo);
52 | return data;
53 | }
54 |
55 | public async deleteMyStory(storyId: string): Promise {
56 | await rollingAxios.delete(`/story/${storyId}`);
57 | }
58 |
59 | public async patchMyStory(input: {
60 | storyId: string;
61 | storyContent: StoryCompanyContentsType;
62 | }): Promise {
63 | await rollingAxios.patch(`/story/${input.storyId}`, input.storyContent);
64 | }
65 | }
66 |
67 | export default new StoryApi();
68 |
--------------------------------------------------------------------------------
/src/services/Story/mutations.ts:
--------------------------------------------------------------------------------
1 | import {
2 | StoryCompanyContentsType,
3 | StoryPostType,
4 | } from "@src/types/Story/story.type";
5 | import { useMutation } from "react-query";
6 | import storyApi from "./api";
7 |
8 | export const usePostStoryMutation = () => {
9 | const mutation = useMutation((storyInfo: StoryPostType) =>
10 | storyApi.postStory(storyInfo)
11 | );
12 | return mutation;
13 | };
14 |
15 | export const useDeleteMyStoryMutation = () => {
16 | const mutation = useMutation((storyId: string) =>
17 | storyApi.deleteMyStory(storyId)
18 | );
19 | return mutation;
20 | };
21 |
22 | export const usePatchMyStoryMutation = () => {
23 | const mutation = useMutation(
24 | (input: { storyId: string; storyContent: StoryCompanyContentsType }) =>
25 | storyApi.patchMyStory(input)
26 | );
27 | return mutation;
28 | };
29 |
--------------------------------------------------------------------------------
/src/services/queryKey.ts:
--------------------------------------------------------------------------------
1 | export const QUERY_KEYS = Object.freeze({
2 | company: {
3 | company: "/company",
4 | getMyListCompany: "/company/my",
5 | getListSearchCompany: (name: string) => ["/company/search", name],
6 | getListAllCompany: "/company/list/all",
7 | getCompanyId: (id: string) => ["/company/list/id", id],
8 | getInfoCompanyId: (id: string) => ["/company/info/id", id],
9 | getInfoCompany: "/company/info/id",
10 | getCompanyRank: "/company/rank",
11 | getCompanyRankTotal: "/company/rank/total",
12 | getCompanyRankSalaryBenefits: "/company/rank/salary-benefit",
13 | getCompanyRankCulture: "/company/rank/culture",
14 | getCompanyRankCareer: "/company/rank/career",
15 | getCompanyRankBalnce: "/company/rank/balnce",
16 | getCompanyRankCategory: (category: string) => ["/company/rank", category],
17 | },
18 | story: {
19 | story: "/story",
20 | getMyStory: "/story/my",
21 | getStoryListMemberId: (id: string) => ["/story/list/member/id", id],
22 | getStoryListCompanyId: (id: string) => ["/story/list/company/id", id],
23 | getStoryInfoId: (id: string) => ["/story/info/id", id],
24 | getStoryMyStatus: "/story/my/status",
25 | },
26 | member: {
27 | getMyMember: "/member",
28 | },
29 | news: {
30 | getNews: (companyName: string) => ["/news/companyName", companyName],
31 | },
32 | employment: {
33 | getMyEmploymentList: "/employment/my",
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/src/stores/auth/auth.store.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "recoil";
2 |
3 | export const SignInModalAtom = atom({
4 | key: "signInModalAtom",
5 | default: false,
6 | });
7 |
--------------------------------------------------------------------------------
/src/stores/common/common.store.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "recoil";
2 |
3 | export const HideHeader = atom({
4 | key: "hideHeader",
5 | default: false,
6 | });
7 |
8 | export const MyInfoModal = atom({
9 | key: "myInfoModal",
10 | default: false,
11 | });
12 |
13 | export const SearchModalAtom = atom({
14 | key: "searchModalAtom",
15 | default: false,
16 | });
17 |
--------------------------------------------------------------------------------
/src/stores/company/company.store.ts:
--------------------------------------------------------------------------------
1 | import { CompanyParam } from "@src/services/Company/api";
2 | import { atom } from "recoil";
3 | import { recoilPersist } from "recoil-persist";
4 |
5 | const { persistAtom } = recoilPersist();
6 |
7 | export const SearchCompanyAtom = atom({
8 | key: "searchCompanyAtom",
9 | default: "",
10 | });
11 |
12 | export const CompanyIdAtom = atom({
13 | key: "companyIdAtom",
14 | default: "",
15 | effects_UNSTABLE: [persistAtom],
16 | });
17 |
18 | export const CompanyModifyAtom = atom({
19 | key: "companyModifyAtom",
20 | default: {
21 | name: "",
22 | address: "",
23 | description: "",
24 | imgUrl: "",
25 | rgb: null,
26 | },
27 | effects_UNSTABLE: [persistAtom],
28 | });
29 |
--------------------------------------------------------------------------------
/src/stores/member/member.store.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "recoil";
2 | import { MemberType } from "@src/types/Member/member.type";
3 |
4 | export const MyMemberInfo = atom({
5 | key: "myMemberInfo",
6 | default: null,
7 | });
8 |
--------------------------------------------------------------------------------
/src/stores/story/story.store.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "recoil";
2 | import { StoryModifiableContentPatchType } from "@src/types/Story/story.type";
3 |
4 | import { recoilPersist } from "recoil-persist";
5 | import { CompanyParam } from "@src/services/Company/api";
6 |
7 | const { persistAtom } = recoilPersist();
8 |
9 | // 스토리 등록페이지 경로유입이 헤더로 들어온 건지 회사단일 조회 페이지에서
10 | // 들어온 건지 판단하는 atom, detail에서 들어오면 기업수정임
11 | export const StoryPagePathInflow = atom<"header" | "detail">({
12 | key: "storyPagePathInflowAtom",
13 | default: "header",
14 | effects_UNSTABLE: [persistAtom],
15 | });
16 |
17 | export const StorySearchCompanyAtom = atom<{
18 | companyName: string;
19 | isExistSearchList: boolean;
20 | }>({
21 | key: "storySearchCompanyAtom",
22 | default: {
23 | companyName: "",
24 | isExistSearchList: false,
25 | },
26 | effects_UNSTABLE: [persistAtom],
27 | });
28 |
29 | /**
30 | * 스토리 등록페이지에서 기업등록해서 서버가 주는 id값을 담은 다음,
31 | * 스토리 남기기를 위해서 해당회사 id로 스토리를 등록할 때 필요한 atom
32 | */
33 | export const StoryCompanyIdAtom = atom({
34 | key: "storyCompanyId",
35 | default: "",
36 | effects_UNSTABLE: [persistAtom],
37 | });
38 |
39 | export const StoryCompanyRegistAtom = atom({
40 | key: "storyCompanyRegistAtom",
41 | default: {
42 | name: "",
43 | address: "",
44 | addressEtc: "",
45 | description: "",
46 | imgUrl: "",
47 | rgb: null,
48 | },
49 | });
50 |
51 | export const StorySetupInitializationDotAtom = atom({
52 | key: "StorySetupInitializationDotAtom",
53 | default: false,
54 | });
55 |
56 | export const StoryModifiableEventAtom = atom({
57 | key: "StoryModifiableEventAtom",
58 | default: false,
59 | });
60 |
61 | export const StoryModifiableIdAtom = atom({
62 | key: "StoryEditionIdAtom",
63 | default: "",
64 | });
65 |
66 | export const StoryModifiableContentAtom = atom(
67 | {
68 | key: "StoryEditionContentAtom",
69 | default: {
70 | position: "",
71 | schoolLife: "",
72 | preparationCourse: "",
73 | employmentProcess: "",
74 | interviewQuestion: "",
75 | mostImportantThing: "",
76 | welfare: "",
77 | commuteTime: "",
78 | meal: "",
79 | pros: "",
80 | cons: "",
81 | etc: "",
82 | },
83 | }
84 | );
85 |
--------------------------------------------------------------------------------
/src/styles/GlobalStyles.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 | import reset from "styled-reset";
3 | import "./font.css";
4 |
5 | const GlobalStyle = createGlobalStyle`
6 | * {
7 | margin: 0;
8 | padding: 0;
9 | box-sizing: border-box;
10 | font-family: 'Pretendard-Regular' !important;
11 | }
12 |
13 | body {
14 | background-color: #f9fafb;
15 | }
16 |
17 | ${reset}
18 | `;
19 |
20 | export default GlobalStyle;
21 |
--------------------------------------------------------------------------------
/src/styles/common.style.ts:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components";
2 | import { Flex } from "./flex";
3 |
4 | export const HoverAnimation = css`
5 | ${Flex({ alignItems: "center", justifyContent: "center" })}
6 |
7 | border-radius: 10px;
8 | transform: scale(1);
9 | transition: all 0.1s ease-in-out;
10 | &:hover {
11 | background-color: #eeeeee;
12 | transform: scale(0.98);
13 | }
14 | &:active {
15 | background-color: #dddddd;
16 | }
17 | `;
18 |
19 | export const StopDrag = css`
20 | -webkit-touch-callout: none;
21 | user-select: none;
22 | -moz-user-select: none;
23 | -ms-user-select: none;
24 | -webkit-user-select: none;
25 | `;
26 |
27 | export const FadeInAnimation = css`
28 | animation: fadein 0.6s;
29 | @keyframes fadein {
30 | from {
31 | opacity: 0;
32 | }
33 | to {
34 | opacity: 1;
35 | }
36 | }
37 | `;
38 |
--------------------------------------------------------------------------------
/src/styles/flex.ts:
--------------------------------------------------------------------------------
1 | import type { CSSProperties } from "react";
2 | import styled, { css } from "styled-components";
3 |
4 | interface Props {
5 | flexDirection?: CSSProperties["flexDirection"];
6 | justifyContent?: CSSProperties["justifyContent"];
7 | alignItems?: CSSProperties["alignItems"];
8 | columnGap?: CSSProperties["columnGap"];
9 | rowGap?: CSSProperties["rowGap"];
10 | gap?: CSSProperties["gap"];
11 | flexWrap?: CSSProperties["flexWrap"];
12 | }
13 |
14 | export const Flex = ({
15 | flexDirection,
16 | justifyContent,
17 | alignItems,
18 | columnGap,
19 | rowGap,
20 | gap,
21 | flexWrap,
22 | }: Props) => {
23 | return css`
24 | display: flex;
25 | flex-direction: ${flexDirection};
26 | justify-content: ${justifyContent};
27 | align-items: ${alignItems};
28 | column-gap: ${columnGap};
29 | row-gap: ${rowGap};
30 | gap: ${gap};
31 | flex-wrap: ${flexWrap};
32 | `;
33 | };
34 |
35 | export interface BaseFlexProps {
36 | $gap?: CSSProperties["gap"];
37 | $columnGap?: CSSProperties["columnGap"];
38 | $rowGap?: CSSProperties["rowGap"];
39 |
40 | $alignItems?: CSSProperties["alignItems"];
41 | $justifyContent?: CSSProperties["justifyContent"];
42 |
43 | $width?: CSSProperties["width"];
44 | $height?: CSSProperties["height"];
45 |
46 | $wrap?: CSSProperties["flexWrap"];
47 | $padding?: CSSProperties["padding"];
48 | $backgroundColor?: CSSProperties["backgroundColor"];
49 | }
50 |
51 | const BaseFlex = styled.div`
52 | display: flex;
53 |
54 | gap: ${({ $gap }) => $gap || "0px"};
55 | row-gap: ${({ $rowGap }) => $rowGap || "0px"};
56 | column-gap: ${({ $columnGap }) => $columnGap || "0px"};
57 |
58 | justify-content: ${({ $justifyContent }) => $justifyContent || "flex-start"};
59 | align-items: ${({ $alignItems }) => $alignItems || "flex-start"};
60 | flex-wrap: ${({ $wrap }) => ($wrap ? "wrap" : "nowrap")};
61 |
62 | width: ${({ $width }) => $width || "auto"};
63 | height: ${({ $height }) => $height || "auto"};
64 |
65 | background-color: ${({ $backgroundColor }) =>
66 | $backgroundColor || "transparent"};
67 |
68 | padding: ${({ $padding }) => $padding};
69 | `;
70 |
71 | export const Column = styled(BaseFlex)`
72 | flex-direction: column;
73 | `;
74 |
75 | export const Row = styled(BaseFlex)`
76 | flex-direction: row;
77 | `;
78 |
--------------------------------------------------------------------------------
/src/styles/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | src: url("../assets/fonts/Pretendard-Regular.woff2");
3 | font-family: "Pretendard-Regular";
4 | }
5 |
6 | @font-face {
7 | src: url("../assets/fonts//Pretendard-Bold.woff2");
8 | font-family: "Pretendard-Bold";
9 | }
10 |
--------------------------------------------------------------------------------
/src/types/Auth/auth.type.ts:
--------------------------------------------------------------------------------
1 | export type DecodeKeyType = "sub" | "authority" | "iat" | "exp";
2 |
3 | export type DecodeType = {
4 | [key in DecodeKeyType]: string | number;
5 | };
6 |
--------------------------------------------------------------------------------
/src/types/Company/company.type.ts:
--------------------------------------------------------------------------------
1 | export interface CompanyInfiniteScrollType {
2 | data: CompanyListType[];
3 | nextPage: number;
4 | }
5 |
6 | export interface CompanyListType {
7 | companyId: {
8 | id: string;
9 | };
10 | status: string;
11 | registrantId: {
12 | id: string;
13 | };
14 | companyDetails: {
15 | name: string;
16 | description: string;
17 | address: {
18 | address: string;
19 | etc: string;
20 | };
21 | logo: {
22 | url: string;
23 | rgb: number;
24 | };
25 | };
26 | companyGrades: {
27 | total: number;
28 | salaryAndBenefits: number;
29 | workLifeBalance: number;
30 | organizationalCulture: number;
31 | careerAdvancement: number;
32 | };
33 | createdAt: string;
34 | modifiedAt: string;
35 | }
36 |
37 | export interface CompanyInfoType extends CompanyStarGrade {
38 | companyId: string;
39 | companyName: string;
40 | companyAddress: string;
41 | companyAddressEtc: string;
42 | companyDescription: string;
43 | companyLogoUrl: string;
44 | companyLogoRGB: number;
45 | companyCreatedAt: string;
46 | companyModifiedAt: string;
47 | registrantId: string;
48 | memberNickName: string;
49 | memberSocialLoginId: string;
50 | memberImageUrl: string;
51 | total: number;
52 | salaryAndBenefits: number;
53 | workLifeBalance: number;
54 | organizationalCulture: number;
55 | careerAdvancement: number;
56 | }
57 |
58 | export interface CompanyInfoId {
59 | id: string;
60 | }
61 |
62 | export interface CompanyStarGrade extends CompanyStarGradeWithoutTotalGrade {
63 | total: number;
64 | }
65 |
66 | export interface CompanyStarGradeWithoutTotalGrade {
67 | salaryAndBenefits: number;
68 | workLifeBalance: number;
69 | organizationalCulture: number;
70 | careerAdvancement: number;
71 | }
72 |
73 | export interface CompanyStarGradeInfo extends CompanyStarGrade {
74 | companyImgUrl: string;
75 | companyLogoRgb: number;
76 | companyName: string;
77 | }
78 |
79 | export interface CompanyRegistErrorType {
80 | name: boolean;
81 | address: boolean;
82 | description: boolean;
83 | }
84 |
85 | export interface CompanyInputRefType {
86 | name: React.RefObject;
87 | address: React.RefObject;
88 | description: React.RefObject;
89 | }
90 |
--------------------------------------------------------------------------------
/src/types/Employment/employment.type.ts:
--------------------------------------------------------------------------------
1 | export interface EmploymentResponse {
2 | employmentId: {
3 | id: string;
4 | };
5 | employeeId: {
6 | id: string;
7 | };
8 | employerId: {
9 | id: string;
10 | };
11 | employmentDetails: {
12 | position: string;
13 | schoolLife: string;
14 | preparationCourse: string;
15 | employmentProcess: string;
16 | interviewQuestion: string;
17 | mostImportantThing: string;
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/types/Member/member.type.ts:
--------------------------------------------------------------------------------
1 | export interface MemberType {
2 | memberId: {
3 | id: string;
4 | };
5 | role: "MEMBER" | "TEMP";
6 | details: {
7 | nickName: string;
8 | name: string;
9 | email: string;
10 | imageUrl: string;
11 | };
12 | socialDetails: {
13 | socialId: string;
14 | socialLoginId: string;
15 | loginType: string;
16 | };
17 | createdAt: string;
18 | modifiedAt: string;
19 | }
20 |
21 | export interface NavMemberProfileType {
22 | imgUrl: string;
23 | name: string;
24 | email?: string;
25 | }
26 |
--------------------------------------------------------------------------------
/src/types/News/news.type.ts:
--------------------------------------------------------------------------------
1 | export interface NewsInfiniteScrollType {
2 | data: NewsResponseType[];
3 | nextPage: number;
4 | }
5 |
6 | export interface NewsResponseType {
7 | total: number;
8 | start: number;
9 | display: number;
10 | items: [
11 | {
12 | title: string;
13 | originallink: string;
14 | link: string;
15 | description: string;
16 | pubDate: string;
17 | }
18 | ];
19 | }
20 |
--------------------------------------------------------------------------------
/src/types/StarGrade/starGrade.type.ts:
--------------------------------------------------------------------------------
1 | export interface StarGradeProps {
2 | rankStatus: { id: number; title: string; star: number }[];
3 | width: number;
4 | height: number;
5 | fontSize?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/types/common/commont.type.ts:
--------------------------------------------------------------------------------
1 | export interface CommonPageParam {
2 | page: number;
3 | }
4 |
5 | export interface CommonIdParam {
6 | id: string;
7 | }
8 |
--------------------------------------------------------------------------------
/src/types/logging/logging.type.ts:
--------------------------------------------------------------------------------
1 | export interface LoggingType {
2 | id: number;
3 | description: string;
4 | module: string;
5 | memberId: {
6 | id: string;
7 | };
8 | createdAt: string;
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/Auth/tokenDecode.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ACCESS_TOKEN_KEY,
3 | REFRESH_TOKEN_KEY,
4 | } from "@src/constants/Auth/auth.constant";
5 | import Token from "@src/libs/Token/Token";
6 | import { DecodeKeyType, DecodeType } from "@src/types/Auth/auth.type";
7 | import jwtDecode from "jwt-decode";
8 |
9 | export const tokenDecode = (
10 | tokenType: "access" | "refresh",
11 | option: DecodeKeyType
12 | ) => {
13 | const token =
14 | tokenType === "access"
15 | ? Token.getToken(ACCESS_TOKEN_KEY)
16 | : Token.getToken(REFRESH_TOKEN_KEY);
17 |
18 | if (token) {
19 | return jwtDecode(token)[option];
20 | }
21 | return;
22 | };
23 |
--------------------------------------------------------------------------------
/src/utils/Error/Auth/authErrorHandler.ts:
--------------------------------------------------------------------------------
1 | export const authErrorHandler = (status: number, message: string) => {
2 | if (status === 400) {
3 | if (message === "Jwt is malformed") {
4 | return "토큰 형태가 알맞지 않습니다.";
5 | }
6 |
7 | if (message === "Jwt is unsupported") {
8 | return "지원하지 않는 토큰입니다.";
9 | }
10 | }
11 |
12 | if (status === 401) {
13 | if (message === "Jwt is expired") {
14 | return "토큰이 만료되었습니다.";
15 | }
16 | }
17 |
18 | if (status === 500) {
19 | return "서버에서 오류가 발생했어요!";
20 | }
21 |
22 | return message;
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/Error/Company/companyErrorHanlder.ts:
--------------------------------------------------------------------------------
1 | export const companyErrorHanlder = (status: number, message: string) => {
2 | if (status === 404) {
3 | if (message === "Company not found") {
4 | return "회사를 찾지 못했습니다.";
5 | }
6 | }
7 |
8 | if (status === 409) {
9 | if (message === "Company name is duplicated") {
10 | return "회사이름이 중복되었습니다.";
11 | }
12 | }
13 |
14 | if (status === 500) {
15 | return "서버에서 오류가 발생했어요!";
16 | }
17 |
18 | return message;
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/Error/Employment/employmentErrorHanlder.ts:
--------------------------------------------------------------------------------
1 | export const employmentErrorHanlder = (status: number, message: string) => {
2 | if (status === 400) {
3 | if (message === "Employment already exists") {
4 | return "회사와 이미 고용관계입니다.";
5 | }
6 | }
7 |
8 | if (status === 404) {
9 | if (message === "Employment not found") {
10 | return "고용을 찾을 수 없습니다.";
11 | }
12 | }
13 |
14 | if (status === 500) {
15 | return "서버에서 오류가 발생했어요!";
16 | }
17 |
18 | return message;
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/Error/File/fileErrorHanlder.ts:
--------------------------------------------------------------------------------
1 | export const fileErrorHanlder = (status: number, message: string) => {
2 | if (status === 403) {
3 | return "동문인증이 필요합니다.";
4 | }
5 |
6 | if (status === 400) {
7 | if (message === "File is empty") {
8 | return "파일이 비어 있습니다.";
9 | }
10 | }
11 |
12 | if (status === 500) {
13 | return "서버에서 오류가 발생했어요!";
14 | }
15 |
16 | return message;
17 | };
18 |
--------------------------------------------------------------------------------
/src/utils/Error/Member/memberErrorHandler.ts:
--------------------------------------------------------------------------------
1 | export const memberErrorHandler = (status: number, message: string) => {
2 | if (status === 403) {
3 | if (message === "Member certify failed") {
4 | return "회원인증을 실패하였습니다.";
5 | }
6 | if (message === "You are not the author/registrant") {
7 | return "저자/등록자가 아닙니다!";
8 | }
9 | }
10 |
11 | if (status === 404) {
12 | if (message === "Member not found") {
13 | return "회원정보를 찾지 못했습니다.";
14 | }
15 | }
16 |
17 | if (status === 409) {
18 | if (message === "Nickname is duplicated") {
19 | return "닉네임이 중복되었습니다.";
20 | }
21 | }
22 |
23 | if (status === 500) {
24 | return "서버에서 오류가 발생했어요!";
25 | }
26 |
27 | return message;
28 | };
29 |
--------------------------------------------------------------------------------
/src/utils/Error/Story/storyErrorHanlder.ts:
--------------------------------------------------------------------------------
1 | export const storyErrorHanlder = (status: number, message: string) => {
2 | if (status === 401) {
3 | return "리뷰를 등록하지 못했습니다.";
4 | }
5 |
6 | if (status === 403) {
7 | return "권한이 없습니다.";
8 | }
9 |
10 | if (status === 404) {
11 | if (message === "Story not found") {
12 | return "리뷰를 찾지 못했습니다.";
13 | }
14 | }
15 |
16 | if (status === 500) {
17 | return "서버에서 오류가 발생했어요!";
18 | }
19 |
20 | return message;
21 | };
22 |
--------------------------------------------------------------------------------
/src/utils/Logging/changeUrlToLoggingText.ts:
--------------------------------------------------------------------------------
1 | export const changeUrlToLoggingText = (url: string) => {
2 | switch (url) {
3 | case "/":
4 | return "메인";
5 | case "/signin":
6 | return "로그인";
7 | case "/mypage/profile":
8 | return "마이페이지의 내정보페이지";
9 | case "/mypage/employment":
10 | return "마이페이지의 고용관계페이지";
11 | case "/mypage/story":
12 | return "마이페이지의 리뷰페이지";
13 | default:
14 | return "";
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/Modal/turnOnOffModal.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction } from "react";
2 |
3 | export const turnOnOffModal = (
4 | setState: Dispatch>,
5 | modalType: "on" | "off"
6 | ) => {
7 | if (modalType === "on") {
8 | setState(true);
9 | document.body.style.overflow = "hidden";
10 | } else {
11 | setState(false);
12 | document.body.style.overflow = "unset";
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/utils/Position/searchPosition.ts:
--------------------------------------------------------------------------------
1 | import { POSITION_ITEMS } from "@src/constants/Position/position.constant";
2 |
3 | export const searchPosition = (position: string) => {
4 | return POSITION_ITEMS.filter((itmes) => itmes.includes(position));
5 | };
6 |
--------------------------------------------------------------------------------
/src/utils/Rank/getCompanyRankIntroduce.ts:
--------------------------------------------------------------------------------
1 | export const getCompanyRankIntroduce = (categoryName: string) => {
2 | switch (categoryName) {
3 | case "total":
4 | return "모든분야에서 높아요!";
5 | case "balance":
6 | return "워라벨을 최우선으로 해요!";
7 | case "salary-benefits":
8 | return "연봉복지가 좋아요!";
9 | case "career":
10 | return "커리어 향상을 중점적으로 봐요!";
11 | case "culture":
12 | return "조직문화가 뛰어나요!";
13 | default:
14 | return "";
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/Rank/getRankIcon.ts:
--------------------------------------------------------------------------------
1 | import total from "@src/assets/icons/Rank/total.svg";
2 | import balance from "@src/assets/icons/Rank/balance.svg";
3 | import career from "@src/assets/icons/Rank/career.svg";
4 | import money from "@src/assets/icons/Rank/money.svg";
5 | import compnay from "@src/assets/icons/Rank/company.svg";
6 |
7 | export const getRankIcon = (categoryName: string) => {
8 | switch (categoryName) {
9 | case "total":
10 | return total;
11 | case "balance":
12 | return balance;
13 | case "salary-benefits":
14 | return money;
15 | case "career":
16 | return career;
17 | case "culture":
18 | return compnay;
19 | default:
20 | return "";
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/src/utils/Rank/getSlideRankBoxWidth.ts:
--------------------------------------------------------------------------------
1 | export const getSlideRankBoxWidth = (width: number) => {
2 | switch (width) {
3 | case 1190:
4 | return 1218;
5 | case 775:
6 | return 915;
7 | case 570:
8 | return 597;
9 | case 480:
10 | return 510;
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/Rgb/getRgb.ts:
--------------------------------------------------------------------------------
1 | export const getRgb = (value: number) => {
2 | // 24비트 rgb 값이 들어옴
3 | const r = (value >> 16) & 0xff; // 16비트만큼 오른쪽으로 시프트 연산 후, 0xff(255)만큼 비트 연산하여 r값 추출
4 | const g = (value >> 8) & 0xff; // 8비트만큼 오른쪽으로 시프트 연산 후, 0xff(255)만큼 비트 연산하여 g값 추출
5 | const b = value & 0xff;
6 |
7 | return `rgb(${r}, ${g}, ${b})`;
8 | };
9 |
--------------------------------------------------------------------------------
/src/utils/StarRating/convertRankingObject.ts:
--------------------------------------------------------------------------------
1 | import { CompanyStarGrade } from "@src/types/Company/company.type";
2 |
3 | export const convertStarRatingObject = (starRating: CompanyStarGrade) => {
4 | return [
5 | {
6 | id: 0,
7 | title: "총합",
8 | star: starRating.total,
9 | },
10 | {
11 | id: 1,
12 | title: "워라벨",
13 | star: starRating.workLifeBalance,
14 | },
15 | {
16 | id: 2,
17 | title: "연봉",
18 | star: starRating.salaryAndBenefits,
19 | },
20 | {
21 | id: 3,
22 | title: "커리어 향상",
23 | star: starRating.careerAdvancement,
24 | },
25 | {
26 | id: 4,
27 | title: "조직문화",
28 | star: starRating.organizationalCulture,
29 | },
30 | ];
31 | };
32 |
--------------------------------------------------------------------------------
/src/utils/StarRating/tieStarGradeToObject.ts:
--------------------------------------------------------------------------------
1 | import { CompanyInfoType } from "@src/types/Company/company.type";
2 |
3 | export const tieStarGradeToObject = (starRatingInfo: CompanyInfoType) => {
4 | return {
5 | companyImgUrl: starRatingInfo.companyLogoUrl,
6 | companyLogoRgb: starRatingInfo.companyLogoRGB,
7 | companyName: starRatingInfo.companyName,
8 | total: starRatingInfo.total,
9 | salaryAndBenefits: starRatingInfo.salaryAndBenefits,
10 | workLifeBalance: starRatingInfo.workLifeBalance,
11 | organizationalCulture: starRatingInfo.organizationalCulture,
12 | careerAdvancement: starRatingInfo.careerAdvancement,
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/src/utils/Story/storyItemsObject.ts:
--------------------------------------------------------------------------------
1 | import cloud from "@src/assets/icons/Story/cloud.svg";
2 | import box from "@src/assets/icons/Story/box.svg";
3 | import blueHeart from "@src/assets/icons/Story/blueHeart.svg";
4 | import redHeart from "@src/assets/icons/Story/redHeart.png";
5 | import { StoryItemType } from "@src/types/Story/story.type";
6 |
7 | export const storyItemsObject = (item: StoryItemType) => {
8 | return [
9 | {
10 | id: 0,
11 | title: "우리 기업의 사내복지는",
12 | valueTitle: "welfare",
13 | icon: box,
14 | placeholder: "ex) 유연한 근무환경 제공",
15 | content: item.welfare,
16 | },
17 | {
18 | id: 1,
19 | prosTitle: "우리 기업의 장점은",
20 | prosValueTitle: "pros",
21 | prosPlaceholder: "기업의 장점을 입력해주세요",
22 | prosIcon: blueHeart,
23 | prosContent: item.pros,
24 | consTitle: "우리 기업의 단점은",
25 | consValueTitle: "cons",
26 | consPlaceholder: "기업의 단점을 입력해주세요",
27 | consIcon: redHeart,
28 | consContent: item.cons,
29 | },
30 |
31 | {
32 | id: 2,
33 | title: "나의 이야기를 롤링해",
34 | valueTitle: "etc",
35 | icon: cloud,
36 | placeholder: `면접 질문, 취업 준비 과정 등\n후배들에게 들려주고 싶은 이야기를 작성해 주세요`,
37 | content: item.etc,
38 | },
39 | ];
40 | };
41 |
--------------------------------------------------------------------------------
/src/utils/github/convertToGithubLink.ts:
--------------------------------------------------------------------------------
1 | export const convertToGithubLink = (githubId: string) => {
2 | return `https://github.com/${githubId}`;
3 | };
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "sourceMap": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": false,
18 | "jsx": "react-jsx",
19 | "paths": {
20 | "@src/*": ["src/*"]
21 | },
22 | "baseUrl": "."
23 | },
24 | "include": ["src"]
25 | }
26 |
--------------------------------------------------------------------------------
/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const RefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
4 | const svgToMiniDataURI = require("mini-svg-data-uri");
5 | const webpack = require("webpack");
6 | const dotenv = require("dotenv");
7 |
8 | dotenv.config();
9 | module.exports = {
10 | entry: {
11 | main: path.resolve("./src/index.tsx"),
12 | },
13 | output: {
14 | path: path.resolve(__dirname, "build/"),
15 | filename: "[name].[chunkhash].js",
16 | publicPath: "/",
17 | clean: true,
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.(js|jsx|ts|tsx)$/,
23 | loader: "esbuild-loader",
24 | options: {
25 | loader: "tsx",
26 | target: "es2015",
27 | },
28 | },
29 | {
30 | test: /\.(png|jpg|gif|mp4|jpeg|ico)$/,
31 | type: "asset",
32 | },
33 | {
34 | test: /\.svg/,
35 | type: "asset",
36 | generator: {
37 | dataUrl: (content) => {
38 | content = content.toString();
39 | return svgToMiniDataURI(content);
40 | },
41 | },
42 | },
43 | {
44 | test: /\.css$/,
45 | use: [
46 | "style-loader",
47 | "css-loader",
48 | {
49 | loader: "esbuild-loader",
50 | options: {
51 | loader: "css",
52 | minify: true,
53 | },
54 | },
55 | ],
56 | },
57 | ],
58 | },
59 | plugins: [
60 | new HtmlWebpackPlugin({
61 | template: "./public/index.html",
62 | filename: "index.html",
63 | favicon: "./public/favicon.ico",
64 | manifest: "./public/manifest.json",
65 | }),
66 | new RefreshWebpackPlugin(),
67 | new webpack.DefinePlugin({
68 | "process.env": JSON.stringify(process.env),
69 | }),
70 | ],
71 | resolve: {
72 | extensions: [".js", ".ts", ".jsx", ".tsx"],
73 | alias: {
74 | "@src": path.resolve(__dirname, "./src"),
75 | },
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const { merge } = require("webpack-merge");
2 | const common = require("./webpack.common.js");
3 |
4 | module.exports = merge(common, {
5 | mode: "development",
6 | devtool: "inline-source-map",
7 | devServer: {
8 | port: 3000,
9 | hot: true,
10 | historyApiFallback: true,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const { merge } = require("webpack-merge");
2 | const common = require("./webpack.common.js");
3 | const { ESBuildMinifyPlugin } = require("esbuild-loader");
4 |
5 | module.exports = merge(common, {
6 | mode: "production",
7 | devtool: "hidden-source-map",
8 | optimization: {
9 | minimizer: [
10 | new ESBuildMinifyPlugin({
11 | target: "es2015",
12 | css: true,
13 | }),
14 | ],
15 | },
16 | });
17 |
--------------------------------------------------------------------------------