├── .circleci └── config.yml ├── .dockerignore ├── .eslintrc.js ├── .gitignore ├── .mergify.yml ├── .stylelintrc ├── .svgo.yml ├── .svgrrc.js ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── docker-compose.yml ├── docker └── run-dev.sh ├── package.json ├── prettier.config.js ├── settings ├── dev.json ├── local.json ├── production.json └── staging.json ├── src ├── App.tsx ├── Routes.jsx ├── ScrollManager.jsx ├── TrackManager.jsx ├── api │ ├── actions.js │ ├── api.js │ ├── constants.js │ ├── index.js │ ├── interceptor.js │ └── middleware.js ├── components │ ├── ActionBar │ │ ├── ActionBar.jsx │ │ ├── ActionButton.jsx │ │ ├── constants.js │ │ ├── index.jsx │ │ └── styles.js │ ├── BookDownLoader.tsx │ ├── Books │ │ ├── BookItem.jsx │ │ ├── Disabled.jsx │ │ ├── EmptyLandscapeBook.jsx │ │ ├── FullButton.jsx │ │ ├── index.jsx │ │ └── refineBookData.jsx │ ├── BooksWrapper │ │ └── index.jsx │ ├── BottomActionBar │ │ ├── BottomActionBar.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── Confirm │ │ ├── index.jsx │ │ └── styles.js │ ├── Dialog │ │ ├── index.jsx │ │ └── styles.js │ ├── Editable.tsx │ ├── EditingBar │ │ ├── SelectAllButton.tsx │ │ ├── index.jsx │ │ └── styles.js │ ├── Empty │ │ ├── EmptyShelves.jsx │ │ └── index.jsx │ ├── Error │ │ ├── BookError.jsx │ │ ├── InternalError.jsx │ │ ├── NotFoundError.jsx │ │ ├── PageLoadError.jsx │ │ ├── base │ │ │ ├── ComponentError.jsx │ │ │ ├── ServiceError.jsx │ │ │ └── serviceErrorStyles.js │ │ └── index.jsx │ ├── ErrorBoundary │ │ └── index.jsx │ ├── Filler.tsx │ ├── FixedToolbarView.jsx │ ├── FlexBar │ │ ├── index.jsx │ │ └── styles.js │ ├── FullScreenLoading.jsx │ ├── HorizontalRuler.jsx │ ├── IconButton.jsx │ ├── LoadingSpinner.jsx │ ├── Maintenance │ │ ├── index.jsx │ │ └── styles.js │ ├── Modal │ │ ├── FilterModal.jsx │ │ ├── Modal.jsx │ │ ├── ModalBackground.jsx │ │ ├── ModalItem.jsx │ │ ├── ModalItemGroup.jsx │ │ ├── MoreModal.jsx │ │ ├── MyMenuModal.jsx │ │ ├── ShelfOrderModal.jsx │ │ ├── UnitSortModal.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── PageAlert │ │ ├── index.jsx │ │ └── styles.js │ ├── PageRedirect.jsx │ ├── Paginator │ │ ├── index.jsx │ │ └── styles.js │ ├── Prompt │ │ ├── index.jsx │ │ └── styles.js │ ├── ResponsivePaginator.jsx │ ├── Scrollable.jsx │ ├── SearchBar │ │ ├── index.jsx │ │ └── styles.js │ ├── SearchBox.jsx │ ├── SelectShelfModal │ │ └── index.jsx │ ├── SerialPreferenceBooks │ │ ├── FullButton.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── SerialPreferenceToolBar │ │ ├── index.jsx │ │ └── styles.js │ ├── SeriesList │ │ ├── index.jsx │ │ └── styles.js │ ├── SeriesToolBar │ │ ├── index.jsx │ │ └── styles.js │ ├── Shelf │ │ ├── ShelfDetailLink.jsx │ │ ├── ShelfSelectButton.jsx │ │ ├── ShelfThumbnail.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── Shelves │ │ └── index.jsx │ ├── ShelvesWrapper │ │ ├── index.jsx │ │ └── styles.js │ ├── SimpleShelvesWrapper │ │ ├── SimpleShelvesItem.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── Skeleton │ │ ├── SkeletonBooks │ │ │ ├── LandscapeBook.jsx │ │ │ ├── PortraitBook.jsx │ │ │ ├── index.jsx │ │ │ └── landscapeBookStyles.js │ │ ├── SkeletonShelves │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── SkeletonSimpleShelves │ │ │ ├── SkeletonSimpleShelf.jsx │ │ │ └── index.jsx │ │ └── SkeletonUnitDetailView │ │ │ ├── index.jsx │ │ │ └── styles.js │ ├── TabBar │ │ ├── TabItem.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── TitleBar │ │ ├── Title.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── Toast.jsx │ ├── Toaster │ │ ├── index.jsx │ │ └── styles.js │ ├── Tool │ │ ├── Add.jsx │ │ ├── Editing.jsx │ │ ├── Filter.jsx │ │ ├── More.jsx │ │ ├── ShelfEdit.jsx │ │ ├── ShelfOrder.jsx │ │ ├── TotalCount.jsx │ │ ├── index.jsx │ │ └── styles.js │ ├── Tooltip │ │ ├── TooltipBackground.jsx │ │ ├── index.jsx │ │ └── styles.js │ └── UnitDetailView │ │ ├── index.jsx │ │ └── styles.js ├── config.js ├── constants │ ├── authorRole.ts │ ├── category.js │ ├── environment.ts │ ├── featureIds.js │ ├── listInstructions.js │ ├── orderOptions.js │ ├── page.js │ ├── serviceType.js │ ├── shelves.js │ ├── unitType.js │ ├── urls.ts │ └── viewType.js ├── index.ejs ├── index.jsx ├── pages │ ├── base │ │ ├── Environment │ │ │ └── index.jsx │ │ ├── Footer │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── GNB │ │ │ ├── FamilyServices.jsx │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── LNB │ │ │ ├── SearchAndEditingBar.jsx │ │ │ ├── TabBar.jsx │ │ │ ├── TitleAndEditingBar.jsx │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Layout.jsx │ │ ├── PageLoadingSpinner │ │ │ └── index.jsx │ │ ├── Responsive │ │ │ ├── index.tsx │ │ │ └── styles.js │ │ └── styles.js │ ├── errors │ │ ├── notFound.jsx │ │ └── pageLoad.jsx │ ├── login │ │ ├── index.jsx │ │ └── styles.js │ ├── purchased │ │ ├── hidden │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ └── main │ │ │ ├── index.tsx │ │ │ └── styles.js │ ├── serialPreference │ │ ├── index.jsx │ │ └── styles.js │ ├── shelves │ │ ├── detail │ │ │ ├── SearchModal │ │ │ │ ├── SearchBar.jsx │ │ │ │ ├── SearchBooks.jsx │ │ │ │ └── index.jsx │ │ │ ├── SelectShelf │ │ │ │ ├── SimpleShelves │ │ │ │ │ ├── SimpleShelf.jsx │ │ │ │ │ ├── index.jsx │ │ │ │ │ └── styles.js │ │ │ │ ├── index.jsx │ │ │ │ └── styles.ts │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ └── list │ │ │ ├── BetaAlert.jsx │ │ │ ├── index.jsx │ │ │ └── styles.js │ └── unit │ │ └── index.jsx ├── services │ ├── account │ │ ├── actions.js │ │ ├── errors.js │ │ ├── reducers.js │ │ ├── requests.js │ │ ├── sagas.js │ │ └── selectors.js │ ├── book │ │ ├── actions.js │ │ ├── constants.js │ │ ├── reducers.js │ │ ├── requests.js │ │ ├── sagas.js │ │ └── selectors.ts │ ├── bookDownload │ │ ├── errors.ts │ │ ├── reducers.ts │ │ ├── requests.ts │ │ ├── sagas.ts │ │ └── selectors.ts │ ├── common │ │ ├── actions.js │ │ ├── constants.js │ │ ├── errors.js │ │ ├── requests.js │ │ └── sagas.js │ ├── confirm │ │ ├── actions.js │ │ ├── reducers.js │ │ └── state.js │ ├── dialog │ │ ├── actions.js │ │ ├── reducers.js │ │ └── state.js │ ├── excelDownload │ │ ├── actions.js │ │ ├── constants.js │ │ ├── reducers.js │ │ ├── requests.js │ │ ├── sagas.js │ │ ├── selectors.js │ │ └── state.js │ ├── feature │ │ ├── actions.js │ │ ├── reducers.js │ │ ├── requests.ts │ │ ├── sagas.js │ │ └── selectors.js │ ├── maintenance │ │ ├── actions.js │ │ ├── reducers.js │ │ ├── requests.js │ │ └── state.js │ ├── prompt │ │ ├── actions.js │ │ ├── reducers.js │ │ └── state.js │ ├── purchased │ │ ├── common │ │ │ ├── actions.js │ │ │ ├── constants.js │ │ │ ├── errors.js │ │ │ ├── reducers.js │ │ │ ├── requests.js │ │ │ ├── sagas │ │ │ │ ├── hideAllExpiredBooksSagas.js │ │ │ │ └── rootSagas.js │ │ │ └── selectors.js │ │ ├── filter │ │ │ ├── actions.js │ │ │ ├── reducers.js │ │ │ ├── requests.js │ │ │ ├── sagas.ts │ │ │ └── selectors.js │ │ ├── hidden │ │ │ ├── actions.js │ │ │ ├── reducers.js │ │ │ ├── requests.js │ │ │ ├── sagas.js │ │ │ ├── selectors.js │ │ │ └── state.js │ │ └── main │ │ │ ├── actions.js │ │ │ ├── reducers.js │ │ │ ├── requests.js │ │ │ ├── sagas.js │ │ │ ├── selectors.ts │ │ │ └── state.js │ ├── selection │ │ ├── reducers.ts │ │ └── selectors.js │ ├── serialPreference │ │ ├── actions.js │ │ ├── reducers.js │ │ ├── requests.js │ │ ├── sagas.js │ │ ├── selectors.js │ │ └── state.js │ ├── shelf │ │ ├── actions.js │ │ ├── constants.ts │ │ ├── reducers.js │ │ ├── requests.ts │ │ ├── sagas.js │ │ └── selectors.js │ ├── toast │ │ ├── actions.ts │ │ ├── constants.ts │ │ ├── reducers.js │ │ ├── sagas.js │ │ └── selectors.js │ ├── tracking │ │ ├── actions.js │ │ ├── constants.js │ │ └── sagas.js │ ├── ui │ │ ├── actions.js │ │ ├── reducers.js │ │ ├── sagas.js │ │ └── state.js │ └── unitPage │ │ ├── actions.js │ │ ├── reducers.js │ │ ├── requests.js │ │ ├── sagas.js │ │ ├── selectors.js │ │ ├── seriesViewSagas.js │ │ ├── state.js │ │ └── utils.js ├── static │ ├── OG │ │ └── library.jpg │ ├── cover │ │ └── adult.png │ ├── favicon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-384x384.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest.json │ ├── separator │ │ ├── portrait_book_w100.png │ │ ├── portrait_book_w110.png │ │ ├── portrait_book_w140.png │ │ └── portrait_book_w86.png │ └── spinner │ │ └── blue_spinner.gif ├── store │ ├── index.js │ ├── reducers.js │ └── sagas.js ├── styles │ ├── books.js │ ├── constants.js │ ├── index.js │ ├── reset.js │ ├── responsive.js │ └── unitDetailViewHeader.js ├── svgs │ ├── ArrowLeft.svg │ ├── ArrowTriangleDown.svg │ ├── ArrowTriangleRight.svg │ ├── BookOutline.svg │ ├── CategoryFilter.svg │ ├── Check.svg │ ├── CheckCircle.svg │ ├── CheckCircleFill.svg │ ├── Close.svg │ ├── Download.svg │ ├── Edit.svg │ ├── ErrorBook.svg │ ├── ExclamationCircleFill.svg │ ├── FeedbackIcon.svg │ ├── FooterNewIcon.svg │ ├── FooterPaperIcon.svg │ ├── HeartOutline.svg │ ├── Loading.svg │ ├── LogoRidi.svg │ ├── LogoRidibooks.svg │ ├── LogoRidibooksApp.svg │ ├── LogoRidiselect.svg │ ├── Logout.svg │ ├── MyMenu-active.svg │ ├── MyMenu.svg │ ├── NoneDashedArrowDown.svg │ ├── NoneDashedArrowLeft.svg │ ├── NoneDashedArrowRight.svg │ ├── NoneDashedDoubleArrowRight.svg │ ├── Note.svg │ ├── NoticeFilled.svg │ ├── On.svg │ ├── Plus.svg │ ├── Review.svg │ ├── Search.svg │ ├── SeriesCompleteIcon.svg │ ├── Shelves.svg │ ├── Star.svg │ ├── Sync.svg │ ├── ThreeDotsHorizontal.svg │ └── ThreeDotsVertical.svg ├── types │ └── svgr-svg.d.ts └── utils │ ├── array.js │ ├── bookMetaData.js │ ├── cookies.js │ ├── dataObject.js │ ├── datetime.js │ ├── delay.js │ ├── device.js │ ├── file.js │ ├── number.js │ ├── order.js │ ├── pagination.js │ ├── retry.js │ ├── scroll.js │ ├── sentry.js │ ├── settings.js │ ├── snakelize.js │ ├── storage.js │ ├── tabFocus.js │ ├── ttl.js │ └── uri.js ├── traefik └── library.toml ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | yarn-error.log 3 | node_modules/ 4 | dist/ 5 | .git 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | 4 | yarn-error.log 5 | node_modules/ 6 | build/ 7 | out/ 8 | dist/ 9 | 10 | /secrets.json 11 | 12 | *.pyc 13 | docker-compose.override.yml 14 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge when CI passes and reviews 3 | conditions: 4 | - base=master 5 | - "#approved-reviews-by>=1" 6 | - "#review-requested=0" 7 | - "#changes-requested-reviews-by=0" 8 | - "status-success=WIP" 9 | actions: 10 | merge: 11 | method: merge 12 | strict: smart 13 | strict_method: rebase 14 | delete_head_branch: 15 | 16 | - name: add label to hotfix branch 17 | conditions: 18 | - base=master 19 | - head~=^hotfix/ 20 | actions: 21 | label: 22 | add: 23 | - hotfix 24 | 25 | - name: backport patches to release branch 26 | conditions: 27 | - base=master 28 | - label=hotfix 29 | actions: 30 | backport: 31 | branches: 32 | - release 33 | 34 | - name: automatic merge for backport 35 | conditions: 36 | - base=release 37 | - -head=master 38 | - "#approved-reviews-by>=1" 39 | actions: 40 | merge: 41 | method: merge 42 | strict: true 43 | strict_method: rebase 44 | delete_head_branch: 45 | 46 | - name: deploy new release 47 | conditions: 48 | - base=release 49 | - head=master 50 | - "#approved-reviews-by>=1" 51 | - "#review-requested=0" 52 | - "#changes-requested-reviews-by=0" 53 | - "#commented-reviews-by=0" 54 | actions: 55 | merge: {} 56 | 57 | - name: delete head branch when closed 58 | conditions: 59 | - -head=master 60 | - -head=release 61 | - closed 62 | actions: 63 | delete_head_branch: 64 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard" 4 | ], 5 | "rules": { 6 | "declaration-empty-line-before": null, 7 | "no-empty-source": null, 8 | "no-extra-semicolons": null, 9 | "no-missing-end-of-source-newline": null, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.svgo.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - removeTitle: true 3 | - removeXMLNS: true 4 | - removeDimensions: true 5 | - removeAttrs: 6 | attrs: 7 | - 'fill' 8 | - 'fill-rule' 9 | - 'stroke' 10 | - 'version' 11 | - 'class' 12 | - sortAttrs: 13 | order: 14 | - 'viewBox' 15 | - 'class' 16 | - collapseGroups: true 17 | -------------------------------------------------------------------------------- /.svgrrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | icon: true, 3 | dimensions: false, 4 | }; 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## PR 기준 2 | 3 | - PR 전 이슈등록 필수 4 | - 허용 대상 5 | - Bug fix - o 6 | - Refactoring - 수정 범위에 따른 선택적 허용 7 | 8 | 좀더 명확한 PR 프로세스를 위해 Branch naming convention, Reviewer list 등 문서 업데이트 및 PR template 이 추가될 예정입니다. 9 | 10 | ``` 11 | 현재 진행중인 프로젝트 이후 TS 적용 및 전반적인 Refactoring, Test 추가 프로젝트가 예정되어 있습니다. 12 | 따라서 당분간 Bug fix 외에는 PR 을 받지 않을 계획임을 참고 부탁드립니다. 13 | ``` 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RIDI Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library-Web 2 | 3 | ## Requirements 4 | 5 | - `nodejs`: v10 이상 6 | 7 | ## Installation 8 | 9 | ``` 10 | yarn install 11 | ``` 12 | 13 | ## Run Development 14 | 15 | ### Docker를 사용해 실행하는 방법 16 | 17 | `docker-compose`를 사용합니다. 18 | 19 | ```sh 20 | docker-compose up 21 | ``` 22 | 23 | ### Docker 없이 실행하는 방법 24 | 25 | 1. 프론트엔드 `traefik` 리포지토리 안에 있는 `traefik` 디렉토리에 이 26 | 리포지토리의 `traefik/library.toml`을 복사해 넣습니다. 27 | 1. `yarn local`으로 개발 서버를 실행합니다. 28 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | const emotionAdditionalPresets = api.env('production') 3 | ? { 4 | sourceMap: true, 5 | hoist: true, 6 | } 7 | : { 8 | sourceMap: true, 9 | autoLabel: true, 10 | }; 11 | 12 | const presets = [ 13 | ['@babel/preset-react'], 14 | [ 15 | '@babel/preset-env', 16 | { 17 | corejs: 3, 18 | useBuiltIns: 'entry', 19 | shippedProposals: true, 20 | }, 21 | ], 22 | [ 23 | '@emotion/babel-preset-css-prop', 24 | { 25 | autoLabel: true, 26 | labelFormat: '[local]', 27 | ...emotionAdditionalPresets, 28 | }, 29 | ], 30 | ]; 31 | const plugins = ['@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-optional-chaining']; 32 | 33 | return { 34 | presets, 35 | plugins, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | library-web: 5 | image: node:12-alpine 6 | container_name: library-web 7 | volumes: 8 | - .:/app:ro 9 | - /app/node_modules 10 | working_dir: /app 11 | labels: 12 | - 'traefik.enable=true' 13 | - 'traefik.docker.network=${EXTERNAL_NETWORK:-ridi}' 14 | - 'traefik.frontend.rule=Host:library.local.ridi.io' 15 | networks: 16 | - traefik 17 | ports: 18 | - 3000 19 | init: true 20 | entrypoint: /app/docker/run-dev.sh 21 | -------------------------------------------------------------------------------- /docker/run-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | yarn --frozen-lockfile 4 | yarn local 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 140, 3 | trailingComma: 'all', 4 | tabWidth: 2, 5 | singleQuote: true, 6 | semi: true, 7 | bracketSpacing: true, 8 | }; 9 | -------------------------------------------------------------------------------- /settings/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_base_url": "https://account.dev.ridi.io/", 3 | "base_url": "https://library.ridi.io/", 4 | "book_api_base_url": "https://book-api.ridibooks.com/", 5 | "book_feedback_api_base_url": "https://book-feedback.dev.ridi.io/", 6 | "environment": "development", 7 | "help_base_url": "https://help.ridibooks.com/hc/ko", 8 | "library_api_base_url": "https://library-api.dev.ridi.io/", 9 | "policy_base_url": "https://policy.ridi.com", 10 | "ridi_logout_url": "https://master.test.ridi.io/account/logout?return_url=https%3A%2F%2Flibrary.ridi.io%2Flogin", 11 | "ridi_oauth2_client_id": "Nkt2Xdc0zMuWmye6MSkYgqCh9q6JjeMCsUiH1kgL", 12 | "ridi_reading_note_url": "https://master.test.ridi.io/reading-note/timeline", 13 | "ridi_review_url": "https://master.test.ridi.io/review", 14 | "ridi_status_url": "https://sorry.ridibooks.com/status", 15 | "ridi_token_authorize_url": "https://account.dev.ridi.io/ridi/authorize", 16 | "select_base_url": "https://select.dev.ridi.io/", 17 | "sentry_dsn": "", 18 | "sentry_env": "development", 19 | "static_url": "https://library.ridi.io/", 20 | "store_api_base_url": "https://master.test.ridi.io/", 21 | "store_base_url": "https://master.test.ridi.io/", 22 | "viewer_api_base_url": "https://viewer-api-v1.dev.ridi.io/" 23 | } 24 | -------------------------------------------------------------------------------- /settings/local.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_base_url": "https://account.dev.ridi.io/", 3 | "base_url": "https://library.local.ridi.io/", 4 | "book_api_base_url": "https://book-api.ridibooks.com/", 5 | "book_feedback_api_base_url": "https://book-feedback.dev.ridi.io/", 6 | "environment": "local", 7 | "help_base_url": "https://help.ridibooks.com/hc/ko", 8 | "library_api_base_url": "https://library-api.dev.ridi.io/", 9 | "policy_base_url": "https://policy.ridi.com", 10 | "ridi_logout_url": "https://master.test.ridi.io/account/logout?return_url=https%3A%2F%2Flibrary.local.ridi.io%2Flogin", 11 | "ridi_oauth2_client_id": "Nkt2Xdc0zMuWmye6MSkYgqCh9q6JjeMCsUiH1kgL", 12 | "ridi_reading_note_url": "https://master.test.ridi.io/reading-note/timeline", 13 | "ridi_review_url": "https://master.test.ridi.io/review", 14 | "ridi_status_url": "https://sorry.ridibooks.com/status", 15 | "ridi_token_authorize_url": "https://account.dev.ridi.io/ridi/authorize", 16 | "select_base_url": "https://select.dev.ridi.io/", 17 | "sentry_dsn": "", 18 | "sentry_env": "local", 19 | "static_url": "https://library.local.ridi.io/", 20 | "store_api_base_url": "https://master.test.ridi.io/", 21 | "store_base_url": "https://master.test.ridi.io/", 22 | "viewer_api_base_url": "https://viewer-api-v1.dev.ridi.io/" 23 | } 24 | -------------------------------------------------------------------------------- /settings/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_base_url": "https://account.ridibooks.com/", 3 | "base_url": "https://library.ridibooks.com/", 4 | "book_api_base_url": "https://book-api.ridibooks.com/", 5 | "book_feedback_api_base_url": "https://book-feedback.ridibooks.com/", 6 | "environment": "production", 7 | "help_base_url": "https://help.ridibooks.com/hc/ko", 8 | "library_api_base_url": "https://library-api.ridibooks.com", 9 | "policy_base_url": "https://policy.ridi.com", 10 | "ridi_logout_url": "https://ridibooks.com/account/logout?return_url=https%3A%2F%2Flibrary.ridibooks.com%2Flogin", 11 | "ridi_oauth2_client_id": "ePgbKKRyPvdAFzTvFg2DvrS7GenfstHdkQ2uvFNd", 12 | "ridi_reading_note_url": "https://ridibooks.com/reading-note/timeline", 13 | "ridi_review_url": "https://ridibooks.com/review", 14 | "ridi_status_url": "https://sorry.ridibooks.com/status", 15 | "ridi_token_authorize_url": "https://account.ridibooks.com/ridi/authorize", 16 | "select_base_url": "https://select.ridibooks.com/", 17 | "sentry_dsn": "https://0100a981cf6840ceac1a206051a199ba@sentry.io/1335489", 18 | "sentry_env": "production", 19 | "static_url": "https://library.ridicdn.net/", 20 | "store_api_base_url": "https://ridibooks.com/", 21 | "store_base_url": "https://ridibooks.com/", 22 | "viewer_api_base_url": "https://ridibooks.com/" 23 | } 24 | -------------------------------------------------------------------------------- /settings/staging.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_base_url": "https://account.ridibooks.com/", 3 | "base_url": "https://library.ridibooks.com/", 4 | "book_api_base_url": "https://book-api.ridibooks.com/", 5 | "book_feedback_api_base_url": "https://book-feedback.ridibooks.com/", 6 | "environment": "staging", 7 | "help_base_url": "https://help.ridibooks.com/hc/ko", 8 | "library_api_base_url": "https://library-api.ridibooks.com", 9 | "policy_base_url": "https://policy.ridi.com", 10 | "ridi_logout_url": "https://ridibooks.com/account/logout?return_url=https%3A%2F%2Flibrary.ridibooks.com%2Flogin", 11 | "ridi_oauth2_client_id": "ePgbKKRyPvdAFzTvFg2DvrS7GenfstHdkQ2uvFNd", 12 | "ridi_reading_note_url": "https://ridibooks.com/reading-note/timeline", 13 | "ridi_review_url": "https://ridibooks.com/review", 14 | "ridi_status_url": "https://sorry.ridibooks.com/status", 15 | "ridi_token_authorize_url": "https://account.ridibooks.com/ridi/authorize", 16 | "select_base_url": "https://select.ridibooks.com/", 17 | "sentry_dsn": "https://0100a981cf6840ceac1a206051a199ba@sentry.io/1335489", 18 | "sentry_env": "staging", 19 | "static_url": "https://library.ridicdn.net/", 20 | "store_api_base_url": "https://ridibooks.com/", 21 | "store_base_url": "https://ridibooks.com/", 22 | "viewer_api_base_url": "https://ridibooks.com/" 23 | } 24 | -------------------------------------------------------------------------------- /src/ScrollManager.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | function ScrollManager({ location }) { 5 | React.useEffect(() => { 6 | const { key, state } = location; 7 | if (window.sessionStorage.getItem(key) == null) { 8 | // state에 주어진 명령에 따라 스크롤 위치를 조작한다 9 | let position = { x: 0, y: 0 }; 10 | if (state != null && state.scroll != null) { 11 | const { scroll } = state; 12 | if ('from' in scroll) { 13 | const scrollDataRaw = window.sessionStorage.getItem(scroll.from); 14 | position = scrollDataRaw ? JSON.parse(scrollDataRaw) : position; 15 | } else if ('x' in scroll && 'y' in scroll) { 16 | position = { 17 | x: Number(scroll.x), 18 | y: Number(scroll.y), 19 | }; 20 | } 21 | } 22 | window.scrollTo(position.x, position.y); 23 | } 24 | 25 | return () => { 26 | window.sessionStorage.setItem(key, JSON.stringify({ x: window.scrollX, y: window.scrollY })); 27 | }; 28 | }, [location]); 29 | 30 | return null; 31 | } 32 | 33 | export default withRouter(ScrollManager); 34 | -------------------------------------------------------------------------------- /src/TrackManager.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { withRouter } from 'react-router-dom'; 4 | 5 | import { initTracker, trackPage } from 'services/tracking/actions'; 6 | 7 | const TrackManager = props => { 8 | const account = useSelector(state => state.account); 9 | const dispatch = useDispatch(); 10 | 11 | const { 12 | location: { pathname }, 13 | } = props; 14 | 15 | React.useEffect(() => { 16 | dispatch(trackPage(pathname)); 17 | }, [account, pathname]); 18 | 19 | React.useEffect(() => { 20 | dispatch(initTracker()); 21 | }, [account]); 22 | 23 | return null; 24 | }; 25 | 26 | export default withRouter(TrackManager); 27 | -------------------------------------------------------------------------------- /src/api/actions.js: -------------------------------------------------------------------------------- 1 | export const GET_API = 'GET_API'; 2 | export const GET_PUBLIC_API = 'GET_PUBLIC_API'; 3 | 4 | export const getAPI = (requestToken = null) => ({ 5 | type: GET_API, 6 | payload: { 7 | requestToken, 8 | }, 9 | }); 10 | 11 | export const getPublicAPI = () => ({ 12 | type: GET_PUBLIC_API, 13 | }); 14 | -------------------------------------------------------------------------------- /src/api/constants.js: -------------------------------------------------------------------------------- 1 | export const HttpStatusCode = { 2 | HTTP_200_SUCCESS: 200, 3 | HTTP_400_BAD_REQUEST: 400, 4 | HTTP_401_UNAUTHORIZED: 401, 5 | HTTP_404_NOT_FOUND: 404, 6 | HTTP_503_SERVICE_UNAVAILABLE: 503, 7 | }; 8 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import API from './api'; 2 | import { createAuthorizationInterceptor, createMaintenanceInterceptor } from './interceptor'; 3 | 4 | let api = null; 5 | 6 | export const initializeApi = (req, store) => { 7 | if (req != null) { 8 | const { token } = req; 9 | api = new API(false, { Cookie: `ridi-at=${token};` }); 10 | return api; 11 | } 12 | 13 | const withCredentials = true; 14 | api = new API(withCredentials); 15 | api.addInterceptors([createAuthorizationInterceptor(store), createMaintenanceInterceptor(store)]); 16 | api.registerInterceptor(); 17 | return api; 18 | }; 19 | 20 | export const getApi = context => { 21 | if (api == null) { 22 | return initializeApi(context && context.req); 23 | } 24 | 25 | return api; 26 | }; 27 | 28 | let publicApi = null; 29 | 30 | export const initializePublicApi = store => { 31 | publicApi = new API(); 32 | publicApi.addInterceptors([createMaintenanceInterceptor(store)]); 33 | publicApi.registerInterceptor(); 34 | 35 | return publicApi; 36 | }; 37 | 38 | export const getPublicApi = () => { 39 | if (publicApi == null) { 40 | return initializePublicApi(); 41 | } 42 | 43 | return publicApi; 44 | }; 45 | -------------------------------------------------------------------------------- /src/api/middleware.js: -------------------------------------------------------------------------------- 1 | import { GET_API, GET_PUBLIC_API } from './actions'; 2 | import { getApi, getPublicApi } from './index'; 3 | 4 | const createApiMiddleware = () => () => next => action => { 5 | if (action.type === GET_API) { 6 | return getApi(); 7 | } 8 | 9 | if (action.type === GET_PUBLIC_API) { 10 | return getPublicApi(); 11 | } 12 | 13 | return next(action); 14 | }; 15 | 16 | export default createApiMiddleware; 17 | -------------------------------------------------------------------------------- /src/components/ActionBar/ActionBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Responsive from '../../pages/base/Responsive'; 4 | import * as styles from './styles'; 5 | 6 | export const ActionBar = ({ children }) => ( 7 | <> 8 |
9 |
10 | 11 |
{children}
12 |
13 |
14 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/components/ActionBar/ActionButton.jsx: -------------------------------------------------------------------------------- 1 | import { ButtonType } from './constants'; 2 | import * as styles from './styles'; 3 | 4 | export const ActionButton = ({ name, onClick, type = ButtonType.NORMAL, disable = false, className = '' }) => 5 | type === ButtonType.SPACER ? ( 6 |
7 | ) : ( 8 | 17 | ); 18 | -------------------------------------------------------------------------------- /src/components/ActionBar/constants.js: -------------------------------------------------------------------------------- 1 | export const ButtonType = { 2 | NORMAL: 'normal', 3 | DANGER: 'danger', 4 | SPACER: 'spacer', 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/ActionBar/index.jsx: -------------------------------------------------------------------------------- 1 | export * from './ActionBar'; 2 | export * from './ActionButton'; 3 | -------------------------------------------------------------------------------- /src/components/ActionBar/styles.js: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/core'; 2 | 3 | import { ButtonType } from './constants'; 4 | 5 | export const ACTION_BAR_HEIGHT = 51; 6 | 7 | export const actionBarPadding = css` 8 | height: ${ACTION_BAR_HEIGHT}px; 9 | `; 10 | 11 | export const actionBarFixedWrapper = { 12 | position: 'fixed', 13 | bottom: 0, 14 | left: 0, 15 | right: 0, 16 | zIndex: 8000, 17 | backgroundColor: '#f7f9fa', 18 | boxShadow: '0 -2px 10px 0 rgba(0, 0, 0, 0.04)', 19 | borderTop: '1px solid #d1d5d9', 20 | }; 21 | 22 | export const actionBar = { 23 | display: 'flex', 24 | height: `${ACTION_BAR_HEIGHT - 1}px`, 25 | }; 26 | 27 | export const actionButton = disable => { 28 | const disabledStyle = disable 29 | ? { 30 | opacity: '0.4', 31 | } 32 | : {}; 33 | return { 34 | fontSize: 15, 35 | lineHeight: '1.2em', 36 | textAlign: 'center', 37 | height: 50, 38 | padding: '0 8px', 39 | ...disabledStyle, 40 | '&:first-of-type': { 41 | paddingLeft: 0, 42 | }, 43 | '&:last-of-type': { 44 | paddingRight: 0, 45 | }, 46 | }; 47 | }; 48 | 49 | export const actionButtonType = type => { 50 | switch (type) { 51 | case ButtonType.DANGER: 52 | return { 53 | color: '#e64938', 54 | }; 55 | case ButtonType.SPACER: 56 | return { 57 | flex: '1', 58 | }; 59 | case ButtonType.NORMAL: 60 | default: 61 | return { 62 | color: '#1f8ce6', 63 | }; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/BookDownLoader.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | 3 | import { getBookDownloadSrc } from 'services/bookDownload/selectors'; 4 | 5 | const BookDownLoader: React.FC = () => { 6 | const src: string = useSelector(getBookDownloadSrc); 7 | return src ?