diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c6c8b362 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes index fcadb2cf..ab2a5332 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ * text eol=lf +*.png binary +*.ttf binary \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..aff82a10 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..13f33ea3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,29 @@ +name: Vite Tests + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm run test + - name: Build project + run: npm run build --if-present + env: + VITE_API_BASE_URL: dummy + VITE_KEYCLOAK_URL: dummy diff --git a/index.html b/index.html index 3eb0a629..561c4b04 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,9 @@ + + + @@ -34,8 +37,8 @@ Bannergress - - + + + + + diff --git a/src/App.less b/src/App.less index b0c753c7..e47dbaad 100644 --- a/src/App.less +++ b/src/App.less @@ -1,67 +1,14 @@ @import 'antd/dist/antd.compact.less'; @import 'antd/dist/antd.dark.less'; -@primary-color: #1DA57A; -@link-color: #1DA57A; -@body-background: #0b0c0d; -@card-head-padding: 0.6rem; -@card-padding-base: 0.6rem; -@border-color-base: #004f4a; - -::-webkit-scrollbar { - width: 4px; - height: 4px; - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: #969696; -} - -.mt-1 { - margin-top: 1rem; -} - -.pl-1 { - padding-left: 1rem; -} - -.pr-1 { - padding-right: 1rem; -} - -.footer-main { - text-align: center; - background-color: #000; - padding: 1.5rem 0; - width: 100%; -} - -.leaflet-container { - margin: unset; -} - -.leaflet-container { - height: 100%; -} - -body, -.ant-layout { - background: #1B1B1B; -} - -body, -.ant-menu, -.ant-input, -.ant-card, -.ant-card-head-title { - font-size: 16px; -} +@import './assets/style/variables.less'; +@import './assets/style/helper.less'; +@import './assets/style/ant-override.less'; #root { height: 100%; - &>.ant-layout { + & > .ant-layout { height: 100%; } } @@ -69,67 +16,8 @@ body, .main { height: 100%; - &>.container { + & > .container { height: 100%; overflow: auto; } } - -.ant-card { - border-radius: 5px; -} - -h1 { - font-size: 36px; - margin: 0.5em 0 0.3em; -} - -h2 { - font-size: 20px; -} - -h3 { - font-size: 16px; - font-weight: bold; - margin: 1em 0 0.05em; -} - -.top-menu, -.bottom-menu { - background: black; - padding: 5px 10px; - - .brand-menu { - max-width: 329px; - flex: 1; - } -} - -/* Make a button look like a link */ -.link-button { - background-color: transparent; - border: none; - cursor: pointer; - display: inline; - margin: 0; - padding: 0; -} - -.warning-text { - color: @color-warning; -} - -.error-text { - color: @color-error; -} - -.subtitle { - color: #888888; -} - -@color-todo: #FFE381; -@color-done: #70C03F; -@color-blacklist: #EF5555; -@color-pin: #6832DA; -@color-warning: #FFB21D; -@color-error: #EF5555; diff --git a/src/App.tsx b/src/App.tsx index b03f394f..c30cad6e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import { CreateBanner } from './pages/create-banner' import { PreviewBanner } from './pages/preview-banner' import { PrivateRoute } from './components/login/private-route' import { Help } from './pages/help' +import { Error } from './pages/error' import { Account } from './pages/account' import MenuMain from './components/menu-main' import Navbar from './components/navbar' @@ -80,6 +81,7 @@ const App: React.FC = () => { /> + { - if (value !== undefined) { + if (Array.isArray(value)) { + fullUrl.searchParams.set(key, value.join(',')) + } else if (value !== undefined) { fullUrl.searchParams.set(key, String(value)) } }) diff --git a/src/fonts/NotoColorEmoji.ttf b/src/assets/fonts/NotoColorEmoji.ttf similarity index 100% rename from src/fonts/NotoColorEmoji.ttf rename to src/assets/fonts/NotoColorEmoji.ttf diff --git a/src/img/icons/add.svg b/src/assets/img/icons/add.svg similarity index 100% rename from src/img/icons/add.svg rename to src/assets/img/icons/add.svg diff --git a/src/img/icons/area.svg b/src/assets/img/icons/area.svg similarity index 100% rename from src/img/icons/area.svg rename to src/assets/img/icons/area.svg diff --git a/src/img/icons/back-arrow-small.svg b/src/assets/img/icons/back-arrow-small.svg similarity index 100% rename from src/img/icons/back-arrow-small.svg rename to src/assets/img/icons/back-arrow-small.svg diff --git a/src/img/icons/back-arrow.svg b/src/assets/img/icons/back-arrow.svg similarity index 100% rename from src/img/icons/back-arrow.svg rename to src/assets/img/icons/back-arrow.svg diff --git a/src/img/icons/blacklist.svg b/src/assets/img/icons/blacklist.svg similarity index 100% rename from src/img/icons/blacklist.svg rename to src/assets/img/icons/blacklist.svg diff --git a/src/img/icons/browse.svg b/src/assets/img/icons/browse.svg similarity index 100% rename from src/img/icons/browse.svg rename to src/assets/img/icons/browse.svg diff --git a/src/img/icons/checked.svg b/src/assets/img/icons/checked.svg similarity index 100% rename from src/img/icons/checked.svg rename to src/assets/img/icons/checked.svg diff --git a/src/img/icons/checkered-flag.svg b/src/assets/img/icons/checkered-flag.svg similarity index 100% rename from src/img/icons/checkered-flag.svg rename to src/assets/img/icons/checkered-flag.svg diff --git a/src/img/icons/chevron.svg b/src/assets/img/icons/chevron.svg similarity index 100% rename from src/img/icons/chevron.svg rename to src/assets/img/icons/chevron.svg diff --git a/src/img/icons/compass.svg b/src/assets/img/icons/compass.svg similarity index 100% rename from src/img/icons/compass.svg rename to src/assets/img/icons/compass.svg diff --git a/src/img/icons/cross.svg b/src/assets/img/icons/cross.svg similarity index 100% rename from src/img/icons/cross.svg rename to src/assets/img/icons/cross.svg diff --git a/src/img/icons/done.svg b/src/assets/img/icons/done.svg similarity index 100% rename from src/img/icons/done.svg rename to src/assets/img/icons/done.svg diff --git a/src/img/icons/edit.svg b/src/assets/img/icons/edit.svg similarity index 100% rename from src/img/icons/edit.svg rename to src/assets/img/icons/edit.svg diff --git a/src/img/icons/explorer.svg b/src/assets/img/icons/explorer.svg similarity index 100% rename from src/img/icons/explorer.svg rename to src/assets/img/icons/explorer.svg diff --git a/src/img/icons/eye-off-outline.svg b/src/assets/img/icons/eye-off-outline.svg similarity index 100% rename from src/img/icons/eye-off-outline.svg rename to src/assets/img/icons/eye-off-outline.svg diff --git a/src/img/icons/hand.svg b/src/assets/img/icons/hand.svg similarity index 100% rename from src/img/icons/hand.svg rename to src/assets/img/icons/hand.svg diff --git a/src/img/icons/help-round.svg b/src/assets/img/icons/help-round.svg similarity index 100% rename from src/img/icons/help-round.svg rename to src/assets/img/icons/help-round.svg diff --git a/src/img/icons/help.svg b/src/assets/img/icons/help.svg similarity index 100% rename from src/img/icons/help.svg rename to src/assets/img/icons/help.svg diff --git a/src/img/icons/home.svg b/src/assets/img/icons/home.svg similarity index 100% rename from src/img/icons/home.svg rename to src/assets/img/icons/home.svg diff --git a/src/img/icons/instagram.svg b/src/assets/img/icons/instagram.svg similarity index 100% rename from src/img/icons/instagram.svg rename to src/assets/img/icons/instagram.svg diff --git a/src/img/icons/intel.svg b/src/assets/img/icons/intel.svg similarity index 100% rename from src/img/icons/intel.svg rename to src/assets/img/icons/intel.svg diff --git a/src/img/icons/list.svg b/src/assets/img/icons/list.svg similarity index 100% rename from src/img/icons/list.svg rename to src/assets/img/icons/list.svg diff --git a/src/img/icons/locality.svg b/src/assets/img/icons/locality.svg similarity index 100% rename from src/img/icons/locality.svg rename to src/assets/img/icons/locality.svg diff --git a/src/img/icons/map.svg b/src/assets/img/icons/map.svg similarity index 100% rename from src/img/icons/map.svg rename to src/assets/img/icons/map.svg diff --git a/src/img/icons/minimize.svg b/src/assets/img/icons/minimize.svg similarity index 100% rename from src/img/icons/minimize.svg rename to src/assets/img/icons/minimize.svg diff --git a/src/img/icons/offline.svg b/src/assets/img/icons/offline.svg similarity index 100% rename from src/img/icons/offline.svg rename to src/assets/img/icons/offline.svg diff --git a/src/img/icons/pointer.svg b/src/assets/img/icons/pointer.svg similarity index 100% rename from src/img/icons/pointer.svg rename to src/assets/img/icons/pointer.svg diff --git a/src/img/icons/right_arrow.svg b/src/assets/img/icons/right_arrow.svg similarity index 100% rename from src/img/icons/right_arrow.svg rename to src/assets/img/icons/right_arrow.svg diff --git a/src/img/icons/search.svg b/src/assets/img/icons/search.svg similarity index 100% rename from src/img/icons/search.svg rename to src/assets/img/icons/search.svg diff --git a/src/img/icons/telegram-bg.svg b/src/assets/img/icons/telegram-bg.svg similarity index 100% rename from src/img/icons/telegram-bg.svg rename to src/assets/img/icons/telegram-bg.svg diff --git a/src/img/icons/telegram.svg b/src/assets/img/icons/telegram.svg similarity index 100% rename from src/img/icons/telegram.svg rename to src/assets/img/icons/telegram.svg diff --git a/src/img/icons/timer.svg b/src/assets/img/icons/timer.svg similarity index 100% rename from src/img/icons/timer.svg rename to src/assets/img/icons/timer.svg diff --git a/src/img/icons/todo.svg b/src/assets/img/icons/todo.svg similarity index 100% rename from src/img/icons/todo.svg rename to src/assets/img/icons/todo.svg diff --git a/src/img/icons/triangle-down.svg b/src/assets/img/icons/triangle-down.svg similarity index 100% rename from src/img/icons/triangle-down.svg rename to src/assets/img/icons/triangle-down.svg diff --git a/src/img/icons/triangle.svg b/src/assets/img/icons/triangle.svg similarity index 100% rename from src/img/icons/triangle.svg rename to src/assets/img/icons/triangle.svg diff --git a/src/img/icons/twitter.svg b/src/assets/img/icons/twitter.svg similarity index 100% rename from src/img/icons/twitter.svg rename to src/assets/img/icons/twitter.svg diff --git a/src/img/icons/up-arrow.svg b/src/assets/img/icons/up-arrow.svg similarity index 98% rename from src/img/icons/up-arrow.svg rename to src/assets/img/icons/up-arrow.svg index ba9c6dc8..0f4fcac8 100644 --- a/src/img/icons/up-arrow.svg +++ b/src/assets/img/icons/up-arrow.svg @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + diff --git a/src/img/icons/warningtriangle.svg b/src/assets/img/icons/warningtriangle.svg similarity index 100% rename from src/img/icons/warningtriangle.svg rename to src/assets/img/icons/warningtriangle.svg diff --git a/src/img/logo/logo192.png b/src/assets/img/logo/logo192.png similarity index 100% rename from src/img/logo/logo192.png rename to src/assets/img/logo/logo192.png diff --git a/src/img/logo/logo64.png b/src/assets/img/logo/logo64.png similarity index 100% rename from src/img/logo/logo64.png rename to src/assets/img/logo/logo64.png diff --git a/src/assets/style/ant-override.less b/src/assets/style/ant-override.less new file mode 100644 index 00000000..596c6bf3 --- /dev/null +++ b/src/assets/style/ant-override.less @@ -0,0 +1,16 @@ +body, +.ant-layout { + background: var(--color-light-black); +} + +body, +.ant-menu, +.ant-input, +.ant-card, +.ant-card-head-title { + font-size: 16px; +} + +.ant-card { + border-radius: 5px; +} diff --git a/src/assets/style/helper.less b/src/assets/style/helper.less new file mode 100644 index 00000000..1076e8fd --- /dev/null +++ b/src/assets/style/helper.less @@ -0,0 +1,4 @@ +@import './helper/button.less'; +@import './helper/class.less'; +@import './helper/color.less'; +@import './helper/text.less'; diff --git a/src/assets/style/helper/button.less b/src/assets/style/helper/button.less new file mode 100644 index 00000000..de6c15a3 --- /dev/null +++ b/src/assets/style/helper/button.less @@ -0,0 +1,60 @@ +.bg-button { + align-items: center; + background-clip: content-box, border-box !important; + background-origin: border-box !important; + border: solid 2px transparent !important; + box-shadow: 2px 1000px 1px var(--color-black) inset !important; + display: flex; + justify-content: center; + transition: linear-gradient ease 0.6s; + + &.bg-button-default { + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), + linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)) !important; + + @media (hover: hover) and (pointer: fine) { + &:hover { + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), + linear-gradient(0deg, var(--color-border-dark), var(--color-border-light)) !important; + } + } + } +} + +.button-default { + background-clip: content-box, border-box !important; + background-color: var(--color-dark-gray); + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)) !important; + background-origin: border-box !important; + border-radius: 4px; + border: solid 2px transparent !important; + box-shadow: 2px 1000px 1px var(--color-light-black) inset !important; + color: var(--color-white) !important; + display: block; + margin: 0 !important; + text-align: center; + transition: linear-gradient ease 0.6s; + width: 100%; + + @media (hover: hover) and (pointer: fine) { + &:hover { + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), + linear-gradient(0deg, var(--color-border-dark), var(--color-border-light)) !important; + } + } +} + +.positive-action-button { + background: var(--color-positive-green) !important; + color: var(--color-white) !important; + padding: 0.5em 2em; + border: none; + border-radius: 5px; + cursor: pointer; + + &:disabled { + background: var(--color-gray); + color: var(--color-light-gray); + cursor: not-allowed; + } +} diff --git a/src/assets/style/helper/class.less b/src/assets/style/helper/class.less new file mode 100644 index 00000000..979bc29f --- /dev/null +++ b/src/assets/style/helper/class.less @@ -0,0 +1,40 @@ +.mt-1 { + margin-top: 1rem; +} + +.p-1 { + padding: 1rem; +} + +.pl-1 { + padding-left: 1rem; +} + +.pr-1 { + padding-right: 1rem; +} + +.px1 { + padding-left: 1rem; + padding-right: 1rem; +} + +.nobr { + white-space: nowrap; +} + +.page-container { + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +} + +.leaflet-container { + margin: unset; + height: 100%; +} diff --git a/src/assets/style/helper/color.less b/src/assets/style/helper/color.less new file mode 100644 index 00000000..11c7f80e --- /dev/null +++ b/src/assets/style/helper/color.less @@ -0,0 +1,11 @@ +.warning-text { + color: var(--color-warning); +} + +.error-text { + color: var(--color-error); +} + +.subtitle { + color: var(--color-gray); +} diff --git a/src/assets/style/helper/text.less b/src/assets/style/helper/text.less new file mode 100644 index 00000000..64d4db8f --- /dev/null +++ b/src/assets/style/helper/text.less @@ -0,0 +1,14 @@ +h1 { + font-size: 36px; + margin: 0.5em 0 0.3em; +} + +h2 { + font-size: 20px; +} + +h3 { + font-size: 16px; + font-weight: bold; + margin: 1em 0 0.05em; +} diff --git a/src/assets/style/mobile.less b/src/assets/style/mobile.less new file mode 100644 index 00000000..600c4469 --- /dev/null +++ b/src/assets/style/mobile.less @@ -0,0 +1,19 @@ +@mobile-detection: ~' screen and (max-width: 880px) '; + +.hide-on-mobile { + @media only @mobile-detection { + display: none !important; + } +} + +.banner-count-place { + @media only @mobile-detection { + display: none; + } +} + +.hide-on-desktop { + @media not @mobile-detection { + display: none !important; + } +} diff --git a/src/assets/style/variables.less b/src/assets/style/variables.less new file mode 100644 index 00000000..f4832056 --- /dev/null +++ b/src/assets/style/variables.less @@ -0,0 +1,37 @@ +:root { + --color-white: #fff; + --color-white-light: #eaeaea; + --color-black: #000; + --color-light-black: #1b1b1b; + --color-gray: #888888; + --color-light-gray: #d3d3d3; + --color-dark-gray: #2e2e2e; + + --color-todo: #ffe381; + --color-done: #70c03f; + --color-blacklist: #ef5555; + --color-pin: #6832da; + --color-warning: #ffb21d; + --color-error: #ef5555; + --color-missing: #ff2f2f; + + --primary-color: #1da57a; + --link-color: #1da57a; + --color-active-green: #0ca589; + + --body-background: #0b0c0d; + --border-color-base: #004f4a; + --scrollbar-color: #969696; + + --card-head-padding: 0.6rem; + --card-padding-base: 0.6rem; + + --color-panel-bg: #404040; + --color-border-dark: #109393; + --color-positive-green: #077561; + --color-border-light: #15d4b2; + --color-positive-green-light: #16d4b2; + + --color-enlightened: #02bf02; + --color-resistance: #0492d0; +} diff --git a/src/components/Issues-list/IssueCard.tsx b/src/components/Issues-list/IssueCard.tsx index ec447ceb..ec243117 100644 --- a/src/components/Issues-list/IssueCard.tsx +++ b/src/components/Issues-list/IssueCard.tsx @@ -2,7 +2,7 @@ import { Button } from 'antd' import React, { FC } from 'react' import { Issue } from './Issue' -import SVGCross from '../../img/icons/cross.svg?react' +import SVGCross from '../../assets/img/icons/cross.svg?react' const IssueCard: FC = ({ issue, onCloseIssue }) => { const onClose = () => { diff --git a/src/components/Issues-list/issues.less b/src/components/Issues-list/issues.less index 67889985..1dd569d3 100644 --- a/src/components/Issues-list/issues.less +++ b/src/components/Issues-list/issues.less @@ -5,18 +5,18 @@ } .issue-card { - background: rgba(46, 46, 46, 0.9); + background: rgba(var(--color-dark-gray), 0.9); border-radius: 4px; padding: 10px 20px; font-size: 14px; line-height: 2.5; - + &.issue-error { - color: #FF2F2F; + color: var(--color-error); } &.issue-warning { - color: #FFB21D; + color: var(--color-warning); } button { @@ -25,7 +25,7 @@ padding: 10px 0; path { - fill: lightgrey; + fill: var(--color-light-gray); } } -} \ No newline at end of file +} diff --git a/src/components/advanced-options/AdvancedOptions.tsx b/src/components/advanced-options/AdvancedOptions.tsx index 3bcba4e6..491fbb22 100644 --- a/src/components/advanced-options/AdvancedOptions.tsx +++ b/src/components/advanced-options/AdvancedOptions.tsx @@ -3,7 +3,7 @@ import { Col, Radio, Row, Slider, Tooltip } from 'antd' import { Trans, useTranslation } from 'react-i18next' import { BannerType } from '../../features/banner' -import SVGHelp from '../../img/icons/help-round.svg?react' +import SVGHelp from '../../assets/img/icons/help-round.svg?react' import './advanced-options.less' import { DatePicker } from '../date-picker/DatePicker' diff --git a/src/components/agent/agent.less b/src/components/agent/agent.less index 2c2896ec..5047a61e 100644 --- a/src/components/agent/agent.less +++ b/src/components/agent/agent.less @@ -1,7 +1,7 @@ -.faction-enlightened { - color: #02bf02; -} - -.faction-resistance { - color: #0492d0; -} +.faction-enlightened { + color: var(--color-enlightened); +} + +.faction-resistance { + color: var(--color-resistance); +} diff --git a/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx b/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx index 2a74fdb1..314dc828 100644 --- a/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx +++ b/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react' import { Radio, Tooltip } from 'antd' import { Trans, useTranslation } from 'react-i18next' -import SVGHelp from '../../img/icons/help-round.svg?react' +import SVGHelp from '../../assets/img/icons/help-round.svg?react' import './algorithm-detection-chooser.less' diff --git a/src/components/algorithm-detection-chooser/algorithm-detection-chooser.less b/src/components/algorithm-detection-chooser/algorithm-detection-chooser.less index e9ffbef0..9a8f4b7f 100644 --- a/src/components/algorithm-detection-chooser/algorithm-detection-chooser.less +++ b/src/components/algorithm-detection-chooser/algorithm-detection-chooser.less @@ -12,7 +12,7 @@ align-self: flex-start; path { - fill: white; + fill: var(--color-white); } } } @@ -26,36 +26,36 @@ &.display-false { display: none; } - } - - .lds-ellipsis div { - position: absolute; - top: 0px; - width: 8px; - height: 8px; - border-radius: 50%; - background: #fff; - animation-timing-function: cubic-bezier(0, 1, 1, 0); - } - .lds-ellipsis div:nth-child(1) { - left: 0px; - animation: lds-ellipsis1 0.6s infinite; - } + div { + position: absolute; + top: 0; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--color-white); + animation-timing-function: cubic-bezier(0, 1, 1, 0); + + &:nth-child(1) { + left: 0; + animation: lds-ellipsis1 0.6s infinite; + } - .lds-ellipsis div:nth-child(2) { - left: 0px; - animation: lds-ellipsis2 0.6s infinite; - } + &:nth-child(2) { + left: 0; + animation: lds-ellipsis2 0.6s infinite; + } - .lds-ellipsis div:nth-child(3) { - left: 16px; - animation: lds-ellipsis2 0.6s infinite; - } + &:nth-child(3) { + left: 16px; + animation: lds-ellipsis2 0.6s infinite; + } - .lds-ellipsis div:nth-child(4) { - left: 32px; - animation: lds-ellipsis3 0.6s infinite; + &:nth-child(4) { + left: 32px; + animation: lds-ellipsis3 0.6s infinite; + } + } } @keyframes lds-ellipsis1 { @@ -87,4 +87,4 @@ transform: translate(16px, 0); } } -} \ No newline at end of file +} diff --git a/src/components/announcement/Announcement.tsx b/src/components/announcement/Announcement.tsx index 69a541b6..c99d9355 100644 --- a/src/components/announcement/Announcement.tsx +++ b/src/components/announcement/Announcement.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { NewsItem } from '../../features/news' import './announcement.less' +import { NewsItem } from '../../features/news/types' const Announcement: React.FC = ({ item }) => (
{item.content}
diff --git a/src/components/announcement/announcement.less b/src/components/announcement/announcement.less index ac7fe353..2adb76c1 100644 --- a/src/components/announcement/announcement.less +++ b/src/components/announcement/announcement.less @@ -1,8 +1,8 @@ -.announcement-item { - background: #2e2e2e; - border-radius: 5px; - padding: 20px; - color: white; - margin-bottom: 10px; - line-height: 19px; -} +.announcement-item { + background: var(--color-dark-gray); + border-radius: 5px; + padding: 20px; + color: white; + margin-bottom: 10px; + line-height: 19px; +} diff --git a/src/components/banner-card/BannerCard.tsx b/src/components/banner-card/BannerCard.tsx index 66310e52..1af9697b 100644 --- a/src/components/banner-card/BannerCard.tsx +++ b/src/components/banner-card/BannerCard.tsx @@ -10,9 +10,9 @@ import { import { createBrowseUri } from '../../features/place' import { Distance } from '../distance/Distance' import BannerPicture from './BannerPicture' -import SVGExplorer from '../../img/icons/explorer.svg?react' -import SVGWarningTriangle from '../../img/icons/warningtriangle.svg?react' -import SVGPointer from '../../img/icons/pointer.svg?react' +import SVGExplorer from '../../assets/img/icons/explorer.svg?react' +import SVGWarningTriangle from '../../assets/img/icons/warningtriangle.svg?react' +import SVGPointer from '../../assets/img/icons/pointer.svg?react' import './banner-card.less' diff --git a/src/components/banner-card/BannerPicture.tsx b/src/components/banner-card/BannerPicture.tsx index 10321f58..ae1ca696 100644 --- a/src/components/banner-card/BannerPicture.tsx +++ b/src/components/banner-card/BannerPicture.tsx @@ -5,7 +5,7 @@ import Scrollbars from 'react-custom-scrollbars-2' import { Trans } from 'react-i18next' import { useLoaded } from '../../hooks/Loaded' -import SVGMinimize from '../../img/icons/minimize.svg?react' +import SVGMinimize from '../../assets/img/icons/minimize.svg?react' const getImageAnimation = ( innerDiv: HTMLDivElement | null, diff --git a/src/components/banner-card/banner-card.less b/src/components/banner-card/banner-card.less index d2fb9716..e1d42986 100644 --- a/src/components/banner-card/banner-card.less +++ b/src/components/banner-card/banner-card.less @@ -1,9 +1,12 @@ @import url(../../App.less); -// Start Temporary styles for list types. Not final yet. +@mobile-detection: ~' screen and (max-width: 880px) '; +@mobile-l-detection: ~' screen and (min-width: 426px) '; + +// Start Temporary styles for list types. Not final yet. .list-style-background() { - content: " "; + content: ' '; z-index: 10; display: block; position: absolute; @@ -15,24 +18,6 @@ border-radius: 5px; } -.banner-card.list-style-todo:after { - .list-style-background(); - background: rgba(@color-todo, 0.05); - border: 1px solid @color-todo; -} - -.banner-card.list-style-done:after { - .list-style-background(); - background: rgba(@color-done, 0.05); - border: 1px solid @color-done; -} - -.banner-card.list-style-blacklist:after { - .list-style-background(); - background: rgba(@color-blacklist, 0.05); - border: 1px solid @color-blacklist; -} - // End temporary Styles ^^ .banner-circle { @@ -42,28 +27,53 @@ width: 60px; height: 60px; border-radius: 50%; - background-color: grey; + background-color: var(--color-gray); margin-right: 8px; margin-top: 8px; } .banner-card { - background: #2e2e2e; + background: var(--color-dark-gray); border-radius: 5px; - border: #2e2e2e 2px solid; + border: var(--color-dark-gray) 2px solid; padding: 18px; - color: white; + color: var(--color-white); position: relative; &.selected { - border-color: #16d4b2; + border-color: var(--color-positive-green-light); + } + + &.list-style { + &-todo { + &:after { + .list-style-background(); + background: rgba(var(--color-todo), 0.05); + border: 1px solid var(--color-todo); + } + } + + &-done { + &:after { + .list-style-background(); + background: rgba(var(--color-done), 0.05); + border: 1px solid var(--color-done); + } + } + + &-blacklist { + &:after { + .list-style-background(); + background: rgba(var(--color-blacklist), 0.05); + border: 1px solid var(--color-blacklist); + } + } } .icon { width: 0.9rem; height: 0.9rem; } - } .banner-card-modal { @@ -73,7 +83,7 @@ height: 100%; width: 100%; display: flex; - background-color: #2e2e2e; + background-color: var(--color-dark-gray); .ant-modal-body { width: 100%; @@ -106,7 +116,6 @@ &.full-size { img { max-height: initial; - max-width: initial; } } } @@ -114,11 +123,11 @@ .close-button { position: absolute; bottom: 15px; - background-color: #2e2e2e; + background-color: var(--color-dark-gray); right: 15px; border: none; line-height: 1em; - color: white !important; + color: var(--color-white) !important; svg, span { @@ -133,14 +142,19 @@ } } -.banner-card-picture-container{ +.banner-card-picture-container { position: relative; display: flex; + justify-content: center; - .offline-overlay{ + @media only @mobile-l-detection { + height: 200px; + } + + .offline-overlay { font-size: 24px; line-height: 18px; - color: #DDDDDD; + color: var(--color-light-gray); position: absolute; left: -20px; @@ -152,9 +166,9 @@ flex-direction: column; justify-content: center; - pointer-events: none; - - .offline-overlay-line { + pointer-events: none; + + .offline-overlay-line { justify-content: center; align-items: center; width: 100%; @@ -163,9 +177,9 @@ flex-direction: row; background-color: rgba(0, 0, 0, 0.7); - svg{ + svg { height: 1em; - width:1em; + width: 1em; fill: currentColor; margin-right: 0.5em; } @@ -181,7 +195,7 @@ .banner-info-item { font-size: 14px; line-height: 18px; - color: #DDDDDD; + color: var(--color-light-gray); margin-top: 4px; display: flex; @@ -192,13 +206,12 @@ fill: currentColor; } } -} -.banner-info-item.warning { - color: @color-blacklist; + &.warning { + color: var(--color-blacklist); + } } - .banner-card-picture { margin: 20px 0; display: flex; @@ -206,13 +219,38 @@ align-items: center; justify-content: center; overflow: hidden; + width: 100%; + + @supports (aspect-ratio: 1/1) { + aspect-ratio: 2 / 1; + } + + @supports not (aspect-ratio: 1/1) { + height: 160px; // 160 Pixel is half of width of .banner-card minus padding + } + + @media only @mobile-detection { + height: auto; + aspect-ratio: 2; + } + &.banner-lines-1 { + @media only @mobile-detection { + aspect-ratio: 6; + } + } + + &.banner-lines-2 { + @media only @mobile-detection { + aspect-ratio: 3; + } + } } .banner-card-picture-inner { max-height: 100%; max-width: 100%; - &>img { + & > img { max-width: 100%; } @@ -241,11 +279,11 @@ display: flex; flex-direction: column; align-items: flex-end; - padding: 0px; + padding: 0; margin-top: 10px; a { - border: 1px solid white; + border: 1px solid var(--color-white); box-sizing: border-box; border-radius: 4px; padding: 5px 10px; @@ -254,22 +292,6 @@ } } - -// Support for browsers that don't support aspect-ratio yet. -// Can be removed once this feature is implemented in all major browsers - -@supports (aspect-ratio: 1/1) { - .banner-card-picture { - aspect-ratio: 2 / 1; - } -} - -@supports not (aspect-ratio: 1/1) { - .banner-card-picture { - height: 160px; // 160 Pixel is half of width of .banner-card minus padding - } -} - .banner-card-title, .banner-info-item { text-overflow: ellipsis; diff --git a/src/components/banner-edit-tools/BannerEditTools.tsx b/src/components/banner-edit-tools/BannerEditTools.tsx index f0e876a3..d2aadf1d 100644 --- a/src/components/banner-edit-tools/BannerEditTools.tsx +++ b/src/components/banner-edit-tools/BannerEditTools.tsx @@ -39,21 +39,33 @@ export const BannerEditTools: FC = ({ banner }) => { const buttons = [] if (creatorPluginAvailable) { buttons.push( - ) } if (owner || authenticated) { buttons.push( - ) } if (authenticated) { buttons.push( - ) diff --git a/src/components/banner-edit-tools/banner-edit-tools.less b/src/components/banner-edit-tools/banner-edit-tools.less index 38567945..ef836a50 100644 --- a/src/components/banner-edit-tools/banner-edit-tools.less +++ b/src/components/banner-edit-tools/banner-edit-tools.less @@ -14,7 +14,7 @@ &.positive-action-button { border-color: transparent; - background-color: #077561; + background-color: var(--color-positive-green); } &.negative-action-button { diff --git a/src/components/banner-info-card/BannerInfoCard.tsx b/src/components/banner-info-card/BannerInfoCard.tsx index 8cf29942..466ff1ec 100644 --- a/src/components/banner-info-card/BannerInfoCard.tsx +++ b/src/components/banner-info-card/BannerInfoCard.tsx @@ -1,6 +1,7 @@ import React, { FC, Fragment } from 'react' import _ from 'underscore' import { LatLng } from 'leaflet' +import Markdown from 'react-markdown' import { Trans, useTranslation } from 'react-i18next' import { TFunction } from 'i18next' import { Tooltip } from 'antd' @@ -25,13 +26,13 @@ import IfUserLoggedIn from '../login/if-user-logged-in' import IfUserLoggedOut from '../login/if-user-logged-out' import LoginButton from '../login/login-button' import { hasLatLng } from '../map-detail/showBannerRouteOnMap' -import SVGList from '../../img/icons/list.svg?react' -import SVGExplorer from '../../img/icons/explorer.svg?react' -import SVGTimer from '../../img/icons/timer.svg?react' -import SVGHand from '../../img/icons/hand.svg?react' -import SVGCompass from '../../img/icons/compass.svg?react' -import SVGChecked from '../../img/icons/checked.svg?react' -import SVGOffline from '../../img/icons/offline.svg?react' +import SVGList from '../../assets/img/icons/list.svg?react' +import SVGExplorer from '../../assets/img/icons/explorer.svg?react' +import SVGTimer from '../../assets/img/icons/timer.svg?react' +import SVGHand from '../../assets/img/icons/hand.svg?react' +import SVGCompass from '../../assets/img/icons/compass.svg?react' +import SVGChecked from '../../assets/img/icons/checked.svg?react' +import SVGOffline from '../../assets/img/icons/offline.svg?react' import i18n from '../../i18n' import './banner-info-card.less' @@ -282,7 +283,7 @@ const getInGameTime = (banner: Banner, t: TFunction) => {
{totalTimeInMS === 0 ? ( - {t('banners.missingData')} + {t('missingData')} ) : ( @@ -399,11 +400,34 @@ const getStartPointButton = (banner: Banner, t: TFunction) => { const BannerInfoCard: FC = ({ banner }) => { const { t } = useTranslation() + const allowedElements = [ + 'p', + 'br', + 'b', + 'strong', + 'em', + 'ul', + 'ol', + 'li', + 'a', + ] return (
{getEvent(banner, t)} - {banner.warning &&

{banner.warning}

} - {banner.description &&

{banner.description}

} + {banner.warning && ( + + {banner.warning} + + )} + {banner.description && ( + + {banner.description} + + )} {getCreatedBy(banner, t)}

diff --git a/src/components/banner-info-card/banner-info-card.less b/src/components/banner-info-card/banner-info-card.less index 4cb11b7b..993fd617 100644 --- a/src/components/banner-info-card/banner-info-card.less +++ b/src/components/banner-info-card/banner-info-card.less @@ -1,5 +1,5 @@ .banner-info-card { - background: #2E2E2E; + background: var(--color-dark-gray); border-radius: 4px; padding: 15px; font-size: 16px; @@ -67,27 +67,25 @@ .banner-info-button { margin: 0 !important; - background-color: #2e2e2e; - border: none; + background-color: var(--color-dark-gray); display: block; border-radius: 4px; padding: 10px; width: 100%; margin-top: 15px !important; - box-shadow: 0 0 2px 0 rgba(157, 96, 212, 0.5) !important; border: solid 2px transparent !important; background-origin: border-box !important; background-clip: content-box, border-box !important; - box-shadow: 2px 1000px 1px #2e2e2e inset !important; + box-shadow: 2px 1000px 1px var(--color-dark-gray) inset !important; transition: linear-gradient ease 0.6s; text-align: center; color: white; - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, #109393, #15D4B2) !important; + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)) !important; @media (hover: hover) and (pointer: fine) { &:hover { - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(0deg, #109393, #15D4B2) !important; + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(0deg, var(--color-border-dark), var(--color-border-light)) !important; } } } -} \ No newline at end of file +} diff --git a/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx b/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx index 6d29a438..2f12c708 100644 --- a/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx +++ b/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react' import { useTranslation } from 'react-i18next' import { useHistory } from 'react-router' -import SVGBackArrow from '../../img/icons/back-arrow.svg?react' +import SVGBackArrow from '../../assets/img/icons/back-arrow.svg?react' import './banner-info-mobile-switch.less' diff --git a/src/components/banner-info-mobile-switch/banner-info-mobile-switch.less b/src/components/banner-info-mobile-switch/banner-info-mobile-switch.less index 230fa9c4..b2d84d6a 100644 --- a/src/components/banner-info-mobile-switch/banner-info-mobile-switch.less +++ b/src/components/banner-info-mobile-switch/banner-info-mobile-switch.less @@ -1,77 +1,75 @@ -.banner-info-mobile-switch{ - display: flex; - flex-direction: column; - background-color: black; - padding-bottom: 5px; - padding-right: 10px; - padding-left: 10px; - font-weight: 400; +.banner-info-mobile-switch { + display: flex; + flex-direction: column; + background-color: black; + padding-bottom: 5px; + padding-right: 10px; + padding-left: 10px; + font-weight: 400; - .banner-info-mobile-switch-back { - flex: 0; - - svg{ - height: 1em; - margin-top: 5px; - } - } - - .mobile-switch-title-row { - align-items: center; - display: flex; - flex-direction: row; - padding-top: 5px; - padding-bottom: 5px; + .banner-info-mobile-switch-back { + flex: 0; + svg { + height: 1em; + margin-top: 5px; } + } - .mobile-switch-tabs-row { - align-items: center; - display: flex; - flex-direction: row; - } + .mobile-switch-title-row { + align-items: center; + display: flex; + flex-direction: row; + padding-top: 5px; + padding-bottom: 5px; + } - .mobile-switch-title { - width: 100%; - font-size: 20px; - white-space: nowrap; - overflow-x: hidden; - text-overflow: ellipsis; - } + .mobile-switch-tabs-row { + align-items: center; + display: flex; + flex-direction: row; + } - button { - background-color: transparent; - outline: none; - border: none; - cursor: pointer; - width: 33%; - font-size: 16px; + .mobile-switch-title { + width: 100%; + font-size: 20px; + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; + } + button { + background-color: transparent; + outline: none; + border: none; + cursor: pointer; + width: 33%; + font-size: 16px; - &.active { - border-radius: 4px; - border: 2px solid #0ca589 !important; - } + &.active { + border-radius: 4px; + border: 2px solid var(--color-active-green) !important; } + } - .positive-action-button { - background: #077561; - color: white; - padding: 0.5em 2em; - border: none; - border-radius: 5px; - cursor: pointer; - - &:disabled { - background: gray; - color: lightgray; - cursor: not-allowed; - } - } + .positive-action-button { + background: var(--color-positive-green); + color: var(--color-white); + padding: 0.5em 2em; + border: none; + border-radius: 5px; + cursor: pointer; - .mobile-switch-submit-button { - flex-grow: 0; - white-space:nowrap; - width: auto; + &:disabled { + background: var(--color-gray); + color: var(--color-light-gray); + cursor: not-allowed; } -} \ No newline at end of file + } + + .mobile-switch-submit-button { + flex-grow: 0; + white-space: nowrap; + width: auto; + } +} diff --git a/src/components/banner-info-overview/banner-info-overview.less b/src/components/banner-info-overview/banner-info-overview.less index ab90015c..2d356d69 100644 --- a/src/components/banner-info-overview/banner-info-overview.less +++ b/src/components/banner-info-overview/banner-info-overview.less @@ -9,7 +9,6 @@ display: flex; flex-direction: column; flex-grow: 1; - width: 100%; padding-right: 4px; width: 370px; @@ -24,12 +23,12 @@ // Hide line below the buttons .ant-tabs-nav::before { border: none; - } + } - // Hide overflow button. We don't need it. - // The screen is never too small to show both buttons + // Hide overflow button. We don't need it. + // The screen is never too small to show both buttons // If we leave it, the screen flackers when resizing the window - // because it is constantly shown and hidden. An alternative would be to + // because it is constantly shown and hidden. An alternative would be to // make the width of the ant-tabs-nav-list smaller or givt it a right margin // or padding .ant-tabs-nav-more { @@ -38,7 +37,7 @@ .ant-tabs-nav-list { width: 100%; - background-color: #2e2e2e; + background-color: var(--color-dark-gray); .ant-tabs-ink-bar { display: none; @@ -46,31 +45,29 @@ .ant-tabs-tab { margin: 0 !important; - background-color: #2e2e2e; - border: none; + background-color: var(--color-dark-gray); border-radius: 4px; width: 100%; - box-shadow: 0 0 2px 0 rgba(157, 96, 212, 0.5) !important; border: solid 2px transparent !important; background-origin: border-box !important; background-clip: content-box, border-box !important; - box-shadow: 2px 1000px 1px #2e2e2e inset !important; + box-shadow: 2px 1000px 1px var(--color-dark-gray) inset !important; transition: linear-gradient ease 0.6s; font-size: 16px; .ant-tabs-tab-btn { text-align: center; - color: white; + color: var(--color-white); width: 100%; } } .ant-tabs-tab-active { - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, #109393, #15D4B2) !important; + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)) !important; @media (hover: hover) and (pointer: fine) { &:hover { - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(0deg, #109393, #15D4B2) !important; + background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(0deg, var(--color-border-dark), var(--color-border-light)) !important; } } } diff --git a/src/components/banner-info-with-map/BannerInfoWithMap.tsx b/src/components/banner-info-with-map/BannerInfoWithMap.tsx index 596e1381..e167817e 100644 --- a/src/components/banner-info-with-map/BannerInfoWithMap.tsx +++ b/src/components/banner-info-with-map/BannerInfoWithMap.tsx @@ -12,7 +12,7 @@ import { } from '../banner-info-mobile-switch' import { MapDetail } from '../map-detail' import { IssuesList, Issue } from '../Issues-list' -import SVGBackArrow from '../../img/icons/back-arrow.svg?react' +import SVGBackArrow from '../../assets/img/icons/back-arrow.svg?react' import './banner-info-with-map.less' @@ -64,7 +64,7 @@ class BannerInfoWithMap extends React.Component< this.setState({ expanded: false, expandedMissionIndexes: [] }) } else { let missionIndexes: Array = [] - if (banner && banner.missions) { + if (banner?.missions) { missionIndexes = mapMissions( banner.missions, (mission, index) => mission && index @@ -120,13 +120,13 @@ class BannerInfoWithMap extends React.Component< desktopView, } = this.state - const infoPaneClassName = mobileView !== 'map' ? '' : 'hide-on-mobile' + const infoPaneClassName = mobileView === 'map' ? 'hide-on-mobile' : '' const infoPaneViewClassName = `banner-info-left-pane-${mobileView}` const mapPaneClassName = mobileView === 'map' ? '' : 'hide-on-mobile' this.viewWasMapBefore = this.viewWasMapBefore || mobileView === 'map' - if (banner && banner.missions) { + if (banner?.missions) { const bounds = getBannerBounds(banner) const issues: Array = [] if (!bounds) { diff --git a/src/components/banner-info-with-map/banner-info-with-map.less b/src/components/banner-info-with-map/banner-info-with-map.less index 725d1b4a..9694203e 100644 --- a/src/components/banner-info-with-map/banner-info-with-map.less +++ b/src/components/banner-info-with-map/banner-info-with-map.less @@ -1,3 +1,5 @@ +@mobile-detection: ~' screen and (max-width: 880px) '; + .banner-info-with-map-container { height: 100%; display: flex; @@ -5,6 +7,82 @@ padding-left: 40px; padding-bottom: 40px; padding-right: 40px; + + @media only @mobile-detection { + display: flex !important; + flex-direction: column !important; + height: inherit !important; + padding: 0 !important; + } + + .banner-info-with-map { + @media only @mobile-detection { + margin: 0 !important; + padding: 0 !important; + justify-content: center !important; + height: 100% !important; + overflow-y: auto !important; + } + + .banner-info { + @media only @mobile-detection { + min-height: 0 !important; + padding: 0.6em !important; + width: 100% !important; + max-width: 864px !important; + min-width: 320px !important; + height: auto !important; + overflow-y: unset !important; + } + } + + .banner-info-left-pane-missions { + .banner-card { + @media only @mobile-detection { + display: none !important; + } + } + } + + .mission-card { + @media only @mobile-detection { + max-width: none !important; + } + } + + .banner-info-additional { + @media only @mobile-detection { + height: 100% !important; + } + } + + .leaflet-container { + @media only @mobile-detection { + height: 100% !important; + width: 100% !important; + min-height: 0 !important; + } + } + } + + .banner-info-card { + @media only @mobile-detection { + margin-top: 0 !important; + } + } + + .ant-tabs-nav { + @media only @mobile-detection { + display: none !important; + margin: none !important; + } + } + + .ant-tabs-tabpane { + @media only @mobile-detection { + margin-top: 0; + } + } } .banner-info-with-map-goback { diff --git a/src/components/banner-list-type-control/BannerListTypeControl.tsx b/src/components/banner-list-type-control/BannerListTypeControl.tsx index ba9dee13..31152f70 100644 --- a/src/components/banner-list-type-control/BannerListTypeControl.tsx +++ b/src/components/banner-list-type-control/BannerListTypeControl.tsx @@ -2,9 +2,9 @@ import React, { FC } from 'react' import { Trans, useTranslation } from 'react-i18next' import { BannerListType } from '../../features/banner' -import SVGTodo from '../../img/icons/todo.svg?react' -import SVGDone from '../../img/icons/done.svg?react' -import SVGBlacklist from '../../img/icons/blacklist.svg?react' +import SVGTodo from '../../assets/img/icons/todo.svg?react' +import SVGDone from '../../assets/img/icons/done.svg?react' +import SVGBlacklist from '../../assets/img/icons/blacklist.svg?react' import './banner-list-type-control.less' diff --git a/src/components/banner-list-type-control/banner-list-type-control.less b/src/components/banner-list-type-control/banner-list-type-control.less index 9b4e421b..60310309 100644 --- a/src/components/banner-list-type-control/banner-list-type-control.less +++ b/src/components/banner-list-type-control/banner-list-type-control.less @@ -1,59 +1,65 @@ +@import '../../assets/style/variables.less'; @import '../../App.less'; .banner-list-type-control { - width: 100%; - display: flex; - flex-direction: row; - padding: 5px; - gap: 1em; - - .banner-list-type { - flex: 1; - - text-align: center; - vertical-align: middle; - - cursor: pointer; - - background: none; - outline: none; - border: none; - - svg { - height: 1.1em; - width:1.1em; - margin-right: 5px; - align-self: center; - - position: relative; - top: .125em; - } - } + display: flex; + flex-direction: row; + gap: 1em; + padding: 5px; + width: 100%; - .banner-list-type-todo.active { - color: @color-todo; - } + .banner-list-type { + background: none; + border: none; + cursor: pointer; + flex: 1; + outline: none; + text-align: center; + vertical-align: middle; - .banner-list-type-done.active { - color: @color-done; + svg { + align-self: center; + height: 1.1em; + margin-right: 5px; + position: relative; + top: 0.125em; + width: 1.1em; + } } - .banner-list-type-blacklist.active { - color: @color-blacklist; + .banner-list-type-todo { + &:hover { + @media (hover: hover) and (pointer: fine) { + color: var(--color-todo); + } + } + + &.active { + color: var(--color-todo); + } } - @media (hover: hover) and (pointer: fine) { - .banner-list-type-todo:hover { - color: @color-todo; + .banner-list-type-done { + &:hover { + @media (hover: hover) and (pointer: fine) { + color: var(--color-done); + } + } + + &.active { + color: var(--color-done); + } + } + + .banner-list-type-blacklist { + &:hover { + @media (hover: hover) and (pointer: fine) { + color: var(--color-blacklist); + } + } + + &.active { + color: var(--color-blacklist); } - - .banner-list-type-done:hover { - color: @color-done; - } - - .banner-list-type-blacklist:hover { - color: @color-blacklist; - } - } -} \ No newline at end of file +} diff --git a/src/components/banner-list-type-navigation/banner-list-type-navigation.less b/src/components/banner-list-type-navigation/banner-list-type-navigation.less index c6e200af..83eb400d 100644 --- a/src/components/banner-list-type-navigation/banner-list-type-navigation.less +++ b/src/components/banner-list-type-navigation/banner-list-type-navigation.less @@ -1,7 +1,6 @@ @import '../../App.less'; .banner-list-type-navigation { - margin-bottom: 20px; button { @@ -22,11 +21,11 @@ .active { font-size: 36px; - color: #FFFFFF; + color: var(--color-white); } .inactive { font-size: 28px; - color: #DDDDDD; + color: var(--color-light-gray); } -} \ No newline at end of file +} diff --git a/src/components/banner-list/BannerList.tsx b/src/components/banner-list/BannerList.tsx index 017ac428..9939dd03 100644 --- a/src/components/banner-list/BannerList.tsx +++ b/src/components/banner-list/BannerList.tsx @@ -15,7 +15,7 @@ const BannerList: FC = ({ loadMoreBanners, selectedBannerId, onSelectBanner, - applyBannerListStlyes, + applyBannerListStyles, hideBlacklisted, showDetailsButton, }) => { @@ -84,7 +84,7 @@ const BannerList: FC = ({ : undefined } linkStartPlace={false} - applyBannerListStlye={applyBannerListStlyes} + applyBannerListStlye={applyBannerListStyles} /> ) return ( @@ -117,7 +117,7 @@ export interface BannerListProps { selectedBannerId?: string loadMoreBanners?: () => Promise onSelectBanner?: (banner: Banner) => void - applyBannerListStlyes: boolean + applyBannerListStyles: boolean hideBlacklisted: boolean showDetailsButton: boolean } diff --git a/src/components/banner-list/banner-list.less b/src/components/banner-list/banner-list.less index 0d514c3f..e4dc6727 100644 --- a/src/components/banner-list/banner-list.less +++ b/src/components/banner-list/banner-list.less @@ -1,23 +1,42 @@ -.banner-list { - display: flex; - align-content: flex-start; - flex-wrap: wrap; - align-items: flex-start; - flex-direction: row; - column-gap: 20px; - row-gap: 20px; - - .banner-list-entry { - max-width: 90vw; - width: 360px; - } - - .banner-card-link { - height: 100%; - width: 100%; - border: none; - background: none; - text-align: initial; - font-size: initial; - } -} +@mobile-xs-detection: ~' screen and (max-width: 321px) '; +@mobile-detection: ~' screen and (max-width: 880px) '; + +.banner-list { + display: flex; + align-content: flex-start; + flex-wrap: wrap; + align-items: flex-start; + flex-direction: row; + column-gap: 20px; + row-gap: 20px; + + @media only @mobile-xs-detection { + justify-content: center; + } + + @media only @mobile-detection { + justify-content: center; + } + + .banner-list-entry { + max-width: 90vw; + width: 360px; + + @media only @mobile-xs-detection { + width: 296px; + } + + @media only @mobile-detection { + width: 360px; + } + } + + .banner-card-link { + height: 100%; + width: 100%; + border: none; + background: none; + text-align: initial; + font-size: initial; + } +} diff --git a/src/components/banner-order-chooser/BannerOrderChooser.tsx b/src/components/banner-order-chooser/BannerOrderChooser.tsx index 88463896..d78bbfe2 100644 --- a/src/components/banner-order-chooser/BannerOrderChooser.tsx +++ b/src/components/banner-order-chooser/BannerOrderChooser.tsx @@ -2,7 +2,7 @@ import { Modal } from 'antd' import React, { FC, useEffect, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' -import SVGBackArrowSmall from '../../img/icons/back-arrow-small.svg?react' +import SVGBackArrowSmall from '../../assets/img/icons/back-arrow-small.svg?react' import './banner-order-chooser.less' import { BannerFilter, BannerOrder } from '../../features/banner/filter' @@ -19,7 +19,6 @@ const BannerOrderChooser: FC = ({ }) => { const [open, setOpen] = useState(false) const [loadingLocation, setLoadingLocation] = useState(false) - const [currentFilter, setCurrentFilter] = useState(filter) const { t } = useTranslation() useEffect(() => { if (loadingLocation) { @@ -27,7 +26,7 @@ const BannerOrderChooser: FC = ({ (pos) => { setLoadingLocation(false) updateFilter({ - ...currentFilter, + ...filter, orderBy: 'proximityStartPoint', orderDirection: 'ASC', proximityLatitude: pos.coords.latitude, @@ -40,7 +39,7 @@ const BannerOrderChooser: FC = ({ { maximumAge: 0, enableHighAccuracy: true } ) } - }) + }, [loadingLocation, setLoadingLocation, filter]) const show = () => { setOpen(true) @@ -52,7 +51,6 @@ const BannerOrderChooser: FC = ({ } const updateFilter = (newFilter: BannerFilter) => { - setCurrentFilter(newFilter) onFilterChanged(newFilter) } @@ -68,51 +66,51 @@ const BannerOrderChooser: FC = ({ } const onOrderClicked = (type: BannerOrder) => { - if (type !== currentFilter.orderBy) { + if (type !== filter.orderBy) { if (type === 'proximityStartPoint') { setLoadingLocation(true) } else { updateFilter({ - ...currentFilter, + ...filter, orderBy: type, orderDirection: getDefaultDirection(type), proximityLatitude: undefined, proximityLongitude: undefined, }) } - } else if (hasBothDirections(currentFilter.orderBy)) { + } else if (hasBothDirections(filter.orderBy)) { updateFilter({ - ...currentFilter, - orderDirection: currentFilter.orderDirection === 'ASC' ? 'DESC' : 'ASC', + ...filter, + orderDirection: filter.orderDirection === 'ASC' ? 'DESC' : 'ASC', }) } } const onOfficialChanged = (includeUnofficial: boolean) => { updateFilter({ - ...currentFilter, + ...filter, onlyOfficialMissions: includeUnofficial ? undefined : true, }) } const onOnlineChanged = (showOffline: boolean) => { updateFilter({ - ...currentFilter, + ...filter, online: showOffline ? undefined : true, }) } const getButtonClass = (type: BannerOrder) => { let classNames = 'order-button' - if (type === currentFilter.orderBy) { + if (type === filter.orderBy) { classNames += ' selected' } return classNames } const getButtonDirection = (type: BannerOrder) => { - return type === currentFilter.orderBy - ? currentFilter.orderDirection + return type === filter.orderBy + ? filter.orderDirection : getDefaultDirection(type) } @@ -147,16 +145,13 @@ const BannerOrderChooser: FC = ({

{t('order.showOfflineBanners')}

- +
{includeOfficial && (

{t('order.showUnofficialBanners')}

@@ -190,8 +185,8 @@ const BannerOrderChooser: FC = ({ components={{ order: ( ), }} @@ -199,10 +194,10 @@ const BannerOrderChooser: FC = ({ {' / '} )} - {currentFilter.online + {filter.online ? t('order.text.excludeOffline') : t('order.text.includeOffline')} - {currentFilter.onlyOfficialMissions && ( + {filter.onlyOfficialMissions && ( <> {' / '} {t('order.onlyOfficial')} diff --git a/src/components/banner-order-chooser/Order.tsx b/src/components/banner-order-chooser/Order.tsx index 263b4730..a3165bbb 100644 --- a/src/components/banner-order-chooser/Order.tsx +++ b/src/components/banner-order-chooser/Order.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react' import { Trans, useTranslation } from 'react-i18next' -import SVGUpArrow from '../../img/icons/up-arrow.svg?react' +import SVGUpArrow from '../../assets/img/icons/up-arrow.svg?react' import './order.less' import { BannerOrder, BannerOrderDirection } from '../../features/banner/filter' @@ -9,6 +9,7 @@ import { BannerOrder, BannerOrderDirection } from '../../features/banner/filter' export const hasBothDirections = (type: BannerOrder) => { switch (type) { case 'relevance': + case 'proximityStartPoint': return false default: return true diff --git a/src/components/banner-order-chooser/banner-order-chooser.less b/src/components/banner-order-chooser/banner-order-chooser.less index 3518afc8..2770eb8f 100644 --- a/src/components/banner-order-chooser/banner-order-chooser.less +++ b/src/components/banner-order-chooser/banner-order-chooser.less @@ -16,7 +16,7 @@ cursor: pointer; &.selected { - background-image: linear-gradient(180deg, #109393, #15D4B2); + background-image: linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)); padding: 2px; .order-button-inner { @@ -25,7 +25,7 @@ } .order-button-inner { - background-color: #1B1B1B; + background-color: var(--color-light-black); border-radius: 4px; flex: 1; padding: 5px 10px; diff --git a/src/components/banners-accordion/BannersAccordion.tsx b/src/components/banners-accordion/BannersAccordion.tsx index ca0f7e36..6f39c6a7 100644 --- a/src/components/banners-accordion/BannersAccordion.tsx +++ b/src/components/banners-accordion/BannersAccordion.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' import { Banner } from '../../features/banner' import BannerCard from '../banner-card' import BannerList from '../banner-list' -import SVGTriangle from '../../img/icons/triangle.svg?react' +import SVGTriangle from '../../assets/img/icons/triangle.svg?react' import './banners-accordion.less' import BannerOrderChooser from '../banner-order-chooser' @@ -78,7 +78,7 @@ const BannerAccordion: FC = ({ hasMoreBanners={hasMoreBanners} onSelectBanner={onSelectBannerCallback} loadMoreBanners={loadMoreBanners} - applyBannerListStlyes + applyBannerListStyles hideBlacklisted showDetailsButton /> diff --git a/src/components/banners-accordion/banners-accordion.less b/src/components/banners-accordion/banners-accordion.less index 17d3500c..ac62c812 100644 --- a/src/components/banners-accordion/banners-accordion.less +++ b/src/components/banners-accordion/banners-accordion.less @@ -3,7 +3,7 @@ bottom: 0; height: 40px; width: 100%; - background-color: #1B1B1B; + background-color: var(--color-light-black); flex-direction: column; .filter-and-sort { @@ -32,7 +32,7 @@ margin: 0 10px; overflow-y: scroll; } - + &.expanded { height: 100%; max-height: 100%; @@ -58,4 +58,4 @@ } } -} \ No newline at end of file +} diff --git a/src/components/date-picker/DatePicker.tsx b/src/components/date-picker/DatePicker.tsx index 0277b693..97ac9468 100644 --- a/src/components/date-picker/DatePicker.tsx +++ b/src/components/date-picker/DatePicker.tsx @@ -2,7 +2,7 @@ import React, { ChangeEvent, useRef } from 'react' import { PlainDate } from '../plain-date' -import SVGEdit from '../../img/icons/edit.svg?react' +import SVGEdit from '../../assets/img/icons/edit.svg?react' export const DatePicker: React.FC = ({ value, diff --git a/src/components/duration/Duration.tsx b/src/components/duration/Duration.tsx index 25205183..b666fad4 100644 --- a/src/components/duration/Duration.tsx +++ b/src/components/duration/Duration.tsx @@ -12,8 +12,12 @@ const toPrecisionByFactor = (x: number, base: number, precision: number) => { } const roundDuration = (durationSeconds: number) => { + if (durationSeconds < 100) { + // Less than 100 seconds + return toPrecisionByFactor(durationSeconds, 1, 1) + } if (durationSeconds < SECONDS_IN_MINUTE * 10) { - // Less than 10 minutes + // Between 100 seconds and 10 minutes return toPrecisionByFactor(durationSeconds, 1, 2) } if (durationSeconds < SECONDS_IN_HOUR * 10) { diff --git a/src/components/events-preview/EventsPreview.tsx b/src/components/events-preview/EventsPreview.tsx index b3e59bdc..986b27b0 100644 --- a/src/components/events-preview/EventsPreview.tsx +++ b/src/components/events-preview/EventsPreview.tsx @@ -26,7 +26,7 @@ const EventsPreview: React.FC = () => { hasMoreBanners={false} hideBlacklisted={false} showDetailsButton={false} - applyBannerListStlyes={true} + applyBannerListStyles={true} /> {data.length >= 4 && (
diff --git a/src/components/faq-question/faq-question.less b/src/components/faq-question/faq-question.less index 58d9d92f..4e455f04 100644 --- a/src/components/faq-question/faq-question.less +++ b/src/components/faq-question/faq-question.less @@ -1,5 +1,5 @@ .faq-question { - background-color: #2e2e2e; + background-color: var(--color-dark-gray); border-radius: 5px; .faq-question-title { diff --git a/src/components/footer-main/FooterMain.tsx b/src/components/footer-main/FooterMain.tsx index bdad4922..bda81fad 100644 --- a/src/components/footer-main/FooterMain.tsx +++ b/src/components/footer-main/FooterMain.tsx @@ -3,28 +3,14 @@ import { Trans } from 'react-i18next' import { getExternalLinkAttributes } from '../../features/utils' -// import SVGInstagram from '../../img/icons/instagram.svg?react' -// import SVGTwitter from '../../img/icons/twitter.svg?react' -import SVGTelegram from '../../img/icons/telegram-bg.svg?react' -import SVGIngress from '../../img/icons/intel.svg?react' +import SVGTelegram from '../../assets/img/icons/telegram-bg.svg?react' +import SVGIngress from '../../assets/img/icons/intel.svg?react' import './footer.less' const FooterMain: React.FC = () => (
- {/* - - - - - */} diff --git a/src/components/footer-main/footer.less b/src/components/footer-main/footer.less index d0e98943..e65b20a7 100644 --- a/src/components/footer-main/footer.less +++ b/src/components/footer-main/footer.less @@ -1,4 +1,9 @@ .footer-main { + text-align: center; + background-color: var(--color-black); + padding: 1.5rem 0; + width: 100%; + .footer-links { padding-bottom: 1em; display: flex; @@ -6,18 +11,18 @@ column-gap: 25px; width: 100%; justify-content: center; - svg { - width: 20px; - height: 20px; - path { - fill: white; - fill-opacity: 1 !important; - } + + svg { + width: 20px; + height: 20px; + path { + fill: var(--color-white); + fill-opacity: 1 !important; } - + } } .footer-disclaimers { font-size: 12px; } -} \ No newline at end of file +} diff --git a/src/components/infinite-banner-list/InfiniteBannerList.tsx b/src/components/infinite-banner-list/InfiniteBannerList.tsx index d519f7b8..e3b91f93 100644 --- a/src/components/infinite-banner-list/InfiniteBannerList.tsx +++ b/src/components/infinite-banner-list/InfiniteBannerList.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import { BannerFilter } from '../../features/banner/filter' import { useBannerList } from '../../features/banner/hooks' import BannerList from '../banner-list' @@ -19,6 +19,10 @@ const InfiniteBannerList: FC = ({ setMaxPages(1) } + useEffect(() => { + onFilterChanged(initialFilter) + }, [initialFilter]) + return (
= ({ setMaxPages(maxPages + 1) } }} - applyBannerListStlyes + applyBannerListStyles hideBlacklisted={false} showDetailsButton={false} /> diff --git a/src/components/loading-overlay/LoadingOverlay.tsx b/src/components/loading-overlay/LoadingOverlay.tsx index 8448c7ac..d02912ac 100644 --- a/src/components/loading-overlay/LoadingOverlay.tsx +++ b/src/components/loading-overlay/LoadingOverlay.tsx @@ -1,115 +1,43 @@ /* eslint-disable i18next/no-literal-string */ -import React, { Component, RefObject } from 'react' +import { FC, useEffect, useRef } from 'react' import { CSSTransition } from 'react-transition-group' import Spinner from './Spinner' -// import STYLES from './styles' import './loading-overlay.less' -class LoadingOverlay extends Component< - LoadingOverlayProps, - LoadingOverlayState -> { - private wrapper: RefObject - - constructor(props: LoadingOverlayProps) { - super(props) - this.wrapper = React.createRef() - } - - // componentDidMount() { - // if (this.wrapper.current) { - // const wrapperStyle = window.getComputedStyle(this.wrapper.current) - // const overflowCSS = ['overflow', 'overflowX', 'overflowY'].reduce( - // (m, i) => { - // if (wrapperStyle[i] !== 'visible') m[i] = 'hidden' - // return m - // }, - // {} - // ) - // this.setState({ overflowCSS }) - // } - // } - - componentDidUpdate() { - const { active } = this.props - if (active && this.wrapper.current) this.wrapper.current.scrollTop = 0 - } - - /** - * Return an emotion css object for a given element key - * If a custom style was provided via props, run it with - * the base css obj. - */ - // getStyles = (key, providedState) => { - // const base = STYLES[key](providedState, this.props) - // const custom = this.props.styles[key] - // if (!custom) return base - // return typeof custom === 'function' ? custom(base, this.props) : custom - // } - - /** - * Convenience cx wrapper to add prefix classes to each of the child - * elements for styling purposes. - */ - // cx = (names, ...args) => { - // const arr = Array.isArray(names) ? names : [names] - // return cx( - // ...arr.map((name) => - // name ? `${this.props.classNamePrefix}${name}` : '' - // ), - // ...args - // ) - // } - - render() { - const { children, className, active, fadeSpeed, spinner, text } = this.props - - return ( -
= ({ active, text }) => { + const ref = useRef(null) + useEffect(() => { + if (active && ref.current) ref.current.scrollTop = 0 + }, [active, ref]) + return ( +
+ - -
-
- <> - {spinner && - (typeof spinner === 'boolean' ? : spinner)} - {text} - -
+
+
+ <> + + {text} +
- - {children} -
- ) - } +
+
+
+ ) } export interface LoadingOverlayProps { active: boolean - fadeSpeed: number - className?: string - classNamePrefix?: string - spinner: boolean | React.Component - text: React.Component | string - styles?: CSSStyleDeclaration - children?: React.ReactNode + text: string } -interface LoadingOverlayState {} - -// LoadingOverlayWrapper.defaultProps = { -// classNamePrefix: '_loading_overlay_', -// fadeSpeed: 500, -// styles: {}, -// } - export default LoadingOverlay diff --git a/src/components/loading-overlay/loading-overlay.less b/src/components/loading-overlay/loading-overlay.less index a2b68208..203f2c8a 100644 --- a/src/components/loading-overlay/loading-overlay.less +++ b/src/components/loading-overlay/loading-overlay.less @@ -1,4 +1,3 @@ - .loading-overlay { .overlay { max-width: initial; @@ -10,8 +9,8 @@ display: flex; text-align: center; font-size: 1.2em; - color: #FFF; - background: rgba(0, 0, 0, 0.7); + color: var(--color-white); + background: rgba(var(--color-black), 0.7); z-index: 2000; transition: 1 500ms ease-in; } @@ -24,11 +23,13 @@ margin: 0 auto 10px auto; width: 50px; max-height: 100%; + &:before { - content: ""; + content: ''; display: block; padding-top: 100%; } + & svg { animation: rotate(0deg) 2s linear infinite; height: 100%; @@ -40,12 +41,13 @@ left: 0; right: 0; margin: auto; + & circle { animation: spinnerDash 1.5s ease-in-out infinite; - stroke-dasharray: 1,200; + stroke-dasharray: 1, 200; stroke-dashoffset: 0; stroke-linecap: round; - stroke: #FFF; + stroke: var(--color-white); } } } @@ -53,30 +55,30 @@ @keyframes spinnerDash { 0% { - stroke-dasharray: 1,200; + stroke-dasharray: 1, 200; stroke-dashoffset: 0; } 50% { - stroke-dasharray: 89,200; + stroke-dasharray: 89, 200; stroke-dashoffset: -35px; } 100% { - stroke-dasharray: 89,200; + stroke-dasharray: 89, 200; stroke-dashoffset: -124px; } } @-webkit-keyframes spinnerDash { 0% { - stroke-dasharray: 1,200; + stroke-dasharray: 1, 200; stroke-dashoffset: 0; } 50% { - stroke-dasharray: 89,200; + stroke-dasharray: 89, 200; stroke-dashoffset: -35px; } 100% { - stroke-dasharray: 89,200; + stroke-dasharray: 89, 200; stroke-dashoffset: -124px; } -} \ No newline at end of file +} diff --git a/src/components/login/login-in-navbar/Login-in-navbar.less b/src/components/login/login-in-navbar/Login-in-navbar.less index 2d529e7e..fb716579 100644 --- a/src/components/login/login-in-navbar/Login-in-navbar.less +++ b/src/components/login/login-in-navbar/Login-in-navbar.less @@ -3,10 +3,10 @@ height: 34px; margin: 0; padding: 0; - border: 1px #0ca589 solid; + border: 1px var(--color-active-green) solid; border-radius: 2px; background-color: transparent; text-align: center; cursor: pointer; display: inline; -} \ No newline at end of file +} diff --git a/src/components/login/private-route/PrivateRoute.tsx b/src/components/login/private-route/PrivateRoute.tsx index 25dbe242..4971ff1a 100644 --- a/src/components/login/private-route/PrivateRoute.tsx +++ b/src/components/login/private-route/PrivateRoute.tsx @@ -20,14 +20,7 @@ export const PrivateRoute: React.FC = ({ {...rest} render={(props) => { if (!initialized) { - return ( - - ) + return } if (authenticated) { return diff --git a/src/components/map-detail/MarkerLabels.tsx b/src/components/map-detail/MarkerLabels.tsx index 9b330177..4bde28af 100644 --- a/src/components/map-detail/MarkerLabels.tsx +++ b/src/components/map-detail/MarkerLabels.tsx @@ -7,8 +7,8 @@ import { getMarkerData, isHiddenMission, } from './MarkerData' -import CheckeredFlagSVG from '../../img/icons/checkered-flag.svg?react' -import EyeOffSVG from '../../img/icons/eye-off-outline.svg?react' +import CheckeredFlagSVG from '../../assets/img/icons/checkered-flag.svg?react' +import EyeOffSVG from '../../assets/img/icons/eye-off-outline.svg?react' const getEndMarkerLabel = () => { return ( diff --git a/src/components/map-detail/map.less b/src/components/map-detail/map.less index a343a111..87428435 100644 --- a/src/components/map-detail/map.less +++ b/src/components/map-detail/map.less @@ -18,14 +18,18 @@ margin-left: -13px; margin-top: -13px; border-radius: 80px; - border: 2px solid rgba(0, 0, 0, 0.4); + border: 2px solid rgba(var(--color-black), 0.4); background-clip: padding-box; display: flex; align-items: center; justify-content: center; - color: rgb(255, 255, 255); - text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; - background-color: @color-pin; + color: var(--color-white); + text-shadow: + -1px 0 var(--color-black), + 0 1px var(--color-black), + 1px 0 var(--color-black), + 0 -1px var(--color-black); + background-color: var(--color-pin); font-size: 12px; font-weight: bold; } @@ -56,7 +60,7 @@ .marker-pin-medium-true { .marker-pin-medium; - background-color: rgb(22, 212, 178); + background-color: var(--color-positive-green-light); } .marker-pin-false { @@ -65,54 +69,69 @@ .marker-pin-true { .marker-pin; - background-color: rgb(22, 212, 178); + background-color: var(--color-positive-green-light); } .marker-pin-medium-false.marker-pin-todo { - background-color: @color-todo; + background-color: var(--color-todo); } .marker-pin-medium-false.marker-pin-done { - background-color: @color-done; + background-color: var(--color-done); } .marker-pin-medium-false.marker-pin-done-todo { - background-image: linear-gradient( -45deg, @color-done 50%, @color-todo 50%); + background-image: linear-gradient( + -45deg, + var(--color-done) 50%, + var(--color-todo) 50% + ); } .marker-pin-medium-false.marker-pin-normal-todo { - background-image: linear-gradient( -45deg, @color-pin 50%, @color-todo 50%); + background-image: linear-gradient( + -45deg, + var(--color-pin) 50%, + var(--color-todo) 50% + ); } .marker-pin-medium-false.marker-pin-normal-done { - background-image: linear-gradient( -45deg, @color-pin 50%, @color-done 50%); + background-image: linear-gradient( + -45deg, + var(--color-pin) 50%, + var(--color-done) 50% + ); } .marker-pin-medium-false.marker-pin-all { - background-image: conic-gradient(@color-pin 0 33%, @color-done 0 66%, @color-todo 0); + background-image: conic-gradient( + var(--color-pin) 0 33%, + var(--color-done) 0 66%, + var(--color-todo) 0 + ); } .square-marker-pin { - display: flex; align-items: center; - justify-content: center; - - width: 20px !important; - height: 20px !important; - margin-left: -6px; - margin-top: -6px; - border: 2px solid rgba(0, 0, 0, 0.4); background-clip: padding-box; - background-color: rgb(22, 212, 177); + background-color: var(--color-positive-green-light); + border: 2px solid rgba(var(--color-black), 0.4); + display: flex; font-size: 12px; font-weight: bold; + height: 20px !important; + justify-content: center; + margin-left: -6px; + margin-top: -6px; + width: 20px !important; } .color-green { - background-color: rgb(22, 212, 177); + background-color: var(--color-positive-green-light); } .color-blue { - background-color: @color-pin; + background-color: var(--color-pin); } .marker-poi-pin { @@ -125,4 +144,3 @@ margin-left: -7px; margin-top: -7px; } - diff --git a/src/components/menu-main/MenuMain.tsx b/src/components/menu-main/MenuMain.tsx index 360dfcfd..9f76f316 100644 --- a/src/components/menu-main/MenuMain.tsx +++ b/src/components/menu-main/MenuMain.tsx @@ -2,11 +2,11 @@ import React from 'react' import { NavLink } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import SVGHome from '../../img/icons/home.svg?react' -import SVGBrowse from '../../img/icons/browse.svg?react' -import SVGMap from '../../img/icons/map.svg?react' -import SVGHelp from '../../img/icons/help.svg?react' -import SVGAdd from '../../img/icons/add.svg?react' +import SVGHome from '../../assets/img/icons/home.svg?react' +import SVGBrowse from '../../assets/img/icons/browse.svg?react' +import SVGMap from '../../assets/img/icons/map.svg?react' +import SVGHelp from '../../assets/img/icons/help.svg?react' +import SVGAdd from '../../assets/img/icons/add.svg?react' import './menu-main.less' diff --git a/src/components/menu-main/menu-main.less b/src/components/menu-main/menu-main.less index 1a81c18f..7db00e69 100644 --- a/src/components/menu-main/menu-main.less +++ b/src/components/menu-main/menu-main.less @@ -1,3 +1,5 @@ +@mobile-detection: ~' screen and (max-width: 880px) '; + .menu-main { display: flex; @@ -6,31 +8,31 @@ flex-direction: column; align-items: center; justify-content: center; - color: #eaeaea; + color: var(--color-white-light); padding: 5px; min-width: 70px; - transition: background-color .3s; - - & > .icon { - fill: #eaeaea; + transition: background-color 0.3s; + + &.active { + color: var(--color-active-green); + + & > .icon { + fill: var(--color-active-green); + } } - } - & > a.active { - color: #0ca589; - & > .icon { - fill: #0ca589; + fill: var(--color-white-light); } } @media (hover: hover) and (pointer: fine) { & a:hover { - background: rgba(29,165,122,.3); - color: white; - + background: rgba(29, 165, 122, 0.3); + color: var(--color-white); + & > .icon { - fill: white; + fill: var(--color-white); } } } @@ -39,9 +41,32 @@ width: 24px; height: 24px; } - } -.bottom-menu .menu-main > a { - flex-grow: 1; +.bottom-menu { + background: var(--color-black); + padding: 5px 10px; + display: none; + + @media only @mobile-detection { + display: block; + } + + .menu-main { + > a { + flex-grow: 1; + } + + a { + @media only @mobile-detection { + flex: 1; + min-width: 0 !important; + } + } + } + + .brand-menu { + max-width: 329px; + flex: 1; + } } diff --git a/src/components/menu-user/MenuUser.tsx b/src/components/menu-user/MenuUser.tsx index 4a24feeb..6e105135 100644 --- a/src/components/menu-user/MenuUser.tsx +++ b/src/components/menu-user/MenuUser.tsx @@ -4,7 +4,7 @@ import { Button, Dropdown, Menu } from 'antd' import { useTranslation } from 'react-i18next' import UserPicture from '../login/user-picture' -import SVGUpArrow from '../../img/icons/up-arrow.svg?react' +import SVGUpArrow from '../../assets/img/icons/up-arrow.svg?react' import './menu-user.less' diff --git a/src/components/mission-card/MissionCard.tsx b/src/components/mission-card/MissionCard.tsx index a3c031ac..0836d046 100644 --- a/src/components/mission-card/MissionCard.tsx +++ b/src/components/mission-card/MissionCard.tsx @@ -4,7 +4,7 @@ import { Mission } from '../../features/mission' import MissionImage from '../mission-image/MissionImage' import StepList from '../step-list' import MissionInfo from '../mission-info' -import SVGChevron from '../../img/icons/chevron.svg?react' +import SVGChevron from '../../assets/img/icons/chevron.svg?react' import './mission-card.less' diff --git a/src/components/mission-card/mission-card.less b/src/components/mission-card/mission-card.less index 01cb708d..6f5c2e07 100644 --- a/src/components/mission-card/mission-card.less +++ b/src/components/mission-card/mission-card.less @@ -5,7 +5,7 @@ margin-top: 0; } margin-top: 1rem; - background: #2e2e2e; + background: var(--color-dark-gray); border-radius: 5px; border: none; // max-width: 400px; diff --git a/src/components/mission-image/mission-image.less b/src/components/mission-image/mission-image.less index eb335734..45d900b2 100644 --- a/src/components/mission-image/mission-image.less +++ b/src/components/mission-image/mission-image.less @@ -1,11 +1,11 @@ .mission-image { - width: 50px; - height: 50px; - border-radius: 50%; - background-repeat: no-repeat; - background-size: 100%; - border: 2px solid #f19e24; - padding: 2px 17px; - font-size: 26px; - font-weight: 700; + background-repeat: no-repeat; + background-size: 100%; + border-radius: 50%; + border: 2px solid var(--color-warning); + font-size: 26px; + font-weight: 700; + height: 50px; + padding: 2px 17px; + width: 50px; } diff --git a/src/components/mission-info/MissionInfo.tsx b/src/components/mission-info/MissionInfo.tsx index 197c0f27..d1b664ab 100644 --- a/src/components/mission-info/MissionInfo.tsx +++ b/src/components/mission-info/MissionInfo.tsx @@ -10,9 +10,9 @@ import { getExternalLinkAttributes } from '../../features/utils' import { Duration } from '../duration/Duration' import { Distance } from '../distance/Distance' -import SVGExplorer from '../../img/icons/explorer.svg?react' -import SVGTimer from '../../img/icons/timer.svg?react' -import SVGIntel from '../../img/icons/intel.svg?react' +import SVGExplorer from '../../assets/img/icons/explorer.svg?react' +import SVGTimer from '../../assets/img/icons/timer.svg?react' +import SVGIntel from '../../assets/img/icons/intel.svg?react' import './mission-info.less' import { isMobile } from '../../features/utils/os' diff --git a/src/components/mission-info/mission-info.less b/src/components/mission-info/mission-info.less index fa1e52bf..2601d96b 100644 --- a/src/components/mission-info/mission-info.less +++ b/src/components/mission-info/mission-info.less @@ -8,20 +8,20 @@ height: 1.2em; min-width: 2em; } - + .mission-info-markedoffline { - color: #FF2F2F; + color: var(--color-missing); } - + .mission-info-intel { display: flex; flex-direction: row; - color: #15D4B2; + color: var(--color-border-light); align-content: center; a { - color: #15D4B2; + color: var(--color-border-light); text-decoration: underline; display: flex; align-items: center; diff --git a/src/components/navbar/Navbar.less b/src/components/navbar/Navbar.less index fa1db540..f72c19b7 100644 --- a/src/components/navbar/Navbar.less +++ b/src/components/navbar/Navbar.less @@ -1,8 +1,12 @@ +@mobile-detection: ~' screen and (max-width: 880px) '; + .top-menu { display: flex; flex-direction: row; justify-content: space-between; align-items: center; + background: var(--color-black); + padding: 5px 10px; > div { display: flex; @@ -13,6 +17,17 @@ width: 250px; } } + + .menu-main { + @media only @mobile-detection { + display: none; + } + } + + .brand-menu { + max-width: 329px; + flex: 1; + } } .brand-logo { @@ -23,6 +38,10 @@ background-size: contain; background-repeat: no-repeat; background-position: left center; + + @media only @mobile-detection { + width: 50vw; + } } .mobile-search-bar { @@ -33,11 +52,11 @@ display: flex; } -.mobile-search-button-container{ +.mobile-search-button-container { display: flex; min-width: 70px; height: 60px; - transition: background-color .3s; + transition: background-color 0.3s; .mobile-search-button { background: none; @@ -51,30 +70,29 @@ display: flex; justify-content: center; align-items: center; - + height: 100%; width: 100%; - - .search-button-icon - { + + .search-button-icon { width: 24px; height: 24px; - fill: #eaeaea; - } - } + fill: var(--color-white-light); + } + } } .mobile-search-button-container.active { - .search-button-icon{ - fill: #0ca589; + .search-button-icon { + fill: var(--color-active-green); } } @media (hover: hover) and (pointer: fine) { .mobile-search-button-container:hover { - background: rgba(29,165,122,.3); - .search-button-icon{ - fill: white; + background: rgba(29, 165, 122, 0.3); + .search-button-icon { + fill: white; } } } diff --git a/src/components/navbar/Navbar.tsx b/src/components/navbar/Navbar.tsx index ffc08739..1f467a50 100644 --- a/src/components/navbar/Navbar.tsx +++ b/src/components/navbar/Navbar.tsx @@ -1,108 +1,84 @@ -import React from 'react' -import { generatePath } from 'react-router' -import { RouteComponentProps, withRouter, NavLink } from 'react-router-dom' -import { Location } from 'history' -import { withTranslation, WithTranslationProps } from 'react-i18next' +import { FC, useEffect, useState } from 'react' +import { generatePath, useHistory, useLocation } from 'react-router' +import { NavLink } from 'react-router-dom' +import { useTranslation } from 'react-i18next' import LoginInNavbar from '../login/login-in-navbar' import SearchInput from '../search-input' import MenuMain from '../menu-main' -import Logo from '../../img/logo/logo64.png' -import SVGSearch from '../../img/icons/search.svg?react' +import Logo from '../../assets/img/logo/logo64.png' +import SVGSearch from '../../assets/img/icons/search.svg?react' import './Navbar.less' -export class Navbar extends React.Component { - constructor(props: NavBarProps) { - super(props) - - this.state = { - mobileSearchBarActive: false, +const Navbar: FC = ({ className }) => { + const [mobileSearchBarActive, setMobileSearchBarActive] = useState(false) + const { t } = useTranslation() + const history = useHistory() + const location = useLocation() + const mobileSearchBarActiveClassName = mobileSearchBarActive ? 'active' : '' + const insideSearch = location.pathname.startsWith('/search/') + useEffect(() => { + if (!insideSearch) { + setMobileSearchBarActive(false) } + }, [insideSearch]) - const { history } = this.props - history.listen(this.locationListen.bind(this)) - } - - locationListen(location: Location) { - // Hide Mobile search bar when navigating away from search + const callSearch = (value: string) => { + const trimmedValue = value.trim() - if (!location.pathname.startsWith('/search/')) { - const { mobileSearchBarActive } = this.state - if (mobileSearchBarActive) { - this.setState({ mobileSearchBarActive: false }) - } + if (trimmedValue !== '') { + const path = generatePath('/search/:term', { term: trimmedValue }) + history.push(path) } } - toggleMobileSeachBar() { - const { mobileSearchBarActive } = this.state - this.setState({ mobileSearchBarActive: !mobileSearchBarActive }) + const toggleMobileSeachBar = () => { + setMobileSearchBarActive(!mobileSearchBarActive) } - render() { - const { history, className, i18n } = this.props - - const callSearch = (value: string) => { - const trimmedValue = encodeURIComponent(value.trim()) - - if (trimmedValue !== '') { - const path = generatePath('/search/:term', { term: trimmedValue }) - history.push(path) - } - } - - const { mobileSearchBarActive } = this.state - const mobileSearchBarActiveClassName = mobileSearchBarActive ? 'active' : '' - - return ( - <> -
- -
-   -
-
- -
-
+
+ +
+   +
+
+ +
+
+ -
-
- -
- + +
-
- {mobileSearchBarActive && ( -
- +
+
- )} - - ) - } + +
+
+ {mobileSearchBarActive && ( +
+ +
+ )} + + ) } export type NavBarProps = { className?: string -} & RouteComponentProps & - WithTranslationProps - -export interface NavBarState { - mobileSearchBarActive: Boolean } -export default withRouter(withTranslation()(Navbar)) +export default Navbar diff --git a/src/components/news-list/NewsList.tsx b/src/components/news-list/NewsList.tsx index a4f71b2f..5f089417 100644 --- a/src/components/news-list/NewsList.tsx +++ b/src/components/news-list/NewsList.tsx @@ -1,41 +1,18 @@ -import React, { useEffect } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { ThunkDispatch } from 'redux-thunk' -import { Trans, useTranslation } from 'react-i18next' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' -import { getNews, loadNews } from '../../features/news' -import { NewsActionTypes } from '../../features/news/actionTypes' -import { RootState } from '../../storeTypes' import Announcement from '../announcement' -import { Issue } from '../Issues-list' +import { useNewsList } from '../../features/news/hooks' -type AppDispatch = ThunkDispatch - -const NewsList: React.FC = ({ setIssues }) => { - const dispatch: AppDispatch = useDispatch() - const news = useSelector(getNews) +const NewsList: FC = () => { + const { data } = useNewsList() const { t } = useTranslation() - useEffect(() => { - dispatch(loadNews()).catch(() => { - setIssues([ - { - key: 'news-fetch-error', - type: 'error', - message: t('news.error'), - field: 'news', - }, - ]) - }) - }, [dispatch, setIssues, t]) - - if (news && news.length) { + if (data.length) { return (
-

- Announcements -

- {news.map((newsItem) => ( +

{t('news.title')}

+ {data.map((newsItem) => ( ))}
@@ -44,8 +21,4 @@ const NewsList: React.FC = ({ setIssues }) => { return <> } -export interface NewsListProps { - setIssues: (issues: Array) => void -} - export default NewsList diff --git a/src/components/place-accordion/PlaceAccordionEntry.tsx b/src/components/place-accordion/PlaceAccordionEntry.tsx index 8e7a24a5..36225b3e 100644 --- a/src/components/place-accordion/PlaceAccordionEntry.tsx +++ b/src/components/place-accordion/PlaceAccordionEntry.tsx @@ -5,7 +5,7 @@ import { Place } from '../../features/place' import PlaceEntry from '../place-list/PlaceEntry' import './place-accordion-entry.less' -import SVGArea from '../../img/icons/area.svg?react' +import SVGArea from '../../assets/img/icons/area.svg?react' export const PlaceAccordionEntry: FC = ({ place, diff --git a/src/components/place-accordion/PlaceAccordionPage.tsx b/src/components/place-accordion/PlaceAccordionPage.tsx index 63f60572..06df6176 100644 --- a/src/components/place-accordion/PlaceAccordionPage.tsx +++ b/src/components/place-accordion/PlaceAccordionPage.tsx @@ -6,8 +6,8 @@ import { Place } from '../../features/place' import { RootState } from '../../storeTypes' import PlaceEntry from '../place-list/PlaceEntry' import { PlaceAccordionEntry } from './PlaceAccordionEntry' -import TriangleUpSVG from '../../img/icons/triangle.svg?react' -import TriangleDownSVG from '../../img/icons/triangle-down.svg?react' +import TriangleUpSVG from '../../assets/img/icons/triangle.svg?react' +import TriangleDownSVG from '../../assets/img/icons/triangle-down.svg?react' import './place-accordion-page.less' diff --git a/src/components/place-accordion/place-accordion.less b/src/components/place-accordion/place-accordion.less index 3cf227bf..84c75690 100644 --- a/src/components/place-accordion/place-accordion.less +++ b/src/components/place-accordion/place-accordion.less @@ -1,3 +1,3 @@ .place-accordion { - background: #2E2E2E; + background: var(--color-dark-gray); } diff --git a/src/components/place-list-flat/place-card.less b/src/components/place-list-flat/place-card.less index 4fbbad98..c7f9b555 100644 --- a/src/components/place-list-flat/place-card.less +++ b/src/components/place-list-flat/place-card.less @@ -1,7 +1,7 @@ .place-card { - background: #2e2e2e; + background: var(--color-dark-gray); border-radius: 5px; - border: #2e2e2e 2px solid; + border: var(--color-dark-gray) 2px solid; padding: 15px; color: white; } @@ -26,7 +26,7 @@ .place-card-name { - white-space: nowrap; + white-space: nowrap; text-overflow: ellipsis; font-weight: 700; overflow-x: hidden; @@ -39,7 +39,7 @@ } .place-card-number-of-banners { - white-space: nowrap; + white-space: nowrap; } .place-card-formatted-address { @@ -60,8 +60,8 @@ } .place-card-icon { - color: #EAEAEA; - fill: #EAEAEA; + color: var(--color-white-light); + fill: var(--color-white-light); margin-right: 1em; height: 1.5em; } diff --git a/src/components/place-list/PlaceEntry.tsx b/src/components/place-list/PlaceEntry.tsx index a93bc917..01ac2a02 100644 --- a/src/components/place-list/PlaceEntry.tsx +++ b/src/components/place-list/PlaceEntry.tsx @@ -4,8 +4,8 @@ import { Place } from '../../features/place' import './place-entry.less' -import SVGLocality from '../../img/icons/locality.svg?react' -import SVGArea from '../../img/icons/area.svg?react' +import SVGLocality from '../../assets/img/icons/locality.svg?react' +import SVGArea from '../../assets/img/icons/area.svg?react' const PlaceEntry: FC = ({ place, diff --git a/src/components/place-list/place-entry.less b/src/components/place-list/place-entry.less index f2045042..4f5b1024 100644 --- a/src/components/place-list/place-entry.less +++ b/src/components/place-list/place-entry.less @@ -3,12 +3,12 @@ } .place-icon { - fill: #eaeaea; + fill: var(--color-white-light); width: 1.1em; height: 1.1em; } @font-face { font-family: 'NotoColorEmoji'; - src: local('NotoColorEmoji'), url(./../../fonts/NotoColorEmoji.ttf) format('truetype'); + src: local('NotoColorEmoji'), url(./../../assets/fonts/NotoColorEmoji.ttf) format('truetype'); } diff --git a/src/components/place-list/place-list.less b/src/components/place-list/place-list.less index bfc5c75f..74f2eddc 100644 --- a/src/components/place-list/place-list.less +++ b/src/components/place-list/place-list.less @@ -1,24 +1,41 @@ .places-list-item { - white-space: nowrap; - text-overflow: ellipsis; + column-gap: 0.3em; cursor: pointer; overflow: hidden; - display: flex; - align-items: baseline; - column-gap: 0.3em; + text-overflow: ellipsis; } .places-list { padding-top: 5em; padding-left: 2em; padding-right: 1em; + + h2 + h2 { + min-width: 270px; + } } .places-list-child { + align-items: center; + display: grid; margin-left: 1em; + grid-template-columns: 1fr 180px 50px; + + .place-flag { + grid-column: 1 / 2; + } .place-name { + grid-column: 2 / 3; overflow: hidden; text-overflow: ellipsis; } + + .place-number-of-banners { + grid-column: 3 / 3; + } + + .place-name + .place-number-of-banners { + grid-column: 3 / 3; + } } diff --git a/src/components/recent-banners/RecentBanners.tsx b/src/components/recent-banners/RecentBanners.tsx index ad7c38ab..2c94f16f 100644 --- a/src/components/recent-banners/RecentBanners.tsx +++ b/src/components/recent-banners/RecentBanners.tsx @@ -1,64 +1,42 @@ -import React, { FC, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useHistory } from 'react-router-dom' -import { ThunkDispatch } from 'redux-thunk' +import { FC, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { getRecentBanners, loadRecentBanners } from '../../features/banner' -import { BannerActionTypes } from '../../features/banner/actionTypes' -import { RootState } from '../../storeTypes' import BannerList from '../banner-list' -import { Issue } from '../Issues-list' import LoadingOverlay from '../loading-overlay' import './recent-banners.less' - -type AppDispatch = ThunkDispatch - -export const RecentBanners: FC = ({ - titleList, - setIssues, - resetIssue, -}) => { - const history = useHistory() - const dispatch: AppDispatch = useDispatch() - const banners = useSelector(getRecentBanners) - const [loading, setLoading] = useState(false) - const { t } = useTranslation(undefined, { keyPrefix: 'banners' }) - - useEffect(() => { - setLoading(true) - resetIssue('recent-fetch-error') - dispatch(loadRecentBanners()) - .catch(() => - setIssues([ - { - key: 'recent-fetch-error', - type: 'error', - message: t('errors.loading'), - field: 'recentBanners', - }, - ]) - ) - .finally(() => setLoading(false)) - }, [dispatch, setIssues, resetIssue, history.location, t]) +import { useUserLoggedIn } from '../../hooks/UserLoggedIn' +import { useBannerList } from '../../features/banner/hooks' +import { BannerFilter } from '../../features/banner/filter' + +export const RecentBanners: FC = ({ titleList }) => { + const { t } = useTranslation() + const { authenticated } = useUserLoggedIn() + const filter = useMemo( + () => ({ + orderBy: 'created', + orderDirection: 'DESC', + online: true, + listTypes: authenticated ? ['none', 'done', 'todo'] : undefined, + }), + [authenticated] + ) + const { status, data } = useBannerList(filter, 1, 12) return (

{titleList}

@@ -67,8 +45,6 @@ export const RecentBanners: FC = ({ export interface RecentBannersProps { titleList: string - setIssues: (issues: Array) => void - resetIssue: (key: string) => void } export default RecentBanners diff --git a/src/components/search-input/SearchInput.tsx b/src/components/search-input/SearchInput.tsx index cb40f91e..dd72b406 100644 --- a/src/components/search-input/SearchInput.tsx +++ b/src/components/search-input/SearchInput.tsx @@ -2,7 +2,7 @@ import React, { FormEvent, Fragment, FC, useRef } from 'react' import { message } from 'antd' import { useTranslation } from 'react-i18next' -import SVGSearch from '../../img/icons/search.svg?react' +import SVGSearch from '../../assets/img/icons/search.svg?react' import './search-input.less' diff --git a/src/components/search-input/search-input.less b/src/components/search-input/search-input.less index d0dc2670..27d9d0fd 100644 --- a/src/components/search-input/search-input.less +++ b/src/components/search-input/search-input.less @@ -1,5 +1,5 @@ .search-input-button-highlighted() { - background: #077561; + background: var(--color-positive-green); fill: white; } @@ -8,18 +8,18 @@ flex-direction: row; width: 100%; - .search-input { + .search-input { padding:5px; padding-left: 10px; - background: #404040; + background: var(--color-panel-bg); border-radius: 4px 0px 0px 4px; border: none; width: 100%; &:focus { - background: #6A6A6A; + background: var(--color-gray); outline: none; - color: white; + color: var(--color-white); } &:focus + .search-input-button { @@ -27,15 +27,15 @@ } &::placeholder { - color: #AAAAAA; + color: var(--color-light-gray); font-size: 12px; } } - .search-input-button { + .search-input-button { display: flex; flex-direction: column; - background: #888888; + background: var(--color-gray); border-radius: 0px 4px 4px 0px; border: none; cursor: pointer; @@ -43,14 +43,14 @@ align-items: center; padding-left: 12px; padding-right: 12px; - fill: #1B1B1B; + fill: var(--color-light-black); @media (hover: hover) and (pointer: fine) { &:hover { .search-input-button-highlighted() } } - + &:focus { .search-input-button-highlighted() } @@ -62,4 +62,4 @@ width: 17.49px; height: 17.49px; } -} \ No newline at end of file +} diff --git a/src/components/search-mission-card/SearchMissionCard.tsx b/src/components/search-mission-card/SearchMissionCard.tsx index ef7d18e9..7c724db9 100644 --- a/src/components/search-mission-card/SearchMissionCard.tsx +++ b/src/components/search-mission-card/SearchMissionCard.tsx @@ -6,7 +6,7 @@ import { getExternalLinkAttributes } from '../../features/utils' import { Agent } from '../agent/Agent' import MissionImage from '../mission-image/MissionImage' import PlaceHolderMission from './PlaceHolderMission' -import SVGIntel from '../../img/icons/intel.svg?react' +import SVGIntel from '../../assets/img/icons/intel.svg?react' import './search-mission-card.less' diff --git a/src/components/search-mission-card/search-mission-card.less b/src/components/search-mission-card/search-mission-card.less index cc94e04f..d23d7b43 100644 --- a/src/components/search-mission-card/search-mission-card.less +++ b/src/components/search-mission-card/search-mission-card.less @@ -1,10 +1,8 @@ -@import "../../index"; +@import '../../index'; .search-mission-card { margin-top: 1rem; - background-color: @color-panel-bg; - margin-top: 1rem; - background-color: #404040; + background-color: var(--color-panel-bg); border-radius: 5px; display: flex; column-gap: 15px; @@ -25,12 +23,15 @@ word-break: break-word; &.placeholder { - color: #808080; + color: var(--color-gray); } } .mission-agent { font-size: 14px; + border: none; + text-align: left; + padding: 0; } .icon { @@ -58,10 +59,4 @@ cursor: pointer; flex-shrink: 0; } - - .mission-agent { - border: none; - text-align: left; - padding: 0; - } -} \ No newline at end of file +} diff --git a/src/components/search-mission-list/SearchMissionList.tsx b/src/components/search-mission-list/SearchMissionList.tsx index c6bf9040..b08e4e63 100644 --- a/src/components/search-mission-list/SearchMissionList.tsx +++ b/src/components/search-mission-list/SearchMissionList.tsx @@ -57,12 +57,7 @@ const SearchMissionList: FC = ({ {hasMoreMissions && !initial && ( - + )} diff --git a/src/components/step-card/step-card.less b/src/components/step-card/step-card.less index a0d55eb9..be3e9609 100644 --- a/src/components/step-card/step-card.less +++ b/src/components/step-card/step-card.less @@ -24,7 +24,7 @@ .step-card-unavailable { text-decoration: line-through; - color: #6f6f6f; + color: var(--color-gray); } .step-card-hidden { diff --git a/src/components/step-list/step-list.less b/src/components/step-list/step-list.less index 58d7a11a..e25291e0 100644 --- a/src/components/step-list/step-list.less +++ b/src/components/step-list/step-list.less @@ -2,6 +2,6 @@ .step-list { .ant-layout { - background-color: @color-panel-bg !important; + background-color: var(--color-panel-bg) !important; } } diff --git a/src/components/switch/switch.less b/src/components/switch/switch.less index 4d86c986..54813bea 100644 --- a/src/components/switch/switch.less +++ b/src/components/switch/switch.less @@ -1,15 +1,15 @@ .bg-switch { border-radius: 4px; - background-color: white; + background-color: var(--color-white); padding: 1px; width: 48px; height: 28px; .ant-switch { - background-color: #1f1f1f; + background-color: var(--color-dark-gray); border-radius: 3px; border: 1px solid; - border-color: #1f1f1f; + border-color: var(--color-dark-gray); width: 46px; height: 26px; } @@ -18,11 +18,11 @@ border-radius: 1px; width: 16px; height: 20px; - background-color: #808080; + background-color: var(--color-gray); } &.bg-switch-selected { - background-image: linear-gradient(180deg, #109393, #15D4B2); + background-image: linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)); .ant-switch { border-color: transparent; @@ -30,7 +30,7 @@ } .ant-switch-handle::before { - background-color: #15D4B2; + background-color: var(--color-border-light); } } } diff --git a/src/components/user-banner-list-preview/UserBannerListPreview.tsx b/src/components/user-banner-list-preview/UserBannerListPreview.tsx index f9e011f4..d6bbfbca 100644 --- a/src/components/user-banner-list-preview/UserBannerListPreview.tsx +++ b/src/components/user-banner-list-preview/UserBannerListPreview.tsx @@ -96,7 +96,7 @@ class UserBannerListPreview extends React.Component< diff --git a/src/components/user-banner-list-preview/user-banner-list-preview.less b/src/components/user-banner-list-preview/user-banner-list-preview.less index b9ac4541..6c6857b7 100644 --- a/src/components/user-banner-list-preview/user-banner-list-preview.less +++ b/src/components/user-banner-list-preview/user-banner-list-preview.less @@ -8,14 +8,14 @@ display: flex; color: white; - background-color: #1B1B1B; + background-color: var(--color-light-black); border-radius: 4px; padding: 5px 10px; } border-radius: 5px; display: flex; - background-image: linear-gradient(180deg, #109393, #15D4B2); + background-image: linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)); padding: 2px; } - + diff --git a/src/components/verify-account/Step1.tsx b/src/components/verify-account/Step1.tsx new file mode 100644 index 00000000..d3dcae8c --- /dev/null +++ b/src/components/verify-account/Step1.tsx @@ -0,0 +1,39 @@ +import { Button, Input } from 'antd' +import { FC, useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' + +interface Step1Props { + onClaim: (agent: string) => void + onAbort: () => void +} + +const isValidAgentName = (agent: string) => { + return agent.length > 3 +} + +export const Step1: FC = ({ onClaim, onAbort }) => { + const [agent, setAgent] = useState('') + const { t } = useTranslation() + const claim = useCallback(() => onClaim(agent), [onClaim, agent]) + return ( +
+

{t('account.linking.step1.title')}

+

{t('account.linking.step1.description')}

+
+ setAgent(e.target.value)} /> +
+
+ + +
+
+ ) +} diff --git a/src/components/verify-account/Step2.tsx b/src/components/verify-account/Step2.tsx new file mode 100644 index 00000000..d7cb16d0 --- /dev/null +++ b/src/components/verify-account/Step2.tsx @@ -0,0 +1,46 @@ +import { FC, useCallback } from 'react' +import { Button, Input } from 'antd' +import { Trans, useTranslation } from 'react-i18next' + +interface Step2Props { + verificationMessage: string | undefined + verificationAgent: string | undefined + onNext: () => void + onAbort: () => void +} + +export const Step2: FC = ({ + verificationMessage, + verificationAgent, + onNext, + onAbort, +}) => { + const { t } = useTranslation() + const copyToken = useCallback(async () => { + await navigator.clipboard.writeText(verificationMessage!) + onNext() + }, [onNext, verificationMessage]) + return ( +
+

{t('account.linking.step2.title')}

+

+ }} + /> +

+
+ +
+
+ + +
+
+ ) +} diff --git a/src/components/verify-account/Step3.tsx b/src/components/verify-account/Step3.tsx new file mode 100644 index 00000000..f973c7ab --- /dev/null +++ b/src/components/verify-account/Step3.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react' +import { Button } from 'antd' +import { useTranslation } from 'react-i18next' + +interface Step3Props { + onBack: () => void +} + +export const Step3: FC = ({ onBack }) => { + const { t } = useTranslation() + return ( +
+

{t('account.linking.step3.title')}

+

{t('account.linking.step3.description')}

+
+ +
+
+ ) +} diff --git a/src/components/verify-account/VerifyAccount.tsx b/src/components/verify-account/VerifyAccount.tsx index 6d214d3a..480db3b6 100644 --- a/src/components/verify-account/VerifyAccount.tsx +++ b/src/components/verify-account/VerifyAccount.tsx @@ -1,189 +1,123 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react' -import { Link } from 'react-router-dom' -import { useDispatch } from 'react-redux' -import { Button, Carousel, Input } from 'antd' +import { FC, useCallback, useEffect, useRef, useState } from 'react' +import { Button, Carousel } from 'antd' import { CarouselRef } from 'antd/lib/carousel' -import { ThunkDispatch, ThunkAction } from 'redux-thunk' import { Trans, useTranslation } from 'react-i18next' -import { - abortClaimUser, - claimUser, - unlinkUser, - User, - verifyUser, -} from '../../features/user' -import { UserActionTypes } from '../../features/user/actionTypes' -import { RootState } from '../../storeTypes' -import { useRefreshToken } from '../../hooks/RefreshToken' import { Agent } from '../agent' -import { Issue } from '../Issues-list' import LoadingOverlay from '../loading-overlay' import './verify-account.less' +import { Step1 } from './Step1' +import { Step2 } from './Step2' +import { User } from '../../features/user' +import { ApiResponse } from '../../api' +import { + abortClaimUser, + claimUser, + getUser, + unlinkUser, +} from '../../features/user/api' +import { Step3 } from './Step3' -type AppDispatch = ThunkDispatch -type AppAction = ThunkAction, RootState, any, UserActionTypes> - -const isAccountLinked = (user: User) => user && user.agent -const isVerifying = (user: User) => !!user.verificationToken +type Status = 'pending' | 'resolved' | 'rejected' -const VerifyAccount: React.FC = ({ - currentUser, - setIssues, -}) => { - const dispatch: AppDispatch = useDispatch() +const VerifyAccount: FC = () => { const slider = useRef() + const [user, setUser] = useState() + const [status, setStatus] = useState('pending') const [isClaiming, setIsClaiming] = useState(false) - const [agent, setAgent] = useState(currentUser?.verificationAgent) - const [loading, setLoading] = useState(false) - const refreshToken = useRefreshToken() + const [isCopied, setIsCopied] = useState(false) const { t } = useTranslation() - useEffect(() => { - if (currentUser.verificationAgent) { - setAgent(currentUser.verificationAgent) - setIsClaiming(true) - if (slider.current) { - slider.current.goTo(1) + const verificationMessage = user?.verificationMessage + const verificationAgent = user?.verificationAgent + + const handleApiRequest = useCallback( + async (promise: Promise>) => { + setStatus('pending') + const result = await promise + if (result.ok) { + setStatus('resolved') + setUser(result.data) + setIsClaiming(Boolean(result.data.verificationMessage)) + if (!result.data.verificationMessage) { + setIsCopied(false) + } + } else { + setStatus('rejected') } - } - }, [currentUser, setAgent, setIsClaiming]) - - const dispatchWithLoading = useCallback( - ( - action: AppAction, - onSuccess?: () => void, - onError?: (err: any) => void - ) => { - setLoading(true) - dispatch(action) - .then(() => { - if (onSuccess) onSuccess() - }) - .catch((err) => { - if (onError) onError(err) - }) - .finally(() => setLoading(false)) }, - [dispatch] + [] ) - const onClaim = useCallback(() => { - if (currentUser.verificationAgent !== agent) { - dispatchWithLoading( - claimUser(agent), - () => slider.current?.next(), - (err) => - setIssues([ - { - key: 'claim', - message: err.message, - type: 'error', - field: 'verify', - }, - ]) - ) - } else { - slider.current?.next() - } - }, [currentUser.verificationAgent, agent, dispatchWithLoading, setIssues]) + const onUnlink = useCallback( + () => handleApiRequest(unlinkUser()), + [handleApiRequest] + ) - const onVerify = useCallback(() => { - dispatchWithLoading( - verifyUser(), - () => { - setIsClaiming(false) - refreshToken() - }, - (err) => - setIssues([ - { - key: 'verify', - message: err.message, - type: 'error', - field: 'verify', - }, - ]) - ) - }, [dispatchWithLoading, refreshToken, setIssues]) + const onStep1Abort = useCallback(() => setIsClaiming(false), [setIsClaiming]) - const onUnlinkUser = useCallback(() => { - dispatchWithLoading(unlinkUser(), undefined, (err) => - setIssues([ - { - key: 'unlink', - message: err.message, - type: 'error', - field: 'verify', - }, - ]) - ) - }, [dispatchWithLoading, setIssues]) + const onStep1Claim = useCallback( + (agent: string) => handleApiRequest(claimUser(agent)), + [handleApiRequest] + ) - const onAbort = useCallback(() => { - if (currentUser.verificationAgent) { - dispatchWithLoading( - abortClaimUser(currentUser.verificationAgent), - () => { - setIsClaiming(false) - setAgent('') - }, - (err) => - setIssues([ - { - key: 'claim', - message: err.message, - type: 'error', - field: 'verify', - }, - ]) - ) - } else { - setIsClaiming(false) - setAgent('') - } - }, [currentUser.verificationAgent, dispatchWithLoading, setIssues]) + const onStep2Abort = useCallback( + () => handleApiRequest(abortClaimUser()), + [handleApiRequest] + ) - const getClaimButtons = useCallback(() => { - if (isAccountLinked(currentUser)) { - return ( -
- - -
- ) - } - return ( -
- -
- ) - }, [currentUser, onUnlinkUser, t]) + const onStep2Next = useCallback(() => setIsCopied(true), [setIsCopied]) - const onNext = useCallback(() => slider.current?.next(), []) + const onStep3Back = useCallback(() => setIsCopied(false), [setIsCopied]) - const onBack = useCallback(() => slider.current?.prev(), []) + useEffect(() => { + handleApiRequest(getUser()) + }, [handleApiRequest]) - const onCopyToken = useCallback(() => { - navigator.clipboard.writeText(currentUser.verificationToken) - onNext() - }, [currentUser.verificationToken, onNext]) + useEffect(() => { + if (verificationMessage && isCopied) { + const id = setInterval(async () => { + try { + await handleApiRequest(getUser()) + } catch (e) { + } + }, 120_000) + return () => clearTimeout(id) + } + }, [verificationMessage, isCopied, handleApiRequest]) - const getCarousel = useCallback( - () => ( + useEffect(() => { + if (isClaiming && slider.current) { + let page + if (verificationMessage && isCopied) { + page = 2 + } else if (verificationMessage) { + page = 1 + } else { + page = 0 + } + slider.current.goTo(page) + } + }, [isClaiming, slider, isCopied, verificationMessage]) + + let linkedAccount + if (user?.agent) { + linkedAccount = ( + , + }} + /> + ) + } else { + linkedAccount = t('account.linking.none') + } + + let actions + if (isClaiming) { + actions = ( { slider.current = c @@ -192,131 +126,56 @@ const VerifyAccount: React.FC = ({ swipe={false} draggable={false} > -
- {(isClaiming || isVerifying(currentUser)) && ( - <> -

{t('account.linking.step1.title')}

-

{t('account.linking.step1.description')}

-
- setAgent(e.target.value)} - /> -
-
- - -
- - )} -
-
-

{t('account.linking.step2.title')}

-

- }} - /> -

-
- -
-
- - -
-
-
-

{t('account.linking.step3.title')}

-

{t('account.linking.step3.description')}

-
- - - {t('account.linking.step3.action')} - -
-
-
-

{t('account.linking.step4.title')}

-

{t('account.linking.step4.description')}

-
- - -
-
+ + +
- ), - [ - agent, - currentUser, - isClaiming, - onAbort, - onBack, - onClaim, - onCopyToken, - onNext, - onVerify, - t, - ] - ) + ) + } else if (user?.agent) { + actions = ( +
+ + +
+ ) + } else { + actions = ( +
+ +
+ ) + } return (

{t('account.linking.title')}

- {!isAccountLinked(currentUser) &&

{t('account.linking.none')}

} - {isAccountLinked(currentUser) && ( -

- - ), - }} - /> -

- )} - {!isClaiming && !isVerifying(currentUser) && getClaimButtons()} - {isClaiming && getCarousel()} +

{linkedAccount}

+ {actions}
) } -export interface VerifyAccountProps { - currentUser: User - setIssues: (issues: Array) => void -} - export default VerifyAccount diff --git a/src/components/verify-account/verify-account.less b/src/components/verify-account/verify-account.less index 365462fc..3c0b1907 100644 --- a/src/components/verify-account/verify-account.less +++ b/src/components/verify-account/verify-account.less @@ -1,4 +1,5 @@ @import '../../index.less'; +@import '../../assets/style/helper/button.less'; .account-linking { width: 100%; @@ -58,4 +59,4 @@ .claim-button { .positive-action-button; } -} \ No newline at end of file +} diff --git a/src/features/banner/filter.ts b/src/features/banner/filter.ts index 993aed63..d8644e03 100644 --- a/src/features/banner/filter.ts +++ b/src/features/banner/filter.ts @@ -1,3 +1,5 @@ +import { BannerListType } from './types' + export type BannerOrder = | 'relevance' | 'listAdded' @@ -18,4 +20,6 @@ export type BannerFilter = { maxEventTimestamp?: string proximityLatitude?: number proximityLongitude?: number + author?: string + listTypes?: BannerListType | BannerListType[] } diff --git a/src/features/banner/reducer.spec.ts b/src/features/banner/reducer.spec.ts deleted file mode 100644 index ea6ee966..00000000 --- a/src/features/banner/reducer.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - BannerActionTypes, - LOAD_BANNER, - LOAD_RECENT_BANNERS, -} from './actionTypes' -import bannerReducer from './reducer' -import { BannerState } from './types' - -describe('features > banner > bannerReducer', () => { - it(`load banner, if ${LOAD_BANNER} action is provided`, () => { - const initialState: BannerState = { - banners: [], - fullBanners: [], - recentBanners: [], - browsedBanners: [], - searchBanners: [], - agentBanners: [], - userBannerListBanners: [], - canBrowseMore: true, - canSearchMore: true, - hasMoreAgentBanners: true, - hasMoreUserBannerListBanners: true, - createdBanner: undefined, - mapBanners: [], - } - - const expectedState: BannerState = { - banners: [ - { - id: '1', - title: 'Banner 1', - numberOfMissions: 0, - numberOfSubmittedMissions: 0, - numberOfDisabledMissions: 0, - startLatitude: 0, - startLongitude: 0, - lengthMeters: 0, - formattedAddress: '', - picture: '', - }, - ], - fullBanners: [], - recentBanners: [], - browsedBanners: [], - searchBanners: [], - agentBanners: [], - userBannerListBanners: [], - canBrowseMore: true, - canSearchMore: true, - hasMoreAgentBanners: true, - hasMoreUserBannerListBanners: true, - createdBanner: undefined, - mapBanners: [], - } - - const action: BannerActionTypes = { - type: LOAD_BANNER, - payload: { - id: '1', - title: 'Banner 1', - numberOfMissions: 0, - startLatitude: 0, - startLongitude: 0, - lengthMeters: 0, - formattedAddress: '', - picture: '', - }, - } - - expect(bannerReducer(initialState, action)).toEqual(expectedState) - }) - it(`load recent banners, if ${LOAD_RECENT_BANNERS} action is provided`, () => { - const initialState: BannerState = { - banners: [], - fullBanners: [], - recentBanners: [], - browsedBanners: [], - searchBanners: [], - agentBanners: [], - userBannerListBanners: [], - canBrowseMore: true, - canSearchMore: true, - hasMoreAgentBanners: true, - hasMoreUserBannerListBanners: true, - createdBanner: undefined, - mapBanners: [], - } - - const expectedState: BannerState = { - banners: [], - fullBanners: [], - recentBanners: [ - { - id: '1', - title: 'Banner 1', - numberOfMissions: 0, - numberOfDisabledMissions: 0, - numberOfSubmittedMissions: 0, - startLatitude: 0, - startLongitude: 0, - lengthMeters: 0, - formattedAddress: '', - picture: '', - }, - ], - browsedBanners: [], - searchBanners: [], - agentBanners: [], - userBannerListBanners: [], - canBrowseMore: true, - canSearchMore: true, - hasMoreAgentBanners: true, - hasMoreUserBannerListBanners: true, - createdBanner: undefined, - mapBanners: [], - } - - const action: BannerActionTypes = { - type: LOAD_RECENT_BANNERS, - payload: [ - { - id: '1', - title: 'Banner 1', - numberOfMissions: 0, - startLatitude: 0, - startLongitude: 0, - lengthMeters: 0, - formattedAddress: '', - picture: '', - }, - ], - } - - expect(bannerReducer(initialState, action)).toEqual(expectedState) - }) -}) diff --git a/src/features/news/actionTypes.ts b/src/features/news/actionTypes.ts deleted file mode 100644 index 79c5fd20..00000000 --- a/src/features/news/actionTypes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RehydrateAction } from '../../storeTypes' -import { NewsItem } from './types' - -export const LOAD_NEWS = 'LOAD_NEWS' - -interface LoadNewsAction { - type: typeof LOAD_NEWS - payload: Array> -} - -export type NewsActionTypes = LoadNewsAction | RehydrateAction diff --git a/src/features/news/actions.ts b/src/features/news/actions.ts deleted file mode 100644 index 79510eec..00000000 --- a/src/features/news/actions.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Dispatch } from 'redux' -import { NewsActionTypes, LOAD_NEWS } from './actionTypes' -import * as api from './api' - -export const loadNewsAction = - () => async (dispatch: Dispatch) => { - const response = await api.getNews() - if (response.ok && response.data !== undefined) { - dispatch({ - type: LOAD_NEWS, - payload: response.data, - }) - } else if (!response.ok) { - throw new Error('Error loading announcements') - } - } diff --git a/src/features/news/hooks.ts b/src/features/news/hooks.ts new file mode 100644 index 00000000..938d218c --- /dev/null +++ b/src/features/news/hooks.ts @@ -0,0 +1,33 @@ +import { useEffect, useState } from 'react' +import { getNews } from './api' +import { NewsItem } from './types' + +interface State { + status: 'pending' | 'resolved' | 'rejected' + data: NewsItem[] +} + +export const useNewsList = function () { + const [state, setState] = useState({ + status: 'pending', + data: [], + }) + useEffect(() => { + const fetchData = async () => { + const response = await getNews() + if (response.ok) { + setState({ + status: 'resolved', + data: response.data, + }) + } else { + setState({ + status: 'rejected', + data: [], + }) + } + } + fetchData() + }, [setState]) + return state +} diff --git a/src/features/news/index.ts b/src/features/news/index.ts deleted file mode 100644 index d4cc455f..00000000 --- a/src/features/news/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as actionTypes from './actionTypes' -import { getNews } from './selectors' -import { loadNewsAction } from './actions' -import { NewsItem, NewsState } from './types' - -export { default as NewsReducer } from './reducer' -export { actionTypes } -export { getNews } -export { loadNewsAction as loadNews } -export type { NewsItem, NewsState } diff --git a/src/features/news/reducer.ts b/src/features/news/reducer.ts deleted file mode 100644 index fa15d63e..00000000 --- a/src/features/news/reducer.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { REHYDRATE } from 'redux-persist/es/constants' - -import { NewsActionTypes, LOAD_NEWS } from './actionTypes' -import { NewsState } from './types' - -const initialState: NewsState = { - news: [], -} - -export default (state = initialState, action: NewsActionTypes) => { - switch (action.type) { - case REHYDRATE: - return { - ...state, - ...action.payload?.news, - } - case LOAD_NEWS: - return { - ...state, - news: action.payload, - } - default: - return state - } -} diff --git a/src/features/news/selectors.ts b/src/features/news/selectors.ts deleted file mode 100644 index 760765a5..00000000 --- a/src/features/news/selectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { RootState } from '../../storeTypes' - -export const getNews = (state: RootState) => state.news.news diff --git a/src/features/news/types.ts b/src/features/news/types.ts index ab5619fa..90bd6d57 100644 --- a/src/features/news/types.ts +++ b/src/features/news/types.ts @@ -3,7 +3,3 @@ export interface NewsItem { content: string created: Date } - -export interface NewsState { - news: Array -} diff --git a/src/features/place/reducer.spec.ts b/src/features/place/reducer.spec.ts index 20563e23..305108d1 100644 --- a/src/features/place/reducer.spec.ts +++ b/src/features/place/reducer.spec.ts @@ -19,6 +19,9 @@ describe('features > place > placeReducer', () => { const expectedState = { countries: [{ id: '1' }], administrativeAreas: {}, + allPlaces: { '1': { id: '1' } }, + canSearchMore: false, + searchPlaces: [], } const action: PlaceActionTypes = { @@ -38,7 +41,7 @@ describe('features > place > placeReducer', () => { } const expectedState = { - allPlaces: [], + allPlaces: { '1': { id: '1' } }, countries: [], administrativeAreas: { de: [{ id: '1' }] }, searchPlaces: [], diff --git a/src/features/user/actionTypes.ts b/src/features/user/actionTypes.ts deleted file mode 100644 index 24bce889..00000000 --- a/src/features/user/actionTypes.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { RehydrateAction } from '../../storeTypes' -import { User } from './types' - -export const LOAD_USER = 'LOAD_USER' -export const CLAIM_USER = 'CLAIM_USER' -export const VERIFY_USER = 'VERIFY_USER' -export const UNLINK_USER = 'UNLINK_USER' -export const ABORT_CLAIM_USER = 'ABORT_CLAIM_USER' - -interface LoadUserAction { - type: typeof LOAD_USER - payload: Partial -} - -interface ClaimUserAction { - type: typeof CLAIM_USER - payload: Partial -} - -interface VerifyUserAction { - type: typeof VERIFY_USER - payload: Partial -} - -interface UnlinkUserAction { - type: typeof UNLINK_USER - payload: Partial -} - -interface AbortClaimUserAction { - type: typeof ABORT_CLAIM_USER - payload: Partial -} - -export type UserActionTypes = - | LoadUserAction - | ClaimUserAction - | VerifyUserAction - | UnlinkUserAction - | AbortClaimUserAction - | RehydrateAction diff --git a/src/features/user/actions.ts b/src/features/user/actions.ts deleted file mode 100644 index 19c8f1fd..00000000 --- a/src/features/user/actions.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Dispatch } from 'redux' -import i18n from '../../i18n' -import { - UserActionTypes, - LOAD_USER, - CLAIM_USER, - VERIFY_USER, - UNLINK_USER, - ABORT_CLAIM_USER, -} from './actionTypes' -import * as api from './api' - -export const loadCurrentUser = - () => async (dispatch: Dispatch) => { - const response = await api.getUser() - if (response.ok && response.data !== undefined) { - dispatch({ - type: LOAD_USER, - payload: response.data, - }) - } else if (!response.ok) { - throw new Error( - i18n.t('account.errors.loadUser', { - defaultValue: 'Error getting user info', - }) - ) - } - } - -export const claimUser = - (agent: string) => async (dispatch: Dispatch) => { - const response = await api.claimUser(agent) - if (response.ok && response.data !== undefined) { - dispatch({ - type: CLAIM_USER, - payload: response.data, - }) - } else if (!response.ok) { - throw new Error( - i18n.t('account.linking.errors.claim', { - defaultValue: 'Error claiming agent', - }) - ) - } - } - -export const verifyUser = () => async (dispatch: Dispatch) => { - const response = await api.verifyUser() - if (response.ok && response.data !== undefined) { - dispatch({ - type: VERIFY_USER, - payload: response.data, - }) - } else if (!response.ok) { - throw new Error( - i18n.t('account.linking.errors.verify', { - defaultValue: 'Error verifying agent', - }) - ) - } -} - -export const unlinkUser = () => async (dispatch: Dispatch) => { - const response = await api.unlinkUser() - if (response.ok && response.data !== undefined) { - dispatch({ - type: UNLINK_USER, - payload: response.data, - }) - } else if (!response.ok) { - throw new Error( - i18n.t('account.linking.errors.unlink', { - defaultValue: 'Error unlinking agent', - }) - ) - } -} - -export const abortClaimUser = - (agent: string) => async (dispatch: Dispatch) => { - const response = await api.abortClaimUser(agent) - if (response.ok && response.data !== undefined) { - dispatch({ - type: ABORT_CLAIM_USER, - payload: response.data, - }) - } else if (!response.ok) { - throw new Error( - i18n.t('account.linking.errors.abort', { - defaultValue: 'Error aborting claim', - }) - ) - } - } diff --git a/src/features/user/api.ts b/src/features/user/api.ts index abaaa6c4..075d3995 100644 --- a/src/features/user/api.ts +++ b/src/features/user/api.ts @@ -1,42 +1,11 @@ import { api } from '../../api' import { User } from './types' -const isMock = import.meta.env.VITE_USE_MOCK === 'true' - -export const getUser = () => - isMock ? { data: {}, ok: true, status: 200 } : api.get('user') +export const getUser = () => api.get('user') export const claimUser = (agent: string) => - isMock - ? { - data: { - verificationAgent: 'mock', - verificationToken: 'some-random-string', - }, - ok: true, - status: 200, - } - : api.post(`user/claim?agent=${agent}`) - -export const verifyUser = () => - isMock - ? { - data: >{ - agent: { name: 'mock', faction: 'enlightened' }, - }, - ok: true, - status: 200, - } - : api.post('user/verify') + api.post(`user/claim?agent=${agent}`) -export const unlinkUser = () => - isMock ? { data: {}, ok: true, status: 200 } : api.post('user/unlink') +export const unlinkUser = () => api.post('user/unlink') -export const abortClaimUser = (agent: string) => - isMock - ? { - data: {}, - ok: true, - status: 200, - } - : api.delete(`user/claim?agent=${agent}`) +export const abortClaimUser = () => api.delete(`user/claim`) diff --git a/src/features/user/index.ts b/src/features/user/index.ts index 2ec07155..7429ab8b 100644 --- a/src/features/user/index.ts +++ b/src/features/user/index.ts @@ -1,16 +1,5 @@ -import { - loadCurrentUser, - claimUser, - verifyUser, - unlinkUser, - abortClaimUser, -} from './actions' -import { getCurrentUser } from './selectors' import { User } from './types' import { createAgentUri } from './helpers' -export { default as UserReducer } from './reducer' -export { loadCurrentUser, claimUser, verifyUser, unlinkUser, abortClaimUser } -export { getCurrentUser } export type { User } export { createAgentUri } diff --git a/src/features/user/reducer.ts b/src/features/user/reducer.ts deleted file mode 100644 index 8806e289..00000000 --- a/src/features/user/reducer.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { REHYDRATE } from 'redux-persist/es/constants' -import { - UserActionTypes, - LOAD_USER, - CLAIM_USER, - VERIFY_USER, - UNLINK_USER, - ABORT_CLAIM_USER, -} from './actionTypes' - -const initialState = { - currentUser: {}, -} - -export default (state = initialState, action: UserActionTypes) => { - switch (action.type) { - case REHYDRATE: - return { - ...state, - ...action.payload?.user, - } - case LOAD_USER: - case CLAIM_USER: - case VERIFY_USER: - case UNLINK_USER: - case ABORT_CLAIM_USER: - return { - ...state, - currentUser: action.payload, - } - default: - return state - } -} diff --git a/src/features/user/selectors.ts b/src/features/user/selectors.ts deleted file mode 100644 index bc51e308..00000000 --- a/src/features/user/selectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { RootState } from '../../storeTypes' - -export const getCurrentUser = (state: RootState) => state.user.currentUser diff --git a/src/features/user/types.ts b/src/features/user/types.ts index baa13648..28f4fdea 100644 --- a/src/features/user/types.ts +++ b/src/features/user/types.ts @@ -1,11 +1,7 @@ import { NamedAgent } from '../mission/types' export interface User { - agent: NamedAgent - verificationAgent: string - verificationToken: string -} - -export interface UserState { - currentUser: User + agent?: NamedAgent + verificationAgent?: string + verificationMessage?: string } diff --git a/src/i18n.ts b/src/i18n.ts index e778ad78..86433b43 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -10,18 +10,13 @@ const supportedLngs = { const getLanguageToUse = () => { const languages = navigator.languages - console.log('preferred language order:', JSON.stringify(languages)) for (const language of languages) { for (const pair of Object.entries(supportedLngs)) { const code = pair[0] const regex = pair[1] - if (regex.test(language)) { - console.log('using language:', code) - return code - } + if (language.match(regex)) return code } } - console.log('using fallback language: en') return 'en' } diff --git a/src/index.less b/src/index.less index 221ea247..3a8908f4 100644 --- a/src/index.less +++ b/src/index.less @@ -1,102 +1,17 @@ body { margin: 0; - font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: + 'Roboto', + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + 'Oxygen', + 'Ubuntu', + 'Cantarell', + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} - -.px1 { - padding-left: 1rem; - padding-right: 1rem; -} - -.p-1 { - padding: 1rem; -} - -.group { - display: flex; - justify-content: space-between; -} - -@color-panel-bg: #404040; - -.bg-button { - box-shadow: 0 0 2px 0 rgba(157, 96, 212, 0.5) !important; - border: solid 2px transparent !important; - background-origin: border-box !important; - background-clip: content-box, border-box !important; - box-shadow: 2px 1000px 1px #000 inset !important; - transition: linear-gradient ease 0.6s; - display: flex; - align-items: center; - justify-content: center; - - &.bg-button-default { - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, #109393, #15D4B2) !important; - - @media (hover: hover) and (pointer: fine) { - &:hover { - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(0deg, #109393, #15D4B2) !important; - } - } - } -} - -.button-default { - margin: 0 !important; - background-color: #2e2e2e; - border: none; - display: block; - border-radius: 4px; - width: 100%; - box-shadow: 0 0 2px 0 rgba(157, 96, 212, 0.5) !important; - border: solid 2px transparent !important; - background-origin: border-box !important; - background-clip: content-box, border-box !important; - box-shadow: 2px 1000px 1px #1B1B1B inset !important; - transition: linear-gradient ease 0.6s; - text-align: center; - color: white !important; - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(180deg, #109393, #15D4B2) !important; - - @media (hover: hover) and (pointer: fine) { - &:hover { - background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), linear-gradient(0deg, #109393, #15D4B2) !important; - } - } -} - -.positive-action-button { - background: #077561 !important; - color: white !important; - padding: 0.5em 2em; - border: none; - border-radius: 5px; - cursor: pointer; - - &:disabled { - background: gray; - color: lightgray; - cursor: not-allowed; - } - -} - -.page-container { - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -.nobr { - white-space: nowrap; -} diff --git a/src/index.tsx b/src/index.tsx index 56f03a45..077343b0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import App from './App' import { store, persistor } from './store' import './index.less' -import './mobile.less' +import './assets/style/mobile.less' import '@fontsource/roboto/400.css' import '@fontsource/roboto/400-italic.css' diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index f49cd37c..992db037 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -15,20 +15,13 @@ "action": "Weiter" }, "step2": { - "title": "2. Code kopieren", - "description": "Wir haben einen Code für dich generiert, der dir ermöglicht, \"{{agent}}\" mit deinem Profil zu verknüpfen. Du musst den Code für den nächsten Schritt kopieren.", + "title": "2. Nachricht kopieren", + "description": "Wir haben eine Nachricht für dich generiert, die dir ermöglicht, \"{{agent}}\" mit deinem Profil zu verknüpfen. Du musst die Nachricht für den nächsten Schritt kopieren.", "action": "Kopieren und weiter" }, "step3": { - "title": "3. Code posten", - "description": "Du musst den Code im Ingress-Community-Forum als Aktivität posten. (Achte darauf, dass du dort angemeldet bist!)", - "action": "Weiter zum Forum", - "link": "//community.ingress.com/en/activity" - }, - "step4": { - "title": "4. Prüfung abschließen", - "description": "Hast du deinen Code bereits gepostet? Dann können wir ihn prüfen.", - "action": "Prüfen" + "title": "3. Nachricht senden", + "description": "Du musst die Nachricht in Ingress im fraktionsübergreifenden Chat senden. Danach kann es einige Zeit dauern, bis die Verlinkung abgeschlossen ist. Bitte habe etwas Geduld." } } }, @@ -71,10 +64,10 @@ "step3": { "title": "<0>3 Informationen", "bannerTitle": "Banner-Titel", - "description": "Beschreibung", + "description": "Beschreibung ", "warning": { - "title": "Warnungstext", - "subtitle": "Wird in einer auffallenderen Farbe angezeigt" + "title": "Warnungstext ", + "subtitle": "Wird in einer auffallenderen Farbe angezeigt. Markdown wird unterstützt." }, "plannedOfflineDate": { "title": "Geplantes Offline-Datum", @@ -90,6 +83,7 @@ }, "options": "Optionen", "help": { + "markdown": "

Dieses Feld unterstützt einfaches Markdown. Die folgenden Formattierungsoptionen sind möglich:

", "banner": "Banner: Menge von Missionen, die in der richtigen Reihenfolge abgeschlossen werden muss, um ein Gesamtbild in deinem Scanner zu erzeugen.", "collection": "Sammlung: Menge von Missionen, die in einer beliebigen Reihenfolge abgeschlossen werden kann, weil sie kein Gesamtbild darstellen." }, @@ -243,7 +237,7 @@ "title": "Häufig gefragt", "question1": { "title": "Was ist bannergress.com?", - "answer": "

Nach dem Verlust unserer beliebten Ingress-Fansite haben sich einige Agents zusammengetan, um dieses Projekt auf die Beine zu stellen.

  • alle einbinden
  • Open Source werden (bald)
  • dauerhafte Bibliothek für Banner sein
  • nie aufhören, weiterzuentwickeln
" + "answer": "

Nach dem Verlust unserer beliebten Ingress-Fansite haben sich einige Agents zusammengetan, um dieses Projekt auf die Beine zu stellen. Das Ziel dieses Projektes ist es:

  • alle einzubinden
  • Open Source zu sein
  • die dauerhafte Bibliothek für Banner sein
  • nie aufzuhören, weiterzuentwickeln
" }, "question2": { "title": "Wie bekomme ich Ingress-Missionen nach bannergress.com? (Teil 1: Installation)", @@ -262,12 +256,12 @@ "answer": "

...das Tool zum Erstellen von Missionen?

...ein Werkzeug zum Schneiden von Bildern?


..die IITC-CE?

...das Bannergress-Plugin?

...ein IITC-Plugin, das mehr als 25 Missionen anzeigt?

" }, "question6": { - "title": "How do I get my own Ingress missions to bannergress.com, using the Ingress Mission Creator?", - "answer": "
  1. You need either the IITC Button browser add-on or Tampermonkey.
  2. Install the bannergress creator plugin.
  3. Open the mission creator (you may be asked to log into your Ingress account). Then, click on “Sync with Bannergress” in the menu (you may be asked to log into your Bannergress account).
" + "title": "Wie bekomme ich meine eigenen Ingress-Missionen auf bannergress.com, indem ich den Ingress Mission Creator benutze?", + "answer": "
  1. Sie benötigen entweder das IITC Button browser add-on oder Tampermonkey.
  2. Installieren Sie das bannergress creator plugin.
  3. Öffnen Sie den the mission creator (Sie werden möglicherweise aufgefordert, sich in Ihr Ingress-Konto einzuloggen). Klicken Sie dann im Menü auf „Sync with Bannergress“ (Sie werden eventuell aufgefordert, sich in Ihr Bannergress-Konto einzuloggen).
" }, "question7": { - "title": "How can I edit my own banners?", - "answer": "
Bannergress needs to know your Ingress agent name before it can provide you with edit permissions:
  1. Sign into Bannergress.
  2. Open your Bannergress account page.
  3. Click on \"Link Ingress Account\" and follow the instructions.
After that, you have an edit button on all banners that you own.
" + "title": "Wie kann ich meine eigenen Banner bearbeiten?", + "answer": "
Bannergress muss den Namen Ihres Ingress-Agenten kennen, bevor es Ihnen Bearbeitungsrechte erteilen kann:
  1. Melden Sie sich bei Bannergress an.
  2. Öffnen Sie Ihre Bannergress Kontoseite.
  3. Klicken Sie auf \"Link Ingress Account\" und folgen Sie den Anweisungen.
Danach haben Sie auf allen Bannern, die Sie besitzen, eine Schaltfläche zum Bearbeiten.
" } }, "help": { @@ -288,6 +282,10 @@ } } }, + "error": { + "title": "Fehler die Seite wurde nicht gefunden", + "newestBanners": "Versuche mit den neuesten Bannern zu beginnen" + }, "search": { "title": "Suche nach: {{searchTerm}}", "button": "Suchen", @@ -378,4 +376,4 @@ "eventAt": "Event am .", "eventFromTo": "Event vom bis ." } -} \ No newline at end of file +} diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index df853d16..e1701e21 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -15,20 +15,13 @@ "action": "Continue" }, "step2": { - "title": "2. Copy the token", - "description": "We have generated a token for you that allows to link \"{{agent}}\" to your account. You need to copy this token for the next step.", + "title": "2. Copy the message", + "description": "We have generated a message for you that allows to link \"{{agent}}\" to your account. You need to copy this message for the next step.", "action": "Copy and continue" }, "step3": { - "title": "3. Post the token", - "description": "You need to post the token as an activity on the Ingress Community Forum. (Make sure you are logged in there!)", - "action": "Take me there and continue", - "link": "//community.ingress.com/en/activity" - }, - "step4": { - "title": "4. Complete verification", - "description": "Have you posted your token yet? Let us check.", - "action": "Verify" + "title": "3. Send the message", + "description": "You need to send the message to the Ingress in-game cross-faction chat. After that, it may take some time until the account linking is complete. Please be patient." } } }, @@ -71,10 +64,10 @@ "step3": { "title": "<0>3 Information", "bannerTitle": "Banner Title", - "description": "Description", + "description": "Description ", "warning": { - "title": "Warning Text", - "subtitle": "Displays in a more noticeable color" + "title": "Warning Text ", + "subtitle": "Displays in a more noticeable color." }, "plannedOfflineDate": { "title": "Planned Offline Date", @@ -90,6 +83,7 @@ }, "options": "Options", "help": { + "markdown": "

You can use basic Markdown in this field. The following formatting options are supported:

", "banner": "Banner: Collection of missions that must be completed in sequence to complete the picture in your profile.", "collection": "Collection: Collection of missions that can be completed in any order as they don't form a picture." }, @@ -244,7 +238,7 @@ "title": "Frequently Asked", "question1": { "title": "What is bannergress.com?", - "answer": "

After the horrific loss of our favorite Ingress fan page, some agents came together to start this project.

  • get everyone involved
  • be open source (soon™)
  • be the long-term solution for finding banners
  • never stop developing
" + "answer": "

After the horrific loss of our favorite Ingress fan page, some agents came together to start this project. The aim of the project is to:

  • get everyone involved
  • be open source
  • be the long-term solution for finding banners
  • never stop developing
" }, "question2": { "title": "How do I get Ingress missions to bannergress.com? (Part 1: setup)", @@ -289,6 +283,10 @@ } } }, + "error": { + "title": "Error the page was not found", + "newestBanners": "Try to start with the newest banners" + }, "search": { "title": "Search for {{searchTerm}}", "button": "Search", @@ -380,4 +378,4 @@ "eventAt": "Event at .", "eventFromTo": "Event from to ." } -} \ No newline at end of file +} diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index c742ebd1..09d560e6 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -15,20 +15,13 @@ "action": "Continuar" }, "step2": { - "title": "2. Copia el token", - "description": "Hemos generado un token que te permite asociar la cuenta de Ingress \"{{agent}}\" a tu cuenta de Bannergress. Necesitarás copiar el token para el siguiente paso.", + "title": "TODO 2. Copia el token", + "description": "TODO Hemos generado un token que te permite asociar la cuenta de Ingress \"{{agent}}\" a tu cuenta de Bannergress. Necesitarás copiar el token para el siguiente paso.", "action": "Copiar y continuar" }, "step3": { - "title": "3. Publica tu token", - "description": "Has de publicar tu token como una actividad en los foros de Ingress. (¡Asegúrate de iniciar sesión primero!)", - "action": "Llévame ahí y continuar", - "link": "//community.ingress.com/en/activity" - }, - "step4": { - "title": "4. Completar verificación", - "description": "Has publicado tu token en el foro? Comprobémoslo.", - "action": "Verificar" + "title": "TODO 3. Publica tu token", + "description": "TODO Has de publicar tu token como una actividad en los foros de Ingress. (¡Asegúrate de iniciar sesión primero!)" } } }, @@ -71,9 +64,14 @@ "step3": { "title": "<0>3 Información", "bannerTitle": "Título", - "description": "Descripción", + "description": "Descripción ", + "warning": { + "title": "Texto de advertencia ", + "subtitle": "Se muestra en un color más fácil de notar." + }, "options": "Opciones", "help": { + "markdown": "

Puedes usar Markdown básico en este campo. Las siguientes opciones de formato están disponibles:

", "banner": "Banner: Colección de misiones que se han de completar seguidas para formar una imagen completa en tu perfil.", "collection": "Colección: Colección de misiones que se pueden completar en cualquier orden." }, @@ -219,7 +217,7 @@ "title": "Preguntas frecuentes", "question1": { "title": "What is bannergress.com?", - "answer": "

After the horrific loss of our favorite Ingress fan page, some agents came together to start this project.

  • get everyone involved
  • be open source (soon™)
  • be the long-term solution for finding banners
  • never stop developing
" + "answer": "

After the horrific loss of our favorite Ingress fan page, some agents came together to start this project. The aim of the project is to:

  • get everyone involved
  • be open source
  • be the long-term solution for finding banners
  • never stop developing
" }, "question2": { "title": "How do I get Ingress missions to bannergress.com? (Part 1: setup)", @@ -264,6 +262,10 @@ } } }, + "error": { + "title": "Error la página no fue encontrada", + "newestBanners": "Intenta empezar con los banners más nuevos" + }, "search": { "title": "Buscando {{searchTerm}}", "button": "Bucar", @@ -283,6 +285,7 @@ "missing_other": "faltan {{count}}", "markedOffline": "Misión marcada como no disponible", "viewOnIntel": "Ver misión en el Intel de Ingress", + "viewInScanner": "Vista en el Escaner", "inBanner": "Misiones en el banner", "placeholder": "Misión de relleno {{index}}", "notFound": "No se han encontrado misiones", @@ -354,4 +357,4 @@ "eventAt": "Evento el .", "eventFromTo": "Evento del al ." } -} \ No newline at end of file +} diff --git a/src/mobile.less b/src/mobile.less deleted file mode 100644 index c03c2d7b..00000000 --- a/src/mobile.less +++ /dev/null @@ -1,168 +0,0 @@ -.bottom-menu { - display: none; -} - -@mobile-detection: ~" screen and (max-width: 880px) "; -@mobile-xs-detection: ~" screen and (max-width: 321px) "; -@mobile-l-detection: ~" screen and (min-width: 426px) "; - -@media only @mobile-xs-detection { - .banner-list { - justify-content: center; - .banner-list-entry { - width: 296px; - } - } -} - -@media only @mobile-l-detection { - .banner-card-picture-container { - height: 200px; - } -} - -@media only @mobile-detection { - .hide-on-mobile { - display: none !important; - } - - .brand-logo { - width: 50vw; - } - - .banner-info-page { - height: 100%; - - .banner-card-title { - display: none !important; - } - .banner-info-overview .banner-info-container { - padding-right: unset; - width: 320px; - } - } - - .banner-info-with-map-container { - display: flex !important; - flex-direction: column !important; - height: inherit !important; - padding: 0 !important; - - .banner-info-with-map { - margin: 0 !important; - padding: 0 !important; - justify-content: center !important; - height: 100% !important; - overflow-y: auto !important; - - .banner-info { - min-height: 0 !important; - padding: 0.6em !important; - width: 100% !important; - max-width: 864px !important; - min-width: 320px !important; - height: auto !important; - overflow-y: unset !important; - } - - .banner-info-left-pane-missions { - .banner-card { - display: none !important; - } - } - - .mission-card { - max-width: none !important; - } - - .banner-info-additional { - height: 100% !important; - } - - .leaflet-container { - height: 100% !important; - width: 100% !important; - min-height: 0 !important; - } - } - - .banner-info-card { - margin-top: 0 !important; - } - - .ant-tabs-nav { - display: none !important; - margin: none !important; - } - - .ant-tabs-tabpane { - margin-top: 0; - } - } - - .banner-count-place { - display: none; - } - - .browser { - .place-accordion { - display: block; - } - - .places-list { - display: none; - } - } - - .bottom-menu { - display: block; - - .menu-main { - a { - flex: 1; - min-width: 0 !important; - } - } - } - - .top-menu .menu-main { - display: none; - } - - .map-explorer { - width: 100% !important; - height: 100%; - display: flex; - flex-direction: column; - } - - .banner-card-picture { - height: auto; - aspect-ratio: 2; - - &.banner-lines-1 { - aspect-ratio: 6; - } - - &.banner-lines-2 { - aspect-ratio: 3; - } - } - - .announcement-and-recent-banners { - margin: 0.4rem; - } - - .banner-list { - justify-content: center; - .banner-list-entry { - width: 360px; - } - } -} - -@media not @mobile-detection { - .hide-on-desktop { - display: none !important; - } -} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 0ab99ce1..d3e9c280 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,53 +1,25 @@ -import React, { useCallback, useState } from 'react' -import _ from 'underscore' +import { FC } from 'react' import { useTranslation } from 'react-i18next' import NewsList from '../components/news-list' import RecentBanners from '../components/recent-banners' import { UserBannerListPreview } from '../components/user-banner-list-preview' import FooterMain from '../components/footer-main' -import { Issue, IssuesList } from '../components/Issues-list' import './home.less' import EventsPreview from '../components/events-preview/EventsPreview' -export const Home: React.FC = () => { - const [issues, setIssues] = useState>([]) +export const Home: FC = () => { const { t } = useTranslation() const titleList: string = t('banners.latest') - const addIssues = useCallback( - (iss: Array) => { - setIssues((prevIssues) => - _(prevIssues) - .chain() - .union(iss) - .uniq(false, (i) => i.key) - .value() - ) - }, - [setIssues] - ) - - const resetIssue = useCallback( - (key: string) => { - setIssues((prevIssues) => _(prevIssues).filter((i) => i.key !== key)) - }, - [setIssues] - ) - return (
- - + - +
diff --git a/src/pages/account/account.tsx b/src/pages/account/account.tsx index 59b891f5..dffad1ed 100644 --- a/src/pages/account/account.tsx +++ b/src/pages/account/account.tsx @@ -1,62 +1,28 @@ -import React, { useCallback, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { ThunkDispatch } from 'redux-thunk' +import { FC } from 'react' import { useTranslation } from 'react-i18next' -import _ from 'underscore' -import { getCurrentUser, loadCurrentUser } from '../../features/user' -import { UserActionTypes } from '../../features/user/actionTypes' -import { RootState } from '../../storeTypes' import UserPicture from '../../components/login/user-picture' import UserName from '../../components/login/user-name' -import { Agent } from '../../components/agent' -import { Issue, IssuesList } from '../../components/Issues-list' import { VerifyAccount } from '../../components/verify-account' import './account.less' -type AppDispatch = ThunkDispatch - -const Account: React.FC = () => { - const dispatch: AppDispatch = useDispatch() - const currentUser = useSelector(getCurrentUser) - const [issues, setIssues] = useState>([]) - const { t } = useTranslation(undefined, { keyPrefix: 'account' }) - - useEffect(() => { - dispatch(loadCurrentUser()).catch((err) => - setIssues([ - { key: 'load', message: err.message, type: 'error', field: 'verify' }, - ]) - ) - }, [dispatch, setIssues]) - - const onCloseIssue = useCallback( - (issue: Issue) => { - setIssues(_(issues).without(issue)) - }, - [issues] - ) +const Account: FC = () => { + const { t } = useTranslation() return (
- -

{t('title')}

+

{t('account.title')}

- {currentUser && currentUser.agent && ( - - - - )}
- +
) diff --git a/src/pages/agent/Agent.tsx b/src/pages/agent/Agent.tsx index 28e5f3b6..74bc0eea 100644 --- a/src/pages/agent/Agent.tsx +++ b/src/pages/agent/Agent.tsx @@ -1,209 +1,47 @@ -import React, { Fragment } from 'react' -import { connect } from 'react-redux' -import { Row, Layout } from 'antd' -import { withRouter, RouteComponentProps } from 'react-router-dom' +import { FC, Fragment } from 'react' +import { useParams } from 'react-router-dom' import { Helmet } from 'react-helmet' -import { Trans } from 'react-i18next' +import { useTranslation } from 'react-i18next' -import { RootState } from '../../storeTypes' -import { - Banner, - getAgentBanners, - getHasMoreAgentBanners, - loadAgentBanners as loadAgentBannersAction, -} from '../../features/banner' - -import { withAuthenticated } from '../../hocs/WithAuthenticated' - -import BannerOrderChooser from '../../components/banner-order-chooser' -import BannerList from '../../components/banner-list' import FooterMain from '../../components/footer-main' import LoginRequired from '../../components/login/login-required' - -import './agent.less' +import InfiniteBannerList from '../../components/infinite-banner-list/InfiniteBannerList' import { BannerFilter } from '../../features/banner/filter' -class Agent extends React.Component { - constructor(props: AgentProps) { - super(props) - this.state = { - filter: { - orderBy: 'created', - orderDirection: 'DESC', - online: true, - }, - agentName: '', - pageBanners: 0, - bannersStatus: 'initial', - } - } - - static getDerivedStateFromProps(props: AgentProps, state: AgentState) { - const { match } = props - const { agentName } = state - const newAgentName = decodeURIComponent(match.params.agentName) - - if (agentName !== newAgentName) { - return { - agentName: newAgentName, - pageBanners: 0, - } - } - - return null - } - - componentDidMount() { - const { filter, agentName } = this.state - - this.doFetchBanners(agentName, filter, 0) - } - - componentDidUpdate(prevProps: AgentProps, prevState: AgentState) { - const { authenticated: prevAuthenticated } = prevProps - const { authenticated } = this.props - - const { agentName: prevAgentName } = prevState - const { agentName, filter } = this.state - - if (prevAgentName !== agentName || prevAuthenticated !== authenticated) { - this.doFetchBanners(agentName, filter, 0) - } - } - - onFilterChanged = (filter: BannerFilter) => { - const { agentName } = this.state - this.setState({ - filter, - pageBanners: 0, - }) - this.doFetchBanners(agentName, filter, 0) - } - - onLoadMoreBanners = () => { - const { fetchBanners } = this.props - const { filter, pageBanners, agentName } = this.state - this.setState({ pageBanners: pageBanners + 1 }) - return fetchBanners(agentName, filter, pageBanners + 1) - } - - getPageTitle() { - const { agentName } = this.state - const title = `Agent ${agentName}` - return title - } - - async doFetchBanners( - agentName: string, - filter: BannerFilter, - pageBanners: number - ) { - const { fetchBanners, authenticated } = this.props - - if (authenticated) { - this.setState({ bannersStatus: 'loading' }) - await fetchBanners(agentName, filter, pageBanners) - this.setState({ bannersStatus: 'success' }) - } - } - - render() { - const title: string = this.getPageTitle() - const { bannersStatus, filter } = this.state - const { banners, hasMoreBanners } = this.props - - return ( - - - {title} - -
-
-

{title}

- - -

- Banners -

- - - - - - {bannersStatus === 'success' && ( - <> - {banners.length > 0 && ( - <> - - - - - )} - - {banners.length === 0 && ( - <> - - - No banners found - - - - )} - - )} +import './agent.less' - {(bannersStatus === 'initial' || - bannersStatus === 'loading') && ( - Loading... - )} - -
-
- +const Agent: FC = () => { + const { agentName } = useParams<{ agentName: string }>() + const { t } = useTranslation() + const title = `Agent ${agentName}` + + const initialFilter: BannerFilter = { + author: agentName, + orderBy: 'created', + orderDirection: 'DESC', + online: true, + } + + return ( + + + {title} + +
+
+

{title}

+ +

{t('banners.title')}

+ +
- - ) - } -} - -export interface AgentProps extends RouteComponentProps<{ agentName: string }> { - banners: Array - hasMoreBanners: Boolean - fetchBanners: ( - agentName: string, - filter: BannerFilter, - pageBanners: number - ) => Promise - authenticated: Boolean -} - -interface AgentState { - filter: BannerFilter - agentName: string - pageBanners: number - bannersStatus: 'initial' | 'success' | 'loading' | 'error' -} - -const mapStateToProps = (state: RootState) => ({ - banners: getAgentBanners(state), - hasMoreBanners: getHasMoreAgentBanners(state), -}) - -const mapDispatchToProps = { - fetchBanners: loadAgentBannersAction, + +
+
+ ) } -export default connect( - mapStateToProps, - mapDispatchToProps -)(withAuthenticated(withRouter(Agent))) +export default Agent diff --git a/src/pages/banner-info/BannerInfo.tsx b/src/pages/banner-info/BannerInfo.tsx index 164fe531..af7e9ddd 100644 --- a/src/pages/banner-info/BannerInfo.tsx +++ b/src/pages/banner-info/BannerInfo.tsx @@ -66,14 +66,7 @@ class BannerInfo extends React.Component { ) } if (status !== 'error') { - return ( - - ) + return } return ( No banners found with that id diff --git a/src/pages/banner-info/banner-info.less b/src/pages/banner-info/banner-info.less index 680c79d1..6e96e01c 100644 --- a/src/pages/banner-info/banner-info.less +++ b/src/pages/banner-info/banner-info.less @@ -1,3 +1,24 @@ +@mobile-detection: ~" screen and (max-width: 880px) "; + .banner-info-page { height: 100%; -} \ No newline at end of file + + @media only @mobile-detection { + height: 100%; + } + + .banner-card-title { + @media only @mobile-detection { + display: none !important; + } + } + + .banner-info-overview { + .banner-info-container { + @media only @mobile-detection { + padding-right: unset; + width: 320px; + } + } + } +} diff --git a/src/pages/browser/Browser.tsx b/src/pages/browser/Browser.tsx index 5a9e54c4..07917aa2 100644 --- a/src/pages/browser/Browser.tsx +++ b/src/pages/browser/Browser.tsx @@ -26,7 +26,7 @@ import BannerList from '../../components/banner-list' import BannerOrderChooser from '../../components/banner-order-chooser' import FooterMain from '../../components/footer-main' import { PlaceAccordion } from '../../components/place-accordion/PlaceAccordion' -import SVGMap from '../../img/icons/map.svg?react' +import SVGMap from '../../assets/img/icons/map.svg?react' import './browser.less' import LoadingOverlay from '../../components/loading-overlay' @@ -178,14 +178,7 @@ class Browser extends React.Component { const { filter, selectedPlaceId, status } = this.state if (status === 'initial') { - return ( - - ) + return } let administrativeAreas: Array | null = null @@ -281,7 +274,7 @@ class Browser extends React.Component { banners={banners} hasMoreBanners={hasMore} loadMoreBanners={this.onLoadMoreBanners} - applyBannerListStlyes + applyBannerListStyles hideBlacklisted showDetailsButton={false} /> diff --git a/src/pages/browser/browser.less b/src/pages/browser/browser.less index 104895d2..a89ea3b8 100644 --- a/src/pages/browser/browser.less +++ b/src/pages/browser/browser.less @@ -1,10 +1,12 @@ +@mobile-detection: ~" screen and (max-width: 880px) "; + .browser-icon { - color: #EAEAEA; - fill: #EAEAEA; - vertical-align: middle; - margin-left: 0.5em; - height: 0.9em; - } + color: var(--color-white-light); + fill: var(--color-white-light); + vertical-align: middle; + margin-left: 0.5em; + height: 0.9em; +} .browser { display: flex; @@ -12,10 +14,18 @@ .places-list { flex: 1; max-width: 400px; + + @media only @mobile-detection { + display: none; + } } .place-accordion { display: none; + + @media only @mobile-detection { + display: block; + } } .places-banners { diff --git a/src/pages/create-banner/CreateBanner.tsx b/src/pages/create-banner/CreateBanner.tsx index bb4ad688..001a3f47 100644 --- a/src/pages/create-banner/CreateBanner.tsx +++ b/src/pages/create-banner/CreateBanner.tsx @@ -2,7 +2,7 @@ import React from 'react' import { connect } from 'react-redux' import { withRouter, RouteComponentProps, Prompt } from 'react-router-dom' import { Beforeunload } from 'react-beforeunload' -import { Input, InputNumber, Button } from 'antd' +import { Input, InputNumber, Button, Tooltip } from 'antd' import { Helmet } from 'react-helmet' import _ from 'underscore' import Scrollbars from 'react-custom-scrollbars-2' @@ -45,8 +45,9 @@ import { import AdvancedOptions from '../../components/advanced-options' import { IssuesList } from '../../components/Issues-list' import LoginRequired from '../../components/login/login-required' -import SVGRightArrow from '../../img/icons/right_arrow.svg?react' -import SVGCross from '../../img/icons/cross.svg?react' +import SVGRightArrow from '../../assets/img/icons/right_arrow.svg?react' +import SVGCross from '../../assets/img/icons/cross.svg?react' +import SVGHelp from '../../assets/img/icons/help-round.svg?react' import { getBannerIssues, MAX_MISSIONS } from './getBannerIssues' import './create-banner.less' @@ -775,6 +776,20 @@ class CreateBanner extends React.Component< bannerTitle, detectedLength ) + const markdownHelp = ( + , + code: , + pre:
,
+          p: 

, + ul: