├── .eslintignore
├── .npmrc
├── .commitlintrc.yml
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── logo
├── logo.jpg
├── favicon.ico
├── logo192.png
├── logo512.png
├── logo-with-text.png
└── README.md
├── src
├── shared
│ ├── history.ts
│ ├── globalStyles.ts
│ ├── utils.ts
│ └── constants.ts
├── containers
│ ├── Home
│ │ ├── Motto
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ └── components
│ │ │ │ └── MottoModal.tsx
│ │ ├── Cover
│ │ │ ├── types.ts
│ │ │ └── typeDefs.ts
│ │ ├── OpenSource
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ └── OpenSource.tsx
│ │ └── Announcement
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ └── components
│ │ │ └── AnnouncementModal.tsx
│ ├── Music
│ │ ├── LiveTour
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ └── LiveTour.tsx
│ │ ├── YanceyMusic
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ └── YanceyMusic.tsx
│ │ ├── BestAlbum
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ └── BestAlbum.tsx
│ │ └── Player
│ │ │ ├── styles.ts
│ │ │ ├── types.ts
│ │ │ └── typeDefs.ts
│ ├── Settings
│ │ ├── Security
│ │ │ ├── components
│ │ │ │ ├── ChangePassword
│ │ │ │ │ └── changePassword.module.scss
│ │ │ │ ├── TwoFactors
│ │ │ │ │ ├── twoFactors.module.scss
│ │ │ │ │ └── TwoFactors.tsx
│ │ │ │ ├── SecurtyIntro
│ │ │ │ │ └── SecurtyIntro.tsx
│ │ │ │ ├── RecoveryCodes
│ │ │ │ │ ├── recoveryCode.module.scss
│ │ │ │ │ └── RecoveryCodes.tsx
│ │ │ │ └── TOTP
│ │ │ │ │ └── totp.module.scss
│ │ │ ├── typeDefs.ts
│ │ │ └── Security.tsx
│ │ ├── Profile
│ │ │ ├── typeDefs.tsx
│ │ │ └── styles.ts
│ │ ├── components
│ │ │ ├── SettingWrapper
│ │ │ │ └── SettingWrapper.tsx
│ │ │ ├── SettingItemWrapper
│ │ │ │ ├── settingItemWrapper.module.scss
│ │ │ │ └── SettingItemWrapper.tsx
│ │ │ └── SettingsHeader
│ │ │ │ └── SettingsHeader.tsx
│ │ ├── GlobalConfig
│ │ │ ├── types.ts
│ │ │ ├── typeDefs.ts
│ │ │ ├── styles.ts
│ │ │ ├── components
│ │ │ │ ├── GrayTheme.tsx
│ │ │ │ ├── CVPicker.tsx
│ │ │ │ └── ReleasePicker.tsx
│ │ │ └── GlobalConfig.tsx
│ │ └── Account
│ │ │ ├── typeDefs.tsx
│ │ │ ├── styles.ts
│ │ │ ├── components
│ │ │ ├── UpdateEmail.tsx
│ │ │ ├── UpdateUserName.tsx
│ │ │ └── DeleteAccount.tsx
│ │ │ └── Account.tsx
│ ├── Agenda
│ │ ├── tools.ts
│ │ ├── types.ts
│ │ ├── components
│ │ │ ├── CustomOpenButton.tsx
│ │ │ ├── CustomTodayButton.tsx
│ │ │ ├── CustomNavigationButton.tsx
│ │ │ ├── ExternalViewSwitcher.tsx
│ │ │ └── Schedule.tsx
│ │ ├── styles.ts
│ │ ├── typeDefs.ts
│ │ └── Agenda.tsx
│ ├── Events
│ │ ├── tools.ts
│ │ ├── types.ts
│ │ ├── components
│ │ │ ├── CustomOpenButton.tsx
│ │ │ ├── CustomTodayButton.tsx
│ │ │ ├── CustomNavigationButton.tsx
│ │ │ ├── ExternalViewSwitcher.tsx
│ │ │ └── Schedule.tsx
│ │ ├── styles.ts
│ │ ├── typeDefs.ts
│ │ └── Agenda.tsx
│ ├── DashBoard
│ │ ├── styles.ts
│ │ ├── types.ts
│ │ ├── typeDefs.ts
│ │ ├── components
│ │ │ ├── StatusCardSkeleton.tsx
│ │ │ ├── CPUChart.tsx
│ │ │ ├── TagClouds.tsx
│ │ │ ├── DiskChart.tsx
│ │ │ ├── NetWorkChart.tsx
│ │ │ ├── UsageStatusSkeleton.tsx
│ │ │ ├── PostRankListSkeleton.tsx
│ │ │ ├── ToggleChart.tsx
│ │ │ ├── BandwagonServiceStatus.tsx
│ │ │ ├── PostStatistics.tsx
│ │ │ ├── PostRankList.tsx
│ │ │ └── StatusCard.tsx
│ │ └── chartjsConfig.ts
│ └── Post
│ │ ├── editors
│ │ ├── editorEmbededPlugin.ts
│ │ ├── editorIO.ts
│ │ └── enhanceEditor.ts
│ │ ├── components
│ │ └── UploaderModal.tsx
│ │ ├── algolia
│ │ └── algoliaSearch.ts
│ │ ├── styles.ts
│ │ ├── types.ts
│ │ ├── typeDefs.ts
│ │ └── PostList.tsx
├── setupTests.ts
├── pages
│ ├── Layouts
│ │ ├── components
│ │ │ ├── Mains
│ │ │ │ ├── styles.ts
│ │ │ │ └── Mains.tsx
│ │ │ ├── Header
│ │ │ │ └── styles.ts
│ │ │ ├── Drawer
│ │ │ │ └── components
│ │ │ │ │ ├── ChildItem.tsx
│ │ │ │ │ └── ParentItem.tsx
│ │ │ └── Footer
│ │ │ │ └── Footer.tsx
│ │ ├── styles.ts
│ │ └── Layouts.tsx
│ └── Auth
│ │ ├── utils.ts
│ │ ├── typeDefs.ts
│ │ └── Auth.module.scss
├── components
│ ├── Uploader
│ │ ├── types.ts
│ │ └── styles.ts
│ ├── Transition
│ │ └── Transition.tsx
│ ├── SkeletonIterator
│ │ └── SkeletonIterator.tsx
│ ├── NotFound
│ │ ├── styles.ts
│ │ └── NotFound.tsx
│ ├── Loading
│ │ ├── Loading.tsx
│ │ └── TwitterLoading.tsx
│ ├── ConfirmModal
│ │ └── ConfirmModal.tsx
│ ├── Toast
│ │ └── Toast.tsx
│ ├── ImagePopup
│ │ └── ImagePopup.tsx
│ ├── ConfirmPoper
│ │ └── ConfirmPoper.tsx
│ ├── TableWrapper
│ │ └── TableWrapper.tsx
│ └── Move
│ │ └── Move.tsx
├── graphql
│ ├── graphqlFragment.ts
│ └── apolloClient.ts
├── assets
│ └── global.scss
├── hooks
│ ├── useOpenModal.ts
│ └── useScript.ts
├── reportWebVitals.ts
├── react-app-env.d.ts
├── index.tsx
└── service-worker.ts
├── .huskyrc.json
├── .prettierrc.json
├── .markdownlintrc
├── CONTRIBUTING.md
├── AUTHORS
├── CODE_OF_CONDUCT.md
├── SECURITY.md
├── .gitignore
├── .babelrc
├── .env.production.example
├── .github
├── dependabot.yml
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── github-actions.yml
│ └── codeql-analysis.yml
├── tsconfig.json
├── .env.development
└── LICENSE
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build
2 | /dist
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.yarnpkg.com/
--------------------------------------------------------------------------------
/.commitlintrc.yml:
--------------------------------------------------------------------------------
1 | extends:
2 | - '@commitlint/config-conventional'
3 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/logo/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/logo/logo.jpg
--------------------------------------------------------------------------------
/logo/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/logo/favicon.ico
--------------------------------------------------------------------------------
/logo/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/logo/logo192.png
--------------------------------------------------------------------------------
/logo/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/logo/logo512.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/logo/logo-with-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yancey-Blog/blog-cms-v2/HEAD/logo/logo-with-text.png
--------------------------------------------------------------------------------
/src/shared/history.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from 'history'
2 |
3 | export default createBrowserHistory()
4 |
--------------------------------------------------------------------------------
/.huskyrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
4 | "pre-commit": "lint-staged"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "bracketSpacing": true,
5 | "proseWrap": "preserve",
6 | "semi": false,
7 | "printWidth": 80
8 | }
9 |
--------------------------------------------------------------------------------
/.markdownlintrc:
--------------------------------------------------------------------------------
1 | {
2 | "MD033": {
3 | "allowed_elements": [
4 | "div",
5 | "img",
6 | "a",
7 | ]
8 | },
9 | "MD013": false
10 | }
--------------------------------------------------------------------------------
/src/containers/Home/Motto/types.ts:
--------------------------------------------------------------------------------
1 | export interface IMotto {
2 | _id: string
3 | weight: number
4 | content: string
5 | createdAt: string
6 | updatedAt: string
7 | }
8 |
9 | export interface Query {
10 | getMottos: IMotto[]
11 | }
12 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Blog CMS v2
2 |
3 | Want to contribute to the Blog CMS v2? There are a few things you need to know.
4 |
5 | We wrote a [contribution guide](https://reactjs.org/contributing/how-to-contribute.html) to help you get started.
6 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom'
6 |
--------------------------------------------------------------------------------
/src/pages/Layouts/components/Mains/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles({
4 | main: {
5 | display: 'flex',
6 | flex: 1,
7 | padding: '0 24px 24px',
8 | },
9 | })
10 |
11 | export default useStyles
12 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | # Below is a list of people and organizations that have contributed
2 | # to the Lighthouse project. Names should be added to the list like so:
3 | #
4 | # Name/Organization
5 |
6 | Yancey Leo
7 | Blog Environment Group
8 |
--------------------------------------------------------------------------------
/src/containers/Music/LiveTour/types.ts:
--------------------------------------------------------------------------------
1 | export interface ILiveTour {
2 | _id: string
3 | title: string
4 | showTime: string
5 | posterUrl: string
6 | createdAt: string
7 | updatedAt: string
8 | }
9 |
10 | export interface Query {
11 | getLiveTours: ILiveTour[]
12 | }
13 |
--------------------------------------------------------------------------------
/src/containers/Home/Cover/types.ts:
--------------------------------------------------------------------------------
1 | export interface ICover {
2 | _id: string
3 | title: string
4 | coverUrl: string
5 | isPublic: boolean
6 | weight: number
7 | createdAt: string
8 | updatedAt: string
9 | }
10 |
11 | export interface Query {
12 | getCovers: ICover[]
13 | }
14 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | [Blog Environment Group](https://github.com/Yancey-Blog) has adopted a Code of Conduct that we expect project participants to adhere to.
4 | Please [read the full text](https://code.fb.com/codeofconduct/) so that you can understand what actions will and will not be tolerated.
5 |
--------------------------------------------------------------------------------
/src/containers/Home/OpenSource/types.ts:
--------------------------------------------------------------------------------
1 | export interface IOpenSource {
2 | _id: string
3 | title: string
4 | description: string
5 | url: string
6 | posterUrl: string
7 | createdAt: string
8 | updatedAt: string
9 | }
10 |
11 | export interface Query {
12 | getOpenSources: IOpenSource[]
13 | }
14 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/ChangePassword/changePassword.module.scss:
--------------------------------------------------------------------------------
1 | .input {
2 | display: block;
3 | margin-bottom: 14px;
4 | input {
5 | width: 450px;
6 | }
7 | }
8 |
9 | .tip {
10 | margin-top: 20px;
11 | margin-bottom: 24px;
12 | font-size: 12px;
13 | color: #5f6368;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Uploader/types.ts:
--------------------------------------------------------------------------------
1 | export interface UploaderResponse {
2 | name: string
3 | url: string
4 | }
5 |
6 | export interface Props {
7 | type?: 'avatar' | 'simple'
8 | variant?: 'elevation' | 'outlined' | undefined
9 | accept?: string
10 | defaultFile?: string
11 | needMarginLeft?: boolean
12 | className?: any
13 | onChange: Function
14 | }
15 |
--------------------------------------------------------------------------------
/src/containers/Settings/Profile/typeDefs.tsx:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const UPDATE_USER = gql`
4 | mutation UpdateUser($input: UpdateUserInput!) {
5 | updateUser(input: $input) {
6 | _id
7 | name
8 | location
9 | organization
10 | website
11 | bio
12 | avatarUrl
13 | }
14 | }
15 | `
16 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Issues
2 |
3 | If you discover a security issue in Blog CMS v2, please report it by sending an email to [developer@yanceyleo.com](mailto:developer@yanceyleo.com).
4 |
5 | This will allow us to assess the risk, and make a fix available before we add a bug report to the GitHub repository.
6 |
7 | Thanks for helping make Blog CMS v2 safe for everyone!
8 |
--------------------------------------------------------------------------------
/src/containers/Music/YanceyMusic/types.ts:
--------------------------------------------------------------------------------
1 | export interface IYanceyMusic {
2 | readonly _id: string
3 | readonly title: string
4 | readonly soundCloudUrl: string
5 | readonly posterUrl: string
6 | readonly releaseDate: string
7 | readonly createdAt: string
8 | readonly updatedAt: string
9 | }
10 |
11 | export interface Query {
12 | getYanceyMusic: IYanceyMusic[]
13 | }
14 |
--------------------------------------------------------------------------------
/src/containers/Music/BestAlbum/types.ts:
--------------------------------------------------------------------------------
1 | export interface IBestAlbum {
2 | readonly _id: string
3 | readonly title: string
4 | readonly artist: string
5 | readonly coverUrl: string
6 | readonly mvUrl: string
7 | readonly releaseDate: string
8 | readonly createdAt: string
9 | readonly updatedAt: string
10 | }
11 |
12 | export interface Query {
13 | getBestAlbums: IBestAlbum[]
14 | }
15 |
--------------------------------------------------------------------------------
/src/containers/Music/Player/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | lrcTxt: {
6 | margin: 0,
7 | padding: '16px',
8 | fontFamily: 'inherit',
9 | },
10 |
11 | btnUploaderGroup: {
12 | margin: '40px 0',
13 | },
14 | }),
15 | )
16 |
17 | export default useStyles
18 |
--------------------------------------------------------------------------------
/src/graphql/graphqlFragment.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const BATCH_DELETE_FRAGMENT = gql`
4 | fragment BatchDeleteFragment on BatchDeleteModel {
5 | n
6 | ok
7 | deletedCount
8 | ids
9 | }
10 | `
11 |
12 | export const BATCH_UPDATE_FRAGMENT = gql`
13 | fragment BatchUpdateFragment on BatchUpdateModel {
14 | n
15 | ok
16 | nModified
17 | ids
18 | }
19 | `
20 |
--------------------------------------------------------------------------------
/src/assets/global.scss:
--------------------------------------------------------------------------------
1 | figure,
2 | ul,
3 | li,
4 | p {
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | a {
10 | color: #1a73e8;
11 | text-decoration: none;
12 | &:hover {
13 | text-decoration: underline;
14 | }
15 | }
16 |
17 | .tui-emebed-icon {
18 | position: relative;
19 | top: -2px;
20 | font-size: 14px !important;
21 | font-weight: bold !important;
22 | background: none !important;
23 | color: #000 !important;
24 | }
25 |
--------------------------------------------------------------------------------
/src/containers/Music/Player/types.ts:
--------------------------------------------------------------------------------
1 | export interface IPlayer {
2 | readonly _id: string
3 | readonly title: string
4 | readonly artist: string
5 | readonly lrc: string
6 | readonly coverUrl: string
7 | readonly musicFileUrl: string
8 | readonly isPublic: boolean
9 | readonly weight: number
10 | readonly createdAt: Date
11 | readonly updatedAt: Date
12 | }
13 |
14 | export interface Query {
15 | getPlayers: IPlayer[]
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /dist
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 | .env.production
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | .eslintcache
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-app"],
3 | "plugins": [
4 | [
5 | "babel-plugin-transform-imports",
6 | {
7 | "@material-ui/core": {
8 | "transform": "@material-ui/core/esm/${member}",
9 | "preventFullImport": true
10 | },
11 | "@material-ui/icons": {
12 | "transform": "@material-ui/icons/esm/${member}",
13 | "preventFullImport": true
14 | }
15 | }
16 | ]
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Transition/Transition.tsx:
--------------------------------------------------------------------------------
1 | import { forwardRef, Ref, ReactElement } from 'react'
2 | import { TransitionProps } from '@material-ui/core/transitions'
3 | import { Slide } from '@material-ui/core'
4 |
5 | const Transition = forwardRef(function Transition(
6 | props: TransitionProps & { children?: ReactElement },
7 | ref: Ref,
8 | ) {
9 | return
10 | })
11 |
12 | export default Transition
13 |
--------------------------------------------------------------------------------
/src/containers/Agenda/tools.ts:
--------------------------------------------------------------------------------
1 | import { Dict, IAgenda } from './types'
2 |
3 | export const formatChangedData = (o: Dict) => {
4 | const id = Object.keys(o)[0]
5 | return {
6 | id,
7 | ...o[id],
8 | }
9 | }
10 |
11 | export const dateStringToDate = (agendaList: IAgenda[]) =>
12 | agendaList.map((agenda) => ({
13 | ...agenda,
14 | id: agenda._id,
15 | startDate: new Date(agenda.startDate),
16 | endDate: new Date(agenda.endDate),
17 | }))
18 |
--------------------------------------------------------------------------------
/src/containers/Events/tools.ts:
--------------------------------------------------------------------------------
1 | import { Dict, IAgenda } from './types'
2 |
3 | export const formatChangedData = (o: Dict) => {
4 | const id = Object.keys(o)[0]
5 | return {
6 | id,
7 | ...o[id],
8 | }
9 | }
10 |
11 | export const dateStringToDate = (agendaList: IAgenda[]) =>
12 | agendaList.map((agenda) => ({
13 | ...agenda,
14 | id: agenda._id,
15 | startDate: new Date(agenda.startDate),
16 | endDate: new Date(agenda.endDate),
17 | }))
18 |
--------------------------------------------------------------------------------
/src/hooks/useOpenModal.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export interface Open {
4 | isOpen: boolean
5 | id?: string
6 | }
7 |
8 | const useOpenModal = () => {
9 | const [open, setOpen] = useState({ isOpen: false })
10 |
11 | const handleOpen = (id?: string) => {
12 | const params: Open = { isOpen: !open.isOpen }
13 | params.id = id ? id : ''
14 | setOpen(params)
15 | }
16 |
17 | return { open, handleOpen }
18 | }
19 |
20 | export default useOpenModal
21 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals'
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry)
7 | getFID(onPerfEntry)
8 | getFCP(onPerfEntry)
9 | getLCP(onPerfEntry)
10 | getTTFB(onPerfEntry)
11 | })
12 | }
13 | }
14 |
15 | export default reportWebVitals
16 |
--------------------------------------------------------------------------------
/.env.production.example:
--------------------------------------------------------------------------------
1 | # IMPORTANT
2 | # Create the `.env.production` file at first
3 |
4 | REACT_APP_BEG_SERVICE_DOMAIN=
5 | REACT_APP_UPLOADER_SERVICE_DOMAIN=
6 | REACT_APP_ALGOLIA_APPLICATION_ID=
7 | REACT_APP_ALGOLIA_ADMIN_API_KEY=
8 | REACT_APP_ALGOLIA_SEARCH_INDEX=
9 | REACT_APP_RECAPTCHA_KEY=
10 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | dashboradWrapper: {
6 | width: '100%',
7 | },
8 |
9 | group: {
10 | display: 'grid',
11 | gridTemplateColumns: '2fr 1fr',
12 | gridColumnGap: 24,
13 | gridTemplateRows: '375px 375px 375px',
14 | gridRowGap: 24,
15 | marginBottom: 24,
16 | },
17 | }),
18 | )
19 |
20 | export default useStyles
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: 'npm'
9 | directory: '/'
10 | schedule:
11 | interval: 'weekly'
12 | target-branch: "develop"
13 | commit-message:
14 | prefix: 'deps'
15 |
--------------------------------------------------------------------------------
/src/components/SkeletonIterator/SkeletonIterator.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ComponentType } from 'react'
2 | import { randomSeries } from 'yancey-js-util'
3 |
4 | interface Props {
5 | count: number
6 | skeletonComponent: ComponentType
7 | }
8 |
9 | const SkeletonIterator: FC = ({
10 | count,
11 | skeletonComponent: Skeleton,
12 | }) => {
13 | return (
14 | <>
15 | {Array.from({ length: count }, () => randomSeries(6)).map((val) => (
16 |
17 | ))}
18 | >
19 | )
20 | }
21 |
22 | export default SkeletonIterator
23 |
--------------------------------------------------------------------------------
/src/containers/Settings/components/SettingWrapper/SettingWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
3 |
4 | const useStyles = makeStyles((theme: Theme) =>
5 | createStyles({
6 | settingWrapper: {
7 | display: 'block',
8 | width: '100%',
9 | },
10 | }),
11 | )
12 |
13 | const SettingWrapper: FC = ({ children }) => {
14 | const classes = useStyles()
15 |
16 | return
17 | }
18 |
19 | export default SettingWrapper
20 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/types.ts:
--------------------------------------------------------------------------------
1 | import { IPostItem } from 'src/containers/Post/types'
2 |
3 | export interface PostFilterProps {
4 | id: string
5 | isFetching: boolean
6 | isSubmitting: boolean
7 | fetchPosts: Function
8 | updateGlobalSettingById: Function
9 | posts: IPostItem[]
10 | }
11 |
12 | export interface IGlobalSetting {
13 | _id: string
14 | releasePostId: string
15 | cvPostId: string
16 | isGrayTheme: boolean
17 | createdAt: string
18 | updatedAt: string
19 | }
20 |
21 | export interface Query {
22 | getGlobalSetting: IGlobalSetting
23 | }
24 |
--------------------------------------------------------------------------------
/src/containers/Agenda/types.ts:
--------------------------------------------------------------------------------
1 | export interface IAgenda {
2 | _id: string
3 | title: string
4 | startDate: Date
5 | endDate: Date
6 | allDay: boolean
7 | notes: string | null
8 | rRule: string | null
9 | exDate: string | null
10 | createdAt: string
11 | updatedAt: string
12 | }
13 |
14 | export interface Query {
15 | getAgenda: IAgenda[]
16 | }
17 |
18 | export interface ScheduleProps {
19 | dataSource: IAgenda[]
20 | createAgenda: Function
21 | updateAgendaById: Function
22 | deleteAgendaById: Function
23 | }
24 |
25 | export interface Dict {
26 | [x: string]: any
27 | }
28 |
--------------------------------------------------------------------------------
/src/containers/Events/types.ts:
--------------------------------------------------------------------------------
1 | export interface IAgenda {
2 | _id: string
3 | title: string
4 | startDate: Date
5 | endDate: Date
6 | allDay: boolean
7 | notes: string | null
8 | rRule: string | null
9 | exDate: string | null
10 | createdAt: string
11 | updatedAt: string
12 | }
13 |
14 | export interface Query {
15 | getAgenda: IAgenda[]
16 | }
17 |
18 | export interface ScheduleProps {
19 | dataSource: IAgenda[]
20 | createAgenda: Function
21 | updateAgendaById: Function
22 | deleteAgendaById: Function
23 | }
24 |
25 | export interface Dict {
26 | [x: string]: any
27 | }
28 |
--------------------------------------------------------------------------------
/src/containers/Settings/Account/typeDefs.tsx:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const UPDATE_USERNAME = gql`
4 | mutation UpdateUserName($username: String!) {
5 | updateUserName(username: $username) {
6 | _id
7 | username
8 | }
9 | }
10 | `
11 |
12 | export const UPDATE_EMAIL = gql`
13 | mutation UpdateEmail($email: String!) {
14 | updateEmail(email: $email) {
15 | _id
16 | email
17 | }
18 | }
19 | `
20 |
21 | export const DELETE_ACCOUNT = gql`
22 | mutation DeleteAccount {
23 | deleteAccount {
24 | _id
25 | }
26 | }
27 | `
28 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/types.ts:
--------------------------------------------------------------------------------
1 | export interface IBandwagonServiceInfo {
2 | data_counter: number
3 | plan_monthly_data: number
4 | plan_disk: number
5 | ve_used_disk_space_b: number
6 | plan_ram: number
7 | mem_available_kb: number
8 | swap_total_kb: number
9 | swap_available_kb: number
10 | }
11 |
12 | export interface IBandwagonUsageStatus {
13 | timestamp: string
14 | network_in_bytes: string
15 | network_out_bytes: string
16 | disk_read_bytes: string
17 | disk_write_bytes: string
18 | cpu_usage: string
19 | }
20 |
21 | export enum PostRankListType {
22 | PV,
23 | LIKE,
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/Auth/utils.ts:
--------------------------------------------------------------------------------
1 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
2 |
3 | export const getBackgroundUrl = () => {
4 | const backgrounds = [
5 | 'login-bg-light.jpg',
6 | 'login-bg-dark.jpg',
7 | 'login-bg-deep-dark.png',
8 | ]
9 | const hour = new Date().getHours()
10 | let backgroundUrl = `${AZURE_BLOB_PATH}/`
11 |
12 | if (hour >= 6 && hour <= 17) {
13 | backgroundUrl += backgrounds[0]
14 | } else if (hour >= 18 && hour <= 22) {
15 | backgroundUrl += backgrounds[1]
16 | } else {
17 | backgroundUrl += backgrounds[2]
18 | }
19 |
20 | return backgroundUrl
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": "."
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Yancey Blog CMS",
3 | "name": "The CMS for Yancey Blog",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/TwoFactors/twoFactors.module.scss:
--------------------------------------------------------------------------------
1 | .arrowIcon {
2 | width: 16px;
3 | height: 16px;
4 | }
5 |
6 | .listGroup {
7 | margin: 24px -24px -32px;
8 |
9 | :global(.MuiListItem-root) {
10 | padding: 16px 0;
11 | }
12 | }
13 |
14 | .title {
15 | margin-left: 24px;
16 | width: 16%;
17 | }
18 |
19 | .isUseTOTP {
20 | display: flex;
21 | align-items: center;
22 | font-size: 14px;
23 | color: #1a73e8;
24 |
25 | svg {
26 | width: 20px;
27 | margin-right: 8px !important;
28 | }
29 | }
30 |
31 | .phone {
32 | font-size: 14px;
33 | color: #5f6368;
34 | }
35 |
--------------------------------------------------------------------------------
/src/containers/Agenda/components/CustomOpenButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Button } from '@material-ui/core'
3 | import { DateNavigator } from '@devexpress/dx-react-scheduler-material-ui'
4 | import useStyles from '../styles'
5 |
6 | const CustomOpenButton: FC = ({
7 | text,
8 | onVisibilityToggle,
9 | }) => {
10 | const classes = useStyles()
11 |
12 | return (
13 |
18 | {text}
19 |
20 | )
21 | }
22 |
23 | export default CustomOpenButton
24 |
--------------------------------------------------------------------------------
/src/containers/Events/components/CustomOpenButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Button } from '@material-ui/core'
3 | import { DateNavigator } from '@devexpress/dx-react-scheduler-material-ui'
4 | import useStyles from '../styles'
5 |
6 | const CustomOpenButton: FC = ({
7 | text,
8 | onVisibilityToggle,
9 | }) => {
10 | const classes = useStyles()
11 |
12 | return (
13 |
18 | {text}
19 |
20 | )
21 | }
22 |
23 | export default CustomOpenButton
24 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module 'react/jsx-runtime' {
4 | export default any
5 | }
6 |
7 | declare namespace NodeJS {
8 | export interface ProcessEnv {
9 | REACT_APP_BEG_SERVICE_DOMAIN: string
10 | REACT_APP_UPLOADER_SERVICE_DOMAIN: string
11 | REACT_APP_ALGOLIA_APPLICATION_ID: string
12 | REACT_APP_ALGOLIA_ADMIN_API_KEY: string
13 | REACT_APP_ALGOLIA_SEARCH_INDEX: string
14 | REACT_APP_RECAPTCHA_KEY: string
15 | PORT: string
16 | }
17 | }
18 |
19 | declare interface Window {
20 | grecaptcha: {
21 | ready: Function
22 | execute: Promise
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # priority
2 | #
3 | # yarn start: .env.development.local > .env.development > .env.local > .env
4 | # yarn build: .env.production.local > .env.production > .env.local > .env
5 | # yarn test: .env.test.local > .env.test > .env
6 |
7 | REACT_APP_BEG_SERVICE_DOMAIN=http://localhost:3002/beg/graphql
8 | REACT_APP_UPLOADER_SERVICE_DOMAIN=http://localhost:3003/uploader
9 | REACT_APP_ALGOLIA_APPLICATION_ID=
10 | REACT_APP_ALGOLIA_ADMIN_API_KEY=
11 | REACT_APP_ALGOLIA_SEARCH_INDEX=
12 | REACT_APP_RECAPTCHA_KEY=
13 | PORT=3001
14 |
--------------------------------------------------------------------------------
/src/containers/Agenda/components/CustomTodayButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Button } from '@material-ui/core'
3 | import { TodayButton } from '@devexpress/dx-react-scheduler-material-ui'
4 | import useStyles from '../styles'
5 |
6 | const CustomTodayButton: FC = ({ setCurrentDate }) => {
7 | const classes = useStyles()
8 |
9 | return (
10 | setCurrentDate(new Date())}
15 | className={classes.customBtn}
16 | >
17 | Today
18 |
19 | )
20 | }
21 |
22 | export default CustomTodayButton
23 |
--------------------------------------------------------------------------------
/src/containers/Events/components/CustomTodayButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Button } from '@material-ui/core'
3 | import { TodayButton } from '@devexpress/dx-react-scheduler-material-ui'
4 | import useStyles from '../styles'
5 |
6 | const CustomTodayButton: FC = ({ setCurrentDate }) => {
7 | const classes = useStyles()
8 |
9 | return (
10 | setCurrentDate(new Date())}
15 | className={classes.customBtn}
16 | >
17 | Today
18 |
19 | )
20 | }
21 |
22 | export default CustomTodayButton
23 |
--------------------------------------------------------------------------------
/src/containers/Post/editors/editorEmbededPlugin.ts:
--------------------------------------------------------------------------------
1 | import ToastUIEditor from '@toast-ui/editor'
2 | import { randomSeries } from 'yancey-js-util'
3 |
4 | const renderEmbeded = (wrapperId: string, iframeEl: string) => {
5 | const el = document.querySelector(`#${wrapperId}`)
6 | if (el) {
7 | el.innerHTML = iframeEl
8 | }
9 | }
10 |
11 | const addEmbededEl = () => {
12 | ToastUIEditor.codeBlockManager.setReplacer('embeded', (iframeEl: string) => {
13 | const wrapperId = `embeded_${randomSeries(6)}`
14 | setTimeout(renderEmbeded.bind(null, wrapperId, iframeEl), 0)
15 | return `
`
16 | })
17 | }
18 |
19 | export default addEmbededEl
20 |
--------------------------------------------------------------------------------
/src/containers/Settings/Profile/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | input: {
6 | marginBottom: theme.spacing(2.5),
7 | flex: 1,
8 | },
9 |
10 | profileContainer: {
11 | display: 'grid',
12 | gridTemplateColumns: '2fr 1fr',
13 | },
14 |
15 | customUploader: {
16 | marginLeft: 60,
17 | width: 180,
18 | height: 180,
19 | borderRadius: '50%',
20 |
21 | '& img': {
22 | width: '100%',
23 | height: '100%',
24 | },
25 | },
26 | }),
27 | )
28 |
29 | export default useStyles
30 |
--------------------------------------------------------------------------------
/src/shared/globalStyles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | addIconFab: {
6 | boxShadow: 'none!important',
7 | background: 'none!important',
8 | color: 'rgba(0, 0, 0, 0.54)',
9 | },
10 |
11 | uploaderGroup: {
12 | display: 'flex',
13 | alignItems: 'center',
14 | marginTop: theme.spacing(2.5),
15 | },
16 |
17 | textFieldSpace: { marginBottom: theme.spacing(2.5) },
18 |
19 | editIcon: {
20 | cursor: 'pointer',
21 | marginRight: theme.spacing(1),
22 | },
23 | }),
24 | )
25 |
26 | export default useStyles
27 |
--------------------------------------------------------------------------------
/src/components/NotFound/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles({
4 | notFound: {
5 | display: 'flex',
6 | flexDirection: 'column',
7 | justifyContent: 'center',
8 | alignItems: 'center',
9 | width: '100%',
10 | },
11 | header: {
12 | margin: 0,
13 | fontSize: 36,
14 | color: '#263238',
15 | },
16 |
17 | tips: {
18 | fontSize: 14,
19 | color: '#546e7a',
20 | },
21 |
22 | image: {
23 | margin: '64px 0',
24 | width: 560,
25 |
26 | '& img': {
27 | width: '100%',
28 | height: '100%',
29 | objectFit: 'cover',
30 | },
31 | },
32 | })
33 |
34 | export default useStyles
35 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const GET_BANWAGON_SERVICE_INFO = gql`
4 | query GetBanwagonServiceInfo {
5 | getBanwagonServiceInfo {
6 | data_counter
7 | plan_monthly_data
8 | plan_disk
9 | ve_used_disk_space_b
10 | plan_ram
11 | mem_available_kb
12 | swap_total_kb
13 | swap_available_kb
14 | }
15 | }
16 | `
17 |
18 | export const GET_BANWAGON_USAGE_STATS = gql`
19 | query GetBanwagonUsageStats {
20 | getBanwagonUsageStats {
21 | timestamp
22 | network_in_bytes
23 | network_out_bytes
24 | disk_read_bytes
25 | disk_write_bytes
26 | cpu_usage
27 | }
28 | }
29 | `
30 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | const GLOBAL_SETTING_FRAGMENT = gql`
4 | fragment GlobalSettingFragment on GlobalSettingModel {
5 | _id
6 | releasePostId
7 | cvPostId
8 | isGrayTheme
9 | }
10 | `
11 |
12 | export const GLOBAL_SETTING = gql`
13 | query GetGlobalSetting {
14 | getGlobalSetting {
15 | ...GlobalSettingFragment
16 | }
17 | }
18 | ${GLOBAL_SETTING_FRAGMENT}
19 | `
20 |
21 | export const UPDATE_GLOBAL_SETTING_BY_ID = gql`
22 | mutation UpdateGlobalSettingById($input: UpdateGlobalSettingInput!) {
23 | updateGlobalSettingById(input: $input) {
24 | ...GlobalSettingFragment
25 | }
26 | }
27 | ${GLOBAL_SETTING_FRAGMENT}
28 | `
29 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | Yancey Blog CMS
15 |
16 |
17 | You need to enable the fucking JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | card: { marginTop: 24, padding: 12, width: 450 },
6 |
7 | input: {
8 | width: 450,
9 | },
10 |
11 | searchBtn: {
12 | marginLeft: 24,
13 | verticalAlign: 'bottom',
14 | },
15 |
16 | btnGroup: {
17 | marginTop: 12,
18 | textAlign: 'right',
19 | },
20 |
21 | checkedId: {
22 | marginBottom: 24,
23 | padding: 12,
24 | width: 450,
25 | fontSize: 16,
26 | color: '#5f6368',
27 | border: '1px dotted #5f6368',
28 | },
29 | }),
30 | )
31 |
32 | export default useStyles
33 |
--------------------------------------------------------------------------------
/src/containers/Settings/components/SettingItemWrapper/settingItemWrapper.module.scss:
--------------------------------------------------------------------------------
1 | .paper {
2 | position: relative;
3 | margin: 24px auto 0;
4 | padding: 24px;
5 | width: 864px;
6 | border-radius: 16px;
7 | background: #ffffff;
8 | box-shadow: rgb(145 158 171 / 24%) 0px 0px 2px 0px,
9 | rgb(145 158 171 / 24%) 0px 16px 32px -4px;
10 | }
11 |
12 | .header {
13 | display: flex;
14 | justify-content: space-between;
15 | h2 {
16 | margin: 0;
17 | font-size: 22px;
18 | font-weight: 400;
19 | color: #202124;
20 | }
21 |
22 | .subHeader {
23 | margin-bottom: 64px;
24 | }
25 | }
26 |
27 | .img {
28 | height: 112px;
29 | object-fit: cover;
30 |
31 | img {
32 | height: 100%;
33 | width: auto;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const CREATE_TOTP = gql`
4 | mutation CreateTOTP {
5 | createTOTP {
6 | qrcode
7 | key
8 | }
9 | }
10 | `
11 |
12 | export const CREATE_RECOVERY_CODES = gql`
13 | mutation CreateRecoveryCodes {
14 | createRecoveryCodes {
15 | recoveryCodes
16 | }
17 | }
18 | `
19 |
20 | export const VALIDATE_TOTP = gql`
21 | mutation ValidateTOTP($input: ValidateTOTPInput!) {
22 | validateTOTP(input: $input) {
23 | _id
24 | isTOTP
25 | }
26 | }
27 | `
28 |
29 | export const CHANGE_PASSWORD = gql`
30 | mutation ChangePassword($input: ChangePasswordInput!) {
31 | changePassword(input: $input) {
32 | _id
33 | }
34 | }
35 | `
36 |
--------------------------------------------------------------------------------
/src/containers/Settings/components/SettingItemWrapper/SettingItemWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import styles from './settingItemWrapper.module.scss'
3 |
4 | interface Props {
5 | title: string
6 | imageUrl?: string
7 | }
8 |
9 | const SettingItemWrapper: FC = ({ children, title, imageUrl }) => {
10 | return (
11 |
12 |
13 | {title}
14 | {imageUrl ? (
15 |
16 |
17 |
18 | ) : null}
19 |
20 |
21 | {children}
22 |
23 | )
24 | }
25 |
26 | export default SettingItemWrapper
27 |
--------------------------------------------------------------------------------
/src/components/Loading/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import CircularProgress from '@material-ui/core/CircularProgress'
4 |
5 | const useStyles = makeStyles({
6 | mask: {
7 | display: 'flex',
8 | justifyContent: 'center',
9 | alignItems: 'center',
10 | position: 'absolute',
11 | left: 0,
12 | top: 0,
13 | width: '100%',
14 | height: '100%',
15 | background: 'rgba(255, 255, 255, 0.4)',
16 | borderRadius: '4px',
17 | zIndex: 9999,
18 | },
19 | })
20 |
21 | const Loading: FC = () => {
22 | const classes = useStyles()
23 |
24 | return (
25 |
28 | )
29 | }
30 |
31 | export default Loading
32 |
--------------------------------------------------------------------------------
/src/containers/Settings/Account/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | tip: {
6 | top: 72,
7 | color: '#5f6368',
8 | width: 460,
9 | position: 'absolute',
10 | fontSize: 14,
11 | },
12 |
13 | checkboxLabel: {
14 | fontSize: 14,
15 | color: 'rgba(0,0,0,0.65)',
16 | },
17 |
18 | sureToDeleteAccount: {
19 | display: 'block',
20 | position: 'relative',
21 | left: -12,
22 | margin: '36px 0 12px',
23 | },
24 |
25 | input: {
26 | display: 'block',
27 | marginBottom: theme.spacing(2.5),
28 | width: 450,
29 | },
30 | }),
31 | )
32 |
33 | export default useStyles
34 |
--------------------------------------------------------------------------------
/src/pages/Auth/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | export const USER_FRAGMENT = gql`
4 | fragment UserFragment on UserModel {
5 | _id
6 | authorization
7 | username
8 | email
9 | role
10 | name
11 | location
12 | organization
13 | website
14 | bio
15 | avatarUrl
16 | isTOTP
17 | createdAt
18 | createdAt
19 | updatedAt
20 | }
21 | `
22 |
23 | export const LOGIN = gql`
24 | query Login($input: LoginInput!) {
25 | login(input: $input) {
26 | ...UserFragment
27 | }
28 | }
29 | ${USER_FRAGMENT}
30 | `
31 |
32 | export const REGISTER = gql`
33 | mutation Register($input: RegisterInput!) {
34 | register(input: $input) {
35 | ...UserFragment
36 | }
37 | }
38 | ${USER_FRAGMENT}
39 | `
40 |
--------------------------------------------------------------------------------
/src/containers/Agenda/components/CustomNavigationButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import classNames from 'classnames'
3 | import { Button } from '@material-ui/core'
4 | import { DateNavigator } from '@devexpress/dx-react-scheduler-material-ui'
5 | import useStyles from '../styles'
6 |
7 | const CustomNavigationButton: FC = ({
8 | type,
9 | onClick,
10 | }) => {
11 | const classes = useStyles()
12 |
13 | return (
14 |
21 | {type === 'forward' ? 'next' : type}
22 |
23 | )
24 | }
25 |
26 | export default CustomNavigationButton
27 |
--------------------------------------------------------------------------------
/src/containers/Events/components/CustomNavigationButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import classNames from 'classnames'
3 | import { Button } from '@material-ui/core'
4 | import { DateNavigator } from '@devexpress/dx-react-scheduler-material-ui'
5 | import useStyles from '../styles'
6 |
7 | const CustomNavigationButton: FC = ({
8 | type,
9 | onClick,
10 | }) => {
11 | const classes = useStyles()
12 |
13 | return (
14 |
21 | {type === 'forward' ? 'next' : type}
22 |
23 | )
24 | }
25 |
26 | export default CustomNavigationButton
27 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/Security.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import ChangePassword from './components/ChangePassword/ChangePassword'
3 | import TwoFactors from './components/TwoFactors/TwoFactors'
4 | import SecurtyIntro from './components/SecurtyIntro/SecurtyIntro'
5 | import SettingsHeader from '../components/SettingsHeader/SettingsHeader'
6 | import SettingWrapper from '../components/SettingWrapper/SettingWrapper'
7 |
8 | const Security: FC = () => {
9 | return (
10 |
11 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export default Security
23 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Before submitting a pull request,** please make sure the following is done:
2 |
3 | 1. Fork the repository and create your branch from master.
4 |
5 | 2. Run yarn in the repository root.
6 |
7 | 3. If you've fixed a bug or added code that should be tested, add tests!
8 |
9 | 4. Ensure the test suite passes (yarn test). Tip: yarn test --watch TestName is helpful in development.
10 |
11 | 5. Run yarn test-prod to test in the production environment. It supports the same options as yarn test.
12 |
13 | 6. If you need a debugger, run yarn debug-test --watch TestName, open chrome://inspect, and press "Inspect".
14 |
15 | 7. Format your code with prettier (yarn prettier).
16 |
17 | 8. Make sure your code lints (yarn lint). Tip: yarn linc to only check changed files.
18 |
19 | 9. If you haven't already, complete the CLA.
20 |
--------------------------------------------------------------------------------
/src/containers/Post/editors/editorIO.ts:
--------------------------------------------------------------------------------
1 | import { RefObject } from 'react'
2 | import { Editor } from '@toast-ui/react-editor'
3 |
4 | export const getMarkdown = (editorRef: RefObject) => {
5 | if (editorRef.current) {
6 | return editorRef.current.getInstance().getMarkdown()
7 | }
8 |
9 | return ''
10 | }
11 |
12 | // TODO: Temporarily block pre tag
13 | export const getHTML = (editorRef: RefObject) => {
14 | if (editorRef.current) {
15 | return editorRef.current
16 | .getInstance()
17 | .getHtml()
18 | .replace(/]*>([\s\S]*?)<\/pre>/gi, '')
19 | }
20 |
21 | return ''
22 | }
23 |
24 | export const setMarkdown = (editorRef: RefObject, content: string) => {
25 | if (editorRef.current) {
26 | return editorRef.current.getInstance().setMarkdown(content)
27 | }
28 |
29 | return ''
30 | }
31 |
--------------------------------------------------------------------------------
/src/containers/Agenda/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles({
4 | header: {
5 | display: 'flex',
6 | alignItems: 'center',
7 | justifyContent: 'space-between',
8 | padding: '0 24px',
9 | },
10 | viewSwitcher: {
11 | position: 'absolute',
12 | top: '18px',
13 | right: '24px',
14 | zIndex: 1,
15 | },
16 | customPaper: {
17 | position: 'relative',
18 | },
19 | navigationButtonSpace: {
20 | margin: '0 4px',
21 | },
22 | customTitle: {
23 | position: 'absolute',
24 | left: '50%',
25 | top: '34px',
26 | transform: 'translate3d(-50%, -50%, 0)',
27 | fontSize: '34px',
28 | fontWeight: 300,
29 | textTransform: 'capitalize',
30 | },
31 | customBtn: {
32 | borderRadius: '24px',
33 | textTransform: 'capitalize',
34 | },
35 | })
36 |
37 | export default useStyles
38 |
--------------------------------------------------------------------------------
/src/containers/Events/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles({
4 | header: {
5 | display: 'flex',
6 | alignItems: 'center',
7 | justifyContent: 'space-between',
8 | padding: '0 24px',
9 | },
10 | viewSwitcher: {
11 | position: 'absolute',
12 | top: '18px',
13 | right: '24px',
14 | zIndex: 1,
15 | },
16 | customPaper: {
17 | position: 'relative',
18 | },
19 | navigationButtonSpace: {
20 | margin: '0 4px',
21 | },
22 | customTitle: {
23 | position: 'absolute',
24 | left: '50%',
25 | top: '34px',
26 | transform: 'translate3d(-50%, -50%, 0)',
27 | fontSize: '34px',
28 | fontWeight: 300,
29 | textTransform: 'capitalize',
30 | },
31 | customBtn: {
32 | borderRadius: '24px',
33 | textTransform: 'capitalize',
34 | },
35 | })
36 |
37 | export default useStyles
38 |
--------------------------------------------------------------------------------
/src/pages/Layouts/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles'
2 | import { DRAWER_WIDTH, FOLDER_DRAWER_WIDTH } from 'src/shared/constants'
3 |
4 | export const transition = (props: 'margin-left' | 'transform') =>
5 | `${props} 300ms cubic-bezier(0.4, 0, 0.6, 1) 0ms`
6 |
7 | const useStyles = makeStyles({
8 | layouts: {
9 | display: 'flex',
10 | overflowX: 'hidden',
11 | },
12 |
13 | expand: {
14 | marginLeft: `${DRAWER_WIDTH}px`,
15 | transition: transition('margin-left'),
16 | },
17 |
18 | shrink: {
19 | marginLeft: `${FOLDER_DRAWER_WIDTH}px`,
20 | transition: transition('margin-left'),
21 | },
22 |
23 | mainWrapper: {
24 | display: 'flex',
25 | flexDirection: 'column',
26 | minWidth: `calc(100% - ${DRAWER_WIDTH}px)`,
27 | width: `calc(100% - ${FOLDER_DRAWER_WIDTH}px)`,
28 | minHeight: '100vh',
29 | },
30 | })
31 |
32 | export default useStyles
33 |
--------------------------------------------------------------------------------
/src/containers/Agenda/components/ExternalViewSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Button, ButtonGroup } from '@material-ui/core'
3 | import { VIEW_DATE } from 'src/shared/constants'
4 | import useStyles from '../styles'
5 |
6 | interface IExternalViewSwitcher {
7 | onChange: (val: string) => void
8 | }
9 |
10 | const ExternalViewSwitcher: FC = ({ onChange }) => {
11 | const classes = useStyles()
12 |
13 | return (
14 |
20 | {VIEW_DATE.map((val) => (
21 | onChange(val)}
24 | className={classes.customBtn}
25 | >
26 | {val}
27 |
28 | ))}
29 |
30 | )
31 | }
32 |
33 | export default ExternalViewSwitcher
34 |
--------------------------------------------------------------------------------
/src/containers/Events/components/ExternalViewSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Button, ButtonGroup } from '@material-ui/core'
3 | import { VIEW_DATE } from 'src/shared/constants'
4 | import useStyles from '../styles'
5 |
6 | interface IExternalViewSwitcher {
7 | onChange: (val: string) => void
8 | }
9 |
10 | const ExternalViewSwitcher: FC = ({ onChange }) => {
11 | const classes = useStyles()
12 |
13 | return (
14 |
20 | {VIEW_DATE.map((val) => (
21 | onChange(val)}
24 | className={classes.customBtn}
25 | >
26 | {val}
27 |
28 | ))}
29 |
30 | )
31 | }
32 |
33 | export default ExternalViewSwitcher
34 |
--------------------------------------------------------------------------------
/src/containers/Settings/components/SettingsHeader/SettingsHeader.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 |
4 | interface Props {
5 | title: string
6 | subTitle: string
7 | }
8 |
9 | const useStyles = makeStyles({
10 | header: { textAlign: 'center' },
11 |
12 | title: {
13 | marginTop: 0,
14 | marginBottom: '8px',
15 | fontSize: '28px',
16 | fontWeight: 500,
17 | lineHeight: 1.3,
18 | color: '#202124',
19 | },
20 |
21 | subTitle: {
22 | marginBottom: '36px',
23 | fontSize: '16px',
24 | color: '#5f6368',
25 | },
26 | })
27 |
28 | const SettingsHeader: FC = ({ title, subTitle }) => {
29 | const classes = useStyles()
30 |
31 | return (
32 |
33 | {title}
34 | {subTitle}
35 |
36 | )
37 | }
38 |
39 | export default SettingsHeader
40 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/SecurtyIntro/SecurtyIntro.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
4 | import SettingItemWrapper from '../../../components/SettingItemWrapper/SettingItemWrapper'
5 |
6 | const useStyles = makeStyles({
7 | tip: {
8 | position: 'absolute',
9 | top: '72px',
10 | width: '460px',
11 | fontSize: '14px',
12 | color: '#5f6368',
13 | },
14 | })
15 |
16 | const SecurtyIntro: FC = () => {
17 | const classes = useStyles()
18 |
19 | return (
20 |
24 |
25 | The Security Checkup gives you personalized recommendations to secure
26 | your account.
27 |
28 |
29 | )
30 | }
31 |
32 | export default SecurtyIntro
33 |
--------------------------------------------------------------------------------
/src/pages/Layouts/Layouts.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import classNames from 'classnames'
3 | import useStyles from './styles'
4 | import Header from './components/Header/Header'
5 | import Drawer from './components/Drawer/Drawer'
6 | import Mains from './components/Mains/Mains'
7 | import Footer from './components/Footer/Footer'
8 |
9 | const Layouts: FC = () => {
10 | const [open, setOpen] = useState(true)
11 |
12 | const classes = useStyles()
13 |
14 | function handleDrawerChange() {
15 | setOpen(!open)
16 | }
17 |
18 | return (
19 |
20 |
21 |
31 |
32 | )
33 | }
34 |
35 | export default Layouts
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 |
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Browser [e.g. stock browser, safari]
37 | - Version [e.g. 22]
38 |
39 | **Additional context**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/StatusCardSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Card } from '@material-ui/core'
3 | import { Skeleton } from '@material-ui/lab'
4 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
5 |
6 | const useStyles = makeStyles((theme: Theme) =>
7 | createStyles({
8 | card: {
9 | padding: 16,
10 | boxShadow:
11 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
12 | borderRadius: 16,
13 | },
14 |
15 | skeleton: {
16 | margin: '8px 0 36px',
17 | },
18 | }),
19 | )
20 |
21 | const StatusCardSkeleton: FC = () => {
22 | const classes = useStyles()
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default StatusCardSkeleton
34 |
--------------------------------------------------------------------------------
/src/components/ConfirmModal/ConfirmModal.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import {
3 | Button,
4 | DialogActions,
5 | DialogTitle,
6 | Dialog,
7 | DialogContent,
8 | DialogContentText,
9 | } from '@material-ui/core'
10 |
11 | interface IConfirmModal {
12 | onSubmit: (ids: string[]) => void
13 | }
14 |
15 | const ConfirmModal: FC = ({ routeState, onSubmit, goBack }) => (
16 |
17 | Are you sure delete those items?
18 |
19 |
20 | Let Google help apps determine location. This means sending anonymous
21 | location data to Google, even when no apps are running.
22 |
23 |
24 |
25 |
26 | Cancel
27 |
28 | onSubmit(routeState)} color="primary">
29 | OK
30 |
31 |
32 |
33 | )
34 |
35 | export default ConfirmModal
36 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/CPUChart.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import { Line } from 'react-chartjs-2'
3 | import chartConfig from '../chartjsConfig'
4 | import { IBandwagonUsageStatus } from '../types'
5 | import ToggleChart from './ToggleChart'
6 | import UsageStatusSkeleton from './UsageStatusSkeleton'
7 |
8 | interface Props {
9 | usageStatus: IBandwagonUsageStatus[]
10 | isFetchingUsageStatus: boolean
11 | }
12 |
13 | const CPUChart: FC = ({ usageStatus, isFetchingUsageStatus }) => {
14 | const [cpuLimit, setCPULimit] = useState(12)
15 |
16 | return (
17 | <>
18 | {isFetchingUsageStatus ? (
19 |
20 | ) : (
21 | setCPULimit(value)}>
22 |
27 |
28 | )}
29 | >
30 | )
31 | }
32 |
33 | export default CPUChart
34 |
--------------------------------------------------------------------------------
/src/containers/Agenda/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | const AGENDA_FRAGMENT = gql`
4 | fragment AgendaFragment on AgendaModel {
5 | _id
6 | title
7 | startDate
8 | endDate
9 | allDay
10 | notes
11 | rRule
12 | exDate
13 | }
14 | `
15 |
16 | export const CREATE_ONE_AGENDA = gql`
17 | mutation CreateAgenda($input: CreateAgendaInput!) {
18 | createAgenda(input: $input) {
19 | ...AgendaFragment
20 | }
21 | }
22 | ${AGENDA_FRAGMENT}
23 | `
24 |
25 | export const UPDATE_ONE_AGENDA = gql`
26 | mutation UpdateAgendaById($input: UpdateAgendaInput!) {
27 | updateAgendaById(input: $input) {
28 | ...AgendaFragment
29 | }
30 | }
31 | ${AGENDA_FRAGMENT}
32 | `
33 |
34 | export const AGENDAS = gql`
35 | query GetAgenda {
36 | getAgenda {
37 | ...AgendaFragment
38 | }
39 | }
40 | ${AGENDA_FRAGMENT}
41 | `
42 |
43 | export const DELETE_ONE_AGENDA = gql`
44 | mutation DeleteAgendaById($id: ID!) {
45 | deleteAgendaById(id: $id) {
46 | ...AgendaFragment
47 | }
48 | }
49 | ${AGENDA_FRAGMENT}
50 | `
51 |
--------------------------------------------------------------------------------
/src/containers/Events/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 |
3 | const AGENDA_FRAGMENT = gql`
4 | fragment AgendaFragment on AgendaModel {
5 | _id
6 | title
7 | startDate
8 | endDate
9 | allDay
10 | notes
11 | rRule
12 | exDate
13 | }
14 | `
15 |
16 | export const CREATE_ONE_AGENDA = gql`
17 | mutation CreateAgenda($input: CreateAgendaInput!) {
18 | createAgenda(input: $input) {
19 | ...AgendaFragment
20 | }
21 | }
22 | ${AGENDA_FRAGMENT}
23 | `
24 |
25 | export const UPDATE_ONE_AGENDA = gql`
26 | mutation UpdateAgendaById($input: UpdateAgendaInput!) {
27 | updateAgendaById(input: $input) {
28 | ...AgendaFragment
29 | }
30 | }
31 | ${AGENDA_FRAGMENT}
32 | `
33 |
34 | export const AGENDAS = gql`
35 | query GetAgenda {
36 | getAgenda {
37 | ...AgendaFragment
38 | }
39 | }
40 | ${AGENDA_FRAGMENT}
41 | `
42 |
43 | export const DELETE_ONE_AGENDA = gql`
44 | mutation DeleteAgendaById($id: ID!) {
45 | deleteAgendaById(id: $id) {
46 | ...AgendaFragment
47 | }
48 | }
49 | ${AGENDA_FRAGMENT}
50 | `
51 |
--------------------------------------------------------------------------------
/src/pages/Layouts/components/Mains/Mains.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Route, Switch } from 'react-router-dom'
3 | import loadable from '@loadable/component'
4 | import { mapRoutes } from 'src/routes'
5 | import Loading from 'src/components/Loading/InstagramLoading'
6 | import NotFound from 'src/components/NotFound/NotFound'
7 | import useStyles from './styles'
8 |
9 | const routeList = mapRoutes()
10 |
11 | const Mains: FC = () => {
12 | const classes = useStyles()
13 |
14 | return (
15 |
16 |
17 | {routeList.map((route) => (
18 | import(`src/containers/${route.component}`),
24 | {
25 | fallback: ,
26 | },
27 | )}
28 | />
29 | ))}
30 |
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
38 | export default Mains
39 |
--------------------------------------------------------------------------------
/src/components/NotFound/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useHistory } from 'react-router-dom'
3 | import { Button } from '@material-ui/core'
4 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
5 | import useStyles from './styles'
6 |
7 | const NotFound: FC = () => {
8 | const classes = useStyles()
9 | const history = useHistory()
10 |
11 | const toHomePage = () => {
12 | history.push('/')
13 | }
14 |
15 | return (
16 |
17 |
18 | 404: The page you are looking for isn’t here
19 |
20 |
21 | You either tried some shady route or you came here by mistake. Whichever
22 | it is, try using the navigation.
23 |
24 |
25 |
26 |
27 |
28 | Back to Home
29 |
30 |
31 | )
32 | }
33 |
34 | export default NotFound
35 |
--------------------------------------------------------------------------------
/src/hooks/useScript.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 |
3 | export const useScriptUrl = (
4 | url: string,
5 | isAsync = true,
6 | htmlEl?: HTMLElement,
7 | ) => {
8 | useEffect(() => {
9 | const $scriptEl = document.createElement('script')
10 |
11 | $scriptEl.src = url
12 | if (isAsync) $scriptEl.async = true
13 |
14 | htmlEl
15 | ? htmlEl.appendChild($scriptEl)
16 | : document.body.appendChild($scriptEl)
17 | return () => {
18 | htmlEl
19 | ? htmlEl.removeChild($scriptEl)
20 | : document.body.removeChild($scriptEl)
21 | }
22 | }, [htmlEl, url, isAsync])
23 | }
24 |
25 | export const useScript = (content: any, htmlEl?: HTMLElement) => {
26 | useEffect(() => {
27 | const $scriptEl = document.createElement('script')
28 | $scriptEl.innerHTML = content
29 |
30 | htmlEl
31 | ? htmlEl.appendChild($scriptEl)
32 | : document.body.appendChild($scriptEl)
33 | return () => {
34 | htmlEl
35 | ? htmlEl.removeChild($scriptEl)
36 | : document.body.removeChild($scriptEl)
37 | }
38 | }, [htmlEl, content])
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/Uploader/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | avatarUploader: {
6 | display: 'inline-flex',
7 | alignItems: 'center',
8 | justifyContent: 'center',
9 | position: 'relative',
10 | width: '128px',
11 | height: '128px',
12 | },
13 |
14 | addBtn: {
15 | width: '48px',
16 | height: '48px',
17 | },
18 |
19 | customInput: {
20 | position: 'absolute',
21 | width: '100%',
22 | height: '100%',
23 | top: 0,
24 | left: 0,
25 | opacity: 0,
26 | cursor: 'pointer',
27 | },
28 |
29 | img: {
30 | width: '114px',
31 | height: '114px',
32 | objectFit: 'cover',
33 | },
34 |
35 | simpleUploader: {
36 | marginLeft: '24px',
37 | },
38 |
39 | simpleContent: {
40 | position: 'absolute',
41 | marginTop: '10px',
42 | },
43 |
44 | customLoadingCircle: {
45 | position: 'absolute',
46 | },
47 | }),
48 | )
49 |
50 | export default useStyles
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Yancey Inc. and its affiliates.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/logo/README.md:
--------------------------------------------------------------------------------
1 | # The Yancey Blog Logo
2 |
3 | This is the only official Yancey Blog Logo.
4 | Don't use any other logos to represent Yancey Blog.
5 |
6 | It comes in several flavors.
7 |
8 | ## Just the Logo
9 |
10 |
11 |
12 | Download as [PNG](./logo.jpg)
13 |
14 | ## Logo with Text
15 |
16 |
17 |
18 | Download as [PNG](./logo-with-text.png).
19 |
20 | ## Modifications
21 |
22 | Whenever possible, we ask you to use the originals provided on this page.
23 |
24 | If for some reason you must change how the title is rendered and can't use the prerendered version we provide, we ask that you use the free [Lato Black](http://www.latofonts.com/lato-free-fonts/) font and ensure there is enough space between the logo and the title.
25 |
26 | When in doubt, use the original logos.
27 |
28 | ## Credits
29 |
30 | The Yancey Blog logo was designed by [Yancey Leo](https://yanceyleo.com/).
31 |
32 | ## License
33 |
34 | The Redux logo is licensed under CC0, waiving all copyright.
35 | [Read the license.](../LICENSE-logo.md)
36 |
--------------------------------------------------------------------------------
/src/pages/Layouts/components/Header/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles({
4 | header: {
5 | display: 'flex',
6 | alignItems: 'center',
7 | justifyContent: 'space-between',
8 | flexDirection: 'row',
9 | padding: '12px 24px 48px',
10 | background: 'transparent',
11 | boxShadow: 'none',
12 | },
13 | fabIcon: {
14 | color: '#999',
15 | backgroundColor: '#fff',
16 | boxShadow: `0 2px 2px 0 rgba(153, 153, 153, 0.14),
17 | 0 3px 1px -2px rgba(153, 153, 153, 0.2),
18 | 0 1px 5px 0 rgba(153, 153, 153, 0.12)`,
19 | '&:hover': { backgroundColor: '#fff' },
20 | },
21 | left: {
22 | display: 'flex',
23 | alignItems: 'center',
24 | },
25 |
26 | title: {
27 | marginLeft: '24px',
28 | color: '#000',
29 | },
30 |
31 | marginRight: {
32 | marginRight: '24px',
33 | },
34 |
35 | anchor: {
36 | color: '#000',
37 |
38 | '&:hover': {
39 | textDecoration: 'none',
40 | },
41 | },
42 |
43 | menu: {
44 | '& .MuiListItemIcon-root': {
45 | minWidth: 42,
46 | },
47 | },
48 | })
49 |
50 | export default useStyles
51 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/TagClouds.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Paper, Chip } from '@material-ui/core'
3 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
4 |
5 | interface Props {
6 | tags: string[]
7 | loading: boolean
8 | }
9 |
10 | const useStyles = makeStyles((theme: Theme) =>
11 | createStyles({
12 | paper: {
13 | padding: 16,
14 | overflowY: 'scroll',
15 | boxShadow:
16 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
17 | borderRadius: 16,
18 | },
19 |
20 | chip: {
21 | margin: 8,
22 | },
23 |
24 | header: {
25 | marginBottom: 16,
26 | fontSize: 16,
27 | fontWeight: 600,
28 | },
29 | }),
30 | )
31 |
32 | const TagClouds: FC = ({ tags }) => {
33 | const classes = useStyles()
34 |
35 | return (
36 |
37 |
38 | {tags.map((tag) => (
39 |
40 | ))}
41 |
42 | )
43 | }
44 |
45 | export default TagClouds
46 |
--------------------------------------------------------------------------------
/src/components/Toast/Toast.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useSnackbar, VariantType, WithSnackbarProps } from 'notistack'
3 |
4 | interface Props {
5 | setUseSnackbarRef: (showSnackbar: WithSnackbarProps) => void
6 | }
7 |
8 | const InnerSnackbarUtilsConfigurator: FC = (props: Props) => {
9 | props.setUseSnackbarRef(useSnackbar())
10 | return null
11 | }
12 |
13 | let useSnackbarRef: WithSnackbarProps
14 | const setUseSnackbarRef = (useSnackbarRefProp: WithSnackbarProps) => {
15 | useSnackbarRef = useSnackbarRefProp
16 | }
17 |
18 | export const SnackbarUtilsConfigurator = () => {
19 | return (
20 |
21 | )
22 | }
23 |
24 | const toast = {
25 | success(msg: string) {
26 | this.toast(msg, 'success')
27 | },
28 | warning(msg: string) {
29 | this.toast(msg, 'warning')
30 | },
31 | info(msg: string) {
32 | this.toast(msg, 'info')
33 | },
34 | error(msg: string) {
35 | this.toast(msg, 'error')
36 | },
37 | toast(msg: string, variant: VariantType = 'default') {
38 | useSnackbarRef.enqueueSnackbar(msg, { variant })
39 | },
40 | }
41 |
42 | export default toast
43 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/RecoveryCodes/recoveryCode.module.scss:
--------------------------------------------------------------------------------
1 | .recoveryCode {
2 | color: #3c4043;
3 |
4 | :global(.MuiDialog-paper) {
5 | width: 456px;
6 | }
7 |
8 | :global(.MuiDialogTitle-root) {
9 | padding-bottom: 0;
10 | }
11 |
12 | :global(.MuiDialogActions-root) {
13 | margin-top: 20px;
14 | }
15 |
16 | ul {
17 | list-style: none;
18 | }
19 | }
20 |
21 | .tips {
22 | color: #3c4043;
23 | font-size: 14px;
24 | }
25 |
26 | .listContainer {
27 | position: relative;
28 | margin: 24px 0;
29 | padding: 16px 0;
30 | text-align: center;
31 | border: 1px dotted #dadce0;
32 | }
33 |
34 | .recoveryCodesGroup {
35 | display: grid;
36 | grid-template-columns: repeat(2, 1fr);
37 | }
38 |
39 | .recoveryCodesItem {
40 | font-size: 20px;
41 | color: #5f6368;
42 | }
43 |
44 | .square {
45 | position: relative;
46 | top: -1px;
47 | display: inline-block;
48 | margin-right: 10px;
49 | width: 12px;
50 | height: 12px;
51 | border: 1px solid;
52 | }
53 |
54 | .tipGroup {
55 | margin-top: 20px;
56 | }
57 |
58 | .loading {
59 | display: flex;
60 | justify-content: center;
61 | align-items: center;
62 | height: 180px;
63 | }
64 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/DiskChart.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import { Bar } from 'react-chartjs-2'
3 | import chartConfig from '../chartjsConfig'
4 | import { IBandwagonUsageStatus } from '../types'
5 | import ToggleChart from './ToggleChart'
6 | import UsageStatusSkeleton from './UsageStatusSkeleton'
7 |
8 | interface Props {
9 | usageStatus: IBandwagonUsageStatus[]
10 | isFetchingUsageStatus: boolean
11 | }
12 |
13 | const DiskChart: FC = ({ usageStatus, isFetchingUsageStatus }) => {
14 | const [diskLimit, setDiskLimit] = useState(12)
15 |
16 | return (
17 | <>
18 | {isFetchingUsageStatus ? (
19 |
20 | ) : (
21 | setDiskLimit(value)}
23 | >
24 |
34 |
35 | )}
36 | >
37 | )
38 | }
39 |
40 | export default DiskChart
41 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/NetWorkChart.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import { Bar } from 'react-chartjs-2'
3 | import chartConfig from '../chartjsConfig'
4 | import { IBandwagonUsageStatus } from '../types'
5 | import ToggleChart from './ToggleChart'
6 | import UsageStatusSkeleton from './UsageStatusSkeleton'
7 |
8 | interface Props {
9 | usageStatus: IBandwagonUsageStatus[]
10 | isFetchingUsageStatus: boolean
11 | }
12 |
13 | const NetWorkChart: FC = ({ usageStatus, isFetchingUsageStatus }) => {
14 | const [networkLimit, setNetworkLimit] = useState(12)
15 |
16 | return (
17 | <>
18 | {isFetchingUsageStatus ? (
19 |
20 | ) : (
21 | setNetworkLimit(value)}
23 | >
24 |
34 |
35 | )}
36 | >
37 | )
38 | }
39 |
40 | export default NetWorkChart
41 |
--------------------------------------------------------------------------------
/src/pages/Layouts/components/Drawer/components/ChildItem.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import classNames from 'classnames'
3 | import { Link } from '@material-ui/icons'
4 | import { getInitials } from 'src/shared/utils'
5 | import { RouteChildren } from 'src/routes'
6 | import useStyles from '../styles'
7 |
8 | interface ChildItemProps {
9 | open: boolean
10 | childRoute: RouteChildren
11 | }
12 |
13 | const ChildItem: FC = ({
14 | open,
15 | childRoute: { name, isExternalLink },
16 | }) => {
17 | const classes = useStyles()
18 |
19 | return (
20 |
25 |
30 | {getInitials(name)}
31 |
32 |
37 | {name}
38 | {isExternalLink && }
39 |
40 |
41 | )
42 | }
43 |
44 | export default ChildItem
45 |
--------------------------------------------------------------------------------
/src/containers/Home/Announcement/types.ts:
--------------------------------------------------------------------------------
1 | import { Open } from 'src/hooks/useOpenModal'
2 |
3 | export interface IAnnouncement {
4 | _id: string
5 | weight: number
6 | content: string
7 | createdAt: string
8 | updatedAt: string
9 | }
10 |
11 | export interface Query {
12 | getAnnouncements: IAnnouncement[]
13 | }
14 |
15 | export interface CreateAnnouncementMutation {
16 | createAnnouncement: IAnnouncement
17 | }
18 |
19 | export interface CreateAnnouncementVars {
20 | input: {
21 | content: string
22 | }
23 | }
24 |
25 | export interface DeleteAnnouncementByIdMutation {
26 | deleteAnnouncementById: IAnnouncement
27 | }
28 |
29 | export interface DeleteAnnouncementByIdVars {
30 | id: string
31 | }
32 |
33 | export interface AnnouncementTableProps {
34 | dataSource: IAnnouncement[]
35 | isFetching: boolean
36 | isDeleting: boolean
37 | isExchanging: boolean
38 | isBatchDeleting: boolean
39 | createAnnouncement: Function
40 | updateAnnouncementById: Function
41 | deleteAnnouncementById: Function
42 | deleteAnnouncements: Function
43 | exchangePosition: Function
44 | }
45 |
46 | export interface AnnouncementModalProps {
47 | open: Open
48 | handleOpen: Function
49 | createAnnouncement: Function
50 | updateAnnouncementById: Function
51 | }
52 |
--------------------------------------------------------------------------------
/src/shared/utils.ts:
--------------------------------------------------------------------------------
1 | import qs from 'query-string'
2 | import history from './history'
3 |
4 | interface Dict {
5 | [index: string]: any
6 | }
7 |
8 | export const getInitials = (txt: string) =>
9 | txt
10 | .split(' ')
11 | .map((val: string) => val[0])
12 | .join('')
13 |
14 | export const getType = (type: T) =>
15 | Object.prototype.toString.call(type).slice(8, -1).toLowerCase()
16 |
17 | export const goBack = () => history.goBack()
18 |
19 | export const parseSearch = (search: string) =>
20 | qs.parse(search, { parseBooleans: true })
21 |
22 | export const stringfySearch = (searchObj: Dict) => qs.stringify(searchObj)
23 |
24 | export const noop = () => {}
25 |
26 | export const isNumber = (type: T) => getType(type) === 'number'
27 |
28 | export const isString = (type: T) => getType(type) === 'string'
29 |
30 | export const isBoolean = (type: T) => getType(type) === 'boolean'
31 |
32 | export const isArray = (type: T) => Array.isArray(type)
33 |
34 | export const logout = () => {
35 | window.localStorage.clear()
36 | history.replace('/login')
37 | }
38 |
39 | export const getURLPathName = (url: string) =>
40 | decodeURI(new URL(url).pathname.slice(1))
41 |
42 | export const generateFile = (data: string, type = 'text/plain') => {
43 | return URL.createObjectURL(new Blob([data], { type }))
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/github-actions.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Actions for Blog CMS v2
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 |
11 | - name: Use Node.js 12.x
12 | uses: actions/setup-node@v2
13 | with:
14 | node-version: '12.x'
15 |
16 | - name: Get yarn cache directory path
17 | id: yarn-cache-dir-path
18 | run: echo "::set-output name=dir::$(yarn cache dir)"
19 |
20 | - uses: actions/cache@v2
21 | id: yarn-cache
22 | with:
23 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
24 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
25 | restore-keys: |
26 | ${{ runner.os }}-yarn-
27 | - name: Install dependencies
28 | run: yarn
29 |
30 | - name: Pre compilation
31 | run: yarn build
32 |
33 | deployment:
34 | runs-on: ubuntu-latest
35 | needs: test
36 | if: startsWith(github.ref, 'refs/tags/v')
37 | steps:
38 | - name: Deploy to server
39 | uses: appleboy/ssh-action@v0.1.4
40 | with:
41 | host: ${{ secrets.HOST }}
42 | username: ${{ secrets.USERNAME }}
43 | password: ${{ secrets.PASSWORD }}
44 | script: sh ./blog-cms-v2-deploy.sh
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/UsageStatusSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Card } from '@material-ui/core'
3 | import { Skeleton } from '@material-ui/lab'
4 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
5 |
6 | const useStyles = makeStyles((theme: Theme) =>
7 | createStyles({
8 | card: {
9 | padding: 16,
10 | boxShadow:
11 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
12 | borderRadius: 16,
13 | },
14 |
15 | skeleton: {
16 | margin: '8px 0 36px',
17 | },
18 |
19 | firstSkeleton: {
20 | float: 'right',
21 | },
22 |
23 | secondSkeleton: {
24 | margin: '0 auto 12px',
25 | },
26 | }),
27 | )
28 |
29 | const UsageStatusSkeleton: FC = () => {
30 | const classes = useStyles()
31 |
32 | return (
33 |
34 |
40 |
46 |
47 |
48 | )
49 | }
50 |
51 | export default UsageStatusSkeleton
52 |
--------------------------------------------------------------------------------
/src/containers/Post/components/UploaderModal.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import {
3 | DialogActions,
4 | DialogTitle,
5 | Dialog,
6 | DialogContent,
7 | Button,
8 | } from '@material-ui/core'
9 | import { makeStyles } from '@material-ui/core/styles'
10 | import Uploader from '../../../components/Uploader/Uploader'
11 |
12 | interface Props {
13 | open: boolean
14 | onOk: Function
15 | onClose: Function
16 | onChange: Function
17 | }
18 |
19 | const useStyles = makeStyles({
20 | uploaderModalContent: {
21 | margin: '24px auto',
22 | },
23 | })
24 |
25 | const UploaderModal: FC = ({ open, onOk, onClose, onChange }) => {
26 | const classes = useStyles()
27 |
28 | const handleOk = () => {
29 | onClose(false)
30 | onOk()
31 | }
32 | return (
33 | onClose(false)}>
34 | Insert image to markdown editor.
35 |
36 |
37 |
38 |
39 | onClose(false)}>
40 | Cancel
41 |
42 |
43 | Insert
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export default UploaderModal
51 |
--------------------------------------------------------------------------------
/src/containers/Post/algolia/algoliaSearch.ts:
--------------------------------------------------------------------------------
1 | import algoliasearch from 'algoliasearch'
2 | import SnackbarUtils from 'src/components/Toast/Toast'
3 |
4 | const {
5 | REACT_APP_ALGOLIA_APPLICATION_ID,
6 | REACT_APP_ALGOLIA_ADMIN_API_KEY,
7 | REACT_APP_ALGOLIA_SEARCH_INDEX,
8 | } = process.env
9 |
10 | const client = algoliasearch(
11 | REACT_APP_ALGOLIA_APPLICATION_ID,
12 | REACT_APP_ALGOLIA_ADMIN_API_KEY,
13 | )
14 | const index = client.initIndex(REACT_APP_ALGOLIA_SEARCH_INDEX)
15 |
16 | export const sendPostToAlgolia = async (
17 | objectID: string,
18 | name: string,
19 | description: string,
20 | content: string,
21 | imageUrl: string,
22 | labels: string[],
23 | ) => {
24 | try {
25 | await index.saveObject(
26 | {
27 | objectID,
28 | name,
29 | description,
30 | content,
31 | imageUrl,
32 | labels,
33 | },
34 | { autoGenerateObjectIDIfNotExist: true },
35 | )
36 | } catch (e) {
37 | SnackbarUtils.error(e.message)
38 | }
39 | }
40 |
41 | export const deletePostOnAlgolia = async (objectID: string) => {
42 | try {
43 | await index.deleteObject(objectID)
44 | } catch (e) {
45 | SnackbarUtils.error(e.message)
46 | }
47 | }
48 |
49 | export const deletePostsOnAlgolia = async (objectIDs: string[]) => {
50 | try {
51 | await index.deleteObjects(objectIDs)
52 | } catch (e) {
53 | SnackbarUtils.error(e.message)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/containers/Music/LiveTour/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const LIVE_TOUR_FRAGMENT = gql`
5 | fragment LiveTourFragment on LiveTourModel {
6 | _id
7 | title
8 | posterUrl
9 | showTime
10 | createdAt
11 | updatedAt
12 | }
13 | `
14 |
15 | export const CREATE_ONE_LIVE_TOUR = gql`
16 | mutation CreateLiveTour($input: CreateLiveTourInput!) {
17 | createLiveTour(input: $input) {
18 | ...LiveTourFragment
19 | }
20 | }
21 | ${LIVE_TOUR_FRAGMENT}
22 | `
23 |
24 | export const UPDATE_ONE_LIVE_TOUR = gql`
25 | mutation UpdateLiveTourById($input: UpdateLiveTourInput!) {
26 | updateLiveTourById(input: $input) {
27 | ...LiveTourFragment
28 | }
29 | }
30 | ${LIVE_TOUR_FRAGMENT}
31 | `
32 |
33 | export const LIVE_TOURS = gql`
34 | query GetLiveTours {
35 | getLiveTours {
36 | ...LiveTourFragment
37 | }
38 | }
39 | ${LIVE_TOUR_FRAGMENT}
40 | `
41 |
42 | export const DELETE_ONE_LIVE_TOUR = gql`
43 | mutation DeleteLiveTourById($id: ID!) {
44 | deleteLiveTourById(id: $id) {
45 | ...LiveTourFragment
46 | }
47 | }
48 | ${LIVE_TOUR_FRAGMENT}
49 | `
50 |
51 | export const BATCH_DELETE_LIVE_TOUR = gql`
52 | mutation DeleteLiveTours($ids: [ID!]!) {
53 | deleteLiveTours(ids: $ids) {
54 | ...BatchDeleteFragment
55 | }
56 | }
57 | ${BATCH_DELETE_FRAGMENT}
58 | `
59 |
--------------------------------------------------------------------------------
/src/pages/Layouts/components/Drawer/components/ParentItem.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import classNames from 'classnames'
3 | import { Link } from '@material-ui/icons'
4 | import { noop } from 'src/shared/utils'
5 | import { Route } from 'src/routes'
6 | import useStyles from '../styles'
7 |
8 | interface ParentItemProps {
9 | open: boolean
10 | route: Route
11 | handleFoldNameChange?: (name: string) => void
12 | }
13 |
14 | const ParentItem: FC = ({
15 | open,
16 | route: { name, icon, isExternalLink },
17 | handleFoldNameChange,
18 | }) => {
19 | const classes = useStyles()
20 |
21 | return (
22 | handleFoldNameChange(name) : noop}
27 | >
28 |
33 | {icon}
34 |
35 |
40 | {name}
41 | {isExternalLink && }
42 | {handleFoldNameChange && }
43 |
44 |
45 | )
46 | }
47 |
48 | export default ParentItem
49 |
--------------------------------------------------------------------------------
/src/containers/Post/styles.ts:
--------------------------------------------------------------------------------
1 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
2 |
3 | const useStyles = makeStyles((theme: Theme) =>
4 | createStyles({
5 | editorWrapper: {
6 | marginTop: '8px',
7 | width: '100%',
8 | },
9 |
10 | header: {
11 | display: 'grid',
12 | gridTemplateColumns: 'repeat(2, 1fr)',
13 | },
14 |
15 | publishTools: {
16 | display: 'flex',
17 | justifyContent: 'flex-end',
18 | alignItems: 'center',
19 | },
20 |
21 | summary: { width: '50%', margin: '24px 0 48px' },
22 |
23 | summaryTxtFiled: {
24 | marginBottom: '24px',
25 | },
26 |
27 | btn: { marginLeft: theme.spacing(1), marginBottom: theme.spacing(1) },
28 |
29 | pagination: {
30 | display: 'flex',
31 | justifyContent: 'flex-end',
32 | marginTop: '24px',
33 | },
34 |
35 | uploadImageIcon: {
36 | position: 'relative',
37 | top: '-4px',
38 | },
39 |
40 | search: {
41 | position: 'absolute',
42 | right: '24px',
43 | top: '83px',
44 | padding: '2px 4px',
45 | display: 'flex',
46 | alignItems: 'center',
47 | width: 400,
48 | zIndex: 1101,
49 | },
50 | input: {
51 | marginLeft: theme.spacing(1),
52 | flex: 1,
53 | },
54 | iconButton: {
55 | padding: 10,
56 | },
57 | divider: {
58 | height: 28,
59 | margin: 4,
60 | },
61 | }),
62 | )
63 |
64 | export default useStyles
65 |
--------------------------------------------------------------------------------
/src/containers/Music/BestAlbum/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const BEST_ALBUM_FRAGMENT = gql`
5 | fragment BestAlbumFragment on BestAlbumModel {
6 | _id
7 | title
8 | artist
9 | coverUrl
10 | mvUrl
11 | releaseDate
12 | createdAt
13 | updatedAt
14 | }
15 | `
16 |
17 | export const CREATE_ONE_BEST_ALBUM = gql`
18 | mutation CreateBestAlbum($input: CreateBestAlbumInput!) {
19 | createBestAlbum(input: $input) {
20 | ...BestAlbumFragment
21 | }
22 | }
23 | ${BEST_ALBUM_FRAGMENT}
24 | `
25 |
26 | export const UPDATE_ONE_BEST_ALBUM = gql`
27 | mutation UpdateBestAlbumById($input: UpdateBestAlbumInput!) {
28 | updateBestAlbumById(input: $input) {
29 | ...BestAlbumFragment
30 | }
31 | }
32 | ${BEST_ALBUM_FRAGMENT}
33 | `
34 |
35 | export const BEST_ALBUMS = gql`
36 | query GetBestAlbums {
37 | getBestAlbums {
38 | ...BestAlbumFragment
39 | }
40 | }
41 | ${BEST_ALBUM_FRAGMENT}
42 | `
43 |
44 | export const DELETE_ONE_BEST_ALBUM = gql`
45 | mutation DeleteBestAlbumById($id: ID!) {
46 | deleteBestAlbumById(id: $id) {
47 | ...BestAlbumFragment
48 | }
49 | }
50 | ${BEST_ALBUM_FRAGMENT}
51 | `
52 |
53 | export const BATCH_DELETE_BEST_ALBUMS = gql`
54 | mutation DeleteBestAlbums($ids: [ID!]!) {
55 | deleteBestAlbums(ids: $ids) {
56 | ...BatchDeleteFragment
57 | }
58 | }
59 | ${BATCH_DELETE_FRAGMENT}
60 | `
61 |
--------------------------------------------------------------------------------
/src/containers/Home/OpenSource/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const OPEN_SOURCE_FRAGMENT = gql`
5 | fragment OpenSourceFragment on OpenSourceModel {
6 | _id
7 | title
8 | description
9 | url
10 | posterUrl
11 | createdAt
12 | updatedAt
13 | }
14 | `
15 |
16 | export const CREATE_ONE_OPEN_SOURCE = gql`
17 | mutation CreateOpenSource($input: CreateOpenSourceInput!) {
18 | createOpenSource(input: $input) {
19 | ...OpenSourceFragment
20 | }
21 | }
22 | ${OPEN_SOURCE_FRAGMENT}
23 | `
24 |
25 | export const UPDATE_ONE_OPEN_SOURCE = gql`
26 | mutation UpdateOpenSourceById($input: UpdateOpenSourceInput!) {
27 | updateOpenSourceById(input: $input) {
28 | ...OpenSourceFragment
29 | }
30 | }
31 | ${OPEN_SOURCE_FRAGMENT}
32 | `
33 |
34 | export const OPEN_SOURCES = gql`
35 | query GetOpenSources {
36 | getOpenSources {
37 | ...OpenSourceFragment
38 | }
39 | }
40 | ${OPEN_SOURCE_FRAGMENT}
41 | `
42 |
43 | export const DELETE_ONE_OPEN_SOURCE = gql`
44 | mutation DeleteOpenSourceById($id: ID!) {
45 | deleteOpenSourceById(id: $id) {
46 | ...OpenSourceFragment
47 | }
48 | }
49 | ${OPEN_SOURCE_FRAGMENT}
50 | `
51 |
52 | export const BATCH_DELETE_OPEN_SOURCE = gql`
53 | mutation DeleteOpenSources($ids: [ID!]!) {
54 | deleteOpenSources(ids: $ids) {
55 | ...BatchDeleteFragment
56 | }
57 | }
58 | ${BATCH_DELETE_FRAGMENT}
59 | `
60 |
--------------------------------------------------------------------------------
/src/containers/Music/YanceyMusic/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const YANCEY_MUSIC_FRAGMENT = gql`
5 | fragment YanceyMusicFragment on YanceyMusicModel {
6 | _id
7 | title
8 | soundCloudUrl
9 | posterUrl
10 | releaseDate
11 | createdAt
12 | updatedAt
13 | }
14 | `
15 |
16 | export const CREATE_ONE_YANCEY_MUSIC = gql`
17 | mutation CreateYanceyMusic($input: CreateYanceyMusicInput!) {
18 | createYanceyMusic(input: $input) {
19 | ...YanceyMusicFragment
20 | }
21 | }
22 | ${YANCEY_MUSIC_FRAGMENT}
23 | `
24 |
25 | export const UPDATE_ONE_YANCEY_MUSIC = gql`
26 | mutation UpdateYanceyMusicById($input: UpdateYanceyMusicInput!) {
27 | updateYanceyMusicById(input: $input) {
28 | ...YanceyMusicFragment
29 | }
30 | }
31 | ${YANCEY_MUSIC_FRAGMENT}
32 | `
33 |
34 | export const YANCEY_MUSIC = gql`
35 | query GetYanceyMusic {
36 | getYanceyMusic {
37 | ...YanceyMusicFragment
38 | }
39 | }
40 | ${YANCEY_MUSIC_FRAGMENT}
41 | `
42 |
43 | export const DELETE_ONE_YANCEY_MUSIC = gql`
44 | mutation DeleteYanceyMusicById($id: ID!) {
45 | deleteYanceyMusicById(id: $id) {
46 | ...YanceyMusicFragment
47 | }
48 | }
49 | ${YANCEY_MUSIC_FRAGMENT}
50 | `
51 |
52 | export const BATCH_DELETE_YANCEY_MUSIC = gql`
53 | mutation DeleteYanceyMusic($ids: [ID!]!) {
54 | deleteYanceyMusic(ids: $ids) {
55 | ...BatchDeleteFragment
56 | }
57 | }
58 | ${BATCH_DELETE_FRAGMENT}
59 | `
60 |
--------------------------------------------------------------------------------
/src/components/ImagePopup/ImagePopup.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import PopupState, { bindTrigger, bindPopover } from 'material-ui-popup-state'
3 | import { Popover } from '@material-ui/core'
4 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
5 | import {
6 | POPOVER_ANCHOR_ORIGIN,
7 | POPOVER_TRANSFORM_ORIGIN,
8 | } from 'src/shared/constants'
9 |
10 | interface Props {
11 | imgUrl: string
12 | imgName: string
13 | }
14 |
15 | const useStyles = makeStyles((theme: Theme) =>
16 | createStyles({
17 | thumb: {
18 | width: 150,
19 | cursor: 'pointer',
20 | },
21 | full: {
22 | display: 'block',
23 | width: 400,
24 | },
25 | }),
26 | )
27 |
28 | const ImagePopup: FC = ({ imgUrl, imgName }) => {
29 | const classes = useStyles()
30 |
31 | return (
32 |
33 | {(popupState) => (
34 |
35 |
41 |
47 |
48 |
49 |
50 | )}
51 |
52 | )
53 | }
54 |
55 | export default ImagePopup
56 |
--------------------------------------------------------------------------------
/src/containers/Home/Motto/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const MOTTO_FRAGMENT = gql`
5 | fragment MottoFragment on MottoModel {
6 | _id
7 | content
8 | weight
9 | createdAt
10 | updatedAt
11 | }
12 | `
13 |
14 | export const CREATE_ONE_MOTTO = gql`
15 | mutation CreateMotto($input: CreateMottoInput!) {
16 | createMotto(input: $input) {
17 | ...MottoFragment
18 | }
19 | }
20 | ${MOTTO_FRAGMENT}
21 | `
22 |
23 | export const UPDATE_ONE_MOTTO = gql`
24 | mutation UpdateMottoById($input: UpdateMottoInput!) {
25 | updateMottoById(input: $input) {
26 | ...MottoFragment
27 | }
28 | }
29 | ${MOTTO_FRAGMENT}
30 | `
31 |
32 | export const EXCHANGE_POSITION = gql`
33 | mutation ExchangePositionMotto($input: ExchangePositionInput!) {
34 | exchangePositionMotto(input: $input) {
35 | ...MottoFragment
36 | }
37 | }
38 | ${MOTTO_FRAGMENT}
39 | `
40 |
41 | export const MOTTOS = gql`
42 | query GetMottos {
43 | getMottos {
44 | ...MottoFragment
45 | }
46 | }
47 | ${MOTTO_FRAGMENT}
48 | `
49 |
50 | export const DELETE_ONE_MOTTO = gql`
51 | mutation DeleteMottoById($id: ID!) {
52 | deleteMottoById(id: $id) {
53 | ...MottoFragment
54 | }
55 | }
56 | ${MOTTO_FRAGMENT}
57 | `
58 |
59 | export const BATCH_DELETE_MOTTO = gql`
60 | mutation DeleteMottos($ids: [ID!]!) {
61 | deleteMottos(ids: $ids) {
62 | ...BatchDeleteFragment
63 | }
64 | }
65 | ${BATCH_DELETE_FRAGMENT}
66 | `
67 |
--------------------------------------------------------------------------------
/src/containers/Music/Player/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const PLAYER_FRAGMENT = gql`
5 | fragment PlayerFragment on PlayerModel {
6 | _id
7 | title
8 | artist
9 | lrc
10 | coverUrl
11 | musicFileUrl
12 | isPublic
13 | weight
14 | createdAt
15 | updatedAt
16 | }
17 | `
18 |
19 | export const CREATE_ONE_PLAYER = gql`
20 | mutation CreatePlayer($input: CreatePlayerInput!) {
21 | createPlayer(input: $input) {
22 | ...PlayerFragment
23 | }
24 | }
25 | ${PLAYER_FRAGMENT}
26 | `
27 |
28 | export const UPDATE_ONE_PLAYER = gql`
29 | mutation UpdatePlayerById($input: UpdatePlayerInput!) {
30 | updatePlayerById(input: $input) {
31 | ...PlayerFragment
32 | }
33 | }
34 | ${PLAYER_FRAGMENT}
35 | `
36 |
37 | export const EXCHANGE_POSITION = gql`
38 | mutation ExchangePositionPlayer($input: ExchangePositionInput!) {
39 | exchangePositionPlayer(input: $input) {
40 | ...PlayerFragment
41 | }
42 | }
43 | ${PLAYER_FRAGMENT}
44 | `
45 |
46 | export const PLAYERS = gql`
47 | query GetPlayers {
48 | getPlayers {
49 | ...PlayerFragment
50 | }
51 | }
52 | ${PLAYER_FRAGMENT}
53 | `
54 |
55 | export const DELETE_ONE_PLAYER = gql`
56 | mutation DeletePlayerById($id: ID!) {
57 | deletePlayerById(id: $id) {
58 | ...PlayerFragment
59 | }
60 | }
61 | ${PLAYER_FRAGMENT}
62 | `
63 |
64 | export const BATCH_DELETE_PLAYER = gql`
65 | mutation DeletePlayers($ids: [ID!]!) {
66 | deletePlayers(ids: $ids) {
67 | ...BatchDeleteFragment
68 | }
69 | }
70 | ${BATCH_DELETE_FRAGMENT}
71 | `
72 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/components/GrayTheme.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ChangeEvent } from 'react'
2 | import { FormControlLabel, Switch } from '@material-ui/core'
3 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
4 | import SettingItemWrapper from '../../components/SettingItemWrapper/SettingItemWrapper'
5 |
6 | interface Props {
7 | id: string
8 | isSubmitting: boolean
9 | isGrayTheme: boolean
10 | updateGlobalSettingById: Function
11 | }
12 |
13 | const useStyles = makeStyles((theme: Theme) =>
14 | createStyles({
15 | label: {
16 | marginLeft: 0,
17 | },
18 | }),
19 | )
20 |
21 | const GrayTheme: FC = ({
22 | id,
23 | isGrayTheme,
24 | isSubmitting,
25 | updateGlobalSettingById,
26 | }) => {
27 | const classes = useStyles()
28 |
29 | const handleSwitchChange = async (e: ChangeEvent) => {
30 | await updateGlobalSettingById({
31 | variables: { input: { isGrayTheme: e.target.checked, id } },
32 | optimisticResponse: {
33 | __typename: 'Mutation',
34 | updateGlobalSettingById: {
35 | id,
36 | __typename: 'GlobalSettingModel',
37 | isGrayTheme: e.target.checked,
38 | },
39 | },
40 | })
41 | }
42 |
43 | return (
44 |
45 |
54 | }
55 | label="Is Gray Theme?"
56 | labelPlacement="start"
57 | />
58 |
59 | )
60 | }
61 |
62 | export default GrayTheme
63 |
--------------------------------------------------------------------------------
/src/containers/Home/Announcement/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const ANNOUNCEMENT_FRAGMENT = gql`
5 | fragment AnnouncementFragment on AnnouncementModel {
6 | _id
7 | content
8 | weight
9 | createdAt
10 | updatedAt
11 | }
12 | `
13 |
14 | export const CREATE_ONE_ANNOUNCEMENT = gql`
15 | mutation CreateAnnouncement($input: CreateAnnouncementInput!) {
16 | createAnnouncement(input: $input) {
17 | ...AnnouncementFragment
18 | }
19 | }
20 | ${ANNOUNCEMENT_FRAGMENT}
21 | `
22 |
23 | export const UPDATE_ONE_ANNOUNCEMENT = gql`
24 | mutation UpdateAnnouncementById($input: UpdateAnnouncementInput!) {
25 | updateAnnouncementById(input: $input) {
26 | ...AnnouncementFragment
27 | }
28 | }
29 | ${ANNOUNCEMENT_FRAGMENT}
30 | `
31 |
32 | export const EXCHANGE_POSITION = gql`
33 | mutation ExchangePositionAnnouncement($input: ExchangePositionInput!) {
34 | exchangePositionAnnouncement(input: $input) {
35 | ...AnnouncementFragment
36 | }
37 | }
38 | ${ANNOUNCEMENT_FRAGMENT}
39 | `
40 |
41 | export const ANNOUNCEMENTS = gql`
42 | query GetAnnouncements {
43 | getAnnouncements {
44 | ...AnnouncementFragment
45 | }
46 | }
47 | ${ANNOUNCEMENT_FRAGMENT}
48 | `
49 |
50 | export const DELETE_ONE_ANNOUNCEMENT = gql`
51 | mutation DeleteAnnouncementById($id: ID!) {
52 | deleteAnnouncementById(id: $id) {
53 | ...AnnouncementFragment
54 | }
55 | }
56 | ${ANNOUNCEMENT_FRAGMENT}
57 | `
58 |
59 | export const BATCH_DELETE_ANNOUNCEMENT = gql`
60 | mutation DeleteAnnouncements($ids: [ID!]!) {
61 | deleteAnnouncements(ids: $ids) {
62 | ...BatchDeleteFragment
63 | }
64 | }
65 | ${BATCH_DELETE_FRAGMENT}
66 | `
67 |
--------------------------------------------------------------------------------
/src/components/ConfirmPoper/ConfirmPoper.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import PopupState, { bindTrigger, bindPopover } from 'material-ui-popup-state'
3 | import {
4 | Button,
5 | Popover,
6 | Paper,
7 | DialogActions,
8 | DialogTitle,
9 | } from '@material-ui/core'
10 | import {
11 | POPOVER_ANCHOR_ORIGIN,
12 | POPOVER_TRANSFORM_ORIGIN,
13 | } from 'src/shared/constants'
14 |
15 | interface Props {
16 | title?: string
17 | onOk: () => void
18 | }
19 |
20 | const ConfirmPoper: FC = ({ children, onOk, title }) => {
21 | return (
22 |
23 | {(popupState) => (
24 | <>
25 |
26 | {children}
27 |
28 |
34 |
35 |
36 | {title ? title : 'Are you sure you want to delete?'}
37 |
38 |
39 |
40 | Cancel
41 |
42 | {
44 | onOk()
45 | popupState.close()
46 | }}
47 | color="primary"
48 | >
49 | OK
50 |
51 |
52 |
53 |
54 | >
55 | )}
56 |
57 | )
58 | }
59 |
60 | export default ConfirmPoper
61 |
--------------------------------------------------------------------------------
/src/containers/Home/Cover/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import {
3 | BATCH_DELETE_FRAGMENT,
4 | BATCH_UPDATE_FRAGMENT,
5 | } from 'src/graphql/graphqlFragment'
6 |
7 | const COVER_FRAGMENT = gql`
8 | fragment CoverFragment on CoverModel {
9 | _id
10 | title
11 | coverUrl
12 | weight
13 | isPublic
14 | createdAt
15 | updatedAt
16 | }
17 | `
18 |
19 | export const CREATE_ONE_COVER = gql`
20 | mutation CreateCover($input: CreateCoverInput!) {
21 | createCover(input: $input) {
22 | ...CoverFragment
23 | }
24 | }
25 | ${COVER_FRAGMENT}
26 | `
27 |
28 | export const UPDATE_ONE_COVER = gql`
29 | mutation UpdateCoverById($input: UpdateCoverInput!) {
30 | updateCoverById(input: $input) {
31 | ...CoverFragment
32 | }
33 | }
34 | ${COVER_FRAGMENT}
35 | `
36 |
37 | export const EXCHANGE_POSITION = gql`
38 | mutation ExchangePositionCover($input: ExchangePositionInput!) {
39 | exchangePositionCover(input: $input) {
40 | ...CoverFragment
41 | }
42 | }
43 | ${COVER_FRAGMENT}
44 | `
45 |
46 | export const COVERS = gql`
47 | query GetCovers {
48 | getCovers {
49 | ...CoverFragment
50 | }
51 | }
52 | ${COVER_FRAGMENT}
53 | `
54 |
55 | export const DELETE_ONE_COVER = gql`
56 | mutation DeleteCoverById($id: ID!) {
57 | deleteCoverById(id: $id) {
58 | ...CoverFragment
59 | }
60 | }
61 | ${COVER_FRAGMENT}
62 | `
63 |
64 | export const BATCH_DELETE_COVERS = gql`
65 | mutation DeleteCovers($ids: [ID!]!) {
66 | deleteCovers(ids: $ids) {
67 | ...BatchDeleteFragment
68 | }
69 | }
70 | ${BATCH_DELETE_FRAGMENT}
71 | `
72 |
73 | export const BATCH_PUBLIC_COVERS = gql`
74 | mutation DeleteCovers($ids: [ID!]!) {
75 | publicCovers(ids: $ids) {
76 | ...BatchUpdateFragment
77 | }
78 | }
79 | ${BATCH_UPDATE_FRAGMENT}
80 | `
81 |
--------------------------------------------------------------------------------
/src/containers/Settings/Account/components/UpdateEmail.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import * as Yup from 'yup'
3 | import { useFormik } from 'formik'
4 | import { Button, TextField } from '@material-ui/core'
5 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
6 | import SettingItemWrapper from '../../components/SettingItemWrapper/SettingItemWrapper'
7 | import useStyles from '../styles'
8 |
9 | interface Props {
10 | email: string
11 | updateEmail: Function
12 | }
13 |
14 | const validationSchema = Yup.object().shape({
15 | email: Yup.string().email().required(),
16 | })
17 |
18 | const UpdateEmail: FC = ({ email, updateEmail }) => {
19 | const classes = useStyles()
20 |
21 | const initialValues = {
22 | email,
23 | }
24 |
25 | const { handleSubmit, getFieldProps, isSubmitting, errors, values } =
26 | useFormik({
27 | initialValues,
28 | validationSchema,
29 | onSubmit: async (values) => {
30 | await updateEmail({
31 | variables: { email: values.email },
32 | })
33 | },
34 | })
35 |
36 | return (
37 |
41 |
60 |
61 | )
62 | }
63 |
64 | export default UpdateEmail
65 |
--------------------------------------------------------------------------------
/src/containers/Agenda/Agenda.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useQuery, useMutation } from '@apollo/client'
3 | import {
4 | AGENDAS,
5 | CREATE_ONE_AGENDA,
6 | UPDATE_ONE_AGENDA,
7 | DELETE_ONE_AGENDA,
8 | } from './typeDefs'
9 | import { IAgenda, Query } from './types'
10 | import Schedule from './components/Schedule'
11 | import { dateStringToDate } from './tools'
12 |
13 | const Agenda: FC = () => {
14 | const { data } = useQuery(AGENDAS, {
15 | notifyOnNetworkStatusChange: true,
16 | })
17 |
18 | const [createAgenda] = useMutation(CREATE_ONE_AGENDA, {
19 | update(proxy, { data: { createAgenda } }) {
20 | const data = proxy.readQuery({ query: AGENDAS })
21 |
22 | if (data) {
23 | proxy.writeQuery({
24 | query: AGENDAS,
25 | data: {
26 | ...data,
27 | getAgenda: [createAgenda, ...data.getAgenda],
28 | },
29 | })
30 | }
31 | },
32 |
33 | onError() {},
34 | })
35 |
36 | const [updateAgendaById] = useMutation(UPDATE_ONE_AGENDA, {
37 | onError() {},
38 | })
39 |
40 | const [deleteAgendaById] = useMutation(DELETE_ONE_AGENDA, {
41 | update(proxy, { data: { deleteAgendaById } }) {
42 | const data = proxy.readQuery({ query: AGENDAS })
43 |
44 | if (data) {
45 | proxy.writeQuery({
46 | query: AGENDAS,
47 | data: {
48 | getAgenda: data.getAgenda.filter(
49 | (agenda: IAgenda) => agenda._id !== deleteAgendaById._id,
50 | ),
51 | },
52 | })
53 | }
54 | },
55 | onError() {},
56 | })
57 |
58 | return (
59 |
65 | )
66 | }
67 |
68 | export default Agenda
69 |
--------------------------------------------------------------------------------
/src/containers/Events/Agenda.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useQuery, useMutation } from '@apollo/client'
3 | import {
4 | AGENDAS,
5 | CREATE_ONE_AGENDA,
6 | UPDATE_ONE_AGENDA,
7 | DELETE_ONE_AGENDA,
8 | } from './typeDefs'
9 | import { IAgenda, Query } from './types'
10 | import Schedule from './components/Schedule'
11 | import { dateStringToDate } from './tools'
12 |
13 | const Agenda: FC = () => {
14 | const { data } = useQuery(AGENDAS, {
15 | notifyOnNetworkStatusChange: true,
16 | })
17 |
18 | const [createAgenda] = useMutation(CREATE_ONE_AGENDA, {
19 | update(proxy, { data: { createAgenda } }) {
20 | const data = proxy.readQuery({ query: AGENDAS })
21 |
22 | if (data) {
23 | proxy.writeQuery({
24 | query: AGENDAS,
25 | data: {
26 | ...data,
27 | getAgenda: [createAgenda, ...data.getAgenda],
28 | },
29 | })
30 | }
31 | },
32 |
33 | onError() {},
34 | })
35 |
36 | const [updateAgendaById] = useMutation(UPDATE_ONE_AGENDA, {
37 | onError() {},
38 | })
39 |
40 | const [deleteAgendaById] = useMutation(DELETE_ONE_AGENDA, {
41 | update(proxy, { data: { deleteAgendaById } }) {
42 | const data = proxy.readQuery({ query: AGENDAS })
43 |
44 | if (data) {
45 | proxy.writeQuery({
46 | query: AGENDAS,
47 | data: {
48 | getAgenda: data.getAgenda.filter(
49 | (agenda: IAgenda) => agenda._id !== deleteAgendaById._id,
50 | ),
51 | },
52 | })
53 | }
54 | },
55 | onError() {},
56 | })
57 |
58 | return (
59 |
65 | )
66 | }
67 |
68 | export default Agenda
69 |
--------------------------------------------------------------------------------
/src/containers/Settings/Account/components/UpdateUserName.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import * as Yup from 'yup'
3 | import { useFormik } from 'formik'
4 | import { Button, TextField } from '@material-ui/core'
5 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
6 | import SettingItemWrapper from '../../components/SettingItemWrapper/SettingItemWrapper'
7 | import useStyles from '../styles'
8 |
9 | interface Props {
10 | username: string
11 | updateUserName: Function
12 | }
13 |
14 | const validationSchema = Yup.object().shape({
15 | username: Yup.string().required(),
16 | })
17 |
18 | const UpdateUserName: FC = ({ username, updateUserName }) => {
19 | const classes = useStyles()
20 |
21 | const initialValues = {
22 | username,
23 | }
24 |
25 | const { handleSubmit, getFieldProps, isSubmitting, errors, values } =
26 | useFormik({
27 | initialValues,
28 | validationSchema,
29 | onSubmit: async (values) => {
30 | await updateUserName({
31 | variables: { username: values.username },
32 | })
33 | },
34 | })
35 |
36 | return (
37 |
41 |
60 |
61 | )
62 | }
63 |
64 | export default UpdateUserName
65 |
--------------------------------------------------------------------------------
/src/containers/Settings/Account/components/DeleteAccount.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import { Button, Checkbox, FormControlLabel } from '@material-ui/core'
3 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
4 | import SettingItemWrapper from '../../components/SettingItemWrapper/SettingItemWrapper'
5 | import useStyles from '../styles'
6 |
7 | interface Props {
8 | isDeletingAccount: boolean
9 | deleteAccount: Function
10 | }
11 |
12 | const DeleteAccount: FC = ({ isDeletingAccount, deleteAccount }) => {
13 | const classes = useStyles()
14 |
15 | const [checked, setChecked] = useState(false)
16 |
17 | return (
18 |
22 |
23 | You’re trying to delete your Account, which provides access to various
24 | Yancey Blog services. You’ll no longer be able to use any of those
25 | services, and your account and data will be lost.
26 |
27 |
28 | setChecked(e.target.checked)}
35 | />
36 | }
37 | label={
38 |
39 | Yes, I want to permanently delete this Yancey Blog CMS Account and
40 | all its data.
41 |
42 | }
43 | labelPlacement="end"
44 | />
45 |
46 | deleteAccount()}
51 | >
52 | Delete account
53 |
54 |
55 | )
56 | }
57 |
58 | export default DeleteAccount
59 |
--------------------------------------------------------------------------------
/src/containers/Post/editors/enhanceEditor.ts:
--------------------------------------------------------------------------------
1 | import { RefObject } from 'react'
2 | import { Editor } from '@toast-ui/react-editor'
3 | import { UploaderResponse } from 'src/components/Uploader/types'
4 | import toast from 'src/components/Toast/Toast'
5 |
6 | export const insertImage = (
7 | editorRef: RefObject,
8 | image: UploaderResponse,
9 | ) => {
10 | if (editorRef.current) {
11 | const instance = editorRef.current.getInstance()
12 | instance.insertText(`\n\n`)
13 | }
14 | }
15 |
16 | export const insertEmbeded = (editorRef: RefObject) => {
17 | if (editorRef.current) {
18 | const instance = editorRef.current.getInstance()
19 | instance.insertText('```embeded\n\n```')
20 | }
21 | }
22 |
23 | export const enhanceUpload = (
24 | editorRef: RefObject,
25 | setOpen: Function,
26 | ) => {
27 | if (editorRef.current) {
28 | const instance = editorRef.current.getInstance()
29 | const toolbar = instance.getUI().getToolbar()
30 |
31 | //@ts-ignore
32 | instance.eventManager.addEventType('uploadImg')
33 |
34 | //@ts-ignore
35 | instance.eventManager.listen('uploadImg', () => {
36 | setOpen(true)
37 | })
38 |
39 | //@ts-ignore
40 | instance.eventManager.addEventType('insertEmbeded')
41 |
42 | //@ts-ignore
43 | instance.eventManager.listen('insertEmbeded', () => {
44 | insertEmbeded(editorRef)
45 | })
46 |
47 | toolbar.insertItem(16, {
48 | type: 'button',
49 | options: {
50 | className: 'tui-image',
51 | event: 'uploadImg',
52 | tooltip: 'Insert Image',
53 | },
54 | })
55 |
56 | toolbar.insertItem(21, {
57 | type: 'button',
58 | options: {
59 | className: 'tui-emebed-icon',
60 | event: 'insertEmbeded',
61 | tooltip: 'Insert Embeded Block',
62 | text: 'EB',
63 | },
64 | })
65 | }
66 | }
67 |
68 | export const enhancePasteUpload = (editorRef: RefObject) => {
69 | // TODO:
70 | toast.error('暂不支持复制上传图片')
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/TableWrapper/TableWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Save } from '@material-ui/icons'
3 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
4 |
5 | const useStyles = makeStyles((theme: Theme) =>
6 | createStyles({
7 | tableWrapper: {
8 | position: 'relative',
9 | width: '100%',
10 | marginTop: '4px',
11 | },
12 |
13 | tableIconContainer: {
14 | display: 'flex',
15 | justifyContent: 'center',
16 | alignItems: 'center',
17 | position: 'absolute',
18 | left: '16px',
19 | width: '62px',
20 | height: '65px',
21 | borderRadius: '3px',
22 | background: 'linear-gradient(60deg, #ec407a, #d81b60)',
23 | boxShadow: `0 4px 20px 0 rgba(0, 0, 0, 0.14),
24 | 0 7px 10px -5px rgba(233, 30, 99, 0.4)`,
25 | },
26 |
27 | tableHeader: {
28 | display: 'flex',
29 | justifyContent: 'flex-start',
30 | alignItems: 'center',
31 | position: 'relative',
32 | top: '16px',
33 | zIndex: 120,
34 | },
35 |
36 | tableIcon: {
37 | width: '20px !important',
38 | height: '20px',
39 | color: '#ffffff',
40 | },
41 |
42 | tableTitle: {
43 | position: 'relative',
44 | top: '18px',
45 | margin: 0,
46 | marginLeft: '94px',
47 | fontSize: '18px',
48 | fontWeight: 300,
49 | color: '#3c4858',
50 | },
51 | }),
52 | )
53 |
54 | interface Props {
55 | tableName: string
56 | icon: string
57 | }
58 |
59 | const TableWrapper: FC = ({ children, tableName, icon }) => {
60 | const classes = useStyles()
61 |
62 | return (
63 |
64 |
65 |
66 |
67 |
68 | {tableName}
69 |
70 | {children}
71 |
72 | )
73 | }
74 |
75 | export default TableWrapper
76 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/PostRankListSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Card } from '@material-ui/core'
3 | import { Skeleton } from '@material-ui/lab'
4 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
5 | import SkeletonIterator from 'src/components/SkeletonIterator/SkeletonIterator'
6 |
7 | const useStyles = makeStyles((theme: Theme) =>
8 | createStyles({
9 | card: {
10 | padding: 16,
11 | boxShadow:
12 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
13 | borderRadius: 16,
14 | },
15 |
16 | header: {
17 | marginBottom: 32,
18 | },
19 |
20 | skeletonItem: {
21 | display: 'flex',
22 | justifyContent: 'space-between',
23 | alignItems: 'center',
24 | paddingTop: 8,
25 | paddingBottom: 8,
26 | },
27 |
28 | skeletonMeta: {
29 | display: 'flex',
30 | justifyContent: 'space-between',
31 | alignItems: 'center',
32 | },
33 |
34 | avatar: {
35 | marginRight: 16,
36 | },
37 | }),
38 | )
39 |
40 | const PostRankListSkeleton: FC = () => {
41 | const classes = useStyles()
42 |
43 | const SkeletonItem = () => (
44 |
45 |
46 |
53 |
54 |
55 |
56 |
57 | )
58 |
59 | return (
60 |
61 |
67 |
68 |
69 | )
70 | }
71 |
72 | export default PostRankListSkeleton
73 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/TOTP/totp.module.scss:
--------------------------------------------------------------------------------
1 | .totpDialog {
2 | :global(.MuiDialog-paper) {
3 | width: 480px;
4 | border-radius: 0;
5 | }
6 | }
7 |
8 | .title {
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | height: 175px;
13 | background-color: #4285f4;
14 | }
15 |
16 | .logoImg {
17 | position: relative;
18 | width: 145px;
19 | height: 145px;
20 |
21 | img {
22 | width: 100%;
23 | }
24 | }
25 |
26 | .closeBtn {
27 | position: absolute;
28 | top: 4px;
29 | right: 4px;
30 | color: rgba(255, 255, 255, 0.749);
31 | }
32 |
33 | .header {
34 | padding-top: 12px;
35 | font-size: 20px;
36 | color: #202124;
37 | }
38 |
39 | .qrcodeWrapper {
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | width: 180px;
44 | height: 210px;
45 | margin: 32px auto 24px;
46 | text-align: center;
47 | img {
48 | display: block;
49 | }
50 | }
51 |
52 | .tipGroup {
53 | padding: 0;
54 | }
55 |
56 | .tipItem {
57 | display: block;
58 | position: relative;
59 | padding-left: 20px;
60 | color: #3c4043;
61 | font-size: 14px;
62 | line-height: 1.6;
63 | &::before {
64 | position: absolute;
65 | content: '';
66 | top: 9px;
67 | left: 0;
68 | width: 4px;
69 | height: 4px;
70 | border-radius: 50%;
71 | background: #000;
72 | }
73 | }
74 |
75 | .totpKey {
76 | margin: 16px 0;
77 | padding: 16px 0;
78 | font-size: 12px;
79 | text-align: center;
80 | border: 1px dotted #ccc;
81 | }
82 |
83 | .keyTxt {
84 | display: block;
85 | cursor: pointer;
86 | transform: scale(0.9);
87 | }
88 |
89 | .bold {
90 | font-weight: bold;
91 | }
92 |
93 | .inputTipHeader {
94 | margin-top: 20px;
95 | font-size: 14px;
96 | color: #3c4043;
97 | }
98 |
99 | .customInput {
100 | margin-top: 10px;
101 | margin-bottom: 48px;
102 | }
103 |
104 | .question {
105 | margin: 20px 0;
106 | font-size: 15px;
107 | color: #5f6368;
108 | }
109 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/chartjsConfig.ts:
--------------------------------------------------------------------------------
1 | import { DateTime } from 'luxon'
2 | import { IBandwagonUsageStatus } from 'src/containers/DashBoard/types'
3 |
4 | const chartConfig = (
5 | usageStatus: IBandwagonUsageStatus[],
6 | limit: number,
7 | type1: Exclude,
8 | type2?: Exclude,
9 | ) => ({
10 | labels: usageStatus
11 | .map(({ timestamp }) => DateTime.fromSeconds(+timestamp).toFormat('HH:mm'))
12 | .slice(-limit),
13 | datasets: [
14 | {
15 | label: type1.split('_').join(' ').toUpperCase(),
16 | fill: false,
17 | lineTension: 0.4,
18 | backgroundColor: 'rgba(255, 99, 132, .8)',
19 | borderColor: '#FF6384',
20 | borderCapStyle: 'butt',
21 | borderDash: [],
22 | borderDashOffset: 0.0,
23 | borderJoinStyle: 'miter',
24 | pointBorderColor: '#FF6384',
25 | pointBackgroundColor: '#fff',
26 | pointBorderWidth: 1,
27 | pointHoverRadius: 5,
28 | pointHoverBackgroundColor: '#FF6384)',
29 | pointHoverBorderColor: 'rgba(220,220,220,1)',
30 | pointHoverBorderWidth: 2,
31 | pointRadius: 1,
32 | pointHitRadius: 10,
33 | data: usageStatus.map((usageStat) => usageStat[type1]).slice(-limit),
34 | },
35 | type2 && {
36 | label: type2.split('_').join(' ').toUpperCase(),
37 | fill: false,
38 | lineTension: 0.4,
39 | backgroundColor: 'rgb(54, 162, 235, .8)',
40 | borderColor: '#36A2EB',
41 | borderCapStyle: 'butt',
42 | borderDash: [],
43 | borderDashOffset: 0.0,
44 | borderJoinStyle: 'miter',
45 | pointBorderColor: '#36A2EB',
46 | pointBackgroundColor: '#fff',
47 | pointBorderWidth: 1,
48 | pointHoverRadius: 5,
49 | pointHoverBackgroundColor: '#36A2EB',
50 | pointHoverBorderColor: 'rgba(220,220,220,1)',
51 | pointHoverBorderWidth: 2,
52 | pointRadius: 1,
53 | pointHitRadius: 10,
54 | data: usageStatus.map((usageStat) => usageStat[type2]).slice(-limit),
55 | },
56 | ].filter(Boolean),
57 | })
58 |
59 | export default chartConfig
60 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/ToggleChart.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, MouseEvent } from 'react'
2 | import { Paper } from '@material-ui/core'
3 | import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'
4 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
5 |
6 | interface Props {
7 | handleToggleChange: Function
8 | }
9 |
10 | // 24 hours, 12 hours, 1 hour
11 | const duration = [24, 12, 1]
12 |
13 | // One data is provided every five minutes,
14 | // so 12 data are provided every hour.
15 | const countByHour = 12
16 |
17 | const useStyles = makeStyles((theme: Theme) =>
18 | createStyles({
19 | paper: {
20 | position: 'relative',
21 | padding: 16,
22 | boxShadow:
23 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
24 | borderRadius: 16,
25 | },
26 |
27 | toggleButtonGroup: {
28 | position: 'absolute',
29 | top: 4,
30 | right: 16,
31 | },
32 |
33 | toggleBtn: {
34 | padding: '0 4px',
35 | border: 'none',
36 | },
37 | }),
38 | )
39 |
40 | const ToggleChart: FC = ({ children, handleToggleChange }) => {
41 | const classes = useStyles()
42 |
43 | const [value, setValue] = useState(countByHour)
44 |
45 | const handleChange = (e: MouseEvent, value: number) => {
46 | handleToggleChange(value)
47 | setValue(value)
48 | }
49 |
50 | return (
51 |
52 |
60 | {duration.map((val) => (
61 |
67 | {val}H
68 |
69 | ))}
70 |
71 | {children}
72 |
73 | )
74 | }
75 |
76 | export default ToggleChart
77 |
--------------------------------------------------------------------------------
/src/shared/constants.ts:
--------------------------------------------------------------------------------
1 | import { PopoverOrigin, SnackbarOrigin } from '@material-ui/core'
2 | import { MUIDataTableOptions } from 'mui-datatables'
3 |
4 | export const SNACKBAR_ANCHOR_ORIGIN: SnackbarOrigin = {
5 | vertical: 'top',
6 | horizontal: 'center',
7 | }
8 |
9 | export const GOOGLE_RECAPTCHA_URL = `https://www.google.com/recaptcha/api.js?render=${process.env.REACT_APP_RECAPTCHA_KEY}`
10 |
11 | export const SNACKBAR_MAX_NUM = 1
12 |
13 | export const SNACKBAR_AUTO_HIDE_DURATION = 3000
14 |
15 | export const POPOVER_ANCHOR_ORIGIN: PopoverOrigin = {
16 | vertical: 'bottom',
17 | horizontal: 'left',
18 | }
19 |
20 | export const POPOVER_TRANSFORM_ORIGIN: PopoverOrigin = {
21 | vertical: 'top',
22 | horizontal: 'center',
23 | }
24 |
25 | export const RECOVERY_CODES_FILE_NAME = 'yancey-blog-cms-recovery-codes.txt'
26 |
27 | export const PASSWORD_REGEXP =
28 | /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^\w\s]).{8,}$/
29 |
30 | export const MARKDOWN_EDITOR_TOOLBAR_ITEMS = [
31 | 'heading',
32 | 'bold',
33 | 'italic',
34 | 'strike',
35 | 'divider',
36 | 'hr',
37 | 'quote',
38 | 'divider',
39 | 'ul',
40 | 'ol',
41 | 'task',
42 | 'indent',
43 | 'outdent',
44 | 'divider',
45 | 'table',
46 | 'link',
47 | 'divider',
48 | 'code',
49 | 'codeblock',
50 | ]
51 |
52 | export const DRAWER_WIDTH = 260
53 |
54 | export const FOLDER_DRAWER_WIDTH = 80
55 |
56 | export const VIEW_DATE = ['Day', 'Week', 'Month']
57 |
58 | export const TABLE_OPTIONS: MUIDataTableOptions = {
59 | filterType: 'textField',
60 | rowsPerPage: 10,
61 | rowsPerPageOptions: [10, 20, 50],
62 | searchPlaceholder: 'Search...',
63 | }
64 |
65 | export const AZURE_BLOB_PATH = 'https://edge.yancey.app/beg'
66 |
67 | export const GOOGLE_AUTHENTICATOR_FOR_IOS =
68 | 'https://itunes.apple.com/us/app/google-authenticator/id388497605'
69 |
70 | export const GOOGLE_AUTHENTICATOR_FOR_ANDROID =
71 | 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2'
72 |
73 | export const YANCEY_BLOG_URL = 'https://www.yanceyleo.com'
74 |
75 | export const YANCEY_GITHUB_URL = 'https://www.github.com/YanceyOfficial'
76 |
77 | export const YANCEY_EMAIL_URL = 'yanceyofficial@gmail.com'
78 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/BandwagonServiceStatus.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
3 | import { IBandwagonServiceInfo } from '../types'
4 | import StatusCard from './StatusCard'
5 | import StatusCardSkeleton from './StatusCardSkeleton'
6 |
7 | interface Props {
8 | serviceInfo: IBandwagonServiceInfo
9 | isFechingServiceInfo: boolean
10 | }
11 |
12 | const useStyles = makeStyles((theme: Theme) =>
13 | createStyles({
14 | statusCardGrid: {
15 | display: 'grid',
16 | gridTemplateColumns: 'repeat(4, 1fr)',
17 | gridColumnGap: 24,
18 | marginBottom: 24,
19 | },
20 | }),
21 | )
22 |
23 | const BandwagonServiceStatus: FC = ({
24 | serviceInfo,
25 | isFechingServiceInfo,
26 | }) => {
27 | const classes = useStyles()
28 |
29 | const {
30 | data_counter,
31 | plan_monthly_data,
32 | plan_disk,
33 | ve_used_disk_space_b,
34 | plan_ram,
35 | mem_available_kb,
36 | swap_total_kb,
37 | swap_available_kb,
38 | } = serviceInfo
39 |
40 | const statusCards = [
41 | {
42 | total: plan_monthly_data,
43 | used: data_counter,
44 | unit: 'GB',
45 | title: 'Bandwidth Usage',
46 | },
47 | {
48 | total: plan_disk,
49 | used: ve_used_disk_space_b,
50 | unit: 'GB',
51 | title: 'Disk Usage',
52 | },
53 | {
54 | total: plan_ram,
55 | used: plan_ram - mem_available_kb * 1024,
56 | unit: 'MB',
57 | title: 'RAM Usage',
58 | },
59 | {
60 | total: swap_total_kb * 1024,
61 | used: (swap_total_kb - swap_available_kb) * 1024,
62 | unit: 'MB',
63 | title: 'SWAP Usage',
64 | },
65 | ]
66 |
67 | return (
68 |
69 | {isFechingServiceInfo
70 | ? statusCards.map((statusCard) => (
71 |
72 | ))
73 | : statusCards.map((statusCard) => (
74 |
81 | ))}
82 |
83 | )
84 | }
85 |
86 | export default BandwagonServiceStatus
87 |
--------------------------------------------------------------------------------
/src/containers/Post/types.ts:
--------------------------------------------------------------------------------
1 | /* Post */
2 | export interface IPost {
3 | readonly page: number
4 | readonly pageSize: number
5 | readonly total: number
6 | readonly items: IPostItem[]
7 | }
8 |
9 | export interface IPostItem {
10 | readonly _id: string
11 | readonly posterUrl: string
12 | readonly title: string
13 | readonly summary: string
14 | readonly content: string
15 | readonly tags: string[]
16 | readonly lastModifiedDate: string
17 | readonly like: number
18 | readonly pv: number
19 | readonly isPublic: boolean
20 | readonly createdAt: string
21 | readonly updatedAt: string
22 | }
23 |
24 | export interface Query {
25 | getPosts: IPost
26 | }
27 |
28 | export interface CreatePostMutation {
29 | createPost: IPostItem
30 | }
31 |
32 | export interface UpdatePostByIdMutation {
33 | updatePostById: IPostItem
34 | }
35 |
36 | export interface CreatePostVars {
37 | input: {
38 | posterUrl: string
39 | title: string
40 | summary: string
41 | content: string
42 | tags: string[]
43 | lastModifiedDate: string
44 | isPublic: boolean
45 | }
46 | }
47 |
48 | export interface UpdatePostVars {
49 | input: {
50 | id: string
51 | posterUrl: string
52 | title: string
53 | summary: string
54 | content: string
55 | tags: string[]
56 | lastModifiedDate: string
57 | isPublic: boolean
58 | }
59 | }
60 |
61 | export enum SaveType {
62 | DRAFT,
63 | FINALIZE,
64 | }
65 |
66 | /* PostStatistics */
67 |
68 | export interface IPostStatisticsGroupItem {
69 | readonly _id: string
70 | readonly date: string
71 | readonly count: number
72 | readonly items: Array<{
73 | readonly postId: string
74 | readonly postName: string
75 | readonly scenes: string
76 | readonly operatedAt: string
77 | }>
78 | }
79 |
80 | export interface IPostStatistics {
81 | readonly _id: string
82 | readonly postId: string
83 | readonly postName: string
84 | readonly scenes: string
85 | readonly createdAt: string
86 | readonly updatedAt: string
87 | }
88 |
89 | export interface PostStatisticsVars {
90 | input: {
91 | postId: string
92 | postName: string
93 | scenes: string
94 | }
95 | }
96 |
97 | export interface CreatePostStatisticsMutation {
98 | createPostStatistics: IPostStatistics
99 | }
100 |
--------------------------------------------------------------------------------
/src/containers/Settings/Account/Account.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useSnackbar } from 'notistack'
3 | import { useMutation } from '@apollo/client'
4 | import { logout } from 'src/shared/utils'
5 | import client from 'src/graphql/apolloClient'
6 | import { UPDATE_USERNAME, UPDATE_EMAIL, DELETE_ACCOUNT } from './typeDefs'
7 | import SettingsHeader from '../components/SettingsHeader/SettingsHeader'
8 | import SettingWrapper from '../components/SettingWrapper/SettingWrapper'
9 | import UpdateUserName from './components/UpdateUserName'
10 | import UpdateEmail from './components/UpdateEmail'
11 | import DeleteAccount from './components/DeleteAccount'
12 |
13 | const Account: FC = () => {
14 | const { enqueueSnackbar } = useSnackbar()
15 |
16 | const { username, email } =
17 | // @ts-ignore
18 | client.cache.data.data[`UserModel:${window.localStorage.getItem('userId')}`]
19 |
20 | const [updateUserName] = useMutation(UPDATE_USERNAME, {
21 | onCompleted(data) {
22 | if (data.updateUserName) {
23 | enqueueSnackbar(`Your username has been updated! Please Re-Login.`, {
24 | variant: 'success',
25 | })
26 |
27 | logout()
28 | }
29 | },
30 | })
31 |
32 | const [updateEmail] = useMutation(UPDATE_EMAIL, {
33 | onCompleted(data) {
34 | if (data.updateEmail) {
35 | enqueueSnackbar(`Your email has been updated! Please Re-Login.`, {
36 | variant: 'success',
37 | })
38 | logout()
39 | }
40 | },
41 | })
42 |
43 | const [deleteAccount, { loading: isDeletingAccount }] = useMutation(
44 | DELETE_ACCOUNT,
45 | {
46 | onCompleted() {
47 | enqueueSnackbar(
48 | `Your account has been deleted successfully! Just fuck off.`,
49 | {
50 | variant: 'success',
51 | },
52 | )
53 |
54 | logout()
55 | },
56 | },
57 | )
58 |
59 | return (
60 |
61 |
65 |
66 |
67 |
68 |
72 |
73 | )
74 | }
75 |
76 | export default Account
77 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/PostStatistics.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { DateTime } from 'luxon'
3 | import { formatJSONDate } from 'yancey-js-util'
4 | import classNames from 'classnames'
5 | import ReactTooltip from 'react-tooltip'
6 | import CalendarHeatmap from 'react-calendar-heatmap'
7 | import 'react-calendar-heatmap/dist/styles.css'
8 | import { Paper } from '@material-ui/core'
9 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
10 | import {
11 | IPostStatistics,
12 | IPostStatisticsGroupItem,
13 | } from 'src/containers/Post/types'
14 |
15 | const useStyles = makeStyles((theme: Theme) =>
16 | createStyles({
17 | paper: {
18 | display: 'flex',
19 | justifyContent: 'center',
20 | alignItems: 'center',
21 | boxShadow:
22 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
23 | borderRadius: 16,
24 | },
25 |
26 | heatmapPaper: {
27 | padding: '0 16px',
28 | '& svg': {
29 | width: '100%',
30 | height: '100%',
31 | },
32 | },
33 | }),
34 | )
35 |
36 | interface Props {
37 | loading: boolean
38 | data: IPostStatistics[]
39 | }
40 |
41 | const PostStatistics: FC = ({ loading, data }) => {
42 | const classes = useStyles()
43 |
44 | return (
45 |
46 | ({
50 | date: postStatisticsItem._id,
51 | ...postStatisticsItem,
52 | }))}
53 | classForValue={(value: IPostStatisticsGroupItem) => {
54 | if (!value) {
55 | return 'color-empty'
56 | }
57 | return `color-gitlab-${value.count}`
58 | }}
59 | tooltipDataAttrs={(value: IPostStatisticsGroupItem) => ({
60 | 'data-tip': value.date
61 | ? `
62 | ${value.items
63 | .map(
64 | (item) =>
65 | `「${item.postName}」 is ${item.scenes} at ${formatJSONDate(
66 | item.operatedAt,
67 | )}`,
68 | )
69 | .join(' ')}
70 | `
71 | : `No contributions on the day.`,
72 | })}
73 | showWeekdayLabels
74 | />
75 |
76 |
77 | )
78 | }
79 |
80 | export default PostStatistics
81 |
--------------------------------------------------------------------------------
/src/components/Loading/TwitterLoading.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 |
3 | class TwitterLoading extends Component<{}, {}> {
4 | constructor(props: {}) {
5 | super(props)
6 | this.state = {}
7 | }
8 |
9 | public render() {
10 | const styles = {
11 | position: 'fixed' as 'fixed',
12 | top: 0,
13 | right: 0,
14 | bottom: 0,
15 | left: 0,
16 | margin: 'auto',
17 | textAlign: 'center' as 'center',
18 | color: '#000000',
19 | width: '96px',
20 | height: '96px',
21 | background:
22 | 'url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4Igp3aWR0aD0iOTYiIGhlaWdodD0iOTYiCnZpZXdCb3g9IjAgMCA0OCA0OCIKc3R5bGU9IiBmaWxsOiMwMDAwMDA7Ij48ZyBpZD0ic3VyZmFjZTEiPjxwYXRoIHN0eWxlPSIgZmlsbDojMDNBOUY0OyIgZD0iTSA0MiAxMi40Mjk2ODggQyA0MC42NzU3ODEgMTMuMDE1NjI1IDM5LjI1MzkwNiAxMy40MDYyNSAzNy43NTM5MDYgMTMuNTg5ODQ0IEMgMzkuMjc3MzQ0IDEyLjY4MzU5NCA0MC40NTMxMjUgMTEuMjM4MjgxIDQxLjAwMzkwNiA5LjUzMTI1IEMgMzkuNTc0MjE5IDEwLjM3MTA5NCAzNy45OTIxODggMTAuOTg0Mzc1IDM2LjMxMjUgMTEuMzA4NTk0IEMgMzQuOTY4NzUgOS44ODI4MTMgMzMuMDUwNzgxIDkgMzAuOTI1NzgxIDkgQyAyNi44NDc2NTYgOSAyMy41MzkwNjMgMTIuMjc3MzQ0IDIzLjUzOTA2MyAxNi4zMjAzMTMgQyAyMy41MzkwNjMgMTYuODkwNjI1IDIzLjYwNTQ2OSAxNy40NDkyMTkgMjMuNzMwNDY5IDE3Ljk4ODI4MSBDIDE3LjU5Mzc1IDE3LjY4MzU5NCAxMi4xNDg0MzggMTQuNzY1NjI1IDguNTA3ODEzIDEwLjMzNTkzOCBDIDcuODY3MTg4IDExLjQxNzk2OSA3LjUwNzgxMyAxMi42ODM1OTQgNy41MDc4MTMgMTQuMDIzNDM4IEMgNy41MDc4MTMgMTYuNTYyNSA4LjgwODU5NCAxOC44MDA3ODEgMTAuNzkyOTY5IDIwLjExNzE4OCBDIDkuNTgyMDMxIDIwLjA4MjAzMSA4LjQ0MTQwNiAxOS43NDIxODggNy40NDUzMTMgMTkuMjAzMTI1IEMgNy40NDUzMTMgMTkuMjI2NTYzIDcuNDQ1MzEzIDE5LjI1NzgxMyA3LjQ0NTMxMyAxOS4yODkwNjMgQyA3LjQ0NTMxMyAyMi44Mzk4NDQgOS45OTIxODggMjUuNzk2ODc1IDEzLjM2NzE4OCAyNi40NzI2NTYgQyAxMi43NSAyNi42NDA2MjUgMTIuMDk3NjU2IDI2LjczNDM3NSAxMS40MjU3ODEgMjYuNzM0Mzc1IEMgMTAuOTQ5MjE5IDI2LjczNDM3NSAxMC40ODQzNzUgMjYuNjc5Njg4IDEwLjAzNTE1NiAyNi41OTc2NTYgQyAxMC45NzI2NTYgMjkuNSAxMy42OTkyMTkgMzEuNjIxMDk0IDE2LjkzMzU5NCAzMS42ODM1OTQgQyAxNC40MDIzNDQgMzMuNjQ0NTMxIDExLjIxODc1IDM0LjgyMDMxMyA3Ljc1NzgxMyAzNC44MjAzMTMgQyA3LjE2MDE1NiAzNC44MjAzMTMgNi41NzQyMTkgMzQuNzg1MTU2IDUuOTk2MDk0IDM0LjcxNDg0NCBDIDkuMjY5NTMxIDM2Ljc4NTE1NiAxMy4xNTIzNDQgMzggMTcuMzIwMzEzIDM4IEMgMzAuOTA2MjUgMzggMzguMzM5ODQ0IDI2Ljg0Mzc1IDM4LjMzOTg0NCAxNy4xNjQwNjMgQyAzOC4zMzk4NDQgMTYuODQ3NjU2IDM4LjMyODEyNSAxNi41MzEyNSAzOC4zMTI1IDE2LjIyMjY1NiBDIDM5Ljc2MTcxOSAxNS4xOTUzMTMgNDEuMDExNzE5IDEzLjkwNjI1IDQyIDEyLjQyOTY4OCAiPjwvcGF0aD48L2c+PC9zdmc+") 50% 50% no-repeat',
23 | backgroundSize: '100%',
24 | }
25 |
26 | return
27 | }
28 | }
29 |
30 | export default TwitterLoading
31 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '29 0 * * 1'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'javascript' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v2
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v1
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v1
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v1
68 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/PostRankList.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import {
3 | Paper,
4 | List,
5 | ListItem,
6 | ListItemAvatar,
7 | ListItemText,
8 | ListItemSecondaryAction,
9 | IconButton,
10 | Avatar,
11 | } from '@material-ui/core'
12 | import { LooksOne, LooksTwo, Looks3, Looks4, Looks5 } from '@material-ui/icons'
13 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
14 | import { IPostItem } from 'src/containers/Post/types'
15 | import PostRankListSkeleton from './PostRankListSkeleton'
16 | import { PostRankListType } from '../types'
17 |
18 | const useStyles = makeStyles((theme: Theme) =>
19 | createStyles({
20 | paper: {
21 | display: 'grid',
22 | boxShadow:
23 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
24 | borderRadius: 16,
25 | },
26 |
27 | list: {
28 | width: '100%',
29 | },
30 |
31 | header: {
32 | marginTop: 16,
33 | marginLeft: 16,
34 | fontSize: 16,
35 | fontWeight: 600,
36 | },
37 | }),
38 | )
39 |
40 | interface Props {
41 | type: PostRankListType
42 | topPosts: IPostItem[]
43 | loading: boolean
44 | }
45 |
46 | const numbersIcon = [
47 | ,
48 | ,
49 | ,
50 | ,
51 | ,
52 | ]
53 |
54 | const PostRankList: FC = ({ type, topPosts, loading }) => {
55 | const classes = useStyles()
56 |
57 | const renderType = (post: IPostItem) => {
58 | if (type === PostRankListType.PV) {
59 | return `(PV: ${post.pv})`
60 | }
61 |
62 | return `(Like: ${post.like})`
63 | }
64 |
65 | return (
66 |
67 | {loading ? (
68 |
69 | ) : (
70 | <>
71 |
72 | TOP 5 POSTS BY {type === PostRankListType.PV ? 'PV' : 'LIKE'}
73 |
74 |
75 |
76 | {topPosts.map((post, index) => (
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | {numbersIcon[index]}
85 |
86 |
87 |
88 | ))}
89 |
90 | >
91 | )}
92 |
93 | )
94 | }
95 |
96 | export default PostRankList
97 |
--------------------------------------------------------------------------------
/src/containers/Post/typeDefs.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client'
2 | import { BATCH_DELETE_FRAGMENT } from 'src/graphql/graphqlFragment'
3 |
4 | const POST_FRAGMENT = gql`
5 | fragment PostFragment on PostItemModel {
6 | _id
7 | posterUrl
8 | title
9 | summary
10 | content
11 | tags
12 | lastModifiedDate
13 | like
14 | pv
15 | isPublic
16 | createdAt
17 | updatedAt
18 | }
19 | `
20 |
21 | export const CREATE_ONE_POST = gql`
22 | mutation CreatePost($input: CreatePostInput!) {
23 | createPost(input: $input) {
24 | ...PostFragment
25 | }
26 | }
27 | ${POST_FRAGMENT}
28 | `
29 |
30 | export const UPDATE_ONE_POST = gql`
31 | mutation UpdatePostById($input: UpdatePostInput!) {
32 | updatePostById(input: $input) {
33 | ...PostFragment
34 | }
35 | }
36 | ${POST_FRAGMENT}
37 | `
38 |
39 | export const POSTS = gql`
40 | query GetPosts($input: PaginationInput!) {
41 | getPosts(input: $input) {
42 | total
43 | page
44 | pageSize
45 | items {
46 | ...PostFragment
47 | }
48 | }
49 | }
50 | ${POST_FRAGMENT}
51 | `
52 |
53 | export const DELETE_ONE_POST = gql`
54 | mutation DeletePostById($id: ID!) {
55 | deletePostById(id: $id) {
56 | ...PostFragment
57 | }
58 | }
59 | ${POST_FRAGMENT}
60 | `
61 |
62 | export const BATCH_DELETE_POSTS = gql`
63 | mutation DeletePosts($ids: [ID!]!) {
64 | deletePosts(ids: $ids) {
65 | ...BatchDeleteFragment
66 | }
67 | }
68 | ${BATCH_DELETE_FRAGMENT}
69 | `
70 |
71 | export const GET_TOP_PV_POSTS = gql`
72 | query GetTopPVPosts($limit: Int!) {
73 | getTopPVPosts(limit: $limit) {
74 | _id
75 | title
76 | posterUrl
77 | pv
78 | }
79 | }
80 | `
81 |
82 | export const GET_TOP_LIKE_POSTS = gql`
83 | query GetTopLikePosts($limit: Int!) {
84 | getTopLikePosts(limit: $limit) {
85 | _id
86 | title
87 | posterUrl
88 | like
89 | }
90 | }
91 | `
92 |
93 | export const GET_ALL_TAGS = gql`
94 | query GetAllTags {
95 | getAllTags {
96 | tags
97 | }
98 | }
99 | `
100 |
101 | export const GET_POST_STATISTICS = gql`
102 | query GetPostStatistics {
103 | getPostStatistics {
104 | _id
105 | count
106 | items {
107 | postId
108 | postName
109 | scenes
110 | operatedAt
111 | }
112 | }
113 | }
114 | `
115 |
116 | export const CREATE_POST_STATISTICS = gql`
117 | mutation CreatePostStatistics($input: CreatePostStatisticsInput!) {
118 | createPostStatistics(input: $input) {
119 | _id
120 | postId
121 | postName
122 | scenes
123 | createdAt
124 | updatedAt
125 | }
126 | }
127 | `
128 |
--------------------------------------------------------------------------------
/src/containers/Agenda/components/Schedule.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import { Paper } from '@material-ui/core'
3 | import {
4 | ViewState,
5 | EditingState,
6 | ChangeSet,
7 | AppointmentModel,
8 | } from '@devexpress/dx-react-scheduler'
9 | import {
10 | Scheduler,
11 | EditRecurrenceMenu,
12 | DayView,
13 | WeekView,
14 | MonthView,
15 | Toolbar,
16 | DateNavigator,
17 | TodayButton,
18 | ConfirmationDialog,
19 | Appointments,
20 | AppointmentTooltip,
21 | AppointmentForm,
22 | AllDayPanel,
23 | DragDropProvider,
24 | CurrentTimeIndicator,
25 | } from '@devexpress/dx-react-scheduler-material-ui'
26 | import ExternalViewSwitcher from './ExternalViewSwitcher'
27 | import CustomNavigationButton from './CustomNavigationButton'
28 | import CustomTodayButton from './CustomTodayButton'
29 | import CustomOpenButton from './CustomOpenButton'
30 | import useStyles from '../styles'
31 | import { ScheduleProps } from '../types'
32 | import { formatChangedData } from '../tools'
33 |
34 | const Schedule: FC = ({
35 | dataSource,
36 | createAgenda,
37 | updateAgendaById,
38 | deleteAgendaById,
39 | }) => {
40 | const classes = useStyles()
41 |
42 | const [currentViewName, setCurrentViewName] = useState('Week')
43 |
44 | const commitChanges = ({ added, changed, deleted }: ChangeSet) => {
45 | if (added) {
46 | createAgenda({ variables: { input: added } })
47 | }
48 | if (changed) {
49 | updateAgendaById({ variables: { input: formatChangedData(changed) } })
50 | }
51 | if (deleted) {
52 | deleteAgendaById({ variables: { id: deleted } })
53 | }
54 | }
55 |
56 | return (
57 |
58 |
59 | setCurrentViewName(val)}
61 | />
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | true} />
80 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default Schedule
91 |
--------------------------------------------------------------------------------
/src/containers/Events/components/Schedule.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import { Paper } from '@material-ui/core'
3 | import {
4 | ViewState,
5 | EditingState,
6 | ChangeSet,
7 | AppointmentModel,
8 | } from '@devexpress/dx-react-scheduler'
9 | import {
10 | Scheduler,
11 | EditRecurrenceMenu,
12 | DayView,
13 | WeekView,
14 | MonthView,
15 | Toolbar,
16 | DateNavigator,
17 | TodayButton,
18 | ConfirmationDialog,
19 | Appointments,
20 | AppointmentTooltip,
21 | AppointmentForm,
22 | AllDayPanel,
23 | DragDropProvider,
24 | CurrentTimeIndicator,
25 | } from '@devexpress/dx-react-scheduler-material-ui'
26 | import ExternalViewSwitcher from './ExternalViewSwitcher'
27 | import CustomNavigationButton from './CustomNavigationButton'
28 | import CustomTodayButton from './CustomTodayButton'
29 | import CustomOpenButton from './CustomOpenButton'
30 | import useStyles from '../styles'
31 | import { ScheduleProps } from '../types'
32 | import { formatChangedData } from '../tools'
33 |
34 | const Schedule: FC = ({
35 | dataSource,
36 | createAgenda,
37 | updateAgendaById,
38 | deleteAgendaById,
39 | }) => {
40 | const classes = useStyles()
41 |
42 | const [currentViewName, setCurrentViewName] = useState('Week')
43 |
44 | const commitChanges = ({ added, changed, deleted }: ChangeSet) => {
45 | if (added) {
46 | createAgenda({ variables: { input: added } })
47 | }
48 | if (changed) {
49 | updateAgendaById({ variables: { input: formatChangedData(changed) } })
50 | }
51 | if (deleted) {
52 | deleteAgendaById({ variables: { id: deleted } })
53 | }
54 | }
55 |
56 | return (
57 |
58 |
59 | setCurrentViewName(val)}
61 | />
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | true} />
80 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default Schedule
91 |
--------------------------------------------------------------------------------
/src/containers/Home/Announcement/components/AnnouncementModal.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect } from 'react'
2 | import * as Yup from 'yup'
3 | import {
4 | Button,
5 | DialogActions,
6 | DialogTitle,
7 | Dialog,
8 | DialogContent,
9 | DialogContentText,
10 | TextField,
11 | } from '@material-ui/core'
12 | import { useFormik } from 'formik'
13 | import client from 'src/graphql/apolloClient'
14 | import { AnnouncementModalProps as Props } from '../types'
15 |
16 | const AnnouncementModal: FC = ({
17 | open,
18 | handleOpen,
19 | createAnnouncement,
20 | updateAnnouncementById,
21 | }) => {
22 | const { isOpen, id } = open
23 |
24 | const initialValues = {
25 | content: '',
26 | }
27 |
28 | const validationSchema = Yup.object().shape({
29 | content: Yup.string().required('Content is required.'),
30 | })
31 |
32 | const {
33 | handleSubmit,
34 | getFieldProps,
35 | setValues,
36 | resetForm,
37 | isSubmitting,
38 | errors,
39 | } = useFormik({
40 | initialValues,
41 | validationSchema,
42 | onSubmit: async (values) => {
43 | if (id) {
44 | await updateAnnouncementById({
45 | variables: { input: { ...values, id } },
46 | })
47 | } else {
48 | await createAnnouncement({ variables: { input: values } })
49 | }
50 |
51 | resetForm()
52 | handleOpen()
53 | },
54 | })
55 |
56 | useEffect(() => {
57 | resetForm()
58 |
59 | if (id) {
60 | // @ts-ignore
61 | const { content } = client.cache.data.data[`AnnouncementModel:${id}`]
62 |
63 | setValues({ content })
64 | }
65 | }, [id, resetForm, setValues])
66 |
67 | return (
68 | handleOpen()}>
69 | {id ? 'Update' : 'Add'} an Announcement
70 |
96 |
97 | )
98 | }
99 |
100 | export default AnnouncementModal
101 |
--------------------------------------------------------------------------------
/src/containers/Home/Motto/components/MottoModal.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect } from 'react'
2 | import * as Yup from 'yup'
3 | import {
4 | Button,
5 | DialogActions,
6 | DialogTitle,
7 | Dialog,
8 | DialogContent,
9 | DialogContentText,
10 | TextField,
11 | } from '@material-ui/core'
12 | import { useFormik } from 'formik'
13 | import client from 'src/graphql/apolloClient'
14 | import { Open } from 'src/hooks/useOpenModal'
15 |
16 | interface Props {
17 | open: Open
18 | handleOpen: Function
19 | createMotto: Function
20 | updateMottoById: Function
21 | }
22 |
23 | const MottoModal: FC = ({
24 | open,
25 | handleOpen,
26 | createMotto,
27 | updateMottoById,
28 | }) => {
29 | const { isOpen, id } = open
30 |
31 | const initialValues = {
32 | content: '',
33 | }
34 |
35 | const validationSchema = Yup.object().shape({
36 | content: Yup.string().required('Content is required.'),
37 | })
38 |
39 | const {
40 | handleSubmit,
41 | getFieldProps,
42 | setValues,
43 | resetForm,
44 | isSubmitting,
45 | errors,
46 | } = useFormik({
47 | initialValues,
48 | validationSchema,
49 | onSubmit: async (values) => {
50 | if (id) {
51 | await updateMottoById({
52 | variables: { input: { ...values, id } },
53 | })
54 | } else {
55 | await createMotto({ variables: { input: values } })
56 | }
57 | resetForm()
58 | handleOpen()
59 | },
60 | })
61 |
62 | useEffect(() => {
63 | resetForm()
64 |
65 | if (id) {
66 | // @ts-ignore
67 | const { content } = client.cache.data.data[`MottoModel:${id}`]
68 |
69 | setValues({ content })
70 | }
71 | }, [id, resetForm, setValues])
72 |
73 | return (
74 | handleOpen()}>
75 | {id ? 'Update' : 'Add'} a Motto
76 |
101 |
102 | )
103 | }
104 |
105 | export default MottoModal
106 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { Switch, Route, Redirect, Router } from 'react-router-dom'
4 | import loadable from '@loadable/component'
5 | import CssBaseline from '@material-ui/core/CssBaseline'
6 | import { ApolloProvider } from '@apollo/client'
7 | import { MuiPickersUtilsProvider } from '@material-ui/pickers'
8 | import LuxonUtils from '@date-io/luxon'
9 | // @ts-ignore
10 | import { SnackbarProvider } from 'notistack'
11 | import * as serviceWorkerRegistration from './serviceWorkerRegistration'
12 | import { SnackbarUtilsConfigurator } from './components/Toast/Toast'
13 | import Login from './pages/Auth/Login'
14 | import Register from './pages/Auth/Register'
15 | import Loading from './components/Loading/InstagramLoading'
16 | import client from './graphql/apolloClient'
17 | import reportWebVitals from './reportWebVitals'
18 | import history from './shared/history'
19 | import {
20 | SNACKBAR_ANCHOR_ORIGIN,
21 | SNACKBAR_MAX_NUM,
22 | SNACKBAR_AUTO_HIDE_DURATION,
23 | } from './shared/constants'
24 | import './assets/global.scss'
25 |
26 | const Layouts = loadable(() => import('./pages/Layouts/Layouts'), {
27 | fallback: ,
28 | })
29 |
30 | ReactDOM.render(
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 | window.localStorage.getItem('token') ? (
53 |
54 | ) : (
55 |
61 | )
62 | }
63 | />
64 |
65 |
66 |
67 |
68 |
69 | ,
70 | document.getElementById('root'),
71 | )
72 |
73 | // If you want your app to work offline and load faster, you can change
74 | // unregister() to register() below. Note this comes with some pitfalls.
75 | // Learn more about service workers: https://cra.link/PWA
76 | serviceWorkerRegistration.unregister()
77 |
78 | // If you want to start measuring performance in your app, pass a function
79 | // to log results (for example: reportWebVitals(console.log))
80 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
81 | reportWebVitals()
82 |
--------------------------------------------------------------------------------
/src/graphql/apolloClient.ts:
--------------------------------------------------------------------------------
1 | import { ApolloClient, InMemoryCache, from } from '@apollo/client'
2 | import { BatchHttpLink } from '@apollo/client/link/batch-http'
3 | import { onError } from '@apollo/client/link/error'
4 | import { setContext } from '@apollo/client/link/context'
5 | import { persistCache } from 'apollo-cache-persist'
6 | import SnackbarUtils from 'src/components/Toast/Toast'
7 | import { logout } from 'src/shared/utils'
8 |
9 | interface CustomGraphQLError {
10 | timestamp: string
11 | code: string
12 | message: string
13 | }
14 |
15 | const isEnvProduction = process.env.NODE_ENV
16 | const graphqlLink = process.env.REACT_APP_BEG_SERVICE_DOMAIN
17 |
18 | const httpLink = new BatchHttpLink({
19 | uri: graphqlLink,
20 | })
21 |
22 | const authLink = setContext((_, { headers }) => {
23 | const token = localStorage.getItem('token')
24 | return {
25 | headers: {
26 | ...headers,
27 | authorization: token ? `Bearer ${token}` : '',
28 | },
29 | }
30 | })
31 |
32 | const errorLink = onError(({ graphQLErrors, networkError }) => {
33 | if (graphQLErrors) {
34 | const isUnauthenticated = graphQLErrors.some((graphQLError) => {
35 | // In production environment, the error structure
36 | // was formatted as `CustomGraphQLError`.
37 | const isEnvProductionUnauthenticated =
38 | isEnvProduction &&
39 | (graphQLError as unknown as CustomGraphQLError).code ===
40 | 'UNAUTHENTICATED'
41 |
42 | // In non-production environment, the error structure
43 | // uses the native `GraphQLError`.
44 | const isUnEnvProductionUnauthenticated =
45 | !isEnvProduction &&
46 | graphQLError.extensions &&
47 | graphQLError.extensions.code === 'UNAUTHENTICATED'
48 |
49 | return isEnvProductionUnauthenticated || isUnEnvProductionUnauthenticated
50 | })
51 |
52 | if (isUnauthenticated) {
53 | alert('Your session has expired. Please log in.')
54 | logout()
55 | return
56 | }
57 |
58 | graphQLErrors.forEach((graphQLError) => {
59 | SnackbarUtils.error(`[GraphQL error]: ${graphQLError.message}`)
60 | })
61 | }
62 |
63 | if (networkError) {
64 | SnackbarUtils.error(`[Network error]: ${networkError.message}`)
65 | }
66 | })
67 |
68 | // @ts-ignore
69 | const additiveLink = from([errorLink, authLink, httpLink])
70 |
71 | const cache = new InMemoryCache()
72 |
73 | async function handlePersistCache() {
74 | await persistCache({
75 | cache,
76 | // @ts-ignore
77 | storage: window.localStorage,
78 | maxSize: false,
79 | })
80 | }
81 |
82 | handlePersistCache()
83 |
84 | const client = new ApolloClient({
85 | cache,
86 | resolvers: {},
87 | link: additiveLink,
88 | defaultOptions: {
89 | watchQuery: {
90 | fetchPolicy: 'cache-and-network',
91 | },
92 | query: {
93 | fetchPolicy: 'network-only',
94 | errorPolicy: 'all',
95 | },
96 | mutate: {
97 | errorPolicy: 'all',
98 | },
99 | },
100 | })
101 |
102 | export default client
103 |
--------------------------------------------------------------------------------
/src/containers/DashBoard/components/StatusCard.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import classNames from 'classnames'
3 | import { Card, LinearProgress, Divider } from '@material-ui/core'
4 | import {
5 | makeStyles,
6 | Theme,
7 | createStyles,
8 | withStyles,
9 | lighten,
10 | } from '@material-ui/core/styles'
11 |
12 | interface Props {
13 | title: string
14 | used: number
15 | total: number
16 | unit: string
17 | }
18 |
19 | const BorderLinearProgress = withStyles({
20 | root: {
21 | width: '100%',
22 | height: 5,
23 | backgroundColor: lighten('#eceff1', 0.1),
24 | borderRadius: 5,
25 | },
26 | bar: {
27 | borderRadius: 5,
28 | backgroundColor: '#3f51b5',
29 | },
30 | })(LinearProgress)
31 |
32 | const useStyles = makeStyles((theme: Theme) =>
33 | createStyles({
34 | card: {
35 | padding: 16,
36 | boxShadow:
37 | 'rgb(145 158 171 / 24%) 0px 0px 2px 0px, rgb(145 158 171 / 24%) 0px 16px 32px -4px',
38 | borderRadius: 16,
39 | },
40 |
41 | cardTitle: {
42 | marginTop: 0,
43 | marginBottom: 8,
44 | fontSize: 11,
45 | color: '#546e7a',
46 | textTransform: 'uppercase',
47 | fontWeight: 500,
48 | lineHeight: '13px',
49 | letterSpacing: '0.33px',
50 | },
51 |
52 | percent: {
53 | marginRight: 20,
54 | fontSize: 24,
55 | fontWeight: 500,
56 | color: '#263238',
57 | },
58 |
59 | vision: {
60 | display: 'flex',
61 | alignItems: 'center',
62 | },
63 |
64 | divider: {
65 | margin: '12px 0 16px',
66 | },
67 |
68 | bottomTxt: {
69 | fontSize: 14,
70 | },
71 |
72 | margin: {
73 | marginRight: 16,
74 | },
75 | }),
76 | )
77 |
78 | const switchUnit = (unit: string, value: number) => {
79 | let res = 0
80 | switch (true) {
81 | case unit === 'GB':
82 | res = value / 1024 / 1024 / 1024
83 | break
84 | case unit === 'MB':
85 | res = value / 1024 / 1024
86 | break
87 | case unit === 'KB':
88 | res = value / 1024
89 | break
90 | default:
91 | res = value
92 | break
93 | }
94 |
95 | return res.toFixed(2)
96 | }
97 |
98 | const StatusCard: FC = ({ title, used, total, unit }) => {
99 | const classes = useStyles()
100 | const percent = (used / total) * 100
101 |
102 | return (
103 |
104 | {title}
105 |
106 | {Math.ceil(percent)}%
107 |
108 |
109 |
110 |
111 |
112 | Used: {switchUnit(unit, used)} {unit}
113 |
114 |
115 | Total: {switchUnit(unit, total)} {unit}
116 |
117 |
118 |
119 | )
120 | }
121 |
122 | export default StatusCard
123 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/components/CVPicker.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, ChangeEvent } from 'react'
2 | import {
3 | Button,
4 | TextField,
5 | ListItem,
6 | ListItemAvatar,
7 | ListItemText,
8 | Avatar,
9 | ListItemIcon,
10 | RadioGroup,
11 | Radio,
12 | Card,
13 | } from '@material-ui/core'
14 | import SettingItemWrapper from '../../components/SettingItemWrapper/SettingItemWrapper'
15 | import { PostFilterProps } from '../types'
16 | import useStyles from '../styles'
17 |
18 | type Props = PostFilterProps & { cvPostId: string }
19 |
20 | const CVPicker: FC = ({
21 | id,
22 | posts,
23 | isFetching,
24 | isSubmitting,
25 | fetchPosts,
26 | cvPostId,
27 | updateGlobalSettingById,
28 | }) => {
29 | const classes = useStyles()
30 |
31 | const [searchValue, setSearchValue] = useState('')
32 | const handleInputChange = (e: ChangeEvent) => {
33 | setSearchValue(e.target.value)
34 | }
35 |
36 | const [checked, setChecked] = useState('')
37 | const handleRadioChange = (e: ChangeEvent) => {
38 | setChecked(e.target.value)
39 | }
40 |
41 | const onResetRadio = () => {
42 | setChecked('')
43 | }
44 |
45 | const onSubmit = async () => {
46 | await updateGlobalSettingById({
47 | variables: { input: { cvPostId: checked, id } },
48 | })
49 | }
50 |
51 | return (
52 |
53 | post ID: {cvPostId}
54 |
55 |
60 | fetchPosts(searchValue)}
67 | >
68 | Search
69 |
70 |
71 | {posts.length > 0 ? (
72 |
73 |
74 | {posts.map((post) => (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ))}
85 |
86 |
87 |
88 |
89 | Reset
90 |
91 |
97 | Save
98 |
99 |
100 |
101 | ) : null}
102 |
103 | )
104 | }
105 |
106 | export default CVPicker
107 |
--------------------------------------------------------------------------------
/src/pages/Layouts/components/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { Link } from '@material-ui/core'
3 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'
4 | import { useQuery } from '@apollo/client'
5 | import {
6 | YANCEY_BLOG_URL,
7 | YANCEY_GITHUB_URL,
8 | YANCEY_EMAIL_URL,
9 | } from 'src/shared/constants'
10 | import { GLOBAL_SETTING } from 'src/containers/Settings/GlobalConfig/typeDefs'
11 | import { Query } from 'src/containers/Settings/GlobalConfig/types'
12 |
13 | const useStyles = makeStyles((theme: Theme) =>
14 | createStyles({
15 | footer: {
16 | display: 'flex',
17 | justifyContent: 'space-between',
18 | alignItems: 'center',
19 | margin: 0,
20 | padding: '12px 24px',
21 | fontSize: '14px',
22 | textAlign: 'right',
23 | color: '#3c4858',
24 | },
25 |
26 | footerList: {
27 | padding: 0,
28 | listStyle: 'none',
29 | },
30 |
31 | footerItem: {
32 | display: 'inline-block',
33 | marginRight: theme.spacing(3.75),
34 | fontSize: '12px',
35 | fontWeight: 500,
36 | color: '#3c4858',
37 | },
38 | }),
39 | )
40 |
41 | const Footer: FC = () => {
42 | const classes = useStyles()
43 |
44 | const { data } = useQuery(GLOBAL_SETTING, {
45 | notifyOnNetworkStatusChange: true,
46 | })
47 |
48 | return (
49 |
105 | )
106 | }
107 |
108 | export default Footer
109 |
--------------------------------------------------------------------------------
/src/service-worker.ts:
--------------------------------------------------------------------------------
1 | ///
2 | /* eslint-disable no-restricted-globals */
3 |
4 | // This service worker can be customized!
5 | // See https://developers.google.com/web/tools/workbox/modules
6 | // for the list of available Workbox modules, or add any other
7 | // code you'd like.
8 | // You can also remove this file if you'd prefer not to use a
9 | // service worker, and the Workbox build step will be skipped.
10 |
11 | import { clientsClaim } from 'workbox-core'
12 | import { ExpirationPlugin } from 'workbox-expiration'
13 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'
14 | import { registerRoute } from 'workbox-routing'
15 | import { StaleWhileRevalidate } from 'workbox-strategies'
16 |
17 | declare const self: ServiceWorkerGlobalScope
18 |
19 | clientsClaim()
20 |
21 | // Precache all of the assets generated by your build process.
22 | // Their URLs are injected into the manifest variable below.
23 | // This variable must be present somewhere in your service worker file,
24 | // even if you decide not to use precaching. See https://cra.link/PWA
25 | precacheAndRoute(self.__WB_MANIFEST)
26 |
27 | // Set up App Shell-style routing, so that all navigation requests
28 | // are fulfilled with your index.html shell. Learn more at
29 | // https://developers.google.com/web/fundamentals/architecture/app-shell
30 | const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$')
31 | registerRoute(
32 | // Return false to exempt requests from being fulfilled by index.html.
33 | ({ request, url }: { request: Request; url: URL }) => {
34 | // If this isn't a navigation, skip.
35 | if (request.mode !== 'navigate') {
36 | return false
37 | }
38 |
39 | // If this is a URL that starts with /_, skip.
40 | if (url.pathname.startsWith('/_')) {
41 | return false
42 | }
43 |
44 | // If this looks like a URL for a resource, because it contains
45 | // a file extension, skip.
46 | if (url.pathname.match(fileExtensionRegexp)) {
47 | return false
48 | }
49 |
50 | // Return true to signal that we want to use the handler.
51 | return true
52 | },
53 | createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'),
54 | )
55 |
56 | // An example runtime caching route for requests that aren't handled by the
57 | // precache, in this case same-origin .png requests like those from in public/
58 | registerRoute(
59 | // Add in any other file extensions or routing criteria as needed.
60 | ({ url }) =>
61 | url.origin === self.location.origin && url.pathname.endsWith('.png'),
62 | // Customize this strategy as needed, e.g., by changing to CacheFirst.
63 | new StaleWhileRevalidate({
64 | cacheName: 'images',
65 | plugins: [
66 | // Ensure that once this runtime cache reaches a maximum size the
67 | // least-recently used images are removed.
68 | new ExpirationPlugin({ maxEntries: 50 }),
69 | ],
70 | }),
71 | )
72 |
73 | // This allows the web app to trigger skipWaiting via
74 | // registration.waiting.postMessage({type: 'SKIP_WAITING'})
75 | self.addEventListener('message', (event) => {
76 | if (event.data && event.data.type === 'SKIP_WAITING') {
77 | self.skipWaiting()
78 | }
79 | })
80 |
81 | // Any other custom service worker logic can go here.
82 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/GlobalConfig.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useLazyQuery, useQuery, useMutation } from '@apollo/client'
3 | import { useSnackbar } from 'notistack'
4 | import Loading from 'src/components/Loading/Loading'
5 | import { POSTS } from 'src/containers/Post/typeDefs'
6 | import { Query as PostsQuery } from 'src/containers/Post/types'
7 | import SettingWrapper from '../components/SettingWrapper/SettingWrapper'
8 | import SettingsHeader from '../components/SettingsHeader/SettingsHeader'
9 | import { GLOBAL_SETTING, UPDATE_GLOBAL_SETTING_BY_ID } from './typeDefs'
10 | import { Query } from './types'
11 | import ReleasePicker from './components/ReleasePicker'
12 | import CVPicker from './components/CVPicker'
13 | import GrayTheme from './components/GrayTheme'
14 |
15 | const GlobalConfig: FC = () => {
16 | const { enqueueSnackbar } = useSnackbar()
17 |
18 | const [fetchPostsByPage, { loading: isFetchingPosts, data: postsData }] =
19 | useLazyQuery(POSTS, {
20 | notifyOnNetworkStatusChange: true,
21 | })
22 |
23 | const fetchPosts = (title: string) => {
24 | fetchPostsByPage({
25 | variables: {
26 | input: {
27 | page: 1,
28 | pageSize: 10,
29 | title,
30 | },
31 | },
32 | })
33 | }
34 |
35 | const { loading: isFetching, data } = useQuery(GLOBAL_SETTING, {
36 | notifyOnNetworkStatusChange: true,
37 | })
38 |
39 | const [updateGlobalSettingById, { loading: isSubmitting }] = useMutation(
40 | UPDATE_GLOBAL_SETTING_BY_ID,
41 | {
42 | onCompleted() {
43 | enqueueSnackbar('Update success!', { variant: 'success' })
44 | },
45 | onError() {},
46 | },
47 | )
48 |
49 | return (
50 |
51 | {isFetching ? (
52 |
53 | ) : (
54 | <>
55 |
59 |
60 |
69 |
78 |
84 | >
85 | )}
86 |
87 | )
88 | }
89 |
90 | export default GlobalConfig
91 |
--------------------------------------------------------------------------------
/src/containers/Settings/GlobalConfig/components/ReleasePicker.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, ChangeEvent } from 'react'
2 | import {
3 | Button,
4 | TextField,
5 | ListItem,
6 | ListItemAvatar,
7 | ListItemText,
8 | Avatar,
9 | ListItemIcon,
10 | RadioGroup,
11 | Radio,
12 | Card,
13 | } from '@material-ui/core'
14 | import SettingItemWrapper from '../../components/SettingItemWrapper/SettingItemWrapper'
15 | import { PostFilterProps } from '../types'
16 | import useStyles from '../styles'
17 |
18 | type Props = PostFilterProps & { releasePostId: string }
19 |
20 | const ReleasePicker: FC = ({
21 | id,
22 | posts,
23 | isFetching,
24 | isSubmitting,
25 | fetchPosts,
26 | releasePostId,
27 | updateGlobalSettingById,
28 | }) => {
29 | const classes = useStyles()
30 |
31 | const [searchValue, setSearchValue] = useState('')
32 | const handleInputChange = (e: ChangeEvent) => {
33 | setSearchValue(e.target.value)
34 | }
35 |
36 | const [checked, setChecked] = useState('')
37 | const handleRadioChange = (e: ChangeEvent) => {
38 | setChecked(e.target.value)
39 | }
40 |
41 | const onResetRadio = () => {
42 | setChecked('')
43 | }
44 |
45 | const onSubmit = async () => {
46 | await updateGlobalSettingById({
47 | variables: { input: { releasePostId: checked, id } },
48 | })
49 | }
50 |
51 | return (
52 |
53 | post ID: {releasePostId}
54 |
55 |
60 | fetchPosts(searchValue)}
67 | >
68 | Search
69 |
70 |
71 | {posts.length > 0 ? (
72 |
73 |
74 | {posts.map((post) => (
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ))}
85 |
86 |
87 |
88 |
89 | Reset
90 |
91 |
97 | Save
98 |
99 |
100 |
101 | ) : null}
102 |
103 | )
104 | }
105 |
106 | export default ReleasePicker
107 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/TwoFactors/TwoFactors.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import {
3 | List,
4 | ListItem,
5 | ListItemText,
6 | ListItemAvatar,
7 | Divider,
8 | } from '@material-ui/core'
9 | import {
10 | ArrowForwardIos,
11 | SentimentVerySatisfied,
12 | SentimentDissatisfied,
13 | } from '@material-ui/icons'
14 | import { AZURE_BLOB_PATH } from 'src/shared/constants'
15 | import client from 'src/graphql/apolloClient'
16 | import SnackbarUtils from 'src/components/Toast/Toast'
17 | import SettingItemWrapper from 'src/containers/Settings/components/SettingItemWrapper/SettingItemWrapper'
18 | import TOTP from '../TOTP/TOTP'
19 | import RecoveryCodes from '../RecoveryCodes/RecoveryCodes'
20 | import styles from './twoFactors.module.scss'
21 |
22 | const TwoFactors: FC = () => {
23 | const [openTOTP, setOpenTOTP] = useState(false)
24 | const [openRecoveryCodes, setOpenRecoveryCodes] = useState(false)
25 |
26 | const { isTOTP } =
27 | // @ts-ignore
28 | client.cache.data.data[`UserModel:${window.localStorage.getItem('userId')}`]
29 |
30 | const openRecoveryCodesDialog = () => {
31 | if (!isTOTP) {
32 | SnackbarUtils.error('Please turn on Authenticator app options first!')
33 | return
34 | }
35 | setOpenRecoveryCodes(true)
36 | }
37 |
38 | return (
39 | <>
40 |
44 |
49 | setOpenTOTP(true)}>
50 |
54 |
58 | {isTOTP ? (
59 |
60 | ) : (
61 |
62 | )}
63 |
64 |
65 | {isTOTP ? 'Enable' : 'Disable'}
66 |
67 |
68 | }
69 | />
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Click to generate
}
81 | className={styles.title}
82 | />
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | >
93 | )
94 | }
95 |
96 | export default TwoFactors
97 |
--------------------------------------------------------------------------------
/src/containers/Post/PostList.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useCallback } from 'react'
2 | import { useLazyQuery, useMutation } from '@apollo/client'
3 | import { useSnackbar } from 'notistack'
4 | import {
5 | POSTS,
6 | DELETE_ONE_POST,
7 | BATCH_DELETE_POSTS,
8 | UPDATE_ONE_POST,
9 | CREATE_POST_STATISTICS,
10 | } from './typeDefs'
11 | import {
12 | Query,
13 | UpdatePostByIdMutation,
14 | PostStatisticsVars,
15 | CreatePostStatisticsMutation,
16 | } from './types'
17 | import PostTable from './components/PostTable'
18 | import {
19 | deletePostOnAlgolia,
20 | deletePostsOnAlgolia,
21 | } from './algolia/algoliaSearch'
22 |
23 | const Post: FC = () => {
24 | const { enqueueSnackbar } = useSnackbar()
25 |
26 | const [fetchPostsByPage, { loading: isFetching, data }] = useLazyQuery(
27 | POSTS,
28 | {
29 | notifyOnNetworkStatusChange: true,
30 | },
31 | )
32 |
33 | const fetchFirstData = useCallback(() => {
34 | fetchPostsByPage({
35 | variables: {
36 | input: {
37 | page: 1,
38 | pageSize: 10,
39 | },
40 | },
41 | })
42 | }, [fetchPostsByPage])
43 |
44 | const [createPostStatistics] = useMutation<
45 | CreatePostStatisticsMutation,
46 | PostStatisticsVars
47 | >(CREATE_POST_STATISTICS)
48 |
49 | const [updatePostById] = useMutation(
50 | UPDATE_ONE_POST,
51 | {
52 | onCompleted(data) {
53 | const { _id, title, isPublic } = data.updatePostById
54 | enqueueSnackbar(`「${title}」 is ${isPublic ? 'public' : 'hide'}.`, {
55 | variant: 'success',
56 | })
57 |
58 | createPostStatistics({
59 | variables: {
60 | input: {
61 | postId: _id,
62 | postName: title,
63 | scenes: `switched to ${isPublic ? 'public' : 'hide'}`,
64 | },
65 | },
66 | })
67 | },
68 | },
69 | )
70 |
71 | const [deletePostById, { loading: isDeleting }] = useMutation(
72 | DELETE_ONE_POST,
73 | {
74 | onCompleted(data) {
75 | const { _id } = data.deletePostById
76 | enqueueSnackbar('Delete success!', { variant: 'success' })
77 | fetchFirstData()
78 | deletePostOnAlgolia(_id)
79 | },
80 | },
81 | )
82 |
83 | const [deletePosts, { loading: isBatchDeleting }] = useMutation(
84 | BATCH_DELETE_POSTS,
85 | {
86 | onCompleted(data) {
87 | const { ids } = data.deletePosts
88 | enqueueSnackbar('Delete success!', { variant: 'success' })
89 | fetchFirstData()
90 | deletePostsOnAlgolia(ids)
91 | },
92 | },
93 | )
94 |
95 | useEffect(() => {
96 | fetchFirstData()
97 | }, [fetchFirstData])
98 |
99 | return (
100 |
113 | )
114 | }
115 |
116 | export default Post
117 |
--------------------------------------------------------------------------------
/src/containers/Music/LiveTour/LiveTour.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useQuery, useMutation } from '@apollo/client'
3 | import { useSnackbar } from 'notistack'
4 | import {
5 | LIVE_TOURS,
6 | CREATE_ONE_LIVE_TOUR,
7 | UPDATE_ONE_LIVE_TOUR,
8 | DELETE_ONE_LIVE_TOUR,
9 | BATCH_DELETE_LIVE_TOUR,
10 | } from './typeDefs'
11 | import { ILiveTour, Query } from './types'
12 | import LiveTourTable from './components/LiveTourTable'
13 |
14 | const LiveTour: FC = () => {
15 | const { enqueueSnackbar } = useSnackbar()
16 |
17 | const { loading: isFetching, data } = useQuery(LIVE_TOURS, {
18 | notifyOnNetworkStatusChange: true,
19 | })
20 |
21 | const [createLiveTour] = useMutation(CREATE_ONE_LIVE_TOUR, {
22 | update(proxy, { data: { createLiveTour } }) {
23 | const data = proxy.readQuery({ query: LIVE_TOURS })
24 |
25 | if (data) {
26 | proxy.writeQuery({
27 | query: LIVE_TOURS,
28 | data: {
29 | ...data,
30 | getLiveTours: [createLiveTour, ...data.getLiveTours],
31 | },
32 | })
33 | }
34 | },
35 |
36 | onCompleted() {
37 | enqueueSnackbar('Create success!', { variant: 'success' })
38 | },
39 | onError() {},
40 | })
41 |
42 | const [updateLiveTourById] = useMutation(UPDATE_ONE_LIVE_TOUR, {
43 | onCompleted() {
44 | enqueueSnackbar('Update success!', { variant: 'success' })
45 | },
46 | })
47 |
48 | const [deleteLiveTourById, { loading: isDeleting }] = useMutation(
49 | DELETE_ONE_LIVE_TOUR,
50 | {
51 | update(proxy, { data: { deleteLiveTourById } }) {
52 | const data = proxy.readQuery({ query: LIVE_TOURS })
53 |
54 | if (data) {
55 | proxy.writeQuery({
56 | query: LIVE_TOURS,
57 | data: {
58 | getLiveTours: data.getLiveTours.filter(
59 | (liveTour: ILiveTour) =>
60 | liveTour._id !== deleteLiveTourById._id,
61 | ),
62 | },
63 | })
64 | }
65 | },
66 | onCompleted() {
67 | enqueueSnackbar('Delete success!', { variant: 'success' })
68 | },
69 | onError() {},
70 | },
71 | )
72 |
73 | const [deleteLiveTours, { loading: isBatchDeleting }] = useMutation(
74 | BATCH_DELETE_LIVE_TOUR,
75 | {
76 | update(proxy, { data: { deleteLiveTours } }) {
77 | const data = proxy.readQuery({ query: LIVE_TOURS })
78 |
79 | if (data) {
80 | proxy.writeQuery({
81 | query: LIVE_TOURS,
82 | data: {
83 | getLiveTours: data.getLiveTours.filter(
84 | (liveTour: ILiveTour) =>
85 | !deleteLiveTours.ids.includes(liveTour._id),
86 | ),
87 | },
88 | })
89 | }
90 | },
91 | onCompleted() {
92 | enqueueSnackbar('Delete success!', { variant: 'success' })
93 | },
94 | onError() {},
95 | },
96 | )
97 |
98 | return (
99 |
109 | )
110 | }
111 |
112 | export default LiveTour
113 |
--------------------------------------------------------------------------------
/src/containers/Settings/Security/components/RecoveryCodes/RecoveryCodes.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, useEffect } from 'react'
2 | import { useMutation } from '@apollo/client'
3 | import {
4 | Dialog,
5 | Button,
6 | DialogTitle,
7 | DialogContent,
8 | DialogActions,
9 | CircularProgress,
10 | } from '@material-ui/core'
11 | import { DateTime } from 'luxon'
12 | import CopyToClipboard from 'react-copy-to-clipboard'
13 | import Transition from 'src/components/Transition/Transition'
14 | import { generateFile } from 'src/shared/utils'
15 | import { RECOVERY_CODES_FILE_NAME } from 'src/shared/constants'
16 | import { CREATE_RECOVERY_CODES } from '../../typeDefs'
17 | import styles from './recoveryCode.module.scss'
18 |
19 | interface Props {
20 | setOpen: Function
21 | open: boolean
22 | }
23 |
24 | const RecoveryCodes: FC = ({ setOpen, open }) => {
25 | const [copyTxt, setCopyTxt] = useState('Copy')
26 | const [recoveryCodes, setRecoveryCodes] = useState([])
27 |
28 | const onClose = () => {
29 | setOpen(false)
30 | }
31 |
32 | const [createRecoveryCodes, { loading }] = useMutation(CREATE_RECOVERY_CODES)
33 |
34 | useEffect(() => {
35 | const fetchTOTPAndRecoveryCodes = async () => {
36 | const res = await createRecoveryCodes()
37 | setRecoveryCodes(res.data.createRecoveryCodes.recoveryCodes)
38 | }
39 |
40 | if (open) fetchTOTPAndRecoveryCodes()
41 | }, [createRecoveryCodes, open])
42 |
43 | return (
44 |
52 | Save your backup codes
53 |
54 | {loading ? (
55 |
56 |
57 |
58 | ) : (
59 | <>
60 |
61 |
62 | {recoveryCodes.map((recoveryCodes) => (
63 |
64 |
65 | {recoveryCodes}
66 |
67 | ))}
68 |
69 |
70 |
71 | You can only use each backup code once.
72 |
73 | These codes were generated on: {DateTime.now().toFormat('DD')}.
74 |
75 |
76 | >
77 | )}
78 |
79 |
80 |
81 | Close
82 |
83 |
90 | Download
91 |
92 | setCopyTxt('Copied!')}
95 | >
96 |
97 | {copyTxt}
98 |
99 |
100 |
101 |
102 | )
103 | }
104 |
105 | export default RecoveryCodes
106 |
--------------------------------------------------------------------------------
/src/containers/Music/BestAlbum/BestAlbum.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useQuery, useMutation } from '@apollo/client'
3 | import { useSnackbar } from 'notistack'
4 | import {
5 | BEST_ALBUMS,
6 | CREATE_ONE_BEST_ALBUM,
7 | UPDATE_ONE_BEST_ALBUM,
8 | DELETE_ONE_BEST_ALBUM,
9 | BATCH_DELETE_BEST_ALBUMS,
10 | } from './typeDefs'
11 | import { IBestAlbum, Query } from './types'
12 | import BestAlbumTable from './components/BestAlbumTable'
13 |
14 | const BestAlbum: FC = () => {
15 | const { enqueueSnackbar } = useSnackbar()
16 |
17 | const { loading: isFetching, data } = useQuery(BEST_ALBUMS, {
18 | notifyOnNetworkStatusChange: true,
19 | })
20 |
21 | const [createBestAlbum] = useMutation(CREATE_ONE_BEST_ALBUM, {
22 | update(proxy, { data: { createBestAlbum } }) {
23 | const data = proxy.readQuery({ query: BEST_ALBUMS })
24 |
25 | if (data) {
26 | proxy.writeQuery({
27 | query: BEST_ALBUMS,
28 | data: {
29 | ...data,
30 | getBestAlbums: [createBestAlbum, ...data.getBestAlbums],
31 | },
32 | })
33 | }
34 | },
35 |
36 | onCompleted() {
37 | enqueueSnackbar('Create success!', { variant: 'success' })
38 | },
39 | onError() {},
40 | })
41 |
42 | const [updateBestAlbumById] = useMutation(UPDATE_ONE_BEST_ALBUM, {
43 | onCompleted() {
44 | enqueueSnackbar('Update success!', { variant: 'success' })
45 | },
46 | })
47 |
48 | const [deleteBestAlbumById, { loading: isDeleting }] = useMutation(
49 | DELETE_ONE_BEST_ALBUM,
50 | {
51 | update(proxy, { data: { deleteBestAlbumById } }) {
52 | const data = proxy.readQuery({ query: BEST_ALBUMS })
53 |
54 | if (data) {
55 | proxy.writeQuery({
56 | query: BEST_ALBUMS,
57 | data: {
58 | getBestAlbums: data.getBestAlbums.filter(
59 | (bestAlbum: IBestAlbum) =>
60 | bestAlbum._id !== deleteBestAlbumById._id,
61 | ),
62 | },
63 | })
64 | }
65 | },
66 | onCompleted() {
67 | enqueueSnackbar('Delete success!', { variant: 'success' })
68 | },
69 | onError() {},
70 | },
71 | )
72 |
73 | const [deleteBestAlbums, { loading: isBatchDeleting }] = useMutation(
74 | BATCH_DELETE_BEST_ALBUMS,
75 | {
76 | update(proxy, { data: { deleteBestAlbums } }) {
77 | const data = proxy.readQuery({ query: BEST_ALBUMS })
78 |
79 | if (data) {
80 | proxy.writeQuery({
81 | query: BEST_ALBUMS,
82 | data: {
83 | getBestAlbums: data.getBestAlbums.filter(
84 | (bestAlbum: IBestAlbum) =>
85 | !deleteBestAlbums.ids.includes(bestAlbum._id),
86 | ),
87 | },
88 | })
89 | }
90 | },
91 | onCompleted() {
92 | enqueueSnackbar('Delete success!', { variant: 'success' })
93 | },
94 | onError() {},
95 | },
96 | )
97 |
98 | return (
99 |
109 | )
110 | }
111 |
112 | export default BestAlbum
113 |
--------------------------------------------------------------------------------
/src/components/Move/Move.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { MUIDataTableMeta } from 'mui-datatables'
3 | import PopupState, { bindTrigger, bindMenu } from 'material-ui-popup-state'
4 | import { MoreVert } from '@material-ui/icons'
5 | import { Menu, MenuItem } from '@material-ui/core'
6 |
7 | interface Props {
8 | dataSource: any[]
9 | tableMeta: MUIDataTableMeta
10 | exchangePosition: Function
11 | }
12 |
13 | const Move: FC = ({ dataSource, tableMeta, exchangePosition }) => {
14 | const move = (
15 | curId: string,
16 | nextId: string,
17 | curWeight: number,
18 | nextWeight: number,
19 | closePoper: Function,
20 | ) => {
21 | closePoper()
22 |
23 | exchangePosition({
24 | variables: {
25 | input: {
26 | id: curId,
27 | exchangedId: nextId,
28 | weight: curWeight,
29 | exchangedWeight: nextWeight,
30 | },
31 | },
32 | })
33 | }
34 |
35 | const curId = tableMeta.rowData[0]
36 | const curWeight = tableMeta.rowData[1]
37 |
38 | const prev = tableMeta.tableData[tableMeta.rowIndex - 1]
39 | const next = tableMeta.tableData[tableMeta.rowIndex + 1]
40 | const top = tableMeta.tableData[0]
41 |
42 | // @ts-ignore
43 | const prevId = prev && prev[0]
44 | // @ts-ignore
45 | const prevWeight = prev && prev[1]
46 |
47 | // @ts-ignore
48 | const nextId = next && next[0]
49 | // @ts-ignore
50 | const nextWeight = next && next[1]
51 |
52 | // @ts-ignore
53 | const topId = top[0]
54 | // @ts-ignore
55 | const topWeight = top[1]
56 |
57 | return (
58 | <>
59 | {dataSource.length < 2 || (
60 |
61 | {(popupState) => (
62 | <>
63 |
67 |
68 |
69 | {curWeight !== dataSource[0].weight ? (
70 |
72 | move(curId, topId, curWeight, topWeight, popupState.close)
73 | }
74 | >
75 | Move to the top
76 |
77 | ) : null}
78 |
79 | {curWeight !== dataSource[0].weight ? (
80 |
82 | move(
83 | curId,
84 | prevId,
85 | curWeight,
86 | prevWeight,
87 | popupState.close,
88 | )
89 | }
90 | >
91 | Move up
92 |
93 | ) : null}
94 |
95 | {curWeight !== 1 ? (
96 |
98 | move(
99 | curId,
100 | nextId,
101 | curWeight,
102 | nextWeight,
103 | popupState.close,
104 | )
105 | }
106 | >
107 | Move down
108 |
109 | ) : null}
110 |
111 | >
112 | )}
113 |
114 | )}
115 | >
116 | )
117 | }
118 |
119 | export default Move
120 |
--------------------------------------------------------------------------------
/src/containers/Home/OpenSource/OpenSource.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useQuery, useMutation } from '@apollo/client'
3 | import { useSnackbar } from 'notistack'
4 | import {
5 | OPEN_SOURCES,
6 | CREATE_ONE_OPEN_SOURCE,
7 | UPDATE_ONE_OPEN_SOURCE,
8 | DELETE_ONE_OPEN_SOURCE,
9 | BATCH_DELETE_OPEN_SOURCE,
10 | } from './typeDefs'
11 | import { IOpenSource, Query } from './types'
12 | import OpenSourceTable from './components/OpenSourceTable'
13 |
14 | const OpenSource: FC = () => {
15 | const { enqueueSnackbar } = useSnackbar()
16 |
17 | const { loading: isFetching, data } = useQuery(OPEN_SOURCES, {
18 | notifyOnNetworkStatusChange: true,
19 | })
20 |
21 | const [createOpenSource] = useMutation(CREATE_ONE_OPEN_SOURCE, {
22 | update(proxy, { data: { createOpenSource } }) {
23 | const data = proxy.readQuery({ query: OPEN_SOURCES })
24 |
25 | if (data) {
26 | proxy.writeQuery({
27 | query: OPEN_SOURCES,
28 | data: {
29 | ...data,
30 | getOpenSources: [createOpenSource, ...data.getOpenSources],
31 | },
32 | })
33 | }
34 | },
35 |
36 | onCompleted() {
37 | enqueueSnackbar('Create success!', { variant: 'success' })
38 | },
39 | onError() {},
40 | })
41 |
42 | const [updateOpenSourceById] = useMutation(UPDATE_ONE_OPEN_SOURCE, {
43 | onCompleted() {
44 | enqueueSnackbar('Update success!', { variant: 'success' })
45 | },
46 | })
47 |
48 | const [deleteOpenSourceById, { loading: isDeleting }] = useMutation(
49 | DELETE_ONE_OPEN_SOURCE,
50 | {
51 | update(proxy, { data: { deleteOpenSourceById } }) {
52 | const data = proxy.readQuery({ query: OPEN_SOURCES })
53 |
54 | if (data) {
55 | proxy.writeQuery({
56 | query: OPEN_SOURCES,
57 | data: {
58 | getOpenSources: data.getOpenSources.filter(
59 | (openSource: IOpenSource) =>
60 | openSource._id !== deleteOpenSourceById._id,
61 | ),
62 | },
63 | })
64 | }
65 | },
66 | onCompleted() {
67 | enqueueSnackbar('Delete success!', { variant: 'success' })
68 | },
69 | onError() {},
70 | },
71 | )
72 |
73 | const [deleteOpenSources, { loading: isBatchDeleting }] = useMutation(
74 | BATCH_DELETE_OPEN_SOURCE,
75 | {
76 | update(proxy, { data: { deleteOpenSources } }) {
77 | const data = proxy.readQuery({ query: OPEN_SOURCES })
78 |
79 | if (data) {
80 | proxy.writeQuery({
81 | query: OPEN_SOURCES,
82 | data: {
83 | getOpenSources: data.getOpenSources.filter(
84 | (openSource: IOpenSource) =>
85 | !deleteOpenSources.ids.includes(openSource._id),
86 | ),
87 | },
88 | })
89 | }
90 | },
91 | onCompleted() {
92 | enqueueSnackbar('Delete success!', { variant: 'success' })
93 | },
94 | onError() {},
95 | },
96 | )
97 |
98 | return (
99 |
109 | )
110 | }
111 |
112 | export default OpenSource
113 |
--------------------------------------------------------------------------------
/src/pages/Auth/Auth.module.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Whitney-400';
3 | src: url('https://edge.yancey.app/beg/Whitney-400.woff') format('woff');
4 | }
5 |
6 | @font-face {
7 | font-family: 'Whitney-500';
8 | src: url('https://edge.yancey.app/beg/Whitney-500.woff') format('woff');
9 | }
10 |
11 | @font-face {
12 | font-family: 'Whitney-600';
13 | src: url('https://edge.yancey.app/beg/Whitney-600.woff') format('woff');
14 | }
15 |
16 | .loginWrapper {
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | width: 100vw;
21 | height: 100vh;
22 | font-family: 'Whitney-400';
23 | background-repeat: no-repeat;
24 | background-position: center center;
25 | background-size: cover;
26 | text-rendering: optimizeLegibility;
27 | }
28 |
29 | .header {
30 | margin-bottom: 8px;
31 | text-align: center;
32 | font-size: 26px;
33 | font-weight: 400;
34 | line-height: 32px;
35 | color: #ffffff;
36 | }
37 |
38 | .headerExtra {
39 | text-align: center;
40 | color: #72767d;
41 | font-family: 'Whitney-500';
42 | font-size: 18px;
43 | font-weight: 500;
44 | line-height: 22px;
45 | }
46 |
47 | .loginForm {
48 | width: 480px;
49 | padding: 40px;
50 | font-size: 18px;
51 | color: #72767d;
52 | background: #36393f;
53 | box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.2);
54 | border-radius: 5px;
55 | }
56 |
57 | .label {
58 | display: block;
59 | margin-bottom: 20px;
60 | font-family: 'Whitney-600';
61 | font-size: 12px;
62 | font-weight: 600;
63 | color: #8e9297;
64 | text-transform: uppercase;
65 | &:first-of-type {
66 | margin-top: 20px;
67 | }
68 | }
69 |
70 | .inputTxt {
71 | margin-top: 6px;
72 | padding: 10px;
73 | height: 40px;
74 | font-size: 16px;
75 | width: 100%;
76 | border-radius: 3px;
77 | color: #dcddde;
78 | background-color: rgba(#000000, 0.1);
79 | border: 1px solid rgba(#000000, 0.3);
80 | outline: none;
81 | &:hover {
82 | border-color: #000000;
83 | }
84 | &:focus {
85 | border-color: #7289da;
86 | }
87 | }
88 |
89 | .submitBtn {
90 | width: 100%;
91 | height: 44px;
92 | font-family: 'Whitney-600';
93 | font-size: 16px;
94 | color: #fff;
95 | background-color: #7289da;
96 | border: none;
97 | border-radius: 3px;
98 | user-select: none;
99 | outline: none;
100 | cursor: pointer;
101 | transition: 300ms ease;
102 | &:hover {
103 | transition: 300ms ease;
104 | background-color: #677bc4;
105 | }
106 | }
107 |
108 | .link {
109 | margin: 8px 0;
110 | font-family: 'Whitney-600';
111 | font-size: 14px;
112 | font-weight: 500;
113 | color: #7289da;
114 | cursor: pointer;
115 | text-decoration: none;
116 | text-transform: initial;
117 | &:hover {
118 | text-decoration: underline;
119 | }
120 | a {
121 | text-decoration: none;
122 | color: inherit;
123 | }
124 | }
125 |
126 | .registerTip {
127 | display: inline-block;
128 | margin-top: 8px;
129 | font-size: 14px;
130 | }
131 |
132 | .license {
133 | margin-top: 20px;
134 | font-size: 12px;
135 | color: #72767d;
136 | a {
137 | color: #7289da;
138 | text-decoration: none;
139 | &:hover {
140 | text-decoration: underline;
141 | }
142 | }
143 | }
144 |
145 | .error {
146 | color: #f04747;
147 | }
148 |
149 | .errorMsg {
150 | font-style: italic;
151 | text-transform: initial;
152 | }
153 |
154 | .errorInputTxt {
155 | border-color: #f04747;
156 | &:hover,
157 | &:focus {
158 | border-color: #f04747;
159 | }
160 | }
161 |
162 | .copyright {
163 | position: absolute;
164 | bottom: 1rem;
165 | }
166 |
--------------------------------------------------------------------------------
/src/containers/Music/YanceyMusic/YanceyMusic.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { useQuery, useMutation } from '@apollo/client'
3 | import { useSnackbar } from 'notistack'
4 | import {
5 | YANCEY_MUSIC,
6 | CREATE_ONE_YANCEY_MUSIC,
7 | UPDATE_ONE_YANCEY_MUSIC,
8 | DELETE_ONE_YANCEY_MUSIC,
9 | BATCH_DELETE_YANCEY_MUSIC,
10 | } from './typeDefs'
11 | import { IYanceyMusic, Query } from './types'
12 | import YanceyMusicTable from './components/YanceyMusicTable'
13 |
14 | const YanceyMusic: FC = () => {
15 | const { enqueueSnackbar } = useSnackbar()
16 |
17 | const { loading: isFetching, data } = useQuery(YANCEY_MUSIC, {
18 | notifyOnNetworkStatusChange: true,
19 | })
20 |
21 | const [createYanceyMusic] = useMutation(CREATE_ONE_YANCEY_MUSIC, {
22 | update(proxy, { data: { createYanceyMusic } }) {
23 | const data = proxy.readQuery({ query: YANCEY_MUSIC })
24 |
25 | if (data) {
26 | proxy.writeQuery({
27 | query: YANCEY_MUSIC,
28 | data: {
29 | ...data,
30 | getYanceyMusic: [createYanceyMusic, ...data.getYanceyMusic],
31 | },
32 | })
33 | }
34 | },
35 |
36 | onCompleted() {
37 | enqueueSnackbar('Create success!', { variant: 'success' })
38 | },
39 | onError() {},
40 | })
41 |
42 | const [updateYanceyMusicById] = useMutation(UPDATE_ONE_YANCEY_MUSIC, {
43 | onCompleted() {
44 | enqueueSnackbar('Update success!', { variant: 'success' })
45 | },
46 | })
47 |
48 | const [deleteYanceyMusicById, { loading: isDeleting }] = useMutation(
49 | DELETE_ONE_YANCEY_MUSIC,
50 | {
51 | update(proxy, { data: { deleteYanceyMusicById } }) {
52 | const data = proxy.readQuery({ query: YANCEY_MUSIC })
53 |
54 | if (data) {
55 | proxy.writeQuery({
56 | query: YANCEY_MUSIC,
57 | data: {
58 | getYanceyMusic: data.getYanceyMusic.filter(
59 | (yanceyMusic: IYanceyMusic) =>
60 | yanceyMusic._id !== deleteYanceyMusicById._id,
61 | ),
62 | },
63 | })
64 | }
65 | },
66 | onCompleted() {
67 | enqueueSnackbar('Delete success!', { variant: 'success' })
68 | },
69 | onError() {},
70 | },
71 | )
72 |
73 | const [deleteYanceyMusic, { loading: isBatchDeleting }] = useMutation(
74 | BATCH_DELETE_YANCEY_MUSIC,
75 | {
76 | update(proxy, { data: { deleteYanceyMusic } }) {
77 | const data = proxy.readQuery({ query: YANCEY_MUSIC })
78 |
79 | if (data) {
80 | proxy.writeQuery({
81 | query: YANCEY_MUSIC,
82 | data: {
83 | getYanceyMusic: data.getYanceyMusic.filter(
84 | (yanceyMusic: IYanceyMusic) =>
85 | !deleteYanceyMusic.ids.includes(yanceyMusic._id),
86 | ),
87 | },
88 | })
89 | }
90 | },
91 | onCompleted() {
92 | enqueueSnackbar('Delete success!', { variant: 'success' })
93 | },
94 | onError() {},
95 | },
96 | )
97 |
98 | return (
99 |
109 | )
110 | }
111 |
112 | export default YanceyMusic
113 |
--------------------------------------------------------------------------------