├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── CI.yml
│ ├── ReleaseNCP.yml
│ ├── releaseRepo.yml
│ ├── slack-notice.yml
│ └── testNCP.yml
├── .gitignore
├── README.md
├── backend
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── build.ts
├── package.json
├── src
│ ├── @types
│ │ └── express
│ │ │ └── index.d.ts
│ ├── Server.ts
│ ├── config
│ │ ├── GithubPassport.ts
│ │ ├── LocalPassport.ts
│ │ └── Upload.ts
│ ├── enum
│ │ └── index.ts
│ ├── index.ts
│ ├── model
│ │ ├── Channel.ts
│ │ ├── Dm.ts
│ │ ├── File.ts
│ │ ├── Reaction.ts
│ │ ├── Reply.ts
│ │ ├── Thread.ts
│ │ ├── User.ts
│ │ ├── UserHasWorkspace.ts
│ │ └── Workspace.ts
│ ├── ormconfig.ts
│ ├── pre-start
│ │ └── index.ts
│ ├── public
│ │ ├── scripts
│ │ │ └── index.js
│ │ └── stylesheets
│ │ │ └── style.css
│ ├── repository
│ │ ├── ChannelRepository.ts
│ │ ├── FileRepository.ts
│ │ ├── ReactionRepository.ts
│ │ ├── ReplyRepository.ts
│ │ ├── ThreadRepository.ts
│ │ ├── UserHasWorkspaceRepository.ts
│ │ ├── UserRepository.ts
│ │ └── WorkspaceRepository.ts
│ ├── routes
│ │ ├── ChannelController.ts
│ │ ├── FilesController.ts
│ │ ├── LoginController.ts
│ │ ├── ReactionController.ts
│ │ ├── ReplyController.ts
│ │ ├── ThreadController.ts
│ │ ├── UserController.ts
│ │ ├── UserHasWorkspaceController.ts
│ │ ├── WorkspaceController.ts
│ │ └── index.ts
│ ├── sample
│ │ ├── InsertChannelSample.ts
│ │ ├── InsertFileSample.ts
│ │ ├── InsertReactionSample.ts
│ │ ├── InsertReplySample.ts
│ │ ├── InsertThreadSample.ts
│ │ ├── InsertUserHasWorkspace.ts
│ │ ├── InsertUserSample.ts
│ │ ├── InsertWorkspaceSample.ts
│ │ ├── index.ts
│ │ └── value
│ │ │ ├── ChannelSampleValue.ts
│ │ │ ├── EmotionSampleValue.ts
│ │ │ ├── FileSampleValue.ts
│ │ │ ├── ReactionSampleValue.ts
│ │ │ ├── ReplySampleValue.ts
│ │ │ ├── ThreadSampleValue.ts
│ │ │ ├── UserHasWorkspaceSampleValue.ts
│ │ │ ├── UserSampleValue.ts
│ │ │ └── WorkspaceSampleValue.ts
│ ├── service
│ │ ├── ChannelService.ts
│ │ ├── FilesService.ts
│ │ ├── ReactionService.ts
│ │ ├── ReplyService.ts
│ │ ├── ThreadService.ts
│ │ ├── UserHasWorkspaceService.ts
│ │ ├── UserService.ts
│ │ └── WorkspaceService.ts
│ ├── shared
│ │ ├── Logger.ts
│ │ ├── constants.ts
│ │ ├── functions.ts
│ │ └── simpleuuid.ts
│ ├── socket.ts
│ ├── util
│ │ └── getNowDate.ts
│ └── views
│ │ └── index.html
├── tsconfig.json
├── tsconfig.prod.json
└── yarn.lock
└── frontend
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── jest.config.js
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.tsx
├── components
│ ├── atoms
│ │ ├── Autocomplete
│ │ │ └── index.tsx
│ │ ├── DivLists
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ErrorBoundary
│ │ │ └── index.tsx
│ │ ├── IconButton
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ImageBox
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ImageButton
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Input
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Label
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── LabeledButton
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── LabeledDefaultButton
│ │ │ └── index.tsx
│ │ ├── Modal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Popup
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── RadioButton
│ │ │ └── index.tsx
│ │ ├── Spinner
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── ToggleButton
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ └── ViewPortInput
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ ├── molecules
│ │ ├── AsyncBranch
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── BrowseChannelHeader
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChannelList
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChatHeader
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── CodeModal
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── EmojiPopup
│ │ │ ├── EmojiPopupTemplate
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── LabeledInput
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── MentionPopup
│ │ │ ├── MentionPopupTemplate
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── MessageContent
│ │ │ ├── MessageActions
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── MessageFileStatusBar
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── MessageFileStatusElement
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── NoOverlayModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── QuestionForm
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SearchBar
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SelectWorkspace
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SelectbrowseChannelPage
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SidebarAddElement
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SidebarChannelElement
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SidebarDivision
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SortedOptionMordal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ThreadContent
│ │ │ ├── ThreadActions
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── ThreadFileStatusBar
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── ThreadFileStatusElement
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ └── WysiwygEditor
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ ├── organisms
│ │ ├── BrowseChannelList
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── BrowseContent
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── BrowseMordalContainer
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChangePasswordContent
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── ChannelAbout
│ │ │ ├── AboutElement
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChannelDescriptionModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChannelInfoModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChannelJoinFooter
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChannelMembers
│ │ │ ├── MemberTemplate
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChannelTopicModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChatContent
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChatInputBar
│ │ │ ├── FileStatusBar
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── FileStatusElement
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── Toolbar
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ChatInputBarForUpdate
│ │ │ ├── index.tsx
│ │ │ ├── styles.ts
│ │ │ └── toolbar
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ ├── CreateChannelModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── CreateLoginModal
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── EmojiModal
│ │ │ ├── EmojiPopupTemplate
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── LoginContent
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── MemberElement
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── PreferenceModal
│ │ │ ├── AboutUs
│ │ │ │ └── AboutUs.tsx
│ │ │ ├── PreferenceMenuContent
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── ThemeSelect
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── WorkspaceOut
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ReactionBar
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ReplyBar
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── ReplyContent
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SetupTeamQuestions
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── SidebarChannelInfoModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── SignupContent
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── SubmitCodeForm
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── UserProfileModal
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── WorkspaceContent
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── WorkspaceHeader
│ │ │ ├── SearchResultTemplate
│ │ │ │ ├── ChannelElement
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.ts
│ │ │ │ ├── SearchResultModal
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── styles.ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── WorkspaceHeaderMenuList
│ │ │ │ ├── index.tsx
│ │ │ │ └── styles.ts
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── WorkspaceListContent
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ └── WorkspaceSidebar
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ ├── pages
│ │ ├── BrowseChannel
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ ├── Changepassword
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── GeneratedCode
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── GetError
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── InvitedCode
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Loading
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Login
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── NotFound
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── NotLogin
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── NotLogout
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── SetupTeam
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Signup
│ │ │ ├── index.tsx
│ │ │ └── style.ts
│ │ ├── Workspace
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ │ └── WorkspaceList
│ │ │ ├── index.tsx
│ │ │ └── styles.ts
│ └── templates
│ │ ├── Code
│ │ ├── index.tsx
│ │ └── styles.ts
│ │ ├── EmptyWorkspace
│ │ ├── index.tsx
│ │ └── styles.ts
│ │ ├── Workspace
│ │ ├── index.tsx
│ │ └── styles.ts
│ │ └── WorkspaceList
│ │ ├── index.tsx
│ │ └── styles.ts
├── enum
│ └── index.ts
├── global
│ ├── api
│ │ ├── channel
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── login
│ │ │ └── index.ts
│ │ ├── reaction
│ │ │ └── index.ts
│ │ ├── reply
│ │ │ └── index.ts
│ │ ├── thread
│ │ │ └── index.ts
│ │ └── workspace
│ │ │ └── index.tsx
│ ├── image
│ │ ├── black.png
│ │ ├── blue.png
│ │ ├── default_account.png
│ │ ├── title_booslack.png
│ │ ├── violet.png
│ │ ├── wavingHandFromSlack.gif
│ │ ├── workspace_default_image.png
│ │ ├── xMark.svg
│ │ └── yellow.png
│ ├── module.d.ts
│ ├── options
│ │ └── index.ts
│ ├── style
│ │ ├── index.ts
│ │ ├── mixin
│ │ │ └── index.ts
│ │ └── theme
│ │ │ └── index.ts
│ ├── type
│ │ └── index.ts
│ └── util
│ │ ├── auth.ts
│ │ ├── file.ts
│ │ ├── index.ts
│ │ ├── inputEventHandlers.ts
│ │ ├── message.ts
│ │ ├── reactQueryUtil.ts
│ │ ├── reaction.ts
│ │ ├── reply.ts
│ │ ├── transfromDate.ts
│ │ └── validatePassword.ts
├── hooks
│ ├── useAbstract.ts
│ ├── useAsync.ts
│ ├── useChannels.ts
│ ├── useInfinityPage.ts
│ ├── useInputs.ts
│ ├── useKeyboardNavigator.ts
│ ├── usePagination.ts
│ ├── useRefLocate.ts
│ ├── useReplys.ts
│ ├── useSocket.ts
│ ├── useThreads.ts
│ ├── useUsers.ts
│ └── useWorkspace.ts
├── index.tsx
├── routes
│ ├── PrivateRoute.tsx
│ └── PublicRoute.tsx
├── state
│ ├── Channel
│ │ └── index.ts
│ ├── modal
│ │ └── index.ts
│ ├── theme
│ │ └── index.ts
│ ├── thread
│ │ └── index.ts
│ ├── user
│ │ └── index.ts
│ └── workspace
│ │ └── index.ts
└── test
│ ├── DefaultEnvironment.tsx
│ ├── page.test.tsx
│ ├── router.test.tsx
│ └── workspacelist.test.tsx
├── tsconfig.json
├── webpack.config.js
├── webpack.prod.config.js
└── yarn.lock
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: 버그 발생 부분을 적는 bug report입니다.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | # 📃 버그 설명
11 |
12 | 자세하게 설명하기
13 |
14 | # 📃 버그 발생 부분 설명
15 |
16 | Ex)
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 | 4. See error
21 |
22 | # 📃 버그 발생 부분 화면
23 |
24 | # 📃 추가 메모 사항
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: feature 탬플릿입니다.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | # 📃 feature 설명
11 |
12 | 자세하게 설명하기
13 |
14 | # 📃 feature 구현 목록
15 |
16 | Ex)
17 | 1. Go to '...'
18 | 2. Click on '....'
19 | 3. Scroll down to '....'
20 |
21 | # 📃 feature 구현시 고려할 사항
22 |
23 | # 📃 추가 메모 사항
24 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## 📃 PR 제목
2 |
3 | ## 📃 작업 목록
4 |
5 | Ex)
6 | - [ ] Go to '...'
7 | - [ ] Click on '....'
8 | - [ ] Scroll down to '....'
9 |
10 | ## 📃 주의 사항
11 |
12 | ## 📃 추가 메모 사항
13 |
--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI test
2 |
3 | on:
4 | push:
5 | branches-ignore:
6 | - master
7 | - dev
8 | - release
9 | - old-release
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | node-version: [14.x]
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 |
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - name: CI test for front
27 | run: |
28 | cd ./frontend/
29 | yarn install
30 | yarn jest
31 | env:
32 | CI: ''
33 |
--------------------------------------------------------------------------------
/.github/workflows/ReleaseNCP.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Deployment for main release
2 | on:
3 | push:
4 | branches: [main]
5 | jobs:
6 | deploy:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Deploy to Server
10 | uses: appleboy/ssh-action@master
11 | with:
12 | password: ${{ secrets.MAINPASSWORD }}
13 | host: ${{ secrets.MAINHOST }}
14 | username: ${{ secrets.MAINUSERNAME }}
15 | port: ${{ secrets.MAINPORT }}
16 | script: |
17 | sh ~/autoCD.sh 2>&1
18 |
--------------------------------------------------------------------------------
/.github/workflows/releaseRepo.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: CD - release repo automatically
5 |
6 | on:
7 | pull_request_target:
8 | types: [closed]
9 | branches: [dev]
10 |
11 | jobs:
12 | backup:
13 | name: backup
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v2
17 | with:
18 | token: ${{ secrets.web06TOKEN}}
19 | ref: 'release'
20 | - name: backup - release
21 | run: |
22 | git checkout -b old-release
23 | git push -f origin old-release
24 | release:
25 | name: release
26 | needs: backup
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v2
30 | with:
31 | token: ${{ secrets.web06TOKEN}}
32 | ref: 'dev'
33 | - name: update release
34 | run: |
35 | git checkout -b release
36 | git push -f origin release
37 |
--------------------------------------------------------------------------------
/.github/workflows/slack-notice.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: slack-notice CI
5 |
6 | on:
7 | push:
8 | branches: [main, dev]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: 8398a7/action-slack@v3
16 | with:
17 | status: ${{ job.status }}
18 | fields: repo,message,commit,author,action,eventName,ref,workflow,job,took # selectable (default: repo,message)
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.web06TOKEN }} # required
21 | SLACK_WEBHOOK_URL: ${{ secrets.SLACKURL }} # required
22 | if: always() # Pick up events even if the job fails or is canceled.
23 |
--------------------------------------------------------------------------------
/.github/workflows/testNCP.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Deployment for Test release
2 | on:
3 | push:
4 | branches: [release]
5 | jobs:
6 | deploy:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Deploy to Server
10 | uses: appleboy/ssh-action@master
11 | with:
12 | password: ${{ secrets.PASSWORD }}
13 | host: ${{ secrets.HOST }}
14 | username: ${{ secrets.USERNAME }}
15 | port: ${{ secrets.PORT }}
16 | script: |
17 | sh /git/autoCD.sh 2>&1
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🖥 booslack
2 |
3 | > 🔗 배포 링크 : http://118.67.128.103:3001/ (중지)
4 |
5 | 샘플 아이디 : ycpycpycp | 비밀번호 : Ycpycp123!
6 |
7 | > ✨ 데모 링크 : https://youtu.be/DyyKPh8H8-U
8 |
9 | booslack은 팀원들과 원활한 소통을 할 수 있게 채팅 및 화상회의를 지원해주는 협업 도구입니다.
10 |
11 |
12 |
13 |
14 |
15 | # 👨👧👦 Member
16 |
17 | |
|
|
|
|
18 | |------|------|------|------|
19 | | 🥐 설민욱 | 🍛이충헌 | 🍣박주원 | 🍜조진성 |
20 | | [@blogSoul](https://github.com/blogSoul)
백엔드 담당 | [@lodado](https://github.com/lodado)
프론트엔드 담당 | [@laz](https://github.com/laz)
프론트엔드 담당 | [@loin3](https://github.com/loin3)
백엔드 담당 |
21 |
22 | ## ⚙ 기술 스택
23 |
24 |
25 |
26 | # 📕 Detail
27 |
28 | ### `booslack` 프로젝트를 더 보고 싶다면 [아래 링크](https://github.com/boostcampwm-2021/web06-booslack/wiki)를 클릭해주세요!
29 |
30 | (2022-01-07 기준으로 배포 서버 변경했습니다)
31 |
--------------------------------------------------------------------------------
/backend/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es2021": true,
4 | "node": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | "airbnb-base"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaVersion": 12,
15 | "sourceType": "module",
16 | "project": "./tsconfig.json"
17 | },
18 | "plugins": ["@typescript-eslint"],
19 | "rules": {
20 | "max-len": [
21 | "error",
22 | {
23 | "code": 100
24 | }
25 | ],
26 | "no-console": 1,
27 | "no-extra-boolean-cast": 0,
28 | "@typescript-eslint/restrict-plus-operands": 0,
29 | "@typescript-eslint/explicit-module-boundary-types": 0,
30 | "@typescript-eslint/no-explicit-any": 0,
31 | "@typescript-eslint/no-floating-promises": 0,
32 | "@typescript-eslint/no-unsafe-member-access": 0,
33 | "@typescript-eslint/no-unsafe-assignment": 0,
34 | "import/no-unresolved": "off",
35 | "import/extensions": "off",
36 | "object-curly-newline": "off",
37 | "@typescript-eslint/no-misused-promises": [
38 | "error",
39 | {
40 | "checksVoidReturn": false,
41 | "checksConditionals": false
42 | }
43 | ]
44 | },
45 | "settings": {
46 | "import/resolver": {
47 | "node": {
48 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/backend/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "tabWidth": 2,
5 | "trailingComma": "all",
6 | "useTabs": false,
7 | "printWidth": 100,
8 | "arrowParens": "always",
9 | "bracketSpacing": true,
10 | "endOfLine": "lf"
11 | }
12 |
--------------------------------------------------------------------------------
/backend/src/@types/express/index.d.ts:
--------------------------------------------------------------------------------
1 | import { IChannel } from 'src/model/Channel';
2 | import { IUser } from 'src/model/User';
3 | import { IWorkspace } from 'src/model/Workspace';
4 |
5 | declare module 'express' {
6 | export interface Request {
7 | body: {
8 | user: IUser;
9 | nickname: string;
10 | email: string;
11 | type: string;
12 | workspace: IWorkspace;
13 | name: string;
14 | password: string;
15 | profile: string;
16 | channel: IChannel;
17 | description: string;
18 | message: string;
19 | time: Date;
20 | code: string;
21 | theme: string;
22 | fileId: number;
23 | topic: string;
24 | emoji: string;
25 | files: any;
26 |
27 | userId: string;
28 | channelId: string;
29 | workspaceId: string;
30 | userHasWorkspaceId: string;
31 | threadId: string;
32 | replyId: string;
33 | reactionId: string;
34 | };
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/backend/src/config/Upload.ts:
--------------------------------------------------------------------------------
1 | import multer from 'multer';
2 | import multerS3 from 'multer-s3';
3 | import AWS from 'aws-sdk';
4 | import { FILE_LIMIT_SIZE } from '../enum';
5 |
6 | const s3 = new AWS.S3({
7 | endpoint: process.env.NCLOUD_S3_ENDPOINT,
8 | accessKeyId: process.env.NCLOUD_ACCESS_KEY,
9 | secretAccessKey: process.env.NCLOUD_SECRET_KEY,
10 | region: process.env.NCLOUD_REGION,
11 | });
12 |
13 | const storage = multerS3({
14 | s3,
15 | bucket: process.env.NCLOUD_BUCKET || 'booslack',
16 | // eslint-disable-next-line @typescript-eslint/unbound-method
17 | contentType: multerS3.AUTO_CONTENT_TYPE,
18 | acl: 'public-read',
19 | key: (req, file, callback) => {
20 | callback(null, file.originalname);
21 | },
22 | metadata: (req, file, callback) => {
23 | callback(null, { fieldName: file.fieldname });
24 | },
25 | });
26 |
27 | const Upload = multer({
28 | storage,
29 | limits: { fileSize: FILE_LIMIT_SIZE },
30 | });
31 |
32 | export default Upload;
33 |
--------------------------------------------------------------------------------
/backend/src/enum/index.ts:
--------------------------------------------------------------------------------
1 | export const OFFSET_START = 0;
2 |
3 | export const PAGE_LIMIT_COUNT = 10;
4 |
5 | export const LOCALTYPE_GITHUB = 0;
6 |
7 | export const LOCALTYPE_LOCAL = 1;
8 |
9 | export const FILE_LIMIT_SIZE = 5 * 1024 * 1024;
10 |
11 | export const WORKSPACELIST_PAGE_LIMIT_COUNT = 7;
12 |
--------------------------------------------------------------------------------
/backend/src/index.ts:
--------------------------------------------------------------------------------
1 | import './pre-start'; // Must be the first import
2 | import { createConnection } from 'typeorm';
3 | import { createServer } from 'http';
4 | import app from './Server';
5 | import logger from './shared/Logger';
6 | import 'reflect-metadata';
7 | import connectionOptions from './ormconfig';
8 | import addSampleData from './sample';
9 | import initializeSocket from './socket';
10 |
11 | createConnection(connectionOptions)
12 | .then(() => {
13 | // Start the server
14 | const port = Number(process.env.PORT || 3000);
15 | const httpServer = createServer(app);
16 | initializeSocket(httpServer);
17 | httpServer.listen(port, () => {
18 | logger.info(`Express server started on port: ${port}`);
19 | });
20 | })
21 | .then(() => {
22 | if (process.env.DB_AUTO_ADD_DATA === 'true') addSampleData();
23 | })
24 | .catch((error) => console.log(error));
25 |
--------------------------------------------------------------------------------
/backend/src/model/Channel.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Column,
3 | ManyToOne,
4 | ManyToMany,
5 | JoinTable,
6 | OneToMany,
7 | Entity,
8 | PrimaryGeneratedColumn,
9 | CreateDateColumn,
10 | } from 'typeorm';
11 | import Thread from './Thread';
12 | import UserHasWorkspace from './UserHasWorkspace';
13 | import Workspace from './Workspace';
14 |
15 | @Entity()
16 | class Channel {
17 | @PrimaryGeneratedColumn()
18 | id!: number;
19 |
20 | @Column()
21 | name!: string;
22 |
23 | @Column({ nullable: true })
24 | description!: string;
25 |
26 | @Column({ nullable: true })
27 | topic!: string;
28 |
29 | @Column({ type: 'tinyint' })
30 | private!: number;
31 |
32 | @Column({ nullable: true })
33 | workspaceId!: number;
34 |
35 | @CreateDateColumn({ type: 'timestamp' })
36 | createdAt!: Date
37 |
38 | @OneToMany(() => Thread, (thread) => thread.channel)
39 | threads!: Thread[];
40 |
41 | @ManyToOne(() => Workspace, (workspace) => workspace.channels)
42 | workspace!: Workspace;
43 |
44 | @ManyToMany(() => UserHasWorkspace)
45 | @JoinTable({
46 | name: 'user_has_workspace_channel',
47 | })
48 | userHasWorkspaces!: UserHasWorkspace[];
49 | }
50 |
51 | export default Channel;
52 |
--------------------------------------------------------------------------------
/backend/src/model/Dm.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Entity, Column,
3 | ManyToOne, OneToMany,
4 | PrimaryGeneratedColumn, CreateDateColumn,
5 | } from 'typeorm';
6 | import Thread from './Thread';
7 | import Workspace from './Workspace';
8 |
9 | @Entity()
10 | class Dm {
11 | @PrimaryGeneratedColumn()
12 | id!: number
13 |
14 | @Column()
15 | name!: string;
16 |
17 | @Column({ nullable: true })
18 | description!: string;
19 |
20 | @Column({ nullable: true })
21 | topic!: string;
22 |
23 | @Column({ nullable: true })
24 | workspaceId!: number;
25 |
26 | @CreateDateColumn({ type: 'timestamp' })
27 | createdAt!: Date
28 |
29 | @OneToMany(() => Thread, (thread) => thread.dm)
30 | threads!: Thread[];
31 |
32 | @ManyToOne(() => Workspace, (workspace) => workspace.dms)
33 | workspace!: Workspace;
34 | }
35 |
36 | export default Dm;
37 |
--------------------------------------------------------------------------------
/backend/src/model/File.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Column, CreateDateColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
2 | import Thread from './Thread';
3 | import Reply from './Reply';
4 |
5 | @Entity()
6 | class File {
7 | @PrimaryGeneratedColumn()
8 | id!: number;
9 |
10 | @Column()
11 | name!: string;
12 |
13 | @Column({ nullable: true })
14 | url!: string;
15 |
16 | @Column()
17 | extension!: string;
18 |
19 | @Column({ nullable: true })
20 | threadId!: number;
21 |
22 | @Column({ nullable: true })
23 | replyId!: number;
24 |
25 | @CreateDateColumn({ type: 'timestamp' })
26 | createdAt!: Date;
27 |
28 | @ManyToOne(() => Thread, (thread) => thread.files, { onUpdate: 'CASCADE' })
29 | thread!: Thread;
30 |
31 | @ManyToOne(() => Reply, (reply) => reply.files, { onUpdate: 'CASCADE' })
32 | reply!: Reply;
33 | }
34 |
35 | export default File;
36 |
--------------------------------------------------------------------------------
/backend/src/model/Reaction.ts:
--------------------------------------------------------------------------------
1 | import { Column, ManyToOne, Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
2 | import Thread from './Thread';
3 | import Reply from './Reply';
4 | import UserHasWorkspace from './UserHasWorkspace';
5 |
6 | @Entity()
7 | class Reaction {
8 | @PrimaryGeneratedColumn()
9 | id!: number;
10 |
11 | @Column({ nullable: true })
12 | threadId!: number;
13 |
14 | @Column({ nullable: true })
15 | emoji!: string;
16 |
17 | @Column({ nullable: true })
18 | userHasWorkspaceId!: number;
19 |
20 | @Column({ nullable: true })
21 | replyId!: number;
22 |
23 | @CreateDateColumn({ type: 'timestamp' })
24 | createdAt!: Date;
25 |
26 | @ManyToOne(() => Thread, (thread) => thread.reactions, { onDelete: 'CASCADE' })
27 | thread!: Thread;
28 |
29 | @ManyToOne(() => UserHasWorkspace, (userHasWorkspace) => userHasWorkspace.reactions, {
30 | onDelete: 'SET NULL',
31 | })
32 | userHasWorkspace!: UserHasWorkspace;
33 |
34 | @ManyToOne(() => Reply, (reply) => reply.reactions, { onDelete: 'CASCADE' })
35 | reply!: Reply;
36 | }
37 |
38 | export default Reaction;
39 |
--------------------------------------------------------------------------------
/backend/src/model/Reply.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Column,
3 | ManyToOne,
4 | Entity,
5 | UpdateDateColumn,
6 | OneToMany,
7 | PrimaryGeneratedColumn,
8 | CreateDateColumn,
9 | } from 'typeorm';
10 | import UserHasWorkspace from './UserHasWorkspace';
11 | import Thread from './Thread';
12 | import Reaction from './Reaction';
13 | import File from './File';
14 |
15 | @Entity()
16 | class Reply {
17 | @PrimaryGeneratedColumn()
18 | id!: number;
19 |
20 | @Column()
21 | message!: string;
22 |
23 | @Column({ nullable: true })
24 | threadId!: number;
25 |
26 | @Column({ nullable: true })
27 | userHasWorkspaceId!: number;
28 |
29 | @CreateDateColumn({ type: 'timestamp' })
30 | createdAt!: Date;
31 |
32 | @UpdateDateColumn({ type: 'timestamp', nullable: true })
33 | updatedAt!: Date;
34 |
35 | @OneToMany(() => Reaction, (reaction) => reaction.reply)
36 | reactions!: Reaction[];
37 |
38 | @OneToMany(() => File, (file) => file.reply)
39 | files!: File[];
40 |
41 | @ManyToOne(() => Thread, (thread) => thread.replys, { onDelete: 'CASCADE' })
42 | thread!: Thread;
43 |
44 | @ManyToOne(() => UserHasWorkspace, (userHasWorkspace) => userHasWorkspace.replys, {
45 | onDelete: 'SET NULL',
46 | })
47 | userHasWorkspace!: UserHasWorkspace;
48 | }
49 |
50 | export default Reply;
51 |
--------------------------------------------------------------------------------
/backend/src/model/Thread.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Column,
3 | ManyToOne,
4 | OneToMany,
5 | UpdateDateColumn,
6 | Entity,
7 | PrimaryGeneratedColumn,
8 | CreateDateColumn,
9 | } from 'typeorm';
10 | import Dm from './Dm';
11 | import Channel from './Channel';
12 | import UserHasWorkspace from './UserHasWorkspace';
13 | import Reply from './Reply';
14 | import File from './File';
15 | import Reaction from './Reaction';
16 |
17 | @Entity()
18 | class Thread {
19 | @PrimaryGeneratedColumn()
20 | id!: number;
21 |
22 | @Column()
23 | message!: string;
24 |
25 | @Column({ nullable: true })
26 | channelId!: number;
27 |
28 | @Column({ nullable: true })
29 | dmId!: number;
30 |
31 | @Column({ nullable: true })
32 | userHasWorkspaceId!: number;
33 |
34 | @CreateDateColumn({ type: 'timestamp' })
35 | createdAt!: Date;
36 |
37 | @UpdateDateColumn({ type: 'timestamp', nullable: true })
38 | updatedAt!: Date;
39 |
40 | @OneToMany(() => Reply, (reply) => reply.thread)
41 | replys!: Reply[];
42 |
43 | @OneToMany(() => File, (file) => file.thread)
44 | files!: File[];
45 |
46 | @OneToMany(() => Reaction, (reaction) => reaction.thread)
47 | reactions!: Reaction[];
48 |
49 | @ManyToOne(() => Channel, (channel) => channel.threads)
50 | channel!: Channel;
51 |
52 | @ManyToOne(() => Dm, (dm) => dm.threads)
53 | dm!: Dm;
54 |
55 | @ManyToOne(() => UserHasWorkspace, (userHasWorkspace) => userHasWorkspace.threads, {
56 | onDelete: 'SET NULL',
57 | })
58 | userHasWorkspace!: UserHasWorkspace;
59 | }
60 |
61 | export default Thread;
62 |
--------------------------------------------------------------------------------
/backend/src/model/User.ts:
--------------------------------------------------------------------------------
1 | import { Column, OneToMany, Entity, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
2 | import UserHasWorkspace from './UserHasWorkspace';
3 |
4 | @Entity()
5 | class User {
6 | @PrimaryGeneratedColumn()
7 | id!: number;
8 |
9 | @Column()
10 | account!: string;
11 |
12 | @Column({ nullable: true })
13 | email!: string;
14 |
15 | @Column({ nullable: true })
16 | password!: string;
17 |
18 | @Column({ type: 'tinyint' })
19 | local!: number;
20 |
21 | @Column('int', { default: 1 })
22 | theme!: number;
23 |
24 | @CreateDateColumn({ type: 'timestamp' })
25 | createdAt!: Date;
26 |
27 | @OneToMany(() => UserHasWorkspace, (userHasWorkspace) => userHasWorkspace.user)
28 | userHasWorkspaces!: UserHasWorkspace[];
29 | }
30 |
31 | export default User;
32 |
--------------------------------------------------------------------------------
/backend/src/model/UserHasWorkspace.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Column,
3 | ManyToOne,
4 | OneToMany,
5 | Entity,
6 | PrimaryGeneratedColumn,
7 | CreateDateColumn,
8 | } from 'typeorm';
9 | import User from './User';
10 | import Workspace from './Workspace';
11 | import Thread from './Thread';
12 | import Reply from './Reply';
13 | import Reaction from './Reaction';
14 |
15 | @Entity()
16 | class UserHasWorkspace {
17 | @PrimaryGeneratedColumn()
18 | id!: number;
19 |
20 | @Column({ nullable: true })
21 | nickname!: string;
22 |
23 | @Column({ nullable: true })
24 | description!: string;
25 |
26 | @Column('int', { default: 1 })
27 | theme!: number;
28 |
29 | @Column({ nullable: true })
30 | workspaceId!: number;
31 |
32 | @Column({ nullable: true })
33 | userId!: number;
34 |
35 | @Column({ nullable: true })
36 | fileId!: number;
37 |
38 | @Column({ nullable: true })
39 | fileUrl!: string;
40 |
41 | @CreateDateColumn({ type: 'timestamp' })
42 | createdAt!: Date;
43 |
44 | @OneToMany(() => Reply, (reply) => reply.userHasWorkspace)
45 | replys!: Reply[];
46 |
47 | @OneToMany(() => Thread, (thread) => thread.userHasWorkspace)
48 | threads!: Thread[];
49 |
50 | @OneToMany(() => Reaction, (reaction) => reaction.userHasWorkspace)
51 | reactions!: Reaction[];
52 |
53 | @ManyToOne(() => Workspace, (workspace) => workspace.userHasWorkspaces)
54 | workspace!: Workspace;
55 |
56 | @ManyToOne(() => User, (user) => user.userHasWorkspaces)
57 | user!: User;
58 | }
59 |
60 | export default UserHasWorkspace;
61 |
--------------------------------------------------------------------------------
/backend/src/model/Workspace.ts:
--------------------------------------------------------------------------------
1 | import { Column, Entity, OneToMany, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
2 | import Channel from './Channel';
3 | import UserHasWorkspace from './UserHasWorkspace';
4 | import Dm from './Dm';
5 |
6 | @Entity()
7 | class Workspace {
8 | @PrimaryGeneratedColumn()
9 | id!: number
10 |
11 | @Column()
12 | name!: string;
13 |
14 | @Column({ nullable: true })
15 | code!: string;
16 |
17 | @Column({ nullable: true })
18 | fileId!: number;
19 |
20 | @CreateDateColumn({ type: 'timestamp' })
21 | createdAt!: Date
22 |
23 | @OneToMany(() => UserHasWorkspace, (userHasWorkspace) => userHasWorkspace.workspace)
24 | userHasWorkspaces!: UserHasWorkspace[];
25 |
26 | @OneToMany(() => Channel, (channel) => channel.workspace)
27 | channels!: Channel[];
28 |
29 | @OneToMany(() => Dm, (dm) => dm.workspace)
30 | dms!: Dm[];
31 | }
32 |
33 | export default Workspace;
34 |
--------------------------------------------------------------------------------
/backend/src/ormconfig.ts:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 | import { ConnectionOptions } from 'typeorm';
3 |
4 | dotenv.config();
5 |
6 | const entitiyPath: string = process.env.DB_ENTITY_PATH || 'src/model/*.ts';
7 |
8 | const ormconfig: ConnectionOptions = {
9 | type: 'mysql',
10 | host: process.env.DB_HOST,
11 | port: Number(process.env.DB_PORT),
12 | username: process.env.DB_USERNAME,
13 | password: process.env.DB_PASSWORD,
14 | database: process.env.DB_DATABASE,
15 | synchronize: true,
16 | logging: false,
17 | entities: [entitiyPath],
18 | extra: { charset: 'utf8mb4_unicode_ci' },
19 | };
20 |
21 | export default ormconfig;
22 |
--------------------------------------------------------------------------------
/backend/src/pre-start/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Pre-start is where we want to place things that must run BEFORE the express server is started.
3 | * This is useful for environment variables, command-line arguments, and cron-jobs.
4 | */
5 |
6 | import path from 'path';
7 | import dotenv from 'dotenv';
8 | import commandLineArgs from 'command-line-args';
9 |
10 | (() => {
11 | // Setup command line options
12 | const options = commandLineArgs([
13 | {
14 | name: 'env',
15 | alias: 'e',
16 | defaultValue: 'development',
17 | type: String,
18 | },
19 | ]);
20 | // Set the env file
21 | const result2 = dotenv.config({
22 | path: path.join(__dirname, `env/${options.env}.env`),
23 | // path: path.join(__dirname, "env/", options.env, ".env"),
24 | });
25 | if (result2.error) {
26 | throw result2.error;
27 | }
28 | })();
29 |
--------------------------------------------------------------------------------
/backend/src/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 100px;
3 | font: 14px 'Lucida Grande', Helvetica, Arial, sans-serif;
4 | }
5 |
6 | body .users-column {
7 | display: inline-block;
8 | margin-right: 2em;
9 | vertical-align: top;
10 | }
11 |
12 | body .users-column .column-header {
13 | padding-bottom: 5px;
14 | font-weight: 700;
15 | font-size: 1.2em;
16 | }
17 |
18 | body .add-user-col input {
19 | margin-bottom: 10px;
20 | }
21 |
22 | body .users-column .user-display-ele {
23 | padding-bottom: 10px;
24 | }
25 |
26 | body .users-column .user-display-ele button {
27 | margin-top: 2px;
28 | margin-bottom: 10px;
29 | }
30 |
31 | body .users-column .user-display-ele .edit-view {
32 | display: none;
33 | }
34 |
--------------------------------------------------------------------------------
/backend/src/repository/FileRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import File from '../model/File';
3 |
4 | @EntityRepository(File)
5 | export default class FileRepository extends Repository {}
6 |
--------------------------------------------------------------------------------
/backend/src/repository/ReactionRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import Reaction from '../model/Reaction';
3 |
4 | @EntityRepository(Reaction)
5 | export default class ReactionRepository extends Repository {}
6 |
--------------------------------------------------------------------------------
/backend/src/repository/ReplyRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import Reply from '../model/Reply';
3 |
4 | @EntityRepository(Reply)
5 | export default class ReplyRepository extends Repository {}
6 |
--------------------------------------------------------------------------------
/backend/src/repository/ThreadRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import Thread from '../model/Thread';
3 |
4 | @EntityRepository(Thread)
5 | export default class ThreadRepository extends Repository {}
6 |
--------------------------------------------------------------------------------
/backend/src/repository/WorkspaceRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, Repository } from 'typeorm';
2 | import Workspace from '../model/Workspace';
3 |
4 | @EntityRepository(Workspace)
5 | export default class WorkspaceRepository extends Repository {}
6 |
--------------------------------------------------------------------------------
/backend/src/routes/ChannelController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | getAllChannels,
4 | getOneChannel,
5 | addOneChannel,
6 | updateOneChannel,
7 | deleteOneChannel,
8 | addUserToChannel,
9 | deleteUserFromChannel,
10 | getChannelsThatUserIn,
11 | getChannels,
12 | } from '../service/ChannelService';
13 |
14 | const channelRouter = Router();
15 |
16 | channelRouter.post('/userToChannel', addUserToChannel);
17 | channelRouter.delete('/userFromChannel', deleteUserFromChannel);
18 | channelRouter.get('/channelsThatUserIn', getChannelsThatUserIn);
19 |
20 | channelRouter.get('/all', getChannels);
21 | channelRouter.get('/', getAllChannels);
22 | channelRouter.get('/:id', getOneChannel);
23 | channelRouter.post('/', addOneChannel);
24 | channelRouter.put('/:id', updateOneChannel);
25 | channelRouter.delete('/:id', deleteOneChannel);
26 |
27 | export default channelRouter;
28 |
--------------------------------------------------------------------------------
/backend/src/routes/FilesController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import Upload from '../config/Upload';
3 | import {
4 | getAllFiles,
5 | getOneFile,
6 | addOneFile,
7 | updateOneFile,
8 | deleteOneFile,
9 | uploadFile,
10 | uploadFiles,
11 | getOneFileByUserHasWorkspaceId,
12 | } from '../service/FilesService';
13 |
14 | const FileRouter = Router();
15 |
16 | FileRouter.post('/upload', Upload.single('file'), uploadFile);
17 | FileRouter.post('/uploads', Upload.array('file'), uploadFiles);
18 |
19 | FileRouter.get('/userhasworkspace/:id', getOneFileByUserHasWorkspaceId);
20 |
21 | FileRouter.get('/', getAllFiles);
22 | FileRouter.get('/:id', getOneFile);
23 | FileRouter.post('/', addOneFile);
24 | FileRouter.put('/:id', updateOneFile);
25 | FileRouter.delete('/:id', deleteOneFile);
26 |
27 | export default FileRouter;
28 |
--------------------------------------------------------------------------------
/backend/src/routes/ReactionController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | addReaction,
4 | addReplyReaction,
5 | deleteReactionById,
6 | deleteReactionByInfo,
7 | deleteReplyReaction,
8 | } from '../service/ReactionService';
9 |
10 | const channelRouter = Router();
11 |
12 | channelRouter.post('/', addReaction);
13 | channelRouter.post('/reply', addReplyReaction);
14 | channelRouter.delete('/reply', deleteReplyReaction);
15 | channelRouter.delete('/:id', deleteReactionById);
16 | channelRouter.delete('/', deleteReactionByInfo);
17 |
18 | export default channelRouter;
19 |
--------------------------------------------------------------------------------
/backend/src/routes/ReplyController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | getAllReplys,
4 | getReply,
5 | addReply,
6 | updateReply,
7 | deleteReply,
8 | getPartialReplysByThreadId,
9 | updateReplyAndFiles,
10 | } from '../service/ReplyService';
11 |
12 | const replyRouter = Router();
13 |
14 | replyRouter.get('/partial', getPartialReplysByThreadId);
15 |
16 | replyRouter.put('/files/:id', updateReplyAndFiles);
17 |
18 | replyRouter.get('/', getAllReplys);
19 | replyRouter.get('/:id', getReply);
20 | replyRouter.post('/', addReply);
21 | replyRouter.put('/:id', updateReply);
22 | replyRouter.delete('/:id', deleteReply);
23 |
24 | export default replyRouter;
25 |
--------------------------------------------------------------------------------
/backend/src/routes/ThreadController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | addThread,
4 | deleteThread,
5 | getPartialThreadsByChannelId,
6 | getThread,
7 | updateThread,
8 | updateThreadAndFiles,
9 | } from '../service/ThreadService';
10 |
11 | const threadRouter = Router();
12 |
13 | threadRouter.put('/files/:id', updateThreadAndFiles);
14 |
15 | threadRouter.get('/', getPartialThreadsByChannelId);
16 | threadRouter.get('/:id', getThread);
17 | threadRouter.post('/', addThread);
18 | threadRouter.put('/:id', updateThread);
19 | threadRouter.delete('/:id', deleteThread);
20 |
21 | export default threadRouter;
22 |
--------------------------------------------------------------------------------
/backend/src/routes/UserController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | getAllUsers,
4 | getOneUser,
5 | addOneUser,
6 | findOneUser,
7 | updateOneUser,
8 | deleteOneUser,
9 | addUserToWorkspace,
10 | getAllUsersWithChannelInfo,
11 | deleteUserFromChannel,
12 | } from '../service/UserService';
13 |
14 | const userRouter = Router();
15 |
16 | userRouter.post('/userToWorkspace', addUserToWorkspace);
17 | userRouter.get('/workspaces', getAllUsersWithChannelInfo);
18 |
19 | userRouter.get('/', getAllUsers);
20 | userRouter.get('/find', findOneUser);
21 | userRouter.get('/:id', getOneUser);
22 | userRouter.post('/', addOneUser);
23 | userRouter.put('/:id', updateOneUser);
24 | userRouter.delete('/:id', deleteOneUser);
25 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
26 | userRouter.delete('/:workspaceId/:channelId', deleteUserFromChannel);
27 | export default userRouter;
28 |
--------------------------------------------------------------------------------
/backend/src/routes/UserHasWorkspaceController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | getUserHasWorkspace,
4 | updateUserHasWorkspace,
5 | deleteUserHasWorkspace,
6 | getUserHasWorkspacesByWorkspaceId,
7 | } from '@service/UserHasWorkspaceService';
8 |
9 | const userHasWorkspaceRouter = Router();
10 |
11 | userHasWorkspaceRouter.get('/all', getUserHasWorkspacesByWorkspaceId);
12 | userHasWorkspaceRouter.get('/', getUserHasWorkspace);
13 | userHasWorkspaceRouter.put('/:workspaceId', updateUserHasWorkspace);
14 | userHasWorkspaceRouter.delete('/:id', deleteUserHasWorkspace);
15 |
16 | export default userHasWorkspaceRouter;
17 |
--------------------------------------------------------------------------------
/backend/src/routes/WorkspaceController.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import {
3 | getAllWorkspaces,
4 | getOneWorkspace,
5 | addOneWorkspace,
6 | updateOneWorkspace,
7 | deleteOneWorkspace,
8 | getAllUserWorkspaces,
9 | addUserToWorkspace,
10 | } from '../service/WorkspaceService';
11 |
12 | const workspaceRouter = Router();
13 | workspaceRouter.post('/code', addUserToWorkspace);
14 | workspaceRouter.get('/user', getAllUserWorkspaces);
15 | workspaceRouter.get('/', getAllWorkspaces);
16 | workspaceRouter.get('/:id', getOneWorkspace);
17 | workspaceRouter.post('/', addOneWorkspace);
18 | workspaceRouter.put('/:id', updateOneWorkspace);
19 | workspaceRouter.delete('/:id', deleteOneWorkspace);
20 |
21 | export default workspaceRouter;
22 |
--------------------------------------------------------------------------------
/backend/src/routes/index.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'express';
2 | import channelRouter from './ChannelController';
3 | import userRouter from './UserController';
4 | import workspaceRouter from './WorkspaceController';
5 | import loginRouter from './LoginController';
6 | import threadRouter from './ThreadController';
7 | import userHasWorkspaceRouter from './UserHasWorkspaceController';
8 | import FileRouter from './FilesController';
9 | import reactionRouter from './ReactionController';
10 | import replyRouter from './ReplyController';
11 |
12 | const baseRouter = Router();
13 | baseRouter.use('/users', userRouter);
14 | baseRouter.use('/userHasWorkspaces', userHasWorkspaceRouter);
15 | baseRouter.use('/workspaces', workspaceRouter);
16 | baseRouter.use('/channels', channelRouter);
17 | baseRouter.use('/threads', threadRouter);
18 | baseRouter.use('/login', loginRouter);
19 | baseRouter.use('/files', FileRouter);
20 | baseRouter.use('/reactions', reactionRouter);
21 | baseRouter.use('/replys', replyRouter);
22 |
23 | export default baseRouter;
24 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertChannelSample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import Channel from '../model/Channel';
3 | import ChannelSampleValue from './value/ChannelSampleValue';
4 |
5 | const insertChannelSample = async () => {
6 | const ChannelCount = await getRepository(Channel).count();
7 | if (ChannelCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(Channel)
12 | .values(ChannelSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertChannelSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertFileSample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import File from '../model/File';
3 | import FileSampleValue from './value/FileSampleValue';
4 |
5 | const insertFileSample = async () => {
6 | const FileCount = await getRepository(File).count();
7 | if (FileCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(File)
12 | .values(FileSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertFileSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertReactionSample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import Reaction from '../model/Reaction';
3 | import ReactionSampleValue from './value/ReactionSampleValue';
4 |
5 | const insertReactionSample = async () => {
6 | const ReactionCount = await getRepository(Reaction).count();
7 | if (ReactionCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(Reaction)
12 | .values(ReactionSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertReactionSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertReplySample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import Reply from '../model/Reply';
3 | import ReplySampleValue from './value/ReplySampleValue';
4 |
5 | const insertReplySample = async () => {
6 | const ReplyCount = await getRepository(Reply).count();
7 | if (ReplyCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(Reply)
12 | .values(ReplySampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertReplySample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertThreadSample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import Thread from '../model/Thread';
3 | import ThreadSampleValue from './value/ThreadSampleValue';
4 |
5 | const insertThreadSample = async () => {
6 | const ThreadCount = await getRepository(Thread).count();
7 | if (ThreadCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(Thread)
12 | .values(ThreadSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertThreadSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertUserHasWorkspace.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import UserHasWorkspace from '../model/UserHasWorkspace';
3 | import UserHasWorkspaceSampleValue from './value/UserHasWorkspaceSampleValue';
4 |
5 | const insertUserHasWorkspaceSample = async () => {
6 | const UserHasWorkspaceCount = await getRepository(UserHasWorkspace).count();
7 | if (UserHasWorkspaceCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(UserHasWorkspace)
12 | .values(UserHasWorkspaceSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertUserHasWorkspaceSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertUserSample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import User from '../model/User';
3 | import UserSampleValue from './value/UserSampleValue';
4 |
5 | const insertUserSample = async () => {
6 | const UserCount = await getRepository(User).count();
7 | if (UserCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(User)
12 | .values(UserSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertUserSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/InsertWorkspaceSample.ts:
--------------------------------------------------------------------------------
1 | import { getConnection, getRepository } from 'typeorm';
2 | import Workspace from '../model/Workspace';
3 | import WorkspaceSampleValue from './value/WorkspaceSampleValue';
4 |
5 | const insertWorkspaceSample = async () => {
6 | const WorkspaceCount = await getRepository(Workspace).count();
7 | if (WorkspaceCount > 0) return;
8 | await getConnection()
9 | .createQueryBuilder()
10 | .insert()
11 | .into(Workspace)
12 | .values(WorkspaceSampleValue)
13 | .execute();
14 | };
15 |
16 | export default insertWorkspaceSample;
17 |
--------------------------------------------------------------------------------
/backend/src/sample/index.ts:
--------------------------------------------------------------------------------
1 | import insertUserSample from './InsertUserSample';
2 | import insertWorkspaceSample from './InsertWorkspaceSample';
3 | import insertUserHasWorkspaceSample from './InsertUserHasWorkspace';
4 | import insertChannelSample from './InsertChannelSample';
5 | import insertThreadSample from './InsertThreadSample';
6 | import insertFileSample from './InsertFileSample';
7 | import insertReactionSample from './InsertReactionSample';
8 | import insertReplySample from './InsertReplySample';
9 |
10 | const addSampleData = async () => {
11 | // await insertUserSample();
12 | // await insertWorkspaceSample();
13 | // await insertUserHasWorkspaceSample();
14 | // await insertChannelSample();
15 | // await insertThreadSample();
16 | // await insertFileSample();
17 | // await insertReactionSample();
18 | // await insertReplySample();
19 | };
20 |
21 | export default addSampleData;
22 |
--------------------------------------------------------------------------------
/backend/src/sample/value/EmotionSampleValue.ts:
--------------------------------------------------------------------------------
1 | const EmotionSampleValue = [
2 | { name: 'smile1' },
3 | { name: 'smile2' },
4 | { name: 'smile3' },
5 | { name: 'smile4' },
6 | { name: 'smile5' },
7 | ];
8 |
9 | export default EmotionSampleValue;
10 |
--------------------------------------------------------------------------------
/backend/src/sample/value/FileSampleValue.ts:
--------------------------------------------------------------------------------
1 | const FileSampleValue = [
2 | { name: 'undefined1', extension: 'txt' },
3 | { name: 'undefined2', extension: 'txt' },
4 | { name: 'undefined3', extension: 'txt' },
5 | { name: 'undefined4', extension: 'txt' },
6 | { name: 'undefined5', extension: 'txt' },
7 | ];
8 |
9 | export default FileSampleValue;
10 |
--------------------------------------------------------------------------------
/backend/src/sample/value/ReactionSampleValue.ts:
--------------------------------------------------------------------------------
1 | const ReactionSampleValue = [
2 | { threadId: 1, userHasWorkspaceId: 1, emoji: '💖' },
3 | { threadId: 1, userHasWorkspaceId: 40, emoji: '💖' },
4 | { threadId: 10, userHasWorkspaceId: 1, emoji: '🥳' },
5 | { threadId: 10, userHasWorkspaceId: 40, emoji: '🥳' },
6 | ];
7 |
8 | export default ReactionSampleValue;
9 |
--------------------------------------------------------------------------------
/backend/src/sample/value/ReplySampleValue.ts:
--------------------------------------------------------------------------------
1 | const ReplySampleValue = [
2 | { message: '화이팅!!', threadId: 1, userHasWorkspaceId: 1 },
3 | { message: '화이팅!!!', threadId: 1, userHasWorkspaceId: 1 },
4 | { message: '화이팅!!!!', threadId: 1, userHasWorkspaceId: 1 },
5 | { message: '화이팅!!!!!', threadId: 1, userHasWorkspaceId: 1 },
6 | { message: '화이팅!!!!!!', threadId: 1, userHasWorkspaceId: 1 },
7 | { message: 'booslack 화이팅!!', threadId: 1, userHasWorkspaceId: 1 },
8 | ];
9 |
10 | export default ReplySampleValue;
11 |
--------------------------------------------------------------------------------
/backend/src/sample/value/WorkspaceSampleValue.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const WorkspaceSampleValue = [
3 | { name: 'random' },
4 | { name: 'Web' },
5 | { name: '안드로이드' },
6 | { name: 'ios' },
7 | { name: 'soccer' },
8 | { name: 'basketball' },
9 | { name: 'piano' },
10 | { name: '헬스' },
11 | { name: '복싱' },
12 | { name: '부동산' },
13 | { name: '부스트캠프' },
14 | { name: '부커톤' },
15 | { name: 'saymyname' },
16 | { name: 'hobby' },
17 | { name: '위로받기' },
18 | { name: 'justtalk' },
19 | { name: 'feelMess' },
20 | { name: '수학문제풀이' },
21 | { name: '알고리즘' },
22 | { name: '개발자취업' },
23 | { name: 'CS스터디' },
24 | { name: '인프라' },
25 | { name: '축구교실' },
26 | { name: '농구교실' },
27 | { name: '야구교실' },
28 | { name: '스우파' },
29 | { name: '고민을털어봐!' },
30 | { name: '잡담' },
31 | { name: '주식' },
32 | { name: '비트코인' },
33 | { name: 'MBTI' },
34 | { name: '노래공유' },
35 | { name: '보컬' },
36 | { name: '기타' },
37 | { name: '휘문고모여라!' },
38 | { name: '군대' },
39 | { name: '화장품' },
40 | { name: '그림' },
41 | { name: 'Web' },
42 | ];
43 | export default WorkspaceSampleValue;
44 |
--------------------------------------------------------------------------------
/backend/src/shared/Logger.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Setup the jet-logger.
3 | *
4 | * Documentation: https://github.com/seanpmaxwell/jet-logger
5 | */
6 |
7 | import Logger from 'jet-logger';
8 |
9 | const logger = new Logger();
10 |
11 | export default logger;
12 |
--------------------------------------------------------------------------------
/backend/src/shared/constants.ts:
--------------------------------------------------------------------------------
1 | // Put shared constants here
2 |
3 | const paramMissingError = 'One or more of the required parameters was missing.';
4 |
5 | export default paramMissingError;
6 |
--------------------------------------------------------------------------------
/backend/src/shared/functions.ts:
--------------------------------------------------------------------------------
1 | import logger from './Logger';
2 |
3 | export const pErr = (err: Error) => {
4 | if (err) {
5 | logger.err(err);
6 | }
7 | };
8 |
9 | export const getRandomInt = () => Math.floor(Math.random() * 1_000_000_000_000);
10 |
--------------------------------------------------------------------------------
/backend/src/shared/simpleuuid.ts:
--------------------------------------------------------------------------------
1 | const allString = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
2 |
3 | /* eslint-disable arrow-body-style */
4 | const generateUniqSerial = (): string => {
5 | return 'xxxxxx'.replace(/[x]/g, () => {
6 | const number = allString[Math.floor(Math.random() * (allString.length - 1))];
7 | return number.toUpperCase();
8 | });
9 | };
10 |
11 | export default generateUniqSerial;
12 |
--------------------------------------------------------------------------------
/backend/src/socket.ts:
--------------------------------------------------------------------------------
1 | import { Server } from 'socket.io';
2 | import * as http from 'http';
3 |
4 | const initializeSocket = (httpServer: http.Server) => {
5 | const io = new Server(httpServer, {
6 | cors: {
7 | origin: [
8 | 'http://localhost:3001',
9 | 'http://118.67.142.116:3001/',
10 | 'http://118.67.142.116:8081/',
11 | ],
12 | },
13 | });
14 |
15 | io.of(/^\/workspace:\d+$/).on('connection', (socket) => {
16 | const namespace = socket.nsp;
17 |
18 | socket.on('threads', (channelId, threadId) => {
19 | namespace.emit('threads', channelId, threadId);
20 | });
21 |
22 | socket.on('channels', (workspaceId) => {
23 | namespace.emit('channels', workspaceId);
24 | });
25 |
26 | socket.on('channel', (channelId) => {
27 | namespace.emit('channel', channelId);
28 | });
29 | });
30 | };
31 |
32 | export default initializeSocket;
33 |
--------------------------------------------------------------------------------
/backend/src/util/getNowDate.ts:
--------------------------------------------------------------------------------
1 | export const getNowDate = () => {
2 | const today: Date = new Date();
3 | return `${today.getFullYear()}-${today.getMonth()}-${today.getDay()}`;
4 | };
5 |
6 | export const getNowDateAndTime = () => {
7 | const today: Date = new Date();
8 | const Dates = `${today.getFullYear()}-${today.getMonth()}-${today.getDay()}`;
9 | const hour = (`0${today.getHours()}`).slice(-2);
10 | const minute = (`0${today.getMinutes()}`).slice(-2);
11 | const second = (`0${today.getSeconds()}`).slice(-2);
12 | return `${Dates} ${hour}-${minute}-${second}`;
13 | };
14 |
--------------------------------------------------------------------------------
/backend/src/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ExpressGeneratorTypeScriptApp
6 |
7 |
8 |
9 | This page does not exist.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/backend/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "sourceMap": false
5 | },
6 | "exclude": ["spec", "src/**/*.mock.ts", "src/public/"]
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/.eslintignore:
--------------------------------------------------------------------------------
1 | *.config.*
--------------------------------------------------------------------------------
/frontend/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "jest/globals": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:react/recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "airbnb",
12 | "airbnb-typescript",
13 | "prettier/prettier",
14 | "eslint:recommended",
15 | "plugin:jest/recommended"
16 | ],
17 | "parser": "@typescript-eslint/parser",
18 | "parserOptions": {
19 | "ecmaFeatures": {
20 | "jsx": true
21 | },
22 | "ecmaVersion": 12,
23 | "sourceType": "module",
24 | "project": "./tsconfig.json"
25 | },
26 | "plugins": ["react", "@typescript-eslint", "prettier", "jest"],
27 | "rules": {
28 | "prettier/prettier": [
29 | "error",
30 | {
31 | "endOfLine": "auto"
32 | }
33 | ],
34 | "object-curly-newline": "off",
35 | "react/jsx-filename-extension": "off",
36 | "no-use-before-define": "off",
37 | "@typescript-eslint/no-use-before-define": ["error"],
38 | "import/extensions": "off",
39 | "import/prefer-default-export": "off"
40 | },
41 | "globals": {
42 | "JSX": true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "tabWidth": 2,
5 | "trailingComma": "all",
6 | "useTabs": false,
7 | "printWidth": 80,
8 | "arrowParens": "always",
9 | "bracketSpacing": true,
10 | "endOfLine": "auto"
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/jest.config.js:
--------------------------------------------------------------------------------
1 | const tsconfig = require('./tsconfig.json');
2 | const moduleNameMapper = require('tsconfig-paths-jest')(tsconfig);
3 |
4 | console.log(moduleNameMapper);
5 |
6 | module.exports = {
7 | transform: {
8 | '^.+\\.tsx?$': 'ts-jest',
9 | '^.+\\.js$': 'babel-jest',
10 | '.+\\.(css|styl|less|sass|scss|png|jpg|gif|ttf|woff|woff2)$':
11 | 'jest-transform-stub',
12 | },
13 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
15 | moduleNameMapper,
16 | testEnvironment: 'jsdom',
17 | globals: {
18 | 'ts-jest': {
19 | diagnostics: false,
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
21 |
22 |
26 | booslack
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/DivLists/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from './styles';
3 |
4 | interface Props {
5 | text: string;
6 | onClick: () => void;
7 | className?: string;
8 | }
9 |
10 | const DivLists = ({ text, onClick, className }: Props): JSX.Element => {
11 | return (
12 |
13 | {text}
14 |
15 | );
16 | };
17 |
18 | DivLists.defaultProps = {
19 | className: '',
20 | };
21 |
22 | export default DivLists;
23 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/DivLists/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Container = styled.div`
4 | width: inherit;
5 | text-overflow: ellipsis;
6 | text-align: center;
7 | overflow: hidden;
8 | border-radius: 4px;
9 | margin: 5px 0 10px 0;
10 | padding: 3px;
11 |
12 | &:hover {
13 | background-color: #1164a3;
14 | cursor: pointer;
15 | color: #fff;
16 | }
17 | `;
18 |
19 | export default Container;
20 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ErrorBoundary/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface Props {
4 | fallback: JSX.Element;
5 | }
6 |
7 | class ErrorBoundary extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = { hasError: false };
11 | }
12 |
13 | static getDerivedStateFromError() {
14 | return { hasError: true };
15 | }
16 |
17 | componentDidCatch(error, errorInfo) {
18 | // console.log(error, errorInfo);
19 | }
20 |
21 | render() {
22 | const { hasError } = this.state;
23 | const { children } = this.props;
24 |
25 | if (hasError) {
26 | return this.props.fallback;
27 | }
28 | return children;
29 | }
30 | }
31 |
32 | export default ErrorBoundary;
33 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/IconButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject } from 'react';
2 | import { IconType } from 'react-icons';
3 | import Container from './styles';
4 |
5 | interface Props {
6 | onClick: () => void;
7 | icon: IconType;
8 | className?: T;
9 | customRef?: undefined | RefObject;
10 | fontSize?: number;
11 | width?: number;
12 | height?: number;
13 | color?: string;
14 | }
15 |
16 | const IconButton = ({
17 | onClick,
18 | icon,
19 | className,
20 | customRef,
21 | fontSize,
22 | width,
23 | height,
24 | color,
25 | children,
26 | }: Props): JSX.Element => {
27 | const Icon = icon;
28 | return (
29 |
36 |
37 | {children}
38 |
39 | );
40 | };
41 |
42 | IconButton.defaultProps = {
43 | className: {},
44 | customRef: undefined,
45 | fontSize: 18,
46 | width: 30,
47 | height: 30,
48 | };
49 |
50 | export default IconButton;
51 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/IconButton/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Container = styled.button`
4 | background: transparent;
5 | border: 0px solid;
6 | `;
7 |
8 | export default Container;
9 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ImageBox/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from './styles';
3 |
4 | interface Props {
5 | image: string;
6 | className?: T;
7 | }
8 |
9 | const ImageBox = ({
10 | image,
11 | className,
12 | }: Props): JSX.Element => {
13 | return ;
14 | };
15 |
16 | ImageBox.defaultProps = {
17 | className: {},
18 | };
19 |
20 | export default ImageBox;
21 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ImageBox/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Container = styled.img`
4 | width: inherit;
5 | height: inherit;
6 | `;
7 |
8 | export default Container;
9 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ImageButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject } from 'react';
2 | import { SetterOrUpdater } from 'recoil';
3 | import Container from './styles';
4 |
5 | interface Props {
6 | onClick: (e: React.MouseEvent) => void | SetterOrUpdater;
7 | width?: number;
8 | height?: number;
9 | image: string;
10 | className?: string;
11 | customRef?: undefined | RefObject;
12 | }
13 |
14 | const ImageButton = ({
15 | onClick,
16 | width,
17 | height,
18 | image,
19 | className,
20 | customRef,
21 | }: Props): JSX.Element => {
22 | return (
23 |
31 | );
32 | };
33 |
34 | ImageButton.defaultProps = {
35 | className: '',
36 | width: 30,
37 | height: 30,
38 | customRef: undefined,
39 | };
40 |
41 | export default ImageButton;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ImageButton/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | width: number;
5 | height: number;
6 | image: string;
7 | }
8 |
9 | const Container = styled.button`
10 | width: ${({ width }) => width}px;
11 | height: ${({ height }) => height}px;
12 | border: 0;
13 | background-color: inherit;
14 | background-image: url(${({ image }) => image});
15 | background-refeat: no-repeat;
16 | background-size: cover;
17 | `;
18 |
19 | export default Container;
20 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Input/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from './styles';
3 |
4 | interface Props {
5 | onChange: (e) => void;
6 | name: string;
7 | value: string;
8 | onClick?: () => void;
9 | width?: number;
10 | height?: number;
11 | type?: string;
12 | placeholder: string;
13 | className?: T;
14 | onInput?: (e) => void;
15 | }
16 |
17 | const Input = ({
18 | onChange,
19 | name,
20 | value,
21 | onClick,
22 | type,
23 | checked,
24 | width,
25 | height,
26 | placeholder,
27 | className,
28 | onInput,
29 | }: Props): JSX.Element => {
30 | return (
31 |
43 | );
44 | };
45 |
46 | export default Input;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Input/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | width: number;
5 | height: number;
6 | }
7 |
8 | const Container = styled.input`
9 | width: ${({ width }) => width}px;
10 | height: ${({ height }) => height}px;
11 | padding: 8px 12px;
12 | `;
13 |
14 | export default Container;
15 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Label/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from './styles';
3 |
4 | interface Props {
5 | text: string;
6 | width?: number;
7 | height?: number;
8 | color?: string;
9 | backgroundColor?: string;
10 | className?: string;
11 | }
12 |
13 | const Label = ({
14 | text,
15 | width,
16 | height,
17 | color,
18 | backgroundColor,
19 | className,
20 | }: Props): JSX.Element => {
21 | return (
22 |
29 | {text}
30 |
31 | );
32 | };
33 |
34 | Label.defaultProps = {
35 | className: '',
36 | };
37 |
38 | export default Label;
39 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Label/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | width: number;
5 | height: number;
6 | color: string;
7 | backgroundColor: string;
8 | }
9 |
10 | const Container = styled.span`
11 | width: ${({ width }) => width}px;
12 | height: ${({ height }) => height}px;
13 | color: ${({ color }) => color};
14 | background-color: ${({ backgroundColor }) => backgroundColor};
15 | `;
16 |
17 | export default Container;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/LabeledButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject } from 'react';
2 | import Container from './styles';
3 |
4 | interface Props {
5 | onClick: (e) => void | React.FormEventHandler;
6 | text: string;
7 | width?: number;
8 | height?: number;
9 | color?: string;
10 | type?: string | undefined;
11 | backgroundColor?: string;
12 | className?: T;
13 | customRef?: undefined | RefObject;
14 | disabled?: boolean;
15 | }
16 |
17 | const LabeledButton = ({
18 | onClick,
19 | text,
20 | width,
21 | height,
22 | color,
23 | type,
24 | backgroundColor = 'transparent',
25 | className,
26 | customRef,
27 | disabled,
28 | }: Props): JSX.Element => {
29 | return (
30 |
41 | {text}
42 |
43 | );
44 | };
45 |
46 | LabeledButton.defaultProps = {
47 | className: {},
48 | width: 30,
49 | height: 30,
50 | color: undefined,
51 | backgroundColor: undefined,
52 | customRef: undefined,
53 | disabled: false,
54 | type: 'button',
55 | };
56 |
57 | export default LabeledButton;
58 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/LabeledButton/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | color: string;
5 | backgroundColor: string;
6 | disabled?: unknown;
7 | }
8 |
9 | const Container = styled.button`
10 | color: ${({ color }) => color};
11 | background-color: ${({ backgroundColor }) => backgroundColor};
12 | border: 0px;
13 | border-radius: 4px;
14 | cursor: pointer;
15 | `;
16 |
17 | export default Container;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/LabeledDefaultButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject } from 'react';
2 | import LabeledButton from '@atoms/LabeledButton';
3 | import { BUTTON_SIZE } from '@enum/index';
4 |
5 | interface Props {
6 | onClick?: (e) => void;
7 | width?: number;
8 | height?: number;
9 | text: string;
10 | color?: string;
11 | backgroundColor?: string;
12 | className?: T;
13 | customRef?: RefObject;
14 | disabled?: boolean;
15 | }
16 |
17 | const {
18 | width: ButtonWidth,
19 | height: ButtonHeight,
20 | color: ButtonColor,
21 | backgroundColor: ButtonBackground,
22 | } = BUTTON_SIZE;
23 |
24 | const LabeledDefaultButton = ({
25 | onClick,
26 | width,
27 | height,
28 | text,
29 | color,
30 | backgroundColor,
31 | className,
32 | customRef,
33 | disabled,
34 | }: Props): JSX.Element => {
35 | return (
36 |
47 | );
48 | };
49 |
50 | LabeledDefaultButton.defaultProps = {
51 | onClick: (e) => {},
52 | width: ButtonWidth as number,
53 | height: ButtonHeight as number,
54 | color: ButtonColor,
55 | backgroundColor: ButtonBackground,
56 | className: {},
57 | customRef: undefined,
58 | disabled: false,
59 | };
60 |
61 | export default LabeledDefaultButton;
62 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Modal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { createPortal } from 'react-dom';
3 | import { Container, Content, Overlay } from './styles';
4 |
5 | const root = document.getElementById('portal');
6 |
7 | interface Props {
8 | isOpen: boolean;
9 | onClose: () => void;
10 | zIndex: number;
11 | children: ReactNode;
12 | className?: string;
13 | }
14 |
15 | const Modal = ({
16 | isOpen,
17 | onClose,
18 | zIndex = 100,
19 | children,
20 | className,
21 | }: Props): JSX.Element => {
22 | return createPortal(
23 |
24 |
25 |
26 | {children}
27 |
28 | ,
29 | root,
30 | );
31 | };
32 |
33 | export default Modal;
34 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Modal/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div<{ visible: boolean }>`
4 | display: ${({ visible }) => (visible ? 'block' : 'none')};
5 | `;
6 |
7 | export const Content = styled.div<{ zIndex: number }>`
8 | position: fixed;
9 | top: 50%;
10 | left: 50%;
11 | transform: translate(-50%, -50%);
12 | width: 100%;
13 | z-index: ${({ zIndex }) => zIndex};
14 | `;
15 |
16 | export const Overlay = styled.div<{ zIndex: number }>`
17 | position: fixed;
18 | top: 0;
19 | left: 0;
20 | bottom: 0;
21 | right: 0;
22 | background-color: rgba(0, 0, 0, 0.6);
23 | z-index: ${({ zIndex }) => zIndex - 1};
24 | `;
25 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Popup/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { createPortal } from 'react-dom';
3 | import { Container, Content, Overlay } from './styles';
4 |
5 | const root = document.getElementById('portal');
6 |
7 | interface Props {
8 | row?: number;
9 | isOpen: boolean;
10 | onClose: () => void;
11 | zIndex: number;
12 | children: ReactNode;
13 | className?: string;
14 | }
15 |
16 | const Popup = ({
17 | isOpen,
18 | onClose,
19 | zIndex = 110,
20 | children,
21 | className,
22 | }: Props): JSX.Element => {
23 | return createPortal(
24 |
25 |
26 |
27 | {children}
28 |
29 | ,
30 | root,
31 | );
32 | };
33 |
34 | export default Popup;
35 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Popup/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div<{ visible: boolean }>`
4 | display: ${({ visible }) => (visible ? 'block' : 'none')};
5 | `;
6 |
7 | export const Content = styled.div<{ zIndex: number }>`
8 | position: absolute;
9 | z-index: ${({ zIndex }) => zIndex};
10 | `;
11 |
12 | export const Overlay = styled.div<{ zIndex: number }>`
13 | position: fixed;
14 | top: 0;
15 | left: 0;
16 | bottom: 0;
17 | right: 0;
18 | z-index: ${({ zIndex }) => zIndex - 1};
19 | `;
20 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/RadioButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent } from 'react';
2 |
3 | interface Props {
4 | name: string;
5 | value: number;
6 | isChecked?: number;
7 | onChange?: (e: ChangeEvent) => void;
8 | className?: string;
9 | }
10 |
11 | const RadioButton = ({
12 | name,
13 | isChecked,
14 | onChange,
15 | value,
16 | className,
17 | }: Props): JSX.Element => {
18 | return (
19 | <>
20 |
28 | {name}
29 | >
30 | );
31 | };
32 |
33 | RadioButton.defaultProps = {
34 | className: '',
35 | isChecked: false,
36 | onChange: () => {},
37 | };
38 |
39 | export default RadioButton;
40 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Spinner/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DotLoader, PacmanLoader } from 'react-spinners';
3 | import { Center, ErrorContainer, PacmanContainer } from './style';
4 |
5 | export const Spinner = ({
6 | size,
7 | color,
8 | }: {
9 | size: number;
10 | color: string;
11 | }): JSX.Element => {
12 | return (
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export const ErrorSpinner = ({
20 | size,
21 | color,
22 | }: {
23 | size: number;
24 | color: string;
25 | }): JSX.Element => {
26 | return (
27 |
28 |
29 |
30 |
31 |
32 | 오류가 발생했습니다. 다시 시도해주세요.
33 |
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/Spinner/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Center = styled.div`
4 | display: flex;
5 | justify-content: center;
6 | width: 100%;
7 | margin: 12px;
8 | padding: 12px;
9 | `;
10 |
11 | export const PacmanContainer = styled.div<{ size: number }>`
12 | display: flex;
13 | justify-content: center;
14 | width: 100%;
15 | height: ${({ size }) => `${size * 2}px`};
16 | margin: 12px;
17 | padding: 12px;
18 | `;
19 |
20 | export const ErrorContainer = styled.div`
21 | display: flex;
22 | justify-content: center;
23 | width: 100%;
24 | margin: 12px;
25 | padding: 12px;
26 | border: none;
27 | `;
28 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ToggleButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container, ToggleOff, ToggleOn } from './styles';
3 |
4 | interface Props {
5 | isOn: boolean;
6 | setIsOn: React.Dispatch>;
7 | }
8 |
9 | const ToggleButton = ({ isOn, setIsOn, className }: Props): JSX.Element => {
10 | return (
11 | setIsOn((prevState) => !prevState)}>
12 | {isOn ? (
13 |
14 | ) : (
15 |
16 | )}
17 |
18 | );
19 | };
20 |
21 | export default ToggleButton;
22 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ToggleButton/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { BsToggleOn, BsToggleOff } from 'react-icons/bs';
3 |
4 | export const Container = styled.button`
5 | background: transparent;
6 | border: 0px solid;
7 | `;
8 |
9 | export const ToggleOn = styled(BsToggleOn)`
10 | color: #34785c;
11 | `;
12 |
13 | export const ToggleOff = styled(BsToggleOff)`
14 | color: #8e8d8e;
15 | `;
16 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ViewPortInput/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import React from 'react';
3 | import Container, { Form } from './styles';
4 |
5 | interface Props {
6 | inputName?: string;
7 | onSubmit?: React.FormEventHandler;
8 | onChange?: React.FormEventHandler;
9 | placeholder: string;
10 | type?: string;
11 | customRef?: React.RefObject;
12 | className?: string;
13 | }
14 |
15 | const ViewportInput = ({
16 | inputName,
17 | onSubmit,
18 | onChange,
19 | type,
20 | placeholder,
21 | customRef,
22 | className,
23 | }: Props): JSX.Element => {
24 | return (
25 |
36 | );
37 | };
38 |
39 | ViewportInput.defaultProps = {
40 | inputName: '',
41 | onSubmit: () => {},
42 | onChange: () => {},
43 | type: 'text',
44 | customRef: undefined,
45 | className: '',
46 | };
47 |
48 | export default ViewportInput;
49 |
--------------------------------------------------------------------------------
/frontend/src/components/atoms/ViewPortInput/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Container = styled.input`
4 | width: 100%;
5 | height: inherit;
6 | `;
7 |
8 | export const Form = styled.form`
9 | width: 100%;
10 | `;
11 |
12 | export default Container;
13 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/AsyncBranch/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, useContext } from 'react';
2 | import { ThemeContext } from 'styled-components';
3 | import ErrorBoundary from '@atoms/ErrorBoundary';
4 | import { ErrorSpinner, Spinner } from '@atoms/Spinner';
5 |
6 | interface Props {
7 | size: number;
8 | children: JSX.Element | JSX.Element[];
9 | }
10 |
11 | const AsyncBranch = ({ size, children }: Props): JSX.Element => {
12 | const themeContext = useContext(ThemeContext);
13 | const color = themeContext.bigHeaderColor;
14 |
15 | return (
16 | }>
17 | }>
18 | {children}
19 |
20 |
21 | );
22 | };
23 |
24 | AsyncBranch.defaultProps = {};
25 |
26 | export default AsyncBranch;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/AsyncBranch/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { theme } from 'styled-tools';
3 | import { PacmanLoader } from 'react-spinners';
4 | import { flexAlignCenter } from '@global/style/mixin';
5 | import { defaultTheme } from '@global/style/theme';
6 |
7 | const Container = styled.div`
8 | width: inherit;
9 | height: 600px;
10 | background: transparent;
11 | border: 0px solid;
12 | ${flexAlignCenter}
13 | overflow : hidden;
14 | `;
15 |
16 | export const SpinnerContainer = styled.div`
17 | width: 55vw;
18 | min-width: 300px;
19 | height: 60px;
20 | `;
21 |
22 | export const MarginDiv = styled.div`
23 | margin-top: 50px;
24 | `;
25 |
26 | export const LoadingSpinner = styled(PacmanLoader)`
27 | margin-top: 50px;
28 | background-color: ${theme('backgroundColor', defaultTheme.backgroundColor)};
29 | `;
30 |
31 | export default Container;
32 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/BrowseChannelHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container } from './styles';
3 |
4 | interface Props {
5 | width?: number;
6 | title: JSX.Element;
7 | content?: null | JSX.Element;
8 | rightButton: JSX.Element;
9 | className?: T;
10 | }
11 |
12 | const BrowseChannelHeader = ({
13 | width = null,
14 | title,
15 | content,
16 | rightButton,
17 | className,
18 | }: Props): JSX.Element => {
19 | return (
20 |
21 | {title}
22 | {content}
23 | {rightButton}
24 |
25 | );
26 | };
27 |
28 | BrowseChannelHeader.defaultProps = {
29 | width: null,
30 | content: <>>,
31 | className: {},
32 | };
33 |
34 | export default BrowseChannelHeader;
35 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/BrowseChannelHeader/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | width?: number;
5 | }
6 |
7 | export const Container = styled.div`
8 | display: flex;
9 | min-height: 48.99px;
10 | width: ${({ width }) => {
11 | if (width) return `${width}vw`;
12 | return 'inherit';
13 | }};
14 | justify-content: space-between;
15 | align-items: center;
16 | }
17 | background-color: #fff;
18 | & > * {
19 | margin: 0 1vw 0 1vw;
20 |
21 | `;
22 |
23 | export default Container;
24 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/ChatHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSetRecoilState } from 'recoil';
3 | import { useParams } from 'react-router-dom';
4 | import { MdPeople } from 'react-icons/md';
5 | import { channelInfoModalState } from '@state/modal';
6 | import { useChannelQuery } from '@hook/useChannels';
7 | import {
8 | Container,
9 | StyledLabeledButton,
10 | StyledIconButton,
11 | HeaderContainer,
12 | StyledLabel,
13 | } from './styles';
14 |
15 | const ChatHeader = (): JSX.Element => {
16 | const { channelId }: { channelId: string } = useParams();
17 |
18 | const setIsOpen = useSetRecoilState(channelInfoModalState);
19 | const { isLoading, isError, data } = useChannelQuery(channelId);
20 |
21 | if (isLoading) return Loading
;
22 | if (isError) return Error
;
23 |
24 | return (
25 |
26 |
27 | setIsOpen({ isOpen: true, isAboutTab: true })}
30 | />
31 | {data.topic && }
32 |
33 | setIsOpen({ isOpen: true, isAboutTab: false })}
36 | />
37 |
38 | );
39 | };
40 |
41 | export default ChatHeader;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/ChatHeader/styles.ts:
--------------------------------------------------------------------------------
1 | import IconButton from '@atoms/IconButton';
2 | import Label from '@atoms/Label';
3 | import LabeledButton from '@atoms/LabeledButton';
4 | import styled from 'styled-components';
5 |
6 | export const Container = styled.div`
7 | display: flex;
8 | justify-content: space-between;
9 | min-height: 48.99px;
10 | align-items: center;
11 | }
12 | background-color: #fff;
13 |
14 | & > * {
15 | margin: 0 1vw 0 1vw;
16 | }
17 |
18 | --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
19 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.12);
20 | `;
21 |
22 | export const HeaderContainer = styled.div`
23 | display: flex;
24 | align-items: baseline;
25 | `;
26 |
27 | export const StyledLabel = styled(Label)`
28 | margin-left: 8px;
29 | font-size: 13px;
30 | color: #1d1c1db3;
31 | `;
32 |
33 | export const StyledLabeledButton = styled(LabeledButton)`
34 | font-size: 18px;
35 | font-weight: bold;
36 | &: hover {
37 | cursor: pointer;
38 | background-color: #f6f6f6;
39 | }
40 | `;
41 |
42 | export const StyledIconButton = styled(IconButton)`
43 | &: hover {
44 | cursor: pointer;
45 | background-color: #f6f6f6;
46 | }
47 | `;
48 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/CodeModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useRecoilState } from 'recoil';
3 | import { codeModalState } from '@state/modal';
4 | import { StyledModal, Container, ModalButton, ModalMessage } from './style';
5 |
6 | interface Props {
7 | Content: string;
8 | }
9 |
10 | const CodeModal = ({ Content }: Props): JSX.Element => {
11 | const [{ status, text }, setObject] = useRecoilState(codeModalState);
12 | return (
13 | setObject({ status: false, text: undefined })}
16 | >
17 |
18 | {text || Content}
19 | setObject({ status: false, text: undefined })}
23 | />
24 |
25 |
26 | );
27 | };
28 |
29 | export default CodeModal;
30 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/CodeModal/style.ts:
--------------------------------------------------------------------------------
1 | import Modal from '@atoms/Modal';
2 | import styled from 'styled-components';
3 | import LabeledButton from '@atoms/LabeledButton';
4 |
5 | export const StyledModal = styled(Modal)`
6 | max-width: 580px;
7 | width: 380px;
8 | height: 250px;
9 | background-color: white;
10 | border-radius: 15px;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | `;
15 |
16 | export const Container = styled.div`
17 | display: flex;
18 | justify-content: space-between;
19 | flex-direction: column;
20 | align-items: center;
21 | height: 170px;
22 | padding: 5px;
23 | `;
24 |
25 | export const ModalButton = styled(LabeledButton)`
26 | background-color: #c8c7ef;
27 | width: 240px;
28 | height: 60px;
29 | `;
30 |
31 | export const ModalMessage = styled.span`
32 | font-size: 26px;
33 | box-sizing: content-box;
34 | font-weight: 500;
35 | width: 330px;
36 | `;
37 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/EmojiPopup/EmojiPopupTemplate/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import useKeyboardNavigator from '@hook/useKeyboardNavigator';
3 | import {
4 | Container,
5 | PrimaryContent,
6 | SecondaryContent,
7 | StyledBoldLabel,
8 | UserContainer,
9 | UserElement,
10 | } from './styles';
11 |
12 | interface Props {
13 | matches: [];
14 | setValue: React.Dispatch;
15 | }
16 |
17 | const EmojiPopupTemplate = ({ matches, setValue }: Props): JSX.Element => {
18 | const index = useKeyboardNavigator(matches, setValue);
19 |
20 | return (
21 |
22 | {matches.map((emoji, idx) => (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ))}
34 |
35 | );
36 | };
37 |
38 | export default EmojiPopupTemplate;
39 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/EmojiPopup/EmojiPopupTemplate/styles.ts:
--------------------------------------------------------------------------------
1 | import Label from '@atoms/Label';
2 | import styled from 'styled-components';
3 | import { RoundScrollBar } from '@global/style/mixin';
4 | import { ifProp } from 'styled-tools';
5 |
6 | export const Container = styled.div`
7 | ${RoundScrollBar};
8 | overflow-y: scroll;
9 | overflow-x: hidden;
10 | width: 100%;
11 | max-height: 300px;
12 | `;
13 |
14 | interface Props {
15 | selected: boolean;
16 | }
17 |
18 | export const UserContainer = styled.div`
19 | padding: 0 1rem;
20 | background-color: ${ifProp({ selected: true }, '#2C639E', 'transparent')};
21 | `;
22 |
23 | export const UserElement = styled.div`
24 | height: 32px;
25 | display: flex;
26 | justify-content: space-between;
27 | align-items: center;
28 | `;
29 |
30 | export const PrimaryContent = styled.div``;
31 |
32 | export const SecondaryContent = styled.div``;
33 |
34 | export const StyledBoldLabel = styled(Label)`
35 | font-weight: bold;
36 | `;
37 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/EmojiPopup/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch, RefObject, useEffect } from 'react';
2 | import * as unicodeEmoji from 'unicode-emoji';
3 | import EmojiPopupTemplate from './EmojiPopupTemplate';
4 | import { StyledPopup } from './styles';
5 |
6 | interface Props {
7 | input: string;
8 | isOpen: boolean;
9 | value: any;
10 | setValue: Dispatch;
11 | close: () => void;
12 | customRef: RefObject;
13 | xWidth: number;
14 | yHeight: number;
15 | }
16 |
17 | const emojis = unicodeEmoji.getEmojis();
18 |
19 | const EmojiPopup = ({
20 | input,
21 | isOpen,
22 | value,
23 | setValue,
24 | close,
25 | customRef,
26 | xWidth,
27 | yHeight,
28 | }: Props): JSX.Element => {
29 | useEffect(() => {
30 | if (value) {
31 | close();
32 | }
33 | }, [value]);
34 |
35 | return (
36 |
43 | emoji.description.includes(input))}
45 | setValue={setValue}
46 | />
47 |
48 | );
49 | };
50 |
51 | export default EmojiPopup;
52 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/EmojiPopup/styles.ts:
--------------------------------------------------------------------------------
1 | import NoOverlayModal from '@molecules/NoOverlayModal';
2 | import styled from 'styled-components';
3 |
4 | export const StyledPopup = styled(NoOverlayModal)`
5 | --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
6 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.08);
7 | line-height: 1rem;
8 | position: fixed;
9 | background-color: white;
10 |
11 | width: 300px;
12 | height: 300px;
13 | `;
14 |
15 | export default StyledPopup;
16 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/LabeledInput/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Input from '@atoms/Input';
3 | import Container, { StyledLabel } from './styles';
4 |
5 | interface Props {
6 | onChange: (e) => void;
7 | name: string;
8 | value: string;
9 | label: string;
10 | placeholder?: string;
11 | }
12 |
13 | const LabeledInput = ({
14 | onChange,
15 | name,
16 | value,
17 | label,
18 | placeholder,
19 | className,
20 | }: Props): JSX.Element => {
21 | return (
22 |
23 |
24 |
30 |
31 | );
32 | };
33 |
34 | export default LabeledInput;
35 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/LabeledInput/styles.ts:
--------------------------------------------------------------------------------
1 | import Label from '@atoms/Label';
2 | import styled from 'styled-components';
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | `;
8 |
9 | export const StyledLabel = styled(Label)`
10 | font-weight: bold;
11 | `;
12 |
13 | export default Container;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/MentionPopup/MentionPopupTemplate/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Label from '@atoms/Label';
3 | import useKeyboardNavigator from '@hook/useKeyboardNavigator';
4 | import defaultImage from '@global/image/default_account.png';
5 | import {
6 | Container,
7 | PrimaryContent,
8 | SecondaryContent,
9 | StyledImageBox,
10 | UserContainer,
11 | UserElement,
12 | } from './styles';
13 |
14 | interface Props {
15 | matches: [];
16 | setValue: React.Dispatch;
17 | }
18 |
19 | const MentionPopupTemplate = ({ matches, setValue }: Props): JSX.Element => {
20 | const index = useKeyboardNavigator(matches, setValue);
21 | return (
22 |
23 | {matches.map((user, idx) => (
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 | {user.inChannel === '0' && }
38 |
39 |
40 |
41 | ))}
42 |
43 | );
44 | };
45 |
46 | export default MentionPopupTemplate;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/MentionPopup/MentionPopupTemplate/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { ifProp } from 'styled-tools';
3 | import ImageBox from '@atoms/ImageBox';
4 |
5 | export const Container = styled.div`
6 | width: 100%;
7 | `;
8 |
9 | interface Props {
10 | selected: boolean;
11 | }
12 |
13 | export const UserContainer = styled.div`
14 | padding: 0 1rem;
15 | background-color: ${ifProp({ selected: true }, '#2C639E', 'transparent')};
16 | `;
17 |
18 | export const UserElement = styled.div`
19 | height: 32px;
20 | display: flex;
21 | justify-content: space-between;
22 | align-items: center;
23 | `;
24 |
25 | export const PrimaryContent = styled.div`
26 | padding: 3px;
27 | display: flex;
28 | justify-content: start;
29 | flex-direction: row;
30 | align-items: center;
31 | `;
32 |
33 | export const SecondaryContent = styled.div``;
34 |
35 | export const StyledImageBox = styled(ImageBox)`
36 | width: 30px;
37 | height: 30px;
38 | margin-right: 5px;
39 | border-radius: 5px;
40 | `;
41 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/MentionPopup/styles.ts:
--------------------------------------------------------------------------------
1 | import NoOverlayModal from '@molecules/NoOverlayModal';
2 | import styled from 'styled-components';
3 |
4 | export const StyledPopup = styled(NoOverlayModal)`
5 | --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
6 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.08);
7 | line-height: 1rem;
8 | position: fixed;
9 | background-color: white;
10 |
11 | width: 300px;
12 | height: 300px;
13 | `;
14 |
15 | export default StyledPopup;
16 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/MessageContent/MessageFileStatusBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | ThreadFileStatusBarLayout,
4 | ThreadFileStatusBarContainer,
5 | } from './styles';
6 | import MessageFileStatusElement from '../MessageFileStatusElement';
7 |
8 | interface Props {
9 | files: File[];
10 | }
11 |
12 | const MessageFileStatusBar = ({ files }: Props): JSX.Element => {
13 | return (
14 | <>
15 |
16 |
17 | {files.map((file, index) => (
18 |
19 | ))}
20 |
21 |
22 | >
23 | );
24 | };
25 |
26 | export default MessageFileStatusBar;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/MessageContent/MessageFileStatusBar/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ThreadFileStatusBarContainer = styled.div`
4 | max-width: 100%;
5 | width: 100%;
6 | display: grid;
7 | grid-template-columns: 1fr 1fr;
8 | grid-auto-rows: 1fr;
9 | gap: 10px 20px;
10 | `;
11 |
12 | export const ThreadFileStatusBarLayout = styled.div`
13 | max-width: 100%;
14 | width: 730px;
15 | margin-bottom: 10px;
16 | `;
17 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/MessageContent/MessageFileStatusElement/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 | import { MdTextSnippet, MdInsertDriveFile } from 'react-icons/md';
4 |
5 | export const MessageFileStatusLayOut = styled.div`
6 | position: relative;
7 | min-width: 100px;
8 | min-height: 100px;
9 | width: 100%;
10 | height: 100%;
11 | max-width: 300px;
12 | max-height: 300px;
13 | border: 1px solid #989898;
14 | border-radius: 5px;
15 | margin-right: 10px;
16 | display: flex;
17 | justify-content: center;
18 | align-items: center;
19 | padding: 5px;
20 | `;
21 |
22 | export const MessageFileStatusElementImage = styled(ImageBox)`
23 | object-fit: contain;
24 | box-sizing: content-box;
25 | width: 95%;
26 | border-radius: 5px;
27 | `;
28 |
29 | export const MessageMdTextSnippet = styled(MdTextSnippet)`
30 | width: 100%;
31 | height: 100%;
32 | border-radius: 5px;
33 | `;
34 |
35 | export const DownloadContainer = styled.a`
36 | width: 100%;
37 | height: 100%;
38 | `;
39 |
40 | export const DownloadCover = styled.div`
41 | position: absolute;
42 | top: 2px;
43 | right: 2px;
44 | display: flex;
45 | justify-content: center;
46 | align-items: center;
47 | font-size: 12px;
48 | color: blueviolet;
49 | box-sizing: content-box;
50 | z-index: -5;
51 | `;
52 |
53 | export const StyleMdInsertDriveFile = styled(MdInsertDriveFile)`
54 | width: 100%;
55 | height: 100%;
56 | border-radius: 5px;
57 | color: black;
58 | opacity: 0.9;
59 | `;
60 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/NoOverlayModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { RefObject } from 'react';
2 | import Container from './styles';
3 |
4 | interface Props {
5 | xWidth: number;
6 | yHeight: number;
7 | isOpened: boolean;
8 | children: JSX.Element;
9 | onClose: () => void;
10 | zIndex?: number;
11 | customRef: RefObject;
12 | className?: string;
13 | }
14 |
15 | const NoOverlayModal = ({
16 | xWidth,
17 | yHeight,
18 | isOpened,
19 | onClose,
20 | customRef,
21 | children,
22 | className,
23 | zIndex,
24 | }: Props): JSX.Element => {
25 | return (
26 |
35 | {children}
36 |
37 | );
38 | };
39 |
40 | NoOverlayModal.defaultProps = {
41 | className: '',
42 | zIndex: 80,
43 | };
44 |
45 | export default NoOverlayModal;
46 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/NoOverlayModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Popup from '@atoms/Popup';
2 | import { RefObject } from 'react';
3 | import styled from 'styled-components';
4 |
5 | interface Props {
6 | x: undefined | number;
7 | y: undefined | number;
8 | customRef: RefObject;
9 | }
10 |
11 | const Container = styled(Popup)`
12 | position: absolute;
13 |
14 | ${({ x, y }) => {
15 | return `top : ${y}px; left: ${x}px;`;
16 | }}
17 | background-color: #F8F8F8;
18 |
19 | border: 1px solid black;
20 | box-shadow: 0 0 0 1px rgb(29 28 29 / 13%), 0 4px 12px 0 rgb(0 0 0 / 12%);
21 | border-radius: 6px;
22 | overflow: hidden;
23 | `;
24 |
25 | export default Container;
26 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/QuestionForm/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import ViewportInput from '@atoms/ViewPortInput';
3 | import { Container, StyledLabel, StyledLabeledDefaultButton } from './styles';
4 |
5 | interface Props {
6 | count: string;
7 | title: string;
8 | content: string;
9 | type?: string;
10 | placeholder?: string;
11 | onSubmit?: React.FormEventHandler;
12 | onChange?: React.FormEventHandler;
13 | onSet: (arg0: { value: string } | { files: File }) => void;
14 | }
15 |
16 | const QuestionForm = ({
17 | count,
18 | type,
19 | title,
20 | content,
21 | placeholder,
22 | onSubmit,
23 | onChange,
24 | onSet,
25 | }: Props): JSX.Element => {
26 | const inputRef = useRef();
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
40 | {
43 | onSet(inputRef?.current);
44 | }}
45 | />
46 |
47 | );
48 | };
49 |
50 | QuestionForm.defaultProps = {
51 | placeholder: '',
52 | type: 'text',
53 | onSubmit: () => {},
54 | onChange: () => {},
55 | };
56 |
57 | export default QuestionForm;
58 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/QuestionForm/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Label from '@atoms/Label';
3 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
4 | import { ThemeButton } from '@global/style/mixin';
5 |
6 | export const Container = styled.div`
7 | position: relative;
8 |
9 | border: 0;
10 |
11 | margin: 5vh 1vw 0 1vw;
12 | overflow: hidden;
13 |
14 | & > span:first-child {
15 | font-size: large;
16 | color: #808080;
17 | margin-bottom: 3vh;
18 | }
19 |
20 | & > span:nth-child(3) {
21 | font-size: xx-large;
22 | font-weight: bold;
23 | }
24 | `;
25 |
26 | export const StyledLabel = styled(Label)`
27 | display: block;
28 | font-weight: bold;
29 | margin-bottom: 2vh;
30 | `;
31 |
32 | export const StyledLabeledDefaultButton = styled(LabeledDefaultButton)`
33 | margin-top: 5vh;
34 | width: 8vw;
35 | height: 6vh;
36 | ${ThemeButton}
37 | `;
38 |
39 | export default Container;
40 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SearchBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container, StyledViewportInput } from './styles';
3 |
4 | interface Props {
5 | placeholder: undefined | string;
6 | onSubmit: (e: React.FormEvent) => void;
7 | }
8 |
9 | const SearchBar = ({ placeholder, onSubmit }: Props): JSX.Element => {
10 | return (
11 |
12 |
17 |
18 | );
19 | };
20 |
21 | export default SearchBar;
22 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SearchBar/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ViewportInput from '@atoms/ViewPortInput';
3 | import { BROWSER_CHANNEL_LIST_SIZE } from '@enum/index';
4 |
5 | interface Props {
6 | width?: number;
7 | height?: number;
8 | }
9 |
10 | const { height: ListHeight } = BROWSER_CHANNEL_LIST_SIZE;
11 |
12 | export const Container = styled.div`
13 | display: flex;
14 | width: ${({ width }) => {
15 | if (width) return `${width}vw`;
16 | return '100%';
17 | }};
18 | `;
19 |
20 | export const StyledViewportInput = styled(ViewportInput)`
21 | display: flex;
22 | width: 100%;
23 | height: ${ListHeight}vh;
24 | background-color: rgba(var(--sk_primary_background, 255, 255, 255), 1);
25 | --saf-0: rgba(var(--sk_primary_foreground, 29, 28, 29), 0.3);
26 | border: 1px solid var(--saf-0);
27 | border-radius: 4px;
28 | color: rgba(var(--sk_foreground_max_solid, 97, 96, 97), 1);
29 | display: flex;
30 | padding: 0 8px;
31 | transition: border 80ms ease-out, box-shadow 80ms ease-out;
32 | font-size: 15px;
33 | line-height: 1.46668;
34 | font-weight: 400;
35 | `;
36 |
37 | export default Container;
38 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SelectWorkspace/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 |
4 | interface Props {
5 | width?: string;
6 | }
7 |
8 | export const Container = styled.div`
9 | display: flex;
10 |
11 | width: ${(props) => props.width ?? 'inherit'};
12 | flex-direction: row;
13 | align-items: center;
14 | height: inherit;
15 |
16 | margin-left: 10px;
17 | overflow: visible;
18 | `;
19 |
20 | export const TextSet = styled.div`
21 | flex-direction: column;
22 | margin: 10px 10px 10px 10px;
23 |
24 | &>: first-child {
25 | font-weight: bold;
26 | }
27 | &>: last-child {
28 | color: grey;
29 | }
30 | `;
31 |
32 | export const StyledImageColumn = styled.div`
33 | min-width: 70px;
34 | min-height: 70px;
35 | width: 70px;
36 | height: 70px;
37 | `;
38 |
39 | export const StyledImageBox = styled(ImageBox)`
40 | border-radius: 5px;
41 | `;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SelectbrowseChannelPage/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Container = styled.div`
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | background: transparent;
8 | width: inherit;
9 | border: 0px solid;
10 |
11 | margin-top: 5vh;
12 | `;
13 |
14 | export const StyledButton = styled.button<{ isCursor?: boolean }>`
15 | background: transparent;
16 | border: 0;
17 | font-size: 20px;
18 | margin: 0 1vw 0 1vw;
19 | width: 30px;
20 | height: 30px;
21 |
22 | color: ${({ isCursor }) => (isCursor ? 'red' : '')};
23 |
24 | &:hover {
25 | cursor: pointer;
26 | }
27 | `;
28 |
29 | export default Container;
30 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SidebarAddElement/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Label from '@atoms/Label';
3 | import { Container, StyledLabel } from './styles';
4 |
5 | interface Props {
6 | onClick: () => void;
7 | label: string;
8 | }
9 |
10 | const SidebarAddElement = ({ onClick, label }: Props): JSX.Element => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default SidebarAddElement;
22 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SidebarAddElement/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { theme } from 'styled-tools';
3 | import { defaultTheme } from '@global/style/theme';
4 |
5 | interface Props {
6 | width: number;
7 | height: number;
8 | }
9 |
10 | export const Container = styled.div`
11 | display: flex;
12 | flex-direction: row;
13 | align-items: center;
14 | height: 36px;
15 | width: 100%;
16 |
17 | color: ${theme('smallText', defaultTheme.smallText)};
18 | &: hover {
19 | cursor: pointer;
20 | background-color: ${theme('focusedMenu', defaultTheme.focusedMenu)};
21 | }
22 | `;
23 |
24 | export const StyledLabel = styled.span`
25 | margin: 1rem;
26 | `;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SidebarChannelElement/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Label from '@atoms/Label';
3 | import { Container, StyledLabel } from './styles';
4 |
5 | interface Props {
6 | onClick: () => void;
7 | label: string;
8 | isPrivate: boolean;
9 | }
10 |
11 | const SidebarChannelElement = ({
12 | onClick,
13 | label,
14 | isPrivate,
15 | }: Props): JSX.Element => {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default SidebarChannelElement;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SidebarChannelElement/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { theme } from 'styled-tools';
3 | import { defaultTheme } from '@global/style/theme';
4 |
5 | interface Props {
6 | width: number;
7 | height: number;
8 | }
9 |
10 | export const Container = styled.div`
11 | display: flex;
12 | flex-direction: row;
13 | align-items: center;
14 | height: 36px;
15 | min-width: 250px;
16 | &: hover {
17 | cursor: pointer;
18 | background-color: ${theme('focusedMenu', defaultTheme.focusedMenu)};
19 | }
20 | `;
21 |
22 | export const StyledLabel = styled.span`
23 | margin: 1rem;
24 | `;
25 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SidebarDivision/index.tsx:
--------------------------------------------------------------------------------
1 | import IconButton from '@atoms/IconButton';
2 | import Label from '@atoms/Label';
3 | import React, { useContext } from 'react';
4 | import { Container, StyledLabel, StyledIconButton } from './styles';
5 | import { MdAdd } from 'react-icons/md';
6 | import { useRecoilState } from 'recoil';
7 | import { channelCreateModalState } from '@state/modal';
8 | import { ThemeContext } from 'styled-components';
9 |
10 | type SidebarDivisionTypes = 'Starred' | 'Channels' | 'Direct Messages';
11 |
12 | interface Props {
13 | label: string;
14 | options?: boolean;
15 | type: SidebarDivisionTypes;
16 | }
17 |
18 | const SidebarDivision = ({
19 | label,
20 | options = true,
21 | type,
22 | }: Props): JSX.Element => {
23 | const [isOpen, setIsOpen] = useRecoilState(channelCreateModalState);
24 |
25 | const themeContext = useContext(ThemeContext);
26 | const { smallText } = themeContext;
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 | setIsOpen(true)}
37 | color={smallText}
38 | />
39 |
40 |
41 | );
42 | };
43 |
44 | export default SidebarDivision;
45 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SidebarDivision/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | width: number;
5 | height: number;
6 | color?: string;
7 | }
8 |
9 | export const Container = styled.div`
10 | display: flex;
11 | flex-direction: row;
12 | align-items: center;
13 | height: 36px;
14 | width: 100%;
15 | &: hover * {
16 | cursor: pointer;
17 | } ;
18 | `;
19 |
20 | export const StyledLabel = styled.span`
21 | flex-grow: 1;
22 | padding: 1rem;
23 | text-decoration: none;
24 | color: ${({ color }) => color};
25 | `;
26 |
27 | export const StyledIconButton = styled.div`
28 | border-radius: 1rem;
29 | right: 1rem;
30 | color: ${({ color }) => color};
31 | `;
32 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/SortedOptionMordal/styles.ts:
--------------------------------------------------------------------------------
1 | import { RefObject } from 'react';
2 | import Popup from '@atoms/Popup';
3 | import { BUTTON_SIZE } from '@enum/index';
4 | import { flexAlignCenter } from '@global/style/mixin';
5 | import styled from 'styled-components';
6 |
7 | const { height: ButtonHeight } = BUTTON_SIZE;
8 |
9 | interface Props {
10 | x: undefined | number;
11 | y: undefined | number;
12 | customRef: RefObject;
13 | }
14 |
15 | const Container = styled(Popup)`
16 | position: absolute;
17 |
18 | ${({ x, y }) => {
19 | return `top : ${ButtonHeight + y + 36}px; left: ${x - 61}px;`;
20 | }}
21 | background-color: white;
22 | width: 200px;
23 | height: 130px;
24 |
25 | --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
26 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.12);
27 | background-color: rgba(var(--sk_foreground_min_solid, 248, 248, 248), 1);
28 | border-radius: 6px;
29 | padding: 0.1px 0;
30 | `;
31 |
32 | export const StyledDiv = styled.div`
33 | display: flx;
34 | ${flexAlignCenter}
35 | flex-direction : row;
36 | width: 100%;
37 | height: 20px;
38 | margin: 5px 0 10px 0;
39 |
40 | & > * {
41 | margin-right: 5px;
42 | }
43 | `;
44 | export default Container;
45 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/ThreadContent/ThreadFileStatusBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | ThreadFileStatusBarLayout,
4 | ThreadFileStatusBarContainer,
5 | } from './styles';
6 | import ThreadFileStatusElement from '../ThreadFileStatusElement';
7 |
8 | interface Props {
9 | files: File[];
10 | }
11 |
12 | const ThreadFileStatusBar = ({ files }: Props): JSX.Element => {
13 | return (
14 | <>
15 |
16 |
17 | {files.map((file, index) => (
18 |
19 | ))}
20 |
21 |
22 | >
23 | );
24 | };
25 |
26 | export default ThreadFileStatusBar;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/ThreadContent/ThreadFileStatusBar/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ThreadFileStatusBarContainer = styled.div`
4 | max-width: 100%;
5 | width: 100%;
6 | display: grid;
7 | grid-template-columns: 1fr 1fr;
8 | grid-auto-rows: 1fr;
9 | gap: 10px 20px;
10 | `;
11 |
12 | export const ThreadFileStatusBarLayout = styled.div`
13 | max-width: 100%;
14 | width: 730px;
15 | margin-bottom: 10px;
16 | `;
17 |
--------------------------------------------------------------------------------
/frontend/src/components/molecules/ThreadContent/ThreadFileStatusElement/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 | import { MdTextSnippet, MdInsertDriveFile } from 'react-icons/md';
4 |
5 | export const ThreadFileStatusLayOut = styled.div`
6 | position: relative;
7 | max-width: calc(100% - 5px);
8 | width: 100%;
9 | height: auto;
10 | border: 1px solid #989898;
11 | border-radius: 5px;
12 | margin-right: 10px;
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | padding: 5px;
17 | `;
18 |
19 | export const ThreadFileStatusElementImage = styled(ImageBox)`
20 | object-fit: contain;
21 | box-sizing: content-box;
22 | width: 95%;
23 | border-radius: 5px;
24 | `;
25 |
26 | export const ThreadMdTextSnippet = styled(MdTextSnippet)`
27 | width: 100%;
28 | height: 100%;
29 | border-radius: 5px;
30 | `;
31 |
32 | export const DownloadContainer = styled.a`
33 | width: 100%;
34 | height: 100%;
35 | `;
36 |
37 | export const DownloadCover = styled.div`
38 | position: absolute;
39 | top: 2px;
40 | right: 2px;
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | font-size: 12px;
45 | color: blueviolet;
46 | box-sizing: content-box;
47 | z-index: -5;
48 | `;
49 |
50 | export const StyleMdInsertDriveFile = styled(MdInsertDriveFile)`
51 | width: 100%;
52 | height: 100%;
53 | border-radius: 5px;
54 | color: black;
55 | opacity: 0.9;
56 | `;
57 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/BrowseContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useRecoilValue, useSetRecoilState } from 'recoil';
3 | import Label from '@atoms/Label';
4 | import BrowseChannelList from '@organisms/BrowseChannelList';
5 | import { channelCreateModalState } from '@state/modal';
6 | import { mainWorkspaceSizeState } from '@state/workspace';
7 | import {
8 | Container,
9 | StyledBrowseChannelHeader,
10 | StyledLabeledButton,
11 | } from './styles';
12 |
13 | const BrowseContent = (): JSX.Element => {
14 | const setIsOpen = useSetRecoilState(channelCreateModalState);
15 | const WIDTHSIZE = useRecoilValue(mainWorkspaceSizeState);
16 |
17 | const Title: JSX.Element = ;
18 | const RightButton = (
19 | {
21 | setIsOpen(true);
22 | }}
23 | text="채널 생성"
24 | />
25 | );
26 |
27 | return (
28 |
29 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default BrowseContent;
40 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/BrowseContent/styles.ts:
--------------------------------------------------------------------------------
1 | import BrowseChannelHeader from '@molecules/BrowseChannelHeader';
2 | import LabeledButton from '@atoms/LabeledButton';
3 | import styled from 'styled-components';
4 |
5 | export const Container = styled.div<{ widthVW: number }>`
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | width: ${({ widthVW }) => widthVW}vw;
10 | height: 95vh;
11 | `;
12 |
13 | export const StyledBrowseChannelHeader = styled(BrowseChannelHeader)`
14 | font-weight: bold;
15 | `;
16 |
17 | export const StyledLabeledButton = styled(LabeledButton)`
18 | transition: all 80ms linear;
19 | background: rgba(var(--sk_primary_background, 255, 255, 255), 1);
20 | --saf-0: rgba(var(--sk_primary_foreground, 29, 28, 29), 0.3);
21 | border: 1px solid var(--saf-0);
22 | background-clip: padding-box;
23 | font-weight: 700;
24 | font-size: 13px;
25 | padding: 6px 12px;
26 |
27 | &:hover {
28 | background-color: #f8f8f8;
29 | text-decoration: none;
30 | }
31 | `;
32 |
33 | export default Container;
34 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/BrowseMordalContainer/styles.ts:
--------------------------------------------------------------------------------
1 | import IconButton from '@atoms/IconButton';
2 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
3 | import Popup from '@atoms/Popup';
4 | import styled from 'styled-components';
5 |
6 | export const Container = styled.div`
7 | display: flex;
8 | position: relative;
9 |
10 | & > button {
11 | margin: 0 5px 0 5px;
12 | }
13 | `;
14 |
15 | export const SortedPopup = styled(Popup)`
16 | background-color: ivory;
17 | `;
18 |
19 | export const StyledLabeledDefaultButton = styled(LabeledDefaultButton)`
20 | font-size: 16px;
21 | color: rgba(var(--sk_foreground_max, 29, 28, 29), 0.7);
22 | padding-left: 1px;
23 | `;
24 |
25 | export const StyledIconButton = styled(IconButton)`
26 | padding-right: 1px;
27 | &:hover {
28 | cursor: pointer;
29 | }
30 | `;
31 |
32 | export const StyledButtonContainer = styled.div`
33 | display: flex;
34 | font-weight: 400 !important;
35 | align-items: center;
36 | border-radius: 4px;
37 | padding: 0 8px;
38 | color: rgba(var(--sk_foreground_max, 29, 28, 29), 0.7);
39 | white-space: nowrap;
40 |
41 | &:hover {
42 | cursor: pointer;
43 | }
44 | `;
45 |
46 | export default Container;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChangePasswordContent/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import LabeledButton from '@atoms/LabeledButton';
3 | import Input from '@atoms/Input';
4 |
5 | export const LoginInput = styled(Input)`
6 | width: 500px;
7 | height: 65px;
8 | border-radius: 12px;
9 | border: 0.5px solid;
10 | `;
11 |
12 | export const RouterLabeledButton = styled(LabeledButton)`
13 | width: 240px;
14 | height: 60px;
15 | background-color: #ecdeec;
16 | border-radius: 12px;
17 | `;
18 |
19 | export const LabelColumn = styled.div`
20 | display: flex;
21 | justify-content: space-between;
22 | flex-direction: row;
23 | align-items: center;
24 | width: 500px;
25 | `;
26 |
27 | export const LoginForm = styled.form`
28 | display: flex;
29 | justify-content: space-between;
30 | flex-direction: column;
31 | align-items: center;
32 | width: 512px;
33 | height: 360px;
34 | `;
35 |
36 | export const NoticeDiv = styled.div`
37 | width: 500px;
38 | font-weight: 500;
39 | color: blue;
40 | `;
41 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelAbout/AboutElement/index.tsx:
--------------------------------------------------------------------------------
1 | import Label from '@atoms/Label';
2 | import React, { FunctionComponent } from 'react';
3 | import {
4 | Container,
5 | StyledBoldLabel,
6 | StyledEditLabel,
7 | StyledLightLabel,
8 | StyledRedLabel,
9 | } from './styles';
10 |
11 | interface Props {
12 | light?: boolean;
13 | edit?: boolean;
14 | red?: boolean;
15 | title: string;
16 | description: string;
17 | onClick?: () => void;
18 | }
19 |
20 | const AboutElement: FunctionComponent = ({
21 | light,
22 | edit,
23 | red,
24 | title,
25 | description,
26 | onClick,
27 | }: Props): JSX.Element => {
28 | return (
29 |
30 | {red || }
31 | {light ? (
32 |
33 | ) : (
34 |
35 | )}
36 | {edit && }
37 | {red && }
38 |
39 | );
40 | };
41 |
42 | AboutElement.defaultProps = {
43 | light: false,
44 | edit: false,
45 | red: false,
46 | onClick: () => {},
47 | };
48 |
49 | export default AboutElement;
50 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelAbout/AboutElement/styles.ts:
--------------------------------------------------------------------------------
1 | import Label from '@atoms/Label';
2 | import styled from 'styled-components';
3 |
4 | interface Props {
5 | border: boolean;
6 | }
7 |
8 | export const Container = styled.div`
9 | display: flex;
10 | flex-direction: column;
11 | position: relative;
12 | padding: 16px 20px;
13 | font-size: 15px;
14 | line-height: 22px;
15 | border-bottom: ${(props) => props.border && '1px solid #dddddd'};
16 |
17 | &: hover {
18 | cursor: pointer;
19 | background-color: #f6f6f6;
20 | }
21 |
22 | &: first-child {
23 | border-top-left-radius: 1rem;
24 | border-top-right-radius: 1rem;
25 | }
26 |
27 | &: last-child {
28 | border-bottom-left-radius: 1rem;
29 | border-bottom-right-radius: 1rem;
30 | }
31 | `;
32 |
33 | export const StyledBoldLabel = styled(Label)`
34 | font-weight: bold;
35 | `;
36 |
37 | export const StyledLightLabel = styled(Label)`
38 | color: grey;
39 | `;
40 |
41 | export const StyledRedLabel = styled(Label)`
42 | font-weight: bold;
43 | color: red;
44 | `;
45 |
46 | export const StyledEditLabel = styled(Label)`
47 | position: absolute;
48 | top: 16px;
49 | right: 20px;
50 | color: #2c649e;
51 | font-weight: bold;
52 | font-size: 100%;
53 | `;
54 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelAbout/styles.ts:
--------------------------------------------------------------------------------
1 | import LabeledButton from '@atoms/LabeledButton';
2 | import styled from 'styled-components';
3 |
4 | export const BackgroundContainer = styled.div`
5 | background-color: #f6f6f6;
6 | `;
7 |
8 | export const Container = styled.div`
9 | background-color: white;
10 | margin: 1rem;
11 | border: 1px solid #dddddd;
12 | border-radius: 1rem;
13 | `;
14 |
15 | export const StyledLabeledButton = styled(LabeledButton)`
16 | color: red;
17 | padding: 16px 20px;
18 | `;
19 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelInfoModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Label from '@atoms/Label';
2 | import LabeledButton from '@atoms/LabeledButton';
3 | import Modal from '@atoms/Modal';
4 | import styled from 'styled-components';
5 |
6 | export const Container = styled.div`
7 | display: flex;
8 | background-color: white;
9 | flex-direction: column;
10 | border-radius: 16px;
11 | `;
12 |
13 | export const StyledLabel = styled(Label)`
14 | margin: 1rem;
15 | font-size: 2rem;
16 | font-weight: bold;
17 | `;
18 |
19 | export const TabContainer = styled.div`
20 | margin: 0 1rem;
21 | `;
22 |
23 | interface Props {
24 | highlight: boolean;
25 | }
26 | export const Tab = styled(LabeledButton)`
27 | margin-right: 1rem;
28 | color: ${(props) => props.highlight || 'grey'};
29 | border-bottom: ${(props) => props.highlight && '2px solid green'};
30 | `;
31 |
32 | export const StyledModal = styled(Modal)`
33 | max-width: 580px;
34 | min-height: 550px;
35 | `;
36 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelJoinFooter/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useRecoilValue } from 'recoil';
3 | import { useParams } from 'react-router-dom';
4 | import userState from '@state/user';
5 | import { joinChannel } from '@global/api/channel';
6 | import {
7 | Container,
8 | PreviewSubtitle,
9 | PreviewMetadata,
10 | StyledLabeledButton,
11 | } from './styles';
12 |
13 | interface Props {
14 | channelName: string;
15 | }
16 |
17 | const ChatInputBackGround = ({ channelName }: Props): JSX.Element => {
18 | const user = useRecoilValue(userState);
19 | const { workspaceId, channelId }: { workspaceId: string; channelId: string } =
20 | useParams();
21 |
22 | return (
23 |
24 |
25 | #{channelName}을(를) 보고 있습니다.
26 |
27 |
28 |
29 | ) => {
31 | e.stopPropagation();
32 | joinChannel(user.id, channelId, workspaceId, user.socket);
33 | }}
34 | text="채널 참여"
35 | className="primary"
36 | />
37 | {}}
39 | text="추가 정보 보기"
40 | className="secondary"
41 | />
42 |
43 |
44 | );
45 | };
46 |
47 | export default ChatInputBackGround;
48 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelJoinFooter/styles.ts:
--------------------------------------------------------------------------------
1 | import LabeledButton from '@atoms/LabeledButton';
2 | import styled from 'styled-components';
3 |
4 | export const Container = styled.div`
5 | padding: 24px 30px;
6 | background-color: rgba(var(--sk_foreground_min, 29, 28, 29), 0.04);
7 | text-align: center;
8 | `;
9 |
10 | export const PreviewSubtitle = styled.div`
11 | font-size: 18px;
12 | line-height: 1.33334;
13 | font-weight: 400;
14 | color: rgba(var(--sk_primary_foreground, 29, 28, 29), 1);
15 | white-space: pre;
16 | `;
17 |
18 | export const PreviewMetadata = styled.div`
19 | display: flex;
20 | justify-content: center;
21 | margin-top: 8px;
22 | margin-bottom: 20px;
23 | `;
24 |
25 | export const StyledLabeledButton = styled(LabeledButton)`
26 | font-size: 15px;
27 | height: 36px;
28 | min-width: 80px;
29 | padding: 0 12px 1px;
30 |
31 | &.primary {
32 | transition: all 80ms linear;
33 | background: #007a5a;
34 | color: #fff;
35 | font-weight: 900;
36 | box-shadow: none;
37 | }
38 |
39 | &.secondary {
40 | margin-left: 16px;
41 | color: rgba(var(--sk_primary_foreground, 29, 28, 29), 1);
42 | background: rgba(var(--sk_primary_background, 255, 255, 255), 1);
43 | --saf-0: rgba(var(--sk_primary_foreground, 29, 28, 29), 0.3);
44 | border: 1px solid var(--saf-0);
45 | background-clip: padding-box;
46 | font-weight: 700;
47 | }
48 | `;
49 |
50 | export default Container;
51 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelMembers/MemberTemplate/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Label from '@atoms/Label';
3 |
4 | export const Container = styled.div`
5 | height: 100%;
6 | `;
7 |
8 | export const StyledLabel = styled(Label)`
9 | padding: 0 1rem;
10 | font-size: 14px;
11 | font-weight: 700;
12 | color: #616061;
13 | `;
14 |
15 | export const GreyContainer = styled.div`
16 | background-color: #f6f6f6;
17 | height: 100%;
18 | `;
19 |
20 | export const NoResultLabel = styled(Label)`
21 | padding: 1rem;
22 | font-weight: 700;
23 | `;
24 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelMembers/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { RoundScrollBar } from '@global/style/mixin';
3 | import { BsSearch } from 'react-icons/bs';
4 |
5 | export const Container = styled.div`
6 | display: flex;
7 | flex-direction: column;
8 | margin-top: 1rem;
9 | height: 400px;
10 | `;
11 |
12 | export const SearchBarContainer = styled.div`
13 | display: flex;
14 | align-items: center;
15 | `;
16 |
17 | export const StyledIconButton = styled(BsSearch)`
18 | margin-left: 12px;
19 | `;
20 |
21 | export const StyledInput = styled.input`
22 | font-size: 100%;
23 | width: 100%;
24 | height: 36px;
25 | margin: 0 1rem;
26 | border: none;
27 |
28 | &:focus {
29 | outline: none;
30 | }
31 | `;
32 |
33 | export const ScrollContainer = styled.div`
34 | ${RoundScrollBar};
35 | display: flex;
36 | flex: 1 1 0;
37 | flex-direction: column;
38 | margin-top: 1rem;
39 | overflow-y: scroll;
40 | overflow-x: hidden;
41 | `;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChannelTopicModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Input from '@atoms/Input';
2 | import Label from '@atoms/Label';
3 | import LabeledButton from '@atoms/LabeledButton';
4 | import Modal from '@atoms/Modal';
5 | import styled from 'styled-components';
6 |
7 | export const Container = styled.div`
8 | display: flex;
9 | background-color: white;
10 | flex-direction: column;
11 | border-radius: 16px;
12 | `;
13 |
14 | export const StyledLabel = styled(Label)`
15 | padding: 1rem;
16 | font-size: 22px;
17 | font-weight: bold;
18 | `;
19 |
20 | export const StyledInput = styled(Input)`
21 | margin: 1rem;
22 | `;
23 |
24 | export const CancelButton = styled(LabeledButton)`
25 | border: 1px solid grey;
26 | border-radius: 4px;
27 | padding: 0 12px;
28 | font-size: 16px;
29 | font-weight: bold;
30 | min-width: 80px;
31 | `;
32 |
33 | export const SaveButton = styled(LabeledButton)`
34 | background-color: green;
35 | color: white;
36 | margin-left: 1rem;
37 | border-radius: 4px;
38 | padding: 12px 12px;
39 | font-size: 16px;
40 | font-weight: bold;
41 | min-width: 80px;
42 | `;
43 |
44 | export const ButtonContainer = styled.div`
45 | padding: 1rem;
46 | display: flex;
47 | justify-content: flex-end;
48 | `;
49 |
50 | export const StyledModal = styled(Modal)`
51 | max-width: 540px;
52 | `;
53 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatContent/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { scrollIfHover } from '@global/style/mixin';
3 |
4 | interface Props {
5 | width?: string;
6 | }
7 |
8 | export const Container = styled.div`
9 | position: relative;
10 | height: inherit;
11 | width: ${(props) => props.width ?? 'inherit'};
12 | ${scrollIfHover}
13 | `;
14 |
15 | export default Container;
16 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatInputBar/FileStatusBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch, SetStateAction } from 'react';
2 | import FileStatusElement from '../FileStatusElement';
3 | import { FileStatusColumn } from './styles';
4 |
5 | interface Props {
6 | selectedFile: any;
7 | selectedFileUrl: any;
8 | setSelectedFile: Dispatch>;
9 | setSelectedFileUrl: Dispatch>;
10 | }
11 |
12 | const FileStatusBar = ({
13 | selectedFile,
14 | selectedFileUrl,
15 | setSelectedFile,
16 | setSelectedFileUrl,
17 | }: Props): JSX.Element => {
18 | return (
19 | <>
20 | {selectedFileUrl.map((url, index) => {
21 | return (
22 |
23 |
30 |
31 | );
32 | })}
33 | >
34 | );
35 | };
36 |
37 | export default FileStatusBar;
38 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatInputBar/FileStatusBar/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const FileStatusColumn = styled.div`
4 | position: relative;
5 | `;
6 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatInputBar/FileStatusElement/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch, SetStateAction, useState } from 'react';
2 | import {
3 | FileStatusElementImg,
4 | FileStatusElementButton,
5 | FileStatusElementContainer,
6 | } from './styles';
7 |
8 | interface Props {
9 | url: string;
10 | selectedFile: any;
11 | selectedFileUrl: any;
12 | setSelectedFile: Dispatch>;
13 | setSelectedFileUrl: Dispatch>;
14 | }
15 |
16 | const FileStatusElement = ({
17 | url,
18 | selectedFile,
19 | selectedFileUrl,
20 | setSelectedFile,
21 | setSelectedFileUrl,
22 | }: Props): JSX.Element => {
23 | const [flag, setFlag] = useState(true);
24 |
25 | const handleDeleteElement = (e) => {
26 | e.stopPropagation();
27 | const index = selectedFileUrl.indexOf(url);
28 | selectedFile.splice(index, 1);
29 | selectedFileUrl.splice(index, 1);
30 | setSelectedFile(selectedFile);
31 | setSelectedFileUrl(selectedFileUrl);
32 | setFlag(false);
33 | };
34 |
35 | return flag ? (
36 |
37 |
38 | {
40 | handleDeleteElement(e);
41 | }}
42 | />
43 |
44 | ) : (
45 | <>>
46 | );
47 | };
48 |
49 | export default FileStatusElement;
50 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatInputBar/FileStatusElement/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 |
4 | export const FileStatusElementImg = styled(ImageBox)`
5 | width: 50px;
6 | height: 50px;
7 | border-radius: 10px;
8 | border: 1px solid rgba(221, 221, 221, 1);
9 | `;
10 |
11 | export const FileStatusElementButton = styled.div`
12 | position: absolute;
13 | top: -4px;
14 | right: 0;
15 | width: 16px;
16 | height: 16px;
17 | border-radius: 8px;
18 | background-color: rgba(221, 221, 221, 1);
19 | color: white;
20 | font-size: 5px;
21 | `;
22 |
23 | export const FileStatusElementContainer = styled.div`
24 | padding-right: 8px;
25 | width: 50px;
26 | height: 50px;
27 | margin-right: 10px;
28 | `;
29 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatInputBar/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | min-width: 309px;
5 | `;
6 |
7 | export const DragAndDropContainer = styled.div`
8 | border: 1px solid #868686;
9 | border-radius: 5px;
10 | margin: 0 20px;
11 | background: #ffffff;
12 | `;
13 |
14 | export const DropAlertContainer = styled.div`
15 | width: inherit;
16 | height: inherit;
17 | padding: 8px 10px;
18 | position: absolute;
19 | z-index: 2;
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | font-size: 40px;
24 | font-weight: 600;
25 | `;
26 |
27 | export const NotificationBar = styled.div`
28 | height: 24px;
29 | padding: 0 12px;
30 | display: flex;
31 | align-items: center;
32 | `;
33 |
34 | export const WysiwygColumn = styled.div`
35 | padding: 8px;
36 | display: flex;
37 | flex-direction: row;
38 | justify-content: start;
39 | align-items: center;
40 | width: 100%;
41 | `;
42 |
43 | export const WysiwygContainer = styled.div`
44 | width: 100%;
45 | background: #ffffff;
46 |
47 | display: grid;
48 | grid-template-columns: auto minmax(0, 1fr) auto;
49 | grid-template-rows: auto minmax(0, 100%) auto auto;
50 | grid-template-areas:
51 | 'context context context'
52 | 'input input input'
53 | 'attachments attachments attachments'
54 | 'prefix single_decker_input suffix';
55 | position: relative;
56 | `;
57 |
58 | export default Container;
59 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ChatInputBarForUpdate/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | padding: 0px 20px;
5 | `;
6 |
7 | export const NotificationBar = styled.div`
8 | height: 24px;
9 | padding: 0px 12px;
10 | display: flex;
11 | align-items: center;
12 | `;
13 |
14 | export const WysiwygContainer = styled.div`
15 | border-color: #868686;
16 | border-radius: 4px;
17 | border-style: solid;
18 | border-width: 1px;
19 | background: #ffffff;
20 |
21 | display: grid;
22 | grid-template-columns: auto minmax(0, 1fr) auto;
23 | grid-template-rows: auto minmax(0, 100%) auto auto;
24 | grid-template-areas:
25 | 'context context context'
26 | 'input input input'
27 | 'attachments attachments attachments'
28 | 'prefix single_decker_input suffix';
29 | position: relative;
30 | `;
31 |
32 | export default Container;
33 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/CreateLoginModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useRecoilState } from 'recoil';
3 | import { LoginModalState } from '@state/modal';
4 | import { StyledModal, Container, ModalButton, ModalMessage } from './style';
5 |
6 | interface Props {
7 | Content: string;
8 | }
9 |
10 | const CreateLoginModal = ({ Content }: Props): JSX.Element => {
11 | const [isOpen, setIsOpen] = useRecoilState(LoginModalState);
12 | return (
13 | setIsOpen(false)}>
14 |
15 | {Content}
16 | setIsOpen(false)}
20 | />
21 |
22 |
23 | );
24 | };
25 |
26 | export default CreateLoginModal;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/CreateLoginModal/style.ts:
--------------------------------------------------------------------------------
1 | import Modal from '@atoms/Modal';
2 | import styled from 'styled-components';
3 | import LabeledButton from '@atoms/LabeledButton';
4 |
5 | export const StyledModal = styled(Modal)`
6 | max-width: 580px;
7 | width: 380px;
8 | height: 250px;
9 | background-color: white;
10 | border-radius: 15px;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | `;
15 |
16 | export const Container = styled.div`
17 | display: flex;
18 | justify-content: space-between;
19 | flex-direction: column;
20 | align-items: center;
21 | height: 170px;
22 | padding: 5px;
23 | `;
24 |
25 | export const ModalButton = styled(LabeledButton)`
26 | background-color: #c8c7ef;
27 | width: 240px;
28 | height: 60px;
29 | `;
30 |
31 | export const ModalMessage = styled.span`
32 | font-size: 26px;
33 | box-sizing: content-box;
34 | font-weight: 500;
35 | width: 330px;
36 | `;
37 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/EmojiModal/EmojiPopupTemplate/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { ifProp } from 'styled-tools';
3 | import Label from '@atoms/Label';
4 | import { RoundScrollBar } from '@global/style/mixin';
5 |
6 | export const Container = styled.div`
7 | ${RoundScrollBar};
8 | overflow-y: scroll;
9 | overflow-x: hidden;
10 | width: 100%;
11 | max-height: 300px;
12 | `;
13 |
14 | interface Props {
15 | selected: boolean;
16 | }
17 |
18 | export const UserContainer = styled.div`
19 | padding: 0 1rem;
20 | background-color: ${ifProp({ selected: true }, '#2C639E', 'transparent')};
21 | `;
22 |
23 | export const UserElement = styled.div`
24 | height: 32px;
25 | display: flex;
26 | justify-content: space-between;
27 | align-items: center;
28 | `;
29 |
30 | export const PrimaryContent = styled.div``;
31 |
32 | export const SecondaryContent = styled.div``;
33 |
34 | export const StyledBoldLabel = styled(Label)`
35 | font-weight: bold;
36 | `;
37 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/EmojiModal/styles.ts:
--------------------------------------------------------------------------------
1 | import Input from '@atoms/Input';
2 | import NoOverlayModal from '@molecules/NoOverlayModal';
3 | import styled from 'styled-components';
4 |
5 | export const StyledPopup = styled(NoOverlayModal)`
6 | --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
7 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.08);
8 | line-height: 1rem;
9 |
10 | width: 300px;
11 | border-radius: 6px;
12 | background: rgba(var(--sk_primary_background, 255, 255, 255), 1);
13 | `;
14 |
15 | export const InputContainer = styled.div`
16 | background: rgba(var(--sk_primary_background, 255, 255, 255), 1);
17 | padding: 14px;
18 | position: relative;
19 | `;
20 |
21 | export const StyledInput = styled(Input)`
22 | padding-top: 2px;
23 | padding-bottom: 4px;
24 | padding-left: 30px;
25 | padding-right: 32px;
26 | font-size: 16px;
27 | margin: 0;
28 | height: 32px;
29 | width: 100%;
30 | border-radius: 4px;
31 | outline: none;
32 | --saf-0: rgba(var(--sk_primary_foreground, 29, 28, 29), 0.3);
33 | border: 1px solid var(--saf-0);
34 | line-height: normal;
35 | color: rgba(var(--sk_primary_foreground, 29, 28, 29), 1);
36 | background-color: rgba(var(--sk_primary_background, 255, 255, 255), 1);
37 | `;
38 |
39 | export default StyledPopup;
40 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/LoginContent/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import LabeledButton from '@atoms/LabeledButton';
3 | import Input from '@atoms/Input';
4 |
5 | export const LoginInput = styled(Input)`
6 | width: 500px;
7 | height: 60px;
8 | border-radius: 12px;
9 | border: 0.5px solid;
10 | `;
11 |
12 | export const LabelColumn = styled.div`
13 | display: flex;
14 | justify-content: space-between;
15 | flex-direction: row;
16 | align-items: center;
17 | width: 500px;
18 | `;
19 |
20 | export const LoginContainer = styled.div`
21 | display: flex;
22 | justify-content: space-between;
23 | flex-direction: column;
24 | align-items: center;
25 | width: 512px;
26 | height: 220px;
27 | `;
28 |
29 | const LoginLabeledButton = styled(LabeledButton)`
30 | display: flex;
31 | justify-content: center;
32 | flex-direction: row;
33 | align-items: center;
34 | width: 500px;
35 | height: 60px;
36 | border-radius: 12px;
37 | `;
38 |
39 | export const GitLabeledButton = styled(LoginLabeledButton)`
40 | background-color: #c8c7ef;
41 | `;
42 |
43 | export const EmailLabeledButton = styled(LoginLabeledButton)`
44 | background-color: #ecdeec;
45 | `;
46 |
47 | export const RouterLabeledButton = styled(LoginLabeledButton)`
48 | width: 240px;
49 | height: 60px;
50 | background-color: #ecdeec;
51 | `;
52 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/MemberElement/styles.ts:
--------------------------------------------------------------------------------
1 | import Label from '@atoms/Label';
2 | import styled from 'styled-components';
3 | import { ifProp } from 'styled-tools';
4 | import ImageBox from '@atoms/ImageBox';
5 |
6 | export const BackgroundContainer = styled.div`
7 | &: hover {
8 | cursor: pointer;
9 | background-color: #f6f6f6;
10 | }
11 | `;
12 |
13 | interface Props {
14 | selected: boolean;
15 | }
16 |
17 | export const Container = styled.div`
18 | background-color: ${ifProp({ selected: true }, '#2C639E', 'transparent')};
19 | color: ${(props) => (props.selected ? '#ffffff' : '#000000')};
20 | padding: 1rem;
21 | display: flex;
22 | justify-content: start;
23 | flex-direction: row;
24 | align-items: center;
25 | `;
26 |
27 | export const StyledLabel = styled(Label)`
28 | padding-right: 1rem;
29 | `;
30 |
31 | export const StyledImageBox = styled(ImageBox)`
32 | width: 30px;
33 | height: 30px;
34 | border-radius: 3px;
35 | border: 1px solid #989898;
36 | margin-right: 5px;
37 | `;
38 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/PreferenceModal/PreferenceMenuContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Container } from './styles';
4 |
5 | const PreferenceMenuContent = ({
6 | children,
7 | }: {
8 | children: JSX.Element;
9 | }): JSX.Element => {
10 | return {children};
11 | };
12 |
13 | export default PreferenceMenuContent;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/PreferenceModal/PreferenceMenuContent/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Label from '@atoms/Label';
3 | import DivLists from '@atoms/DivLists';
4 |
5 | export const Container = styled.div`
6 | display: flex;
7 | width: 75%;
8 | height: 100%;
9 | flex-direction: column;
10 | padding: 20px;
11 | `;
12 |
13 | export const StyledTitleLabel = styled(Label)`
14 | padding: 20px 1rem 20px 3rem;
15 | font-size: 2rem;
16 | font-weight: bold;
17 | border-bottom: 1px solid #f8f8f8;
18 | `;
19 |
20 | export const StyledLabel = styled(Label)`
21 | margin: 1rem;
22 | `;
23 |
24 | export const MenuContainer = styled.div`
25 | width: 25%;
26 | height: 100%;
27 | min-width: 150px;
28 | margin: 0 1rem;
29 | `;
30 |
31 | export const StyledDivLists = styled(DivLists)<{
32 | isClicked: number;
33 | index: number;
34 | color?: string;
35 | }>`
36 | width: 100%;
37 |
38 | margin: 30px 0px 30px 0px;
39 | padding: 3px;
40 | ${({ isClicked, index }) => {
41 | return isClicked === index ? 'color: #fff; background-color: #1164a3;' : '';
42 | }});
43 |
44 | color: ${({ color }) => color};
45 | `;
46 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/PreferenceModal/ThemeSelect/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 | import { flexAlignCenter } from '@global/style/mixin';
4 |
5 | export const StyledBigImageBox = styled(ImageBox)`
6 | width: inherit;
7 | height: 100%;
8 | margin-bottom: 5px;
9 | `;
10 |
11 | export const AlignCenterDiv = styled.div`
12 | width: 100%;
13 | height: 60%;
14 | ${flexAlignCenter}
15 | margin-bottom : 50px;
16 | `;
17 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/PreferenceModal/WorkspaceOut/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { theme } from 'styled-tools';
3 | import Label from '@atoms/Label';
4 | import { flexAlignCenter } from '@global/style/mixin';
5 | import { defaultTheme } from '@global/style/theme';
6 |
7 | export const AlignCenterDiv = styled.div`
8 | width: 100%;
9 | height: 60%;
10 | ${flexAlignCenter}
11 | margin-bottom : 50px;
12 | `;
13 |
14 | export const StyledLabel = styled(Label)`
15 | font-size: 4rem;
16 | color: ${theme('backgroundColor', defaultTheme.backgroundColor)};
17 |
18 | margin: 15px;
19 | `;
20 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ReplyBar/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Label from '@atoms/Label';
3 | import ImageButton from '@atoms/ImageButton';
4 | import BrowseChannelHeader from '@molecules/BrowseChannelHeader';
5 |
6 | const Container = styled.div<{ widthVW: number; isOpened: boolean }>`
7 | display: ${({ isOpened }) => (isOpened ? 'flex' : 'none')};
8 | flex-direction: column;
9 | width: ${({ widthVW }) => widthVW}vw;
10 | height: 100vh;
11 | background-color: #fff;
12 |
13 | border-left: 1px solid #f8f8f8;
14 | `;
15 |
16 | export const StyledBrowseChannelHeader = styled(BrowseChannelHeader)`
17 | z-index: 5;
18 | --saf-0: rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
19 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.12);
20 | `;
21 |
22 | export const StyledLabel = styled(Label)`
23 | font-weight: bold;
24 | `;
25 |
26 | export const XImageButton = styled(ImageButton)`
27 | border-radius: 6px;
28 | &:hover {
29 | background-color: #f8f8f8;
30 | }
31 | `;
32 |
33 | export default Container;
34 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/ReplyContent/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Label from '@atoms/Label';
3 | import MessageContent from '@molecules/MessageContent';
4 | import { RoundScrollBar } from '@global/style/mixin';
5 |
6 | interface Props {
7 | width?: string;
8 | }
9 |
10 | export const Container = styled.div`
11 | position: relative;
12 | height: inherit;
13 | min-height: 40vh;
14 | width: ${(props) => props.width ?? 'inherit'};
15 | ${RoundScrollBar}
16 | overflow-y: scroll;
17 | overflow-x: hidden;
18 | `;
19 | export const AbsoluteLabel = styled(Label)`
20 | position: absolute;
21 | background-color: #fff;
22 | z-index: 1;
23 |
24 | margin: 10px 8px 0 6px;
25 | padding-right: 10px;
26 | `;
27 |
28 | export const RowDiv = styled.div`
29 | display: flex;
30 | width: 100%;
31 | height: 10px;
32 | flex-direction: row;
33 | position: relative;
34 |
35 | margin: 2px 0 25px 0;
36 | `;
37 |
38 | export const StyledMessageContent = styled(MessageContent)`
39 | margin-top: 25px;
40 | `;
41 |
42 | export const GreyLine = styled.div`
43 | display: flex;
44 | width: inherit;
45 | height: 16px;
46 |
47 | border-top: 0;
48 | border-bottom: 1px solid rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
49 | margin-block-start: 0.5em;
50 | margin-block-end: 0.5em;
51 |
52 | margin: 3px 0 3px 6px;
53 | `;
54 |
55 | export const MarginDiv = styled.div`
56 | margin-top: 20vh;
57 | `;
58 |
59 | export default Container;
60 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/SetupTeamQuestions/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Label from '@atoms/Label';
3 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
4 | import { ThemeButton } from '@global/style/mixin';
5 |
6 | const Container = styled.div`
7 | width: 80vw;
8 | height: 95vh;
9 |
10 | position: relative;
11 |
12 | border: 0;
13 |
14 | margin: 5vh 1vw 0 1vw;
15 | overflow: hidden;
16 | & > span:not(:first-child) {
17 | font-size: x-large;
18 | }
19 |
20 | & > span:first-child {
21 | font-size: xx-large;
22 | margin-bottom: 3vh;
23 | font-weight: bold;
24 | }
25 | `;
26 |
27 | export const MarginedDiv = styled.div`
28 | & > * {
29 | margin: 0 3px 0 10px;
30 | }
31 | `;
32 |
33 | export const StyledLabel = styled(Label)`
34 | display: block;
35 | `;
36 |
37 | export const StyledButton = styled(LabeledDefaultButton)`
38 | margin-top: 5vh;
39 | ${ThemeButton}
40 | `;
41 |
42 | export const DropZoneContainer = styled.div`
43 | width: 350px;
44 | height: 120px;
45 | display: flex;
46 | justify-content: center;
47 | align-items: center;
48 | border-radius: 10px;
49 | border: 1px solid #868686;
50 | margin: 10px 10px 20px 10px;
51 | padding: 20px;
52 | `;
53 |
54 | export default Container;
55 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/SidebarChannelInfoModal/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Modal from '@atoms/Modal';
3 |
4 | interface Props {
5 | width: number;
6 | height: number;
7 | }
8 |
9 | export const StyledModal = styled(Modal)`
10 | max-width: 360px;
11 | min-width: 200px;
12 | `;
13 |
14 | export const Container = styled.div`
15 | display: flex;
16 | background-color: white;
17 | flex-direction: column;
18 | border-radius: 16px;
19 | padding: 12px 24px;
20 | `;
21 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/SignupContent/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import LabeledButton from '@atoms/LabeledButton';
3 | import Input from '@atoms/Input';
4 |
5 | export const LoginInput = styled(Input)`
6 | width: 500px;
7 | height: 65px;
8 | border-radius: 12px;
9 | border: 0.5px solid;
10 | `;
11 |
12 | export const LabelColumn = styled.div`
13 | display: flex;
14 | justify-content: space-between;
15 | flex-direction: row;
16 | align-items: center;
17 | width: 500px;
18 | `;
19 |
20 | export const LoginForm = styled.form`
21 | display: flex;
22 | justify-content: space-between;
23 | flex-direction: column;
24 | align-items: center;
25 | width: 512px;
26 | height: 360px;
27 | `;
28 |
29 | export const RouterLabeledButton = styled(LabeledButton)`
30 | width: 240px;
31 | height: 60px;
32 | background-color: #ecdeec;
33 | border-radius: 12px;
34 | `;
35 |
36 | export const NoticeDiv = styled.div`
37 | width: 500px;
38 | font-weight: 500;
39 | color: blue;
40 | `;
41 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/SubmitCodeForm/styles.ts:
--------------------------------------------------------------------------------
1 | import { ChangeEvent } from 'react';
2 | import styled from 'styled-components';
3 | import Input from '@atoms/Input';
4 |
5 | const Container = styled.form`
6 | width: 100vw;
7 | display: flex;
8 | flex-direction: row;
9 | text-align: center;
10 | justify-content: center;
11 | background: transparent;
12 | `;
13 |
14 | export const StyledInput = styled(Input)<{
15 | OnChange: ChangeEvent;
16 | }>`
17 | display: flex;
18 | height: 92px;
19 | width: 92px;
20 | font-size: 50px;
21 | line-height: 56px;
22 | text-align: center;
23 | background: 0 0;
24 | border: none;
25 | box-shadow: none;
26 | text-transform: uppercase;
27 | `;
28 |
29 | export const StyledInputContainer = styled.div`
30 | display: flex;
31 | flex-direction: row;
32 | border: 1px solid #000;
33 | border-radius: 4px;
34 |
35 | & > input:nth-child(2) {
36 | border-left: 1px solid #000;
37 | border-right: 1px solid #000;
38 | }
39 | `;
40 |
41 | export const StyledDiv = styled.div`
42 | display: flex;
43 | height: inherit;
44 | flex-direction: column;
45 | justify-content: center;
46 | text-align: center;
47 | `;
48 |
49 | export default Container;
50 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceContent/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div<{ WIDTHVW: number }>`
4 | display: flex;
5 | width: ${({ WIDTHVW }) => WIDTHVW}vw;
6 | height: 95vh;
7 | flex-direction: column;
8 | `;
9 |
10 | export const MarginedDiv = styled.div`
11 | & > * {
12 | margin: 0 3px 0 10px;
13 | }
14 | `;
15 |
16 | export default Container;
17 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceHeader/SearchResultTemplate/ChannelElement/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSetRecoilState } from 'recoil';
3 | import { useHistory, useParams } from 'react-router-dom';
4 | import { Channel } from '@global/type';
5 | import { searchModalState } from '@state/modal';
6 | import { shouldScrollDownState } from '@state/thread';
7 | import { BackgroundContainer, Container, StyledLabel } from './styles';
8 |
9 | interface Props {
10 | channel: Channel;
11 | selected?: boolean;
12 | }
13 |
14 | const ChannelElement = ({ channel, selected }: Props): JSX.Element => {
15 | const { workspaceId }: { workspaceId: string } = useParams();
16 | const history = useHistory();
17 | const setSearchModalState = useSetRecoilState(searchModalState);
18 | const setShouldScrollDownState = useSetRecoilState(shouldScrollDownState);
19 |
20 | return (
21 |
22 | {
25 | setSearchModalState(false);
26 | setShouldScrollDownState(true);
27 | history.push(`/client/${workspaceId}/${channel.id}`);
28 | }}
29 | >
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | ChannelElement.defaultProps = {
38 | selected: false,
39 | };
40 |
41 | export default ChannelElement;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceHeader/SearchResultTemplate/ChannelElement/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { ifProp } from 'styled-tools';
3 | import Label from '@atoms/Label';
4 |
5 | export const BackgroundContainer = styled.div`
6 | &: hover {
7 | cursor: pointer;
8 | background-color: #f6f6f6;
9 | }
10 | `;
11 |
12 | interface Props {
13 | selected: boolean;
14 | }
15 |
16 | export const Container = styled.div`
17 | background-color: ${ifProp({ selected: true }, '#2C639E', 'transparent')};
18 | color: ${(props) => (props.selected ? '#ffffff' : '#000000')};
19 | padding: 1rem;
20 | `;
21 |
22 | export const StyledLabel = styled(Label)`
23 | padding-right: 1rem;
24 | `;
25 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceHeader/SearchResultTemplate/SearchResultModal/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import IconButton from '@atoms/IconButton';
3 | import LabeledButton from '@atoms/LabeledButton';
4 | import NoOverlayModal from '@molecules/NoOverlayModal';
5 |
6 | export const StyledSearchModal = styled(NoOverlayModal)`
7 | width: 45vw;
8 | min-width: 450px;
9 | height: 250px;
10 | border: 1px solid #e2e2e2;
11 | box-shadow: 0 0 0 1px var(--saf-0), 0 4px 12px 0 rgba(0, 0, 0, 0.12);
12 | background-color: #ffffff;
13 | border-radius: 6px;
14 | padding: 0.1px 0;
15 | `;
16 |
17 | export const Container = styled.div`
18 | display: flex;
19 | align-items: center;
20 | border-bottom: 1px solid #e2e2e2;
21 | `;
22 |
23 | export const StyledIconButton = styled(IconButton)`
24 | margin-left: 12px;
25 | margin-right: 12px;
26 | height: 24px;
27 | `;
28 |
29 | export const StyledLabeledButton = styled(LabeledButton)`
30 | font-size: 15px;
31 | color: #8e8e8e;
32 | padding-right: 12px;
33 | height: 24px;
34 | border-right: 1px solid #e2e2e2;
35 | `;
36 |
37 | export const StyledInput = styled.input`
38 | font-size: 15px;
39 | width: 40vw;
40 | min-width: 400px;
41 | height: 44px;
42 | padding: 8px 0;
43 | border: none;
44 |
45 | &::placeholder {
46 | // deep grey for placeholder
47 | color: #8e8e8e;
48 | }
49 | &:focus {
50 | outline: none;
51 | }
52 | `;
53 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceHeader/SearchResultTemplate/index.tsx:
--------------------------------------------------------------------------------
1 | import { UserHasWorkspace, Channel } from '@global/type';
2 | import React from 'react';
3 | import useKeyboardNavigator from '@hook/useKeyboardNavigator';
4 | import MemberElement from '@organisms/MemberElement';
5 | import ChannelElement from './ChannelElement';
6 |
7 | interface Props {
8 | matches: Array;
9 | setValue: React.Dispatch;
10 | }
11 |
12 | const SearchResultTemplate = ({ matches, setValue }: Props): JSX.Element => {
13 | const index = useKeyboardNavigator(matches, setValue);
14 |
15 | return (
16 |
17 | {matches.map((match, idx) => {
18 | return match.nickname ? (
19 |
24 | ) : (
25 |
30 | );
31 | })}
32 |
33 | );
34 | };
35 |
36 | export default SearchResultTemplate;
37 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceHeader/SearchResultTemplate/styles.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/components/organisms/WorkspaceHeader/SearchResultTemplate/styles.ts
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceHeader/WorkspaceHeaderMenuList/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import DivLists from '@atoms/DivLists';
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | width: inherit;
8 | align-items: center;
9 | `;
10 |
11 | export const RedDivLists = styled(DivLists)`
12 | color: red;
13 | `;
14 |
15 | export const GreyLine = styled.div`
16 | display: flex;
17 | width: inherit;
18 | height: 0.5px;
19 | border-top: 1px solid rgba(var(--sk_foreground_low, 29, 28, 29), 0.13);
20 | border-bottom: 0;
21 | margin-block-start: 0.5em;
22 | margin-block-end: 0.5em;
23 | margin: 3px 0 3px 0;
24 | `;
25 |
26 | export default Container;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/organisms/WorkspaceSidebar/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { theme } from 'styled-tools';
3 | import { defaultTheme } from '@global/style/theme';
4 | import { scrollIfHover } from '@global/style/mixin';
5 |
6 | export const Container = styled.div`
7 | width: 18vw;
8 | min-width: 18vw;
9 | height: 100vh;
10 | background-color: ${theme('backgroundColor', defaultTheme.backgroundColor)};
11 |
12 | ${scrollIfHover}
13 | `;
14 |
15 | export const MarginDiv = styled.div`
16 | margin-top: 20vh;
17 | `;
18 |
19 | export default Container;
20 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/BrowseChannel/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BrowseContent from '@organisms/BrowseContent';
3 | import WorkspaceTemplate from '@templates/Workspace';
4 |
5 | const BrowseChannel = (): JSX.Element => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default BrowseChannel;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/BrowseChannel/styles.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/components/pages/BrowseChannel/styles.ts
--------------------------------------------------------------------------------
/frontend/src/components/pages/Changepassword/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ChangePasswordContent from '@organisms/ChangePasswordContent';
3 | import titleBooslack from '@global/image/title_booslack.png';
4 | import { StyledImageBox } from '@pages/Login/style';
5 | import { Container, Layout, LabelContainer } from './style';
6 |
7 | const Changepassword = (): JSX.Element => {
8 | if (window.sessionStorage) window.sessionStorage.clear();
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Changepassword;
22 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Changepassword/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 |
4 | export const Layout = styled.div`
5 | height: 100vh;
6 | width: 100vw;
7 | display: flex;
8 | justify-content: center;
9 | flex-direction: column;
10 | align-items: center;
11 | `;
12 |
13 | export const Container = styled.div`
14 | height: 500px;
15 | width: 900px;
16 | display: flex;
17 | justify-content: space-between;
18 | flex-direction: column;
19 | align-items: center;
20 | `;
21 |
22 | export const LabelContainer = styled.div`
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | width: 300px;
27 | height: 100px;
28 | font-size: 60px;
29 | font-weight: 600;
30 | `;
31 |
32 | export const LabelColumn = styled.div`
33 | display: flex;
34 | justify-content: space-between;
35 | flex-direction: row;
36 | align-items: center;
37 | width: 500px;
38 | `;
39 |
40 | export const StyledImageBox = styled(ImageBox)``;
41 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/GeneratedCode/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory, useLocation } from 'react-router-dom';
3 | import CodeModal from '@molecules/CodeModal';
4 | import SubmitCodeForm from '@organisms/SubmitCodeForm';
5 | import CodeTemplate from '@templates/Code';
6 | import { Container } from './style';
7 |
8 | const GeneratedCode = (): JSX.Element => {
9 | const history = useHistory();
10 | const location = useLocation();
11 |
12 | const {
13 | data: { code, nextPage },
14 | } = location.state as { data: { code: string; nextPage: string } };
15 |
16 | return (
17 | <>
18 | history.push(nextPage)}
21 | code={code}
22 | >
23 |
24 |
30 |
31 |
32 |
33 | >
34 | );
35 | };
36 |
37 | export default GeneratedCode;
38 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/GeneratedCode/style.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-tabs */
2 | import styled from 'styled-components';
3 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
4 | import Label from '@atoms/Label';
5 | import { bouncing } from '@global/style/mixin';
6 |
7 | export const Container = styled.div``;
8 |
9 | export const StyledLabel = styled(Label)`
10 | font-size: 120px;
11 | color: #3881e7;
12 | `;
13 |
14 | export const StyledButton = styled(LabeledDefaultButton)`
15 | position: absolute;
16 | bottom: 3vh;
17 | height: 5vh;
18 | width: 10vw;
19 | min-width: 200px;
20 | min-height: 30px;
21 |
22 | ${bouncing}
23 | `;
24 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/GetError/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import Label from '@atoms/Label';
4 | import { Container, StyledLabel, StyledButton } from './style';
5 |
6 | const GetError = (): JSX.Element => {
7 | const history = useHistory();
8 | return (
9 |
10 |
11 |
12 | history.goBack()} />
13 |
14 | );
15 | };
16 |
17 | export default GetError;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/GetError/style.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-tabs */
2 | import styled from 'styled-components';
3 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
4 | import ImageBox from '@atoms/ImageBox';
5 | import Label from '@atoms/Label';
6 | import { bouncing, flexAlignCenter } from '@global/style/mixin';
7 |
8 | export const Container = styled.div`
9 | font-family: Arial, sans-serif;
10 | font-weight: bold;
11 | font-size: 14px;
12 | height: 100vh;
13 | width: 100vw;
14 | ${flexAlignCenter}
15 | align-items: center;
16 | & > * {
17 | margin-bottom: 5vh;
18 | }
19 | `;
20 |
21 | export const StyledImageBox = styled(ImageBox)`
22 | width: 100vw;
23 | height: 100vh;
24 | `;
25 |
26 | export const StyledLabel = styled(Label)`
27 | font-size: 40px;
28 | color: #3881e7;
29 | `;
30 |
31 | export const StyledButton = styled(LabeledDefaultButton)`
32 | position: absolute;
33 | bottom: 3vh;
34 | height: 5vh;
35 | width: 10vw;
36 | min-width: 200px;
37 | min-height: 30px;
38 |
39 | background-color: #5c8dcf;
40 | ${bouncing}
41 | `;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Loading/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import { StyledButton } from '@pages/NotFound/style';
4 | import { Container, LoadingLoader, StyledLabel } from './style';
5 |
6 | const Loading = (): JSX.Element => {
7 | const history = useHistory();
8 | return (
9 |
10 |
11 |
12 | history.goBack()} />
13 |
14 | );
15 | };
16 |
17 | export default Loading;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Loading/style.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-tabs */
2 | import styled from 'styled-components';
3 | import { DotLoader } from 'react-spinners';
4 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
5 | import Label from '@atoms/Label';
6 | import { bouncing, flexAlignCenter } from '@global/style/mixin';
7 |
8 | export const Container = styled.div`
9 | font-family: Arial, sans-serif;
10 | font-weight: bold;
11 | font-size: 14px;
12 | height: 100vh;
13 | width: 100vw;
14 | ${flexAlignCenter}
15 | align-items: center;
16 | & > * {
17 | margin-bottom: 5vh;
18 | }
19 | `;
20 |
21 | export const StyledLabel = styled(Label)`
22 | padding-top: 30px;
23 | font-size: 22px;
24 | `;
25 |
26 | export const StyledButton = styled(LabeledDefaultButton)`
27 | position: absolute;
28 | bottom: 3vh;
29 | height: 5vh;
30 | width: 10vw;
31 | min-width: 200px;
32 | min-height: 30px;
33 |
34 | background-color: #5c8dcf;
35 | ${bouncing}
36 | `;
37 |
38 | export const LoadingLoader = styled(DotLoader)``;
39 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LoginContent from '@organisms/LoginContent';
3 | import titleBooslack from '@global/image/title_booslack.png';
4 | import { Container, Layout, LabelContainer, StyledImageBox } from './style';
5 |
6 | const Login = (): JSX.Element => {
7 | if (window.sessionStorage) window.sessionStorage.clear();
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default Login;
21 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Login/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 |
4 | export const Layout = styled.div`
5 | height: 100vh;
6 | width: 100vw;
7 | display: flex;
8 | justify-content: center;
9 | flex-direction: column;
10 | align-items: center;
11 | `;
12 |
13 | export const Container = styled.div`
14 | height: 500px;
15 | width: 900px;
16 | display: flex;
17 | justify-content: space-between;
18 | flex-direction: column;
19 | align-items: center;
20 | `;
21 |
22 | export const LabelContainer = styled.div`
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | width: 300px;
27 | height: 100px;
28 | font-size: 60px;
29 | font-weight: 600;
30 | `;
31 |
32 | export const LabelColumn = styled.div`
33 | display: flex;
34 | justify-content: space-between;
35 | flex-direction: row;
36 | align-items: center;
37 | width: 500px;
38 | `;
39 |
40 | export const StyledImageBox = styled(ImageBox)``;
41 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/NotFound/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import Label from '@atoms/Label';
4 | import { Container, StyledLabel, StyledButton } from './style';
5 |
6 | const NotFound = (): JSX.Element => {
7 | const history = useHistory();
8 | return (
9 |
10 |
11 |
12 | history.goBack()} />
13 |
14 | );
15 | };
16 |
17 | export default NotFound;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/NotFound/style.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-tabs */
2 | import styled from 'styled-components';
3 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
4 | import ImageBox from '@atoms/ImageBox';
5 | import Label from '@atoms/Label';
6 | import { bouncing, flexAlignCenter } from '@global/style/mixin';
7 |
8 | export const Container = styled.div`
9 | font-family: Arial, sans-serif;
10 | font-weight: bold;
11 | font-size: 14px;
12 | height: 100vh;
13 | width: 100vw;
14 | ${flexAlignCenter}
15 | align-items: center;
16 | & > * {
17 | margin-bottom: 5vh;
18 | }
19 | `;
20 |
21 | export const StyledImageBox = styled(ImageBox)`
22 | width: 100vw;
23 | height: 100vh;
24 | `;
25 |
26 | export const StyledLabel = styled(Label)`
27 | font-size: 120px;
28 | color: #3881e7;
29 | `;
30 |
31 | export const StyledButton = styled(LabeledDefaultButton)`
32 | position: absolute;
33 | bottom: 3vh;
34 | height: 5vh;
35 | width: 10vw;
36 | min-width: 200px;
37 | min-height: 30px;
38 |
39 | background-color: #5c8dcf;
40 | ${bouncing}
41 | `;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/NotLogin/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import Label from '@atoms/Label';
4 | import {
5 | Container,
6 | StyledLabel,
7 | StyledButton,
8 | StyledButtonColumn,
9 | } from './style';
10 |
11 | const NotLogin = (): JSX.Element => {
12 | const history = useHistory();
13 | return (
14 |
15 |
16 |
17 |
18 | history.push('/login')}
21 | />
22 | history.goBack()} />
23 |
24 |
25 | );
26 | };
27 |
28 | export default NotLogin;
29 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/NotLogin/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
3 | import ImageBox from '@atoms/ImageBox';
4 | import Label from '@atoms/Label';
5 | import { bouncing, flexAlignCenter } from '@global/style/mixin';
6 |
7 | export const Container = styled.div`
8 | font-family: Arial, sans-serif;
9 | font-weight: bold;
10 | font-size: 14px;
11 | height: 100vh;
12 | width: 100vw;
13 | ${flexAlignCenter}
14 | & > * {
15 | margin-bottom: 5vh;
16 | }
17 | `;
18 |
19 | export const StyledImageBox = styled(ImageBox)`
20 | width: 100vw;
21 | height: 100vh;
22 | `;
23 |
24 | export const StyledLabel = styled(Label)`
25 | font-size: 120px;
26 | color: #3881e7;
27 | `;
28 |
29 | export const StyledButtonColumn = styled.div`
30 | position: absolute;
31 | bottom: 3vh;
32 | height: 5vh;
33 | display: flex;
34 | justify-content: space-between;
35 | width: 22vw;
36 | min-width: 440px;
37 | `;
38 |
39 | export const StyledButton = styled(LabeledDefaultButton)`\
40 | width:10vw;
41 | min-width:200px;
42 | min-height:30px;
43 |
44 | background-color: #5c8dcf;
45 | ${bouncing}
46 | `;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/NotLogout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import Label from '@atoms/Label';
4 | import { logout } from '@global/util/auth';
5 | import {
6 | Container,
7 | StyledLabel,
8 | StyledButton,
9 | StyledButtonColumn,
10 | } from './style';
11 |
12 | const NotLogout = (): JSX.Element => {
13 | const history = useHistory();
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | history.goBack()} />
21 |
22 |
23 | );
24 | };
25 |
26 | export default NotLogout;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/NotLogout/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
3 | import ImageBox from '@atoms/ImageBox';
4 | import Label from '@atoms/Label';
5 | import { bouncing, flexAlignCenter } from '@global/style/mixin';
6 |
7 | export const Container = styled.div`
8 | font-family: Arial, sans-serif;
9 | font-weight: bold;
10 | font-size: 14px;
11 | height: 100vh;
12 | width: 100vw;
13 | ${flexAlignCenter}
14 | & > * {
15 | margin-bottom: 5vh;
16 | }
17 | `;
18 |
19 | export const StyledImageBox = styled(ImageBox)`
20 | width: 100vw;
21 | height: 100vh;
22 | `;
23 |
24 | export const StyledLabel = styled(Label)`
25 | font-size: 120px;
26 | color: #3881e7;
27 | `;
28 |
29 | export const StyledButtonColumn = styled.div`
30 | position: absolute;
31 | bottom: 3vh;
32 | height: 5vh;
33 | display: flex;
34 | justify-content: space-between;
35 | width: 22vw;
36 | min-width: 440px;
37 | `;
38 |
39 | export const StyledButton = styled(LabeledDefaultButton)`\
40 | width:10vw;
41 | min-width:200px;
42 | min-height:30px;
43 |
44 | background-color: #5c8dcf;
45 | ${bouncing}
46 | `;
47 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/SetupTeam/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useResetRecoilState } from 'recoil';
3 | import CodeModal from '@molecules/CodeModal';
4 | import SetupTeamQuestions from '@organisms/SetupTeamQuestions';
5 | import EmptyWorkspaceTemplate from '@templates/EmptyWorkspace';
6 | import { codeModalState } from '@state/modal';
7 |
8 | const SetupTeam = (): JSX.Element => {
9 | const resetModalState = useResetRecoilState(codeModalState);
10 |
11 | useEffect(() => {
12 | return () => {
13 | resetModalState();
14 | };
15 | });
16 |
17 | return (
18 | <>
19 |
20 |
21 |
22 |
23 | >
24 | );
25 | };
26 |
27 | export default SetupTeam;
28 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/SetupTeam/style.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/components/pages/SetupTeam/style.ts
--------------------------------------------------------------------------------
/frontend/src/components/pages/Signup/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SignupContent from '@organisms/SignupContent';
3 | import titleBooslack from '@global/image/title_booslack.png';
4 | import { StyledImageBox } from '@pages/Login/style';
5 | import { Container, Layout, LabelContainer } from './style';
6 |
7 | const Signup = (): JSX.Element => {
8 | if (window.sessionStorage) window.sessionStorage.clear();
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Signup;
22 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Signup/style.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import ImageBox from '@atoms/ImageBox';
3 |
4 | export const Layout = styled.div`
5 | height: 100vh;
6 | width: 100vw;
7 | display: flex;
8 | justify-content: center;
9 | flex-direction: column;
10 | align-items: center;
11 | `;
12 |
13 | export const Container = styled.div`
14 | height: 500px;
15 | width: 900px;
16 | display: flex;
17 | justify-content: space-between;
18 | flex-direction: column;
19 | align-items: center;
20 | `;
21 |
22 | export const LabelContainer = styled.div`
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | width: 300px;
27 | height: 100px;
28 | font-size: 60px;
29 | font-weight: 600;
30 | `;
31 |
32 | export const LabelColumn = styled.div`
33 | display: flex;
34 | justify-content: space-between;
35 | flex-direction: row;
36 | align-items: center;
37 | width: 500px;
38 | `;
39 |
40 | export const StyledImageBox = styled(ImageBox)``;
41 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Workspace/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WorkspaceContent from '@organisms/WorkspaceContent';
3 | import WorkspaceTemplate from '@templates/Workspace';
4 |
5 | const Workspace = (): JSX.Element => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default Workspace;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/Workspace/styles.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/components/pages/Workspace/styles.ts
--------------------------------------------------------------------------------
/frontend/src/components/pages/WorkspaceList/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useResetRecoilState } from 'recoil';
3 | import WorkSpaceListContent from '@organisms/WorkspaceListContent';
4 | import WorkspaceListTemplate from '@templates/WorkspaceList';
5 | import { replyToggleState } from '@state/workspace';
6 | import { Container } from './styles';
7 |
8 | const WorkspaceList = (): JSX.Element => {
9 | const setReplyToggleState = useResetRecoilState(replyToggleState);
10 |
11 | useEffect(() => {
12 | setReplyToggleState();
13 | }, []);
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default WorkspaceList;
25 |
--------------------------------------------------------------------------------
/frontend/src/components/pages/WorkspaceList/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { theme } from 'styled-tools';
3 | import { defaultTheme } from '@global/style/theme';
4 |
5 | export const Container = styled.div`
6 | height: 100vh;
7 | width: 100vw;
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 |
12 | background-color: ${theme('backgroundColor', defaultTheme.backgroundColor)};
13 | `;
14 |
15 | export default Container;
16 |
--------------------------------------------------------------------------------
/frontend/src/components/templates/Code/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useResetRecoilState } from 'recoil';
3 | import Label from '@atoms/Label';
4 | import { codeModalState } from '@state/modal';
5 | import { copyText } from '@global/util';
6 | import { Container, StyledLabel, StyledButton, StyledDiv } from './styles';
7 |
8 | interface Props {
9 | onClick: () => void;
10 | children: JSX.Element;
11 | text: string;
12 | code: string;
13 | }
14 |
15 | const CodeTemplate = ({
16 | children,
17 | onClick,
18 | text,
19 | code,
20 | }: Props): JSX.Element => {
21 | const resetModalState = useResetRecoilState(codeModalState);
22 |
23 | useEffect(() => {
24 | return () => {
25 | resetModalState();
26 | };
27 | });
28 |
29 | return (
30 |
31 |
32 |
33 |
34 | {children}
35 |
36 | copyText(code)} />
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default CodeTemplate;
44 |
--------------------------------------------------------------------------------
/frontend/src/components/templates/Code/styles.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-tabs */
2 | import styled from 'styled-components';
3 | import LabeledDefaultButton from '@atoms/LabeledDefaultButton';
4 | import Label from '@atoms/Label';
5 | import { ThemeButton, bouncing, RoundScrollBar } from '@global/style/mixin';
6 |
7 | export const Container = styled.div`
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 |
12 | font-family: Arial, sans-serif;
13 | font-weight: bold;
14 | font-size: 14px;
15 | height: 100vh;
16 | width: 100vw;
17 | align-items: center;
18 | & > * {
19 | margin-bottom: 5vh;
20 | }
21 |
22 | overflow-x: hidden;
23 | overflow-y: scroll;
24 | ${RoundScrollBar}
25 | `;
26 |
27 | export const StyledLabel = styled(Label)`
28 | margin-top: 5vh;
29 | font-size: 55px;
30 | `;
31 |
32 | export const StyledButton = styled(LabeledDefaultButton)`
33 | min-width: 300px;
34 | min-height: 200px;
35 | height: 60px;
36 | width: 100px;
37 | margin: 1vh 5vw 1vh 5vw;
38 | ${ThemeButton}
39 | ${bouncing}
40 | `;
41 |
42 | export const StyledDiv = styled.div`
43 | display: flex;
44 |
45 | width: 100vw;
46 | height: 20vh;
47 |
48 | flex-direction: row;
49 | justify-content: center;
50 |
51 | margin-top: 30px;
52 | `;
53 |
--------------------------------------------------------------------------------
/frontend/src/components/templates/EmptyWorkspace/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WorkspaceHeaderContainer from '@organisms/WorkspaceHeader/styles';
3 | import WorkspaceSidebarContainer from '@organisms/WorkspaceSidebar/style';
4 | import { RowDiv } from './styles';
5 |
6 | interface Props {
7 | children: JSX.Element;
8 | }
9 |
10 | const EmptyWorkspaceTemplate = ({ children }: Props): JSX.Element => {
11 | return (
12 | <>
13 |
14 |
15 |
16 | {children}
17 |
18 | >
19 | );
20 | };
21 |
22 | export default EmptyWorkspaceTemplate;
23 |
--------------------------------------------------------------------------------
/frontend/src/components/templates/EmptyWorkspace/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RowDiv = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | width: 100vw;
7 | height: 100vh;
8 | `;
9 |
10 | export default RowDiv;
11 |
--------------------------------------------------------------------------------
/frontend/src/components/templates/Workspace/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const RowDiv = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | width: 100vw;
7 | height: 100%;
8 | `;
9 |
10 | export default RowDiv;
11 |
--------------------------------------------------------------------------------
/frontend/src/components/templates/WorkspaceList/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import { logout } from '@global/util/auth';
4 | import {
5 | StyledLabel,
6 | Container,
7 | StyledLabeledButton,
8 | StyledHeader,
9 | } from './styles';
10 |
11 | interface Props {
12 | children: JSX.Element;
13 | }
14 |
15 | const WorkspaceListTemplate = ({ children }: Props): JSX.Element => {
16 | const history = useHistory();
17 |
18 | const Title: JSX.Element = ;
19 |
20 | const RightButtonDiv: JSX.Element = (
21 |
22 | history.push('/invitecode')}
25 | />
26 | history.push('/setupteam')}
29 | />
30 |
31 |
32 | );
33 |
34 | return (
35 |
36 |
37 | {children}
38 |
39 | );
40 | };
41 |
42 | export default WorkspaceListTemplate;
43 |
--------------------------------------------------------------------------------
/frontend/src/enum/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/naming-convention */
2 | export const PAGE_LIMIT_COUNT = 10;
3 |
4 | export const STATUSCODES = {
5 | BADREQUEST: 400,
6 | CONFLICT: 409,
7 | };
8 |
9 | export enum BUTTON_SIZE {
10 | width = 81.92,
11 | height = 30,
12 | color = 'black',
13 | backgroundColor = 'white',
14 | }
15 |
16 | export enum BROWSER_CHANNEL_LIST_SIZE {
17 | width = 75,
18 | height = 5,
19 | }
20 | export const CHANNELTYPE = {
21 | 1: '🔒',
22 | 0: '#',
23 | } as const;
24 |
--------------------------------------------------------------------------------
/frontend/src/global/api/login/index.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import API from '@global/api';
3 |
4 | export const getUserInfo = async () => {
5 | const res = await axios.get(API.get.login);
6 | return res.data;
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/global/api/workspace/index.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import API from '@global/api';
3 |
4 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
5 | export const deleteUserFromWorkspace = async (workspaceId: number | string) => {
6 | return axios.delete(`${API.delete.userHasWorkspace.id}/${workspaceId}`);
7 | };
8 |
9 | export const getUserHasWorkspace = async (
10 | userId: string,
11 | workspaceId: string,
12 | ) => {
13 | const res = await axios.get(
14 | `${API.get.userHasWorkspaces}?userId=${userId}&workspaceId=${workspaceId}`,
15 | );
16 |
17 | return res.data.userHasWorkspace;
18 | };
19 |
--------------------------------------------------------------------------------
/frontend/src/global/image/black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/black.png
--------------------------------------------------------------------------------
/frontend/src/global/image/blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/blue.png
--------------------------------------------------------------------------------
/frontend/src/global/image/default_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/default_account.png
--------------------------------------------------------------------------------
/frontend/src/global/image/title_booslack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/title_booslack.png
--------------------------------------------------------------------------------
/frontend/src/global/image/violet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/violet.png
--------------------------------------------------------------------------------
/frontend/src/global/image/wavingHandFromSlack.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/wavingHandFromSlack.gif
--------------------------------------------------------------------------------
/frontend/src/global/image/workspace_default_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/workspace_default_image.png
--------------------------------------------------------------------------------
/frontend/src/global/image/xMark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/global/image/yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/web06-booslack/df85700401e6635c2a7ddd35e75552a3f314fe7e/frontend/src/global/image/yellow.png
--------------------------------------------------------------------------------
/frontend/src/global/module.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.jpg';
2 | declare module '*.png';
3 | declare module '*.svg';
4 | declare module '*.gif';
5 |
--------------------------------------------------------------------------------
/frontend/src/global/options/index.ts:
--------------------------------------------------------------------------------
1 | const SECOND = 1000;
2 | const HOUR = 60 * 60;
3 |
4 | export const socketOption = {
5 | staleTime: Infinity,
6 | refetchOnWindowFocus: false,
7 | suspense: true,
8 | };
9 |
10 | export const hourlyExpirationOption = {
11 | staleTime: SECOND * 10,
12 | cacheTime: SECOND * HOUR,
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/global/util/auth.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const logout = async (): Promise => {
4 | try {
5 | const response = await axios({
6 | method: 'GET',
7 | url: '/api/login/logout',
8 | });
9 | if (response.data) window.location.href = '/login';
10 | } catch (error) {
11 | throw new Error('logout error');
12 | }
13 | };
14 |
15 | export const checkUser = async (
16 | username: string,
17 | password: string,
18 | ): Promise => {
19 | try {
20 | const response = await axios({
21 | method: 'GET',
22 | url: '/api/users/find',
23 | params: {
24 | username,
25 | password,
26 | },
27 | });
28 | return response.data;
29 | } catch (error) {
30 | throw new Error('check user error');
31 | }
32 | };
33 |
34 | export const checkUserLogin = async (
35 | username: string,
36 | password: string,
37 | ): Promise => {
38 | try {
39 | const response = await axios({
40 | method: 'POST',
41 | url: '/api/login/login',
42 | data: {
43 | username,
44 | password,
45 | },
46 | });
47 | return response.data;
48 | } catch (error) {
49 | throw new Error('check user error');
50 | }
51 | };
52 |
53 | export default logout;
54 |
--------------------------------------------------------------------------------
/frontend/src/global/util/file.ts:
--------------------------------------------------------------------------------
1 | export const MAX_FILE_SIZE = 200000000;
2 |
--------------------------------------------------------------------------------
/frontend/src/global/util/message.ts:
--------------------------------------------------------------------------------
1 | import { deleteReply } from '@global/api/reply';
2 | import { deleteMessage } from '@global/api/thread';
3 | import { Message } from '@global/type';
4 | import { IreplyToggle } from '@state/workspace';
5 | import { SetterOrUpdater } from 'recoil';
6 | import { Socket } from 'socket.io-client';
7 |
8 | export const removeMessage = (
9 | messageObject: Message,
10 | replyToggle: IreplyToggle,
11 | setReplyToggle: SetterOrUpdater,
12 | channelId: string,
13 | socket: Socket,
14 | ): void => {
15 | // 메시지 오브젝트가 스레드 아이디가 없으면 쓰레드라는 것임
16 | if (!messageObject.threadId) {
17 | // 지우려는 스레드가 현재 사이드에 띄워져 있는거면 닫아야함
18 | if (replyToggle.message?.id === messageObject.id) {
19 | setReplyToggle({
20 | isOpened: false,
21 | message: undefined,
22 | channelName: undefined,
23 | });
24 | }
25 | deleteMessage(messageObject.id, channelId, socket);
26 | } else {
27 | deleteReply(messageObject.id, replyToggle?.message.id, channelId, socket);
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/frontend/src/global/util/reactQueryUtil.ts:
--------------------------------------------------------------------------------
1 | import { flatMap } from 'lodash';
2 |
3 | export const queryFlatMap = (
4 | data: { pages: unknown[] },
5 | key: string,
6 | ): T[] => {
7 | return flatMap(data?.pages, (element) => element[key]);
8 | };
9 |
--------------------------------------------------------------------------------
/frontend/src/global/util/reaction.ts:
--------------------------------------------------------------------------------
1 | import { postReaction, postReplyReaction } from '@global/api/reaction';
2 | import { IUserState } from '@state/user';
3 |
4 | export const onEmojiSet = (
5 | user: IUserState,
6 | messageId: string,
7 | parentThreadId: undefined | string,
8 | channelId: string,
9 | ) => {
10 | return (emoji: string): void => {
11 | if (parentThreadId !== undefined) {
12 | postReplyReaction(
13 | user.userHasWorkspaceId,
14 | channelId,
15 | emoji,
16 | messageId,
17 | parentThreadId,
18 | user.socket,
19 | );
20 | } else {
21 | postReaction(
22 | user.userHasWorkspaceId,
23 | channelId,
24 | emoji,
25 | messageId,
26 | user.socket,
27 | );
28 | }
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/frontend/src/global/util/reply.ts:
--------------------------------------------------------------------------------
1 | import { Message } from '@global/type';
2 | import { IreplyToggle } from '@state/workspace';
3 |
4 | export const checkIsInReplySide = (
5 | messageObject: Message,
6 | replyToggle: IreplyToggle,
7 | ): boolean => {
8 | // 현재 메시지 객체의 스레드 아이디가 존재한다면 리플라이이고
9 | // 없더라도 replyToggle.message?.id === messageObject.id 라면 오른쪽 사이드바에 띄어져 있는 스레드이다.
10 | return (
11 | messageObject.threadId !== undefined ||
12 | replyToggle.message?.id === messageObject.id
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/src/global/util/transfromDate.ts:
--------------------------------------------------------------------------------
1 | function makeTwoZeroString(number) {
2 | const num = Number(number);
3 | if (number >= 0 && num < 10) return `0${num}`;
4 | return `${num}`;
5 | }
6 |
7 | export const transfromDate = (timestamp) => {
8 | const date = new Date(timestamp);
9 | return `${makeTwoZeroString(date.getFullYear())}-${makeTwoZeroString(
10 | date.getMonth() + 1,
11 | )}-${makeTwoZeroString(date.getDate())} ${makeTwoZeroString(
12 | date.getHours(),
13 | )}:${makeTwoZeroString(date.getMinutes())}:${makeTwoZeroString(
14 | date.getSeconds(),
15 | )}`;
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/src/global/util/validatePassword.ts:
--------------------------------------------------------------------------------
1 | export const validatePassword = (password: string): string => {
2 | const pattern1 = /[0-9]/;
3 | const pattern2 = /[a-zA-Z]/;
4 | const pattern3 = /[`~!@#$%^&*()_+|<>?:{}]/;
5 | if (password.search(/\s/) !== -1) return '공백이 존재하면 안됩니다.';
6 | if (!pattern1.test(password)) {
7 | return '비밀번호는 숫자가 하나 이상 구성되어야 합니다.';
8 | }
9 | if (!pattern3.test(password)) {
10 | return '비밀번호는 특수문자가 하나 이상 구성되어야 합니다.';
11 | }
12 | if (!pattern2.test(password)) {
13 | return '비밀번호는 영어 대문자/소문자, 숫자, 특수문자로 구성되어야 합니다.';
14 | }
15 | return 'success';
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAbstract.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useQuery } from 'react-query';
3 | import { PREFIX } from '@global/api';
4 | import { hourlyExpirationOption } from '@global/options';
5 |
6 | export const useAbstractQuery = (
7 | name: string,
8 | id: string,
9 | path: undefined | string = undefined,
10 | option: typeof Object = undefined,
11 | ) => {
12 | const realPath = path || `${name}s`;
13 | return useQuery(
14 | [name, id],
15 | async () => {
16 | const res = await axios.get(`${PREFIX}/${realPath}/${id}`);
17 | return res?.data[name];
18 | },
19 | {
20 | ...hourlyExpirationOption,
21 | ...option,
22 | },
23 | );
24 | };
25 |
26 | export const useAbstractQueryList = (
27 | name: string,
28 | id: string,
29 | params: any,
30 | path: undefined | string = undefined,
31 | option: typeof Object = undefined,
32 | ) => {
33 | const realPath = path || `${name}`;
34 |
35 | return useQuery(
36 | [name, id],
37 | async () => {
38 | const res = await axios.get(`${PREFIX}/${realPath}`, {
39 | params: { ...params },
40 | });
41 | return res?.data[name];
42 | },
43 | {
44 | ...hourlyExpirationOption,
45 | ...option,
46 | },
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useChannels.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query';
2 | import axios from 'axios';
3 | import API from '@global/api';
4 | import { socketOption } from '@global/options';
5 |
6 | export const useChannelListQuery = (userId: string, workspaceId: string) => {
7 | return useQuery(
8 | ['channels', workspaceId, userId],
9 | async () => {
10 | const res = await axios.get(
11 | `${API.get.channel.userChannels}?userId=${userId}&workspaceId=${workspaceId}`,
12 | );
13 | return res.data.channels;
14 | },
15 | {
16 | ...socketOption,
17 | enabled: !!workspaceId,
18 | },
19 | );
20 | };
21 |
22 | export const useChannelQuery = (id: string) => {
23 | return useQuery(
24 | ['channel', id],
25 | async () => {
26 | const res = await axios.get(`${API.get.channel.base}/${id}`);
27 | return res.data.channel;
28 | },
29 | socketOption,
30 | );
31 | };
32 |
33 | // literally all channels workspace
34 | export const useChannelsQuery = (workspaceId: string) => {
35 | return useQuery(
36 | ['channels', 'all', workspaceId],
37 | async () => {
38 | const res = await axios.get(
39 | `${API.get.channel.base}/all?workspaceId=${workspaceId}`,
40 | );
41 | return res.data.channels;
42 | },
43 | socketOption,
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useInfinityPage.ts:
--------------------------------------------------------------------------------
1 | import { useInfiniteQuery, QueryFunction } from 'react-query';
2 |
3 | interface Icursor {
4 | nextCursor: number;
5 | prevCursor: number;
6 | }
7 |
8 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
9 | const useInfinityScroll = (
10 | key: string,
11 | axiosFunction: QueryFunction,
12 | ) => {
13 | return useInfiniteQuery(`infinity${key}`, axiosFunction, {
14 | getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
15 | getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
16 | suspense: true,
17 | });
18 | };
19 |
20 | export default useInfinityScroll;
21 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useInputs.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useInputs = (initialData) => {
4 | const [data, setData] = useState(initialData);
5 |
6 | const onChange = (e) => {
7 | const { name, value } = e.target;
8 | setData((prevState) => ({
9 | ...prevState,
10 | [name]: value,
11 | }));
12 | };
13 | const clear = () => setData(initialData);
14 |
15 | return [data, onChange, clear];
16 | };
17 |
18 | export default useInputs;
19 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useKeyboardNavigator.ts:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const useKeyboardNavigator = (
4 | array: any[],
5 | setValue: React.Dispatch,
6 | ): number => {
7 | const [index, setIndex] = useState(0);
8 | const [selected, setSelected] = useState(false);
9 |
10 | const handleKeyDown = (e) => {
11 | if (e.key === 'ArrowDown') {
12 | e.preventDefault();
13 | setIndex((prevIndex) => {
14 | return prevIndex === array.length - 1 ? 0 : prevIndex + 1;
15 | });
16 | } else if (e.key === 'ArrowUp') {
17 | e.preventDefault();
18 | setIndex((prevIndex) => {
19 | return prevIndex === 0 ? array.length - 1 : prevIndex - 1;
20 | });
21 | } else if (e.key === 'Enter') {
22 | e.preventDefault();
23 | setSelected(true);
24 | }
25 | };
26 |
27 | useEffect(() => {
28 | window.addEventListener('keydown', handleKeyDown);
29 | setIndex(0);
30 | return () => {
31 | window.removeEventListener('keydown', handleKeyDown);
32 | };
33 | }, [array]);
34 |
35 | useEffect(() => {
36 | if (!selected) return;
37 | setValue(array[index]);
38 | setSelected(false);
39 | }, [selected]);
40 |
41 | return index;
42 | };
43 |
44 | export default useKeyboardNavigator;
45 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useRefLocate.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, RefObject } from 'react';
2 |
3 | const useRefLocate = (
4 | customRef: RefObject,
5 | timer = 100,
6 | ): [number, number] => {
7 | const [xWidth, setWidth] = useState(null);
8 | const [yHeight, setHeight] = useState(null);
9 | const [throttle, setThrottle] = useState(false);
10 |
11 | const handleResize = () => {
12 | if (throttle) return;
13 | if (!customRef) return;
14 |
15 | setThrottle(true);
16 | setTimeout(() => {
17 | setWidth(customRef?.current?.getBoundingClientRect()?.x);
18 | setHeight(customRef?.current?.getBoundingClientRect()?.y);
19 | setThrottle(false);
20 | }, timer);
21 | };
22 |
23 | useEffect(() => {
24 | window.addEventListener('resize', handleResize);
25 | handleResize();
26 | return () => {
27 | window.removeEventListener('resize', handleResize);
28 | };
29 | }, []);
30 |
31 | return [xWidth, yHeight];
32 | };
33 |
34 | export default useRefLocate;
35 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useReplys.ts:
--------------------------------------------------------------------------------
1 | import { useAbstractQueryList } from './useAbstract';
2 |
3 | export const useReplyListQuery = (_threadId: string) => {
4 | const threadId = String(_threadId);
5 | return useAbstractQueryList('replys', threadId, { threadId });
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useSocket.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { QueryClient } from 'react-query';
3 | import { Socket } from 'socket.io-client';
4 |
5 | export const initializeSocket = (socket: Socket, queryClient: QueryClient) => {
6 | useEffect(() => {
7 | if (!socket) return;
8 |
9 | socket.on('threads', (_channelId: string, _threadId: string) => {
10 | const channelId = String(_channelId);
11 | const threadId = String(_threadId);
12 | queryClient.invalidateQueries(['threads', channelId], {
13 | refetchActive: true,
14 | });
15 |
16 | queryClient.invalidateQueries(['thread', threadId], {
17 | refetchActive: true,
18 | });
19 |
20 | queryClient.invalidateQueries(['replys', threadId], {
21 | refetchActive: true,
22 | });
23 | });
24 |
25 | socket.on('channels', (_workspaceId) => {
26 | const workspaceId = String(_workspaceId);
27 | queryClient.invalidateQueries(['channels', workspaceId], {
28 | refetchActive: true,
29 | });
30 |
31 | queryClient.invalidateQueries(['channels', 'all', workspaceId], {
32 | refetchActive: true,
33 | });
34 | });
35 |
36 | socket.on('channel', (_channelId) => {
37 | const channelId = String(_channelId);
38 | queryClient.invalidateQueries(['channel', channelId], {
39 | refetchActive: true,
40 | });
41 | });
42 |
43 | // workspaceId가 변경되어 unmount될 시 소켓을 닫으면서 모든 리스너도 함께 삭제됨
44 | }, [socket, queryClient]);
45 | };
46 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useThreads.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useInfiniteQuery } from 'react-query';
3 | import { useAbstractQuery } from '@hook/useAbstract';
4 | import API from '@global/api';
5 | import { socketOption } from '@global/options';
6 |
7 | const MAX_THREADS = 1000000000;
8 |
9 | export const useThreadQuery = (_threadId: string) => {
10 | const threadId = String(_threadId);
11 | return useAbstractQuery('thread', threadId);
12 | };
13 |
14 | export const usePartialThreadListQuery = (_channelId: string) => {
15 | const channelId = String(_channelId);
16 | return useInfiniteQuery(
17 | ['threads', channelId],
18 | async ({ pageParam = MAX_THREADS }) => {
19 | const res = await axios.get(
20 | `${API.get.threads}?channelId=${channelId}&cursor=${pageParam}`,
21 | );
22 | return res.data.threads;
23 | },
24 | {
25 | ...socketOption,
26 | getPreviousPageParam: (firstPage) => firstPage[0]?.id ?? false,
27 | },
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useUsers.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query';
2 | import axios from 'axios';
3 | import API from '@global/api';
4 | import { hourlyExpirationOption } from '@global/options';
5 |
6 | export const useUserListWithChannelInfoQuery = (
7 | _workspaceId: string,
8 | _channelId: string,
9 | ) => {
10 | const workspaceId = String(_workspaceId);
11 | const channelId = String(_channelId);
12 | return useQuery(
13 | ['users', channelId],
14 | async () => {
15 | const res = await axios.get(
16 | `${API.get.users.workspaces}?workspaceId=${workspaceId}&channelId=${channelId}`,
17 | );
18 | return res.data.users;
19 | },
20 | {
21 | ...hourlyExpirationOption,
22 | suspense: true,
23 | },
24 | );
25 | };
26 |
27 | export const useUsersQuery = (_workspaceId: string) => {
28 | const workspaceId = String(_workspaceId);
29 | return useQuery(
30 | ['users', 'all', workspaceId],
31 | async () => {
32 | const res = await axios.get(
33 | `${API.get.userHasWorkspaces}/all?workspaceId=${workspaceId}`,
34 | );
35 | return res.data.userHasWorkspaces;
36 | },
37 | {
38 | ...hourlyExpirationOption,
39 | suspense: true,
40 | },
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useWorkspace.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query';
2 | import axios from 'axios';
3 | import API from '@global/api';
4 | import { hourlyExpirationOption } from '@global/options';
5 |
6 | export const useWorkspaceQuery = (_workspaceId: string) => {
7 | const workspaceId = String(_workspaceId);
8 | return useQuery(
9 | ['workspace', workspaceId],
10 | async () => {
11 | const res = await axios.get(`${API.get.workspace.base}/${workspaceId}`);
12 | return res.data.workspace;
13 | },
14 | {
15 | ...hourlyExpirationOption,
16 | enabled: !!workspaceId,
17 | },
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/frontend/src/routes/PrivateRoute.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect, Route } from 'react-router-dom';
3 | import { useRecoilValue } from 'recoil';
4 | import userState from '@state/user';
5 |
6 | interface Props {
7 | component: () => JSX.Element;
8 | path: string;
9 | }
10 |
11 | const PrivateRoute = ({
12 | component: Component,
13 | path,
14 | ...rest
15 | }: Props): JSX.Element => {
16 | const user = useRecoilValue(userState);
17 |
18 | return (
19 |
22 | user ? :
23 | }
24 | />
25 | );
26 | };
27 |
28 | export default PrivateRoute;
29 |
--------------------------------------------------------------------------------
/frontend/src/routes/PublicRoute.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect } from 'react-router-dom';
3 | import { useRecoilValue } from 'recoil';
4 | import userState from '@state/user';
5 |
6 | interface Props {
7 | component: () => JSX.Element;
8 | path: string;
9 | }
10 |
11 | const PublicRoute = ({
12 | component: Component,
13 | path,
14 | ...rest
15 | }: Props): JSX.Element => {
16 | const user = useRecoilValue(userState);
17 |
18 | return (
19 |
22 | user ? :
23 | }
24 | />
25 | );
26 | };
27 |
28 | export default PublicRoute;
29 |
--------------------------------------------------------------------------------
/frontend/src/state/Channel/index.ts:
--------------------------------------------------------------------------------
1 | import { PAGE_LIMIT_COUNT } from '@enum/index';
2 | import { SortOption } from '@global/type';
3 | import { atom, selector, selectorFamily } from 'recoil';
4 | import axios from 'axios';
5 |
6 | export const channelListState = atom({
7 | key: 'ChannelList',
8 | default: [],
9 | });
10 |
11 | export const browseChannelSortOption = atom({
12 | key: 'browseChannelSortOption',
13 | default: 'alpha',
14 | });
15 |
16 | export const browseCursor = atom({
17 | key: 'browseCursor',
18 | default: 1,
19 | });
20 |
21 | export const browseCursorValue = selector({
22 | key: 'browseCursorValue',
23 | get: ({ get }) => (get(browseCursor) - 1) * PAGE_LIMIT_COUNT,
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/state/modal/index.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 |
3 | export const channelCreateModalState = atom({
4 | key: 'createChannel',
5 | default: false,
6 | });
7 |
8 | export const channelInfoModalState = atom({
9 | key: 'channelInfo',
10 | default: {
11 | isOpen: false,
12 | isAboutTab: true,
13 | },
14 | });
15 |
16 | export const channelDescriptionModalState = atom({
17 | key: 'channelDescription',
18 | default: false,
19 | });
20 |
21 | export const channelTopicModalState = atom({
22 | key: 'channelTopic',
23 | default: false,
24 | });
25 |
26 | export const LoginModalState = atom({
27 | key: 'Login',
28 | default: false,
29 | });
30 |
31 | export const preferenceModalState = atom({
32 | key: 'preferenceModal',
33 | default: false,
34 | });
35 |
36 | export const codeModalState = atom({
37 | key: 'codeModal',
38 | default: {
39 | status: false,
40 | text: undefined,
41 | },
42 | });
43 |
44 | export const SortedOptionMordalState = atom({
45 | key: 'sortedOptionMordal',
46 | default: [true, true, true],
47 | });
48 |
49 | export const userProfileModalState = atom({
50 | key: 'userProfile',
51 | default: {
52 | isOpen: false,
53 | userHasWorkspace: null,
54 | x: null,
55 | y: null,
56 | },
57 | });
58 |
59 | export const searchModalState = atom({
60 | key: 'searchModal',
61 | default: false,
62 | });
63 |
--------------------------------------------------------------------------------
/frontend/src/state/theme/index.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 | import { defaultTheme, Itheme } from '@global/style/theme';
3 |
4 | export const themeState = atom({
5 | key: 'themeState',
6 | default: defaultTheme,
7 | });
8 |
9 | export default themeState;
10 |
--------------------------------------------------------------------------------
/frontend/src/state/thread/index.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 |
3 | export const shouldScrollDownState = atom({
4 | key: 'shouldScrollDown',
5 | default: false,
6 | });
7 |
--------------------------------------------------------------------------------
/frontend/src/state/user/index.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil';
2 | import { Socket } from 'socket.io-client';
3 | import { Itheme } from '@global/theme';
4 |
5 | type ZeroOrOne = 1 | 2;
6 |
7 | export interface IUserState {
8 | account: string;
9 | createdAt: Date;
10 | description: string;
11 | email: string;
12 | id: string;
13 | local: ZeroOrOne;
14 | nickname: string;
15 | password: string;
16 | socket: Socket;
17 | theme: Itheme;
18 | userHasWorkspaceId: string;
19 | }
20 |
21 | const userState = atom({
22 | key: 'userState',
23 | default: undefined,
24 | dangerouslyAllowMutability: true,
25 | });
26 |
27 | export default userState;
28 |
--------------------------------------------------------------------------------
/frontend/src/state/workspace/index.ts:
--------------------------------------------------------------------------------
1 | import { Message } from '@global/type';
2 | import { atom, selector } from 'recoil';
3 |
4 | const PAGEVW = 100;
5 | const OPENSIZE = 82;
6 | const SIDEBARSIZE = 18;
7 | const CLOSEDSIZE = 61;
8 |
9 | export const sizestate = atom({
10 | key: 'sizeState',
11 | default: OPENSIZE,
12 | });
13 |
14 | export interface IreplyToggle {
15 | isOpened: boolean;
16 | message: undefined | Message;
17 | channelName: undefined | string[];
18 | }
19 |
20 | export const replyToggleState = atom({
21 | key: 'replyToggleState',
22 | default: {
23 | isOpened: false,
24 | message: undefined,
25 | channelName: undefined,
26 | },
27 | });
28 |
29 | export const mainWorkspaceSizeState = selector({
30 | key: 'mainWorkspaceSizeState',
31 | get: ({ get }) => {
32 | const { isOpened } = get(replyToggleState);
33 | const OPENEDSIZE = get(sizestate);
34 |
35 | if (isOpened) return CLOSEDSIZE;
36 | return OPENEDSIZE;
37 | },
38 | });
39 |
40 | export const replyWorkspaceState = selector({
41 | key: 'replyWorkspaceSizeState',
42 | get: ({ get }) => {
43 | const { isOpened } = get(replyToggleState);
44 |
45 | if (!isOpened) return 0;
46 |
47 | const size = PAGEVW - get(mainWorkspaceSizeState) - SIDEBARSIZE;
48 | return size;
49 | },
50 | });
51 |
--------------------------------------------------------------------------------
/frontend/src/test/DefaultEnvironment.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2 | import React from 'react';
3 | import { RecoilRoot, useRecoilValue } from 'recoil';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import { QueryClient, QueryClientProvider } from 'react-query';
6 |
7 | import themeState from '@state/theme';
8 | import { Itheme } from '@global/theme';
9 | import { ThemeProvider } from 'styled-components';
10 |
11 | const queryClient = new QueryClient();
12 |
13 | const DefaultEnvironment = ({
14 | children,
15 | initializeState,
16 | }: {
17 | children: JSX.Element;
18 | initializeState: any;
19 | }) => {
20 | const SetTheme = ({ context }: { context: JSX.Element }): JSX.Element => {
21 | const currentTheme = useRecoilValue(themeState);
22 |
23 | return {context};
24 | };
25 | queryClient.clear();
26 |
27 | const Component = (
28 |
29 | {children}
30 |
31 | );
32 |
33 | return (
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default DefaultEnvironment;
41 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "jsx": "react",
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "esModuleInterop": true,
8 | "rootDir": "./",
9 | "outDir": "dist",
10 | "allowJs": true,
11 | "sourceMap": true,
12 | "baseUrl": ".",
13 | "paths": {
14 | "@atoms/*": ["src/components/atoms/*"],
15 | "@molecules/*": ["src/components/molecules/*"],
16 | "@organisms/*": ["src/components/organisms/*"],
17 | "@templates/*": ["src/components/templates/*"],
18 | "@pages/*": ["src/components/pages/*"],
19 | "@enum/*": ["src/enum/*"],
20 | "@global/*": ["src/global/*"],
21 | "@hook/*": ["src/hooks/*"],
22 | "@state/*": ["src/state/*"],
23 | "@routes/*": ["src/routes/*"]
24 | },
25 | "plugins": [{ "name": "typescript-styled-plugin", "validate": false }],
26 | "skipLibCheck": true
27 | },
28 | "include": ["src/**/*", "src/global/module.d.ts"],
29 | "exclude": ["node_modules"]
30 | }
31 |
--------------------------------------------------------------------------------