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/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..d7861602 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +npm run stylelint:fix +npm run eslint:fix diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 00000000..272fe7ac --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,31 @@ +{ + "extends": ["stylelint-config-standard-scss"], + "plugins": ["stylelint-order"], + "rules": { + "alpha-value-notation": "number", + "at-rule-no-unknown": null, + "declaration-empty-line-before": "never", + "declaration-no-important": true, + "function-name-case": "lower", + "media-feature-range-notation": "prefix", + "order/properties-alphabetical-order": true, + "order/order": [ + "custom-properties", + "dollar-variables", + { + "type": "at-rule", + "name": "include", + "hasBlock": false + }, + "declarations", + "at-variables", + "at-rules", + "rules" + ], + "color-function-notation": "modern", + "scss/double-slash-comment-empty-line-before": null, + "scss/at-rule-no-unknown": null, + "selector-max-id": 0, + "unit-allowed-list": [ "%", "em", "fr", "deg", "ms", "px", "rem", "s", "vh", "vw"] + } +} 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 deleted file mode 100644 index b0c753c7..00000000 --- a/src/App.less +++ /dev/null @@ -1,135 +0,0 @@ -@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; -} - -#root { - height: 100%; - - &>.ant-layout { - height: 100%; - } -} - -.main { - height: 100%; - - &>.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.scss b/src/App.scss new file mode 100644 index 00000000..704f0275 --- /dev/null +++ b/src/App.scss @@ -0,0 +1,18 @@ +@use "/src/assets/stylesheets/main" as *; + +#root { /* stylelint-disable-line */ + height: 100%; + + & > .ant-layout { /* stylelint-disable-line */ + height: 100%; + } +} + +.main { + height: 100%; + + & > .container { + height: 100%; + overflow: auto; + } +} diff --git a/src/App.tsx b/src/App.tsx index b03f394f..5650fdf1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,12 +21,13 @@ 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' import './i18n' -import './App.less' +import './App.scss' import Events from './pages/events/Events' const App: React.FC = () => { @@ -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/stylesheets/base/_class.scss b/src/assets/stylesheets/base/_class.scss new file mode 100644 index 00000000..678d9646 --- /dev/null +++ b/src/assets/stylesheets/base/_class.scss @@ -0,0 +1,68 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + +.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; +} + +.warning-text { + color: var(--color-warning); +} + +.error-text { + color: var(--color-error); +} + +.subtitle { + color: var(--color-gray); +} + + +.hide-on-mobile { + @include media-max-md { + display: none !important; + } +} + +.hide-on-desktop { + @include media-min-md { + display: none !important; + } +} + +.banner-count-place { + @include media-max-md { + display: none; + } +} diff --git a/src/assets/stylesheets/base/_mixins.scss b/src/assets/stylesheets/base/_mixins.scss new file mode 100644 index 00000000..b46db6c4 --- /dev/null +++ b/src/assets/stylesheets/base/_mixins.scss @@ -0,0 +1,41 @@ +$media-breakpoint-xs: 480px; +$media-breakpoint-md: 880px; +$media-breakpoint-xl: 1200px; + +// MIN VALUES +@mixin media-min-xs($value: 0) { + @media (min-width: ($media-breakpoint-xs + $value)) { + @content; + } +} + +@mixin media-min-md($value: 0) { + @media (min-width: ($media-breakpoint-md + $value)) { + @content; + } +} + +@mixin media-min-xl($value: 0) { + @media (min-width: ($media-breakpoint-xl + $value)) { + @content; + } +} + +// MAX VALUES +@mixin media-max-xs($value: 0) { + @media (max-width: ($media-breakpoint-xs + $value)) { + @content; + } +} + +@mixin media-max-md($value: 0) { + @media (max-width: ($media-breakpoint-md + $value)) { + @content; + } +} + +@mixin media-max-xl($value: 0) { + @media (max-width: ($media-breakpoint-xl + $value)) { + @content; + } +} diff --git a/src/assets/stylesheets/base/_typography.scss b/src/assets/stylesheets/base/_typography.scss new file mode 100644 index 00000000..1042427c --- /dev/null +++ b/src/assets/stylesheets/base/_typography.scss @@ -0,0 +1,18 @@ +@use '@fontsource/roboto/400.css' as *; +@use '@fontsource/roboto/400-italic.css' as *; +@use '@fontsource/roboto/700.css' as *; + +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/stylesheets/base/_variables.scss b/src/assets/stylesheets/base/_variables.scss new file mode 100644 index 00000000..53515b11 --- /dev/null +++ b/src/assets/stylesheets/base/_variables.scss @@ -0,0 +1,174 @@ +@property --color-white { + inherits: false; + initial-value: #fff; + syntax: ""; +} + +@property --color-white-light { + inherits: false; + initial-value: #eaeaea; + syntax: ""; +} + +@property --color-black { + inherits: false; + initial-value: #000; + syntax: ""; +} + +@property --color-light-black { + inherits: false; + initial-value: #1b1b1b; + syntax: ""; +} + +@property --color-gray { + inherits: false; + initial-value: #888; + syntax: ""; +} + +@property --color-light-gray { + inherits: false; + initial-value: #d3d3d3; + syntax: ""; +} + +@property --color-dark-gray { + inherits: false; + initial-value: #2e2e2e; + syntax: ""; +} + +@property --color-todo { + inherits: false; + initial-value: #ffe381; + syntax: ""; +} + +@property --color-done { + inherits: false; + initial-value: #70c03f; + syntax: ""; +} + +@property --color-blacklist { + inherits: false; + initial-value: #ef5555; + syntax: ""; +} + +@property --color-pin { + inherits: false; + initial-value: #6832da; + syntax: ""; +} + +@property --color-warning { + inherits: false; + initial-value: #ffb21d; + syntax: ""; +} + +@property --color-error { + inherits: false; + initial-value: #ef5555; + syntax: ""; +} + +@property --color-missing { + inherits: false; + initial-value: #ff2f2f; + syntax: ""; +} + +@property --primary-color { + inherits: false; + initial-value: #1da57a; + syntax: ""; +} + +@property --link-color { + inherits: false; + initial-value: #1da57a; + syntax: ""; +} + +@property --color-active-green { + inherits: false; + initial-value: #0ca589; + syntax: ""; +} + +@property --body-background { + inherits: false; + initial-value: #0b0c0d; + syntax: ""; +} + +@property --border-color-base { + inherits: false; + initial-value: #004f4a; + syntax: ""; +} + +@property --scrollbar-color { + inherits: false; + initial-value: #969696; + syntax: ""; +} + +@property --color-panel-bg { + inherits: false; + initial-value: #404040; + syntax: ""; +} + +@property --color-border-dark { + inherits: false; + initial-value: #109393; + syntax: ""; +} + +@property --color-positive-green { + inherits: false; + initial-value: #077561; + syntax: ""; +} + +@property --color-border-light { + inherits: false; + initial-value: #15d4b2; + syntax: ""; +} + +@property --color-positive-green-light { + inherits: false; + initial-value: #16d4b2; + syntax: ""; +} + +@property --color-enlightened { + inherits: false; + initial-value: #02bf02; + syntax: ""; +} + +@property --color-resistance { + inherits: false; + initial-value: #0492d0; + syntax: ""; +} + +@property --card-head-padding { + inherits: false; + initial-value: 0.6rem; + syntax: ""; +} + +@property --card-padding-base { + inherits: false; + initial-value: 0.6rem; + syntax: ""; +} + diff --git a/src/assets/stylesheets/components/_button.scss b/src/assets/stylesheets/components/_button.scss new file mode 100644 index 00000000..6fb9108f --- /dev/null +++ b/src/assets/stylesheets/components/_button.scss @@ -0,0 +1,63 @@ +// TODO: test if we need all the !important rules + +@mixin positive-action-button { + background: var(--color-positive-green) !important; /* stylelint-disable-line */ + border: none; + border-radius: 5px; + color: var(--color-white) !important; /* stylelint-disable-line */ + cursor: pointer; + padding: 0.5em 2em; + + &:disabled { + background: var(--color-gray); + color: var(--color-light-gray); + cursor: not-allowed; + } +} + +.bg-button { + align-items: center; + background-clip: content-box, border-box !important; /* stylelint-disable-line */ + background-origin: border-box !important; /* stylelint-disable-line */ + border: solid 2px transparent !important; /* stylelint-disable-line */ + box-shadow: 2px 1000px 1px var(--color-black) inset !important; /* stylelint-disable-line */ + display: flex; + justify-content: center; + transition: linear-gradient ease 0.6s; + + &.bg-button-default { + background-image: linear-gradient(rgb(255 255 255 / 0), rgb(255 255 255 / 0)), linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)) !important; /* stylelint-disable-line */ + + @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; /* stylelint-disable-line */ + } + } + } +} + +.button-default { + background-clip: content-box, border-box !important; /* stylelint-disable-line */ + 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; /* stylelint-disable-line */ + background-origin: border-box !important; /* stylelint-disable-line */ + border-radius: 4px; + border: solid 2px transparent !important; /* stylelint-disable-line */ + box-shadow: 2px 1000px 1px var(--color-light-black) inset !important; /* stylelint-disable-line */ + color: var(--color-white) !important; /* stylelint-disable-line */ + display: block; + margin: 0 !important; /* stylelint-disable-line */ + 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; /* stylelint-disable-line */ + } + } + + @media (prefers-reduced-motion: reduce) { + transition: none; + } +} diff --git a/src/assets/stylesheets/main.scss b/src/assets/stylesheets/main.scss new file mode 100644 index 00000000..19f02dec --- /dev/null +++ b/src/assets/stylesheets/main.scss @@ -0,0 +1,26 @@ +@charset "UTF-8"; + +// 1. Vendors & Framework +@use "antd/dist/antd.compact.min.css" as *; +@use "antd/dist/antd.dark.min.css" as *; + +// 2. Framework overrides +@use "override/ant-override"; + +// 3. Base +@use "base/variables"; +@use 'base/class'; +@use 'base/typography'; +@use "base/mixins"; + +// 4. Layout-related sections +//@use 'layout/footer'; +//@use 'layout/header'; + +// 5. Components +@use 'components/button'; + +// 6. Pages +//@use 'pages/home'; + +// 7. Themes diff --git a/src/assets/stylesheets/override/_ant-override.scss b/src/assets/stylesheets/override/_ant-override.scss new file mode 100644 index 00000000..62acb815 --- /dev/null +++ b/src/assets/stylesheets/override/_ant-override.scss @@ -0,0 +1,14 @@ +.ant-layout { + background: var(--color-light-black); +} + +.ant-menu, +.ant-input, +.ant-card, +.ant-card-head-title { + font-size: 16px; +} + +.ant-card { + border-radius: 5px; +} diff --git a/src/assets/stylesheets/override/_leaflet_override.scss b/src/assets/stylesheets/override/_leaflet_override.scss new file mode 100644 index 00000000..f7bfbb22 --- /dev/null +++ b/src/assets/stylesheets/override/_leaflet_override.scss @@ -0,0 +1,4 @@ +.leaflet-container { + margin: unset; + height: 100%; +} 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/IssuesList.scss similarity index 67% rename from src/components/Issues-list/issues.less rename to src/components/Issues-list/IssuesList.scss index 67889985..1dd569d3 100644 --- a/src/components/Issues-list/issues.less +++ b/src/components/Issues-list/IssuesList.scss @@ -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/Issues-list/IssuesList.tsx b/src/components/Issues-list/IssuesList.tsx index 75610719..6e42e788 100644 --- a/src/components/Issues-list/IssuesList.tsx +++ b/src/components/Issues-list/IssuesList.tsx @@ -3,7 +3,7 @@ import React, { FC } from 'react' import { Issue } from './Issue' import IssueCard from './IssueCard' -import './issues.less' +import './IssuesList.scss' const IssuesList: FC = ({ issues, onCloseIssue }) => (
diff --git a/src/components/advanced-options/advanced-options.less b/src/components/advanced-options/AdvancedOptions.scss similarity index 92% rename from src/components/advanced-options/advanced-options.less rename to src/components/advanced-options/AdvancedOptions.scss index cd690057..38d7b572 100644 --- a/src/components/advanced-options/advanced-options.less +++ b/src/components/advanced-options/AdvancedOptions.scss @@ -1,14 +1,13 @@ .banner-advanced-options { - h4 { - display: flex; column-gap: 5px; + display: flex; svg { align-self: flex-start; path { - fill: white; + fill: var(--color-white); } } } @@ -22,16 +21,16 @@ } .date-picker-hidden { - position: absolute; - visibility: hidden; - width: 0; height: 0; + position: absolute; right: 0; top: 0; + visibility: hidden; + width: 0; } .date-picker-visible { - width: 100%; margin-bottom: 5px; + width: 100%; } -} \ No newline at end of file +} diff --git a/src/components/advanced-options/AdvancedOptions.tsx b/src/components/advanced-options/AdvancedOptions.tsx index 3bcba4e6..34527ee2 100644 --- a/src/components/advanced-options/AdvancedOptions.tsx +++ b/src/components/advanced-options/AdvancedOptions.tsx @@ -3,9 +3,9 @@ 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 './AdvancedOptions.scss' import { DatePicker } from '../date-picker/DatePicker' import { useUserLoggedIn } from '../../hooks/UserLoggedIn' diff --git a/src/components/agent/Agent.scss b/src/components/agent/Agent.scss new file mode 100644 index 00000000..5047a61e --- /dev/null +++ b/src/components/agent/Agent.scss @@ -0,0 +1,7 @@ +.faction-enlightened { + color: var(--color-enlightened); +} + +.faction-resistance { + color: var(--color-resistance); +} diff --git a/src/components/agent/Agent.tsx b/src/components/agent/Agent.tsx index 631bc01d..08716637 100644 --- a/src/components/agent/Agent.tsx +++ b/src/components/agent/Agent.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom' import { NamedAgent } from '../../features/mission' import { createAgentUri } from '../../features/user' -import './agent.less' +import './Agent.scss' const getInnerSpan = (agent: NamedAgent) => ( {agent.name} diff --git a/src/components/agent/agent.less b/src/components/agent/agent.less deleted file mode 100644 index 2c2896ec..00000000 --- a/src/components/agent/agent.less +++ /dev/null @@ -1,7 +0,0 @@ -.faction-enlightened { - color: #02bf02; -} - -.faction-resistance { - color: #0492d0; -} diff --git a/src/components/algorithm-detection-chooser/algorithm-detection-chooser.less b/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.scss similarity index 54% rename from src/components/algorithm-detection-chooser/algorithm-detection-chooser.less rename to src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.scss index e9ffbef0..9a8f4b7f 100644 --- a/src/components/algorithm-detection-chooser/algorithm-detection-chooser.less +++ b/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.scss @@ -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/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx b/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx index 2a74fdb1..508ac9ea 100644 --- a/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx +++ b/src/components/algorithm-detection-chooser/AlgorithmDetectionChooser.tsx @@ -2,9 +2,9 @@ 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' +import './AlgorithmDetectionChooser.scss' const extractionHelp = ( = ({ item }) => (
{item.content}
diff --git a/src/components/banner-card/banner-card.less b/src/components/banner-card/BannerCard.scss similarity index 55% rename from src/components/banner-card/banner-card.less rename to src/components/banner-card/BannerCard.scss index d2fb9716..08c5a3bc 100644 --- a/src/components/banner-card/banner-card.less +++ b/src/components/banner-card/BannerCard.scss @@ -1,79 +1,85 @@ -@import url(../../App.less); +@use "/src/assets/stylesheets/base/mixins" as *; +@use "/src/assets/stylesheets/base/variables" as *; -// Start Temporary styles for list types. Not final yet. - -.list-style-background() { - content: " "; - z-index: 10; +/* Start Temporary styles for list types. Not final yet. */ +@mixin list-style-background { + border-radius: 5px; + content: ' '; display: block; - position: absolute; height: 100%; - top: 0; left: 0; - right: 0; pointer-events: none; - 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; + position: absolute; + right: 0; + top: 0; + z-index: 10; } -// End temporary Styles ^^ - +/* End temporary Styles */ .banner-circle { - display: flex; align-items: center; - justify-content: center; - width: 60px; - height: 60px; + background-color: var(--color-gray); border-radius: 50%; - background-color: grey; + display: flex; + height: 60px; + justify-content: center; margin-right: 8px; margin-top: 8px; + width: 60px; } .banner-card { - background: #2e2e2e; + background: var(--color-dark-gray); + border: var(--color-dark-gray) 2px solid; border-radius: 5px; - border: #2e2e2e 2px solid; + color: var(--color-white); padding: 18px; - color: white; position: relative; &.selected { - border-color: #16d4b2; + border-color: var(--color-positive-green-light); + } + + &.list-style { + &-todo { + &::after { + @include list-style-background; + background: rgba(var(--color-todo), 0.05); + border: 1px solid var(--color-todo); + } + } + + &-done { + &::after { + @include list-style-background; + background: rgba(var(--color-done), 0.05); + border: 1px solid var(--color-done); + } + } + + &-blacklist { + &::after { + @include list-style-background; + background: rgba(var(--color-blacklist), 0.05); + border: 1px solid var(--color-blacklist); + } + } } .icon { - width: 0.9rem; height: 0.9rem; + width: 0.9rem; } - } .banner-card-modal { height: 95%; .ant-modal-content { + background-color: var(--color-dark-gray); + display: flex; height: 100%; width: 100%; - display: flex; - background-color: #2e2e2e; .ant-modal-body { width: 100%; @@ -84,46 +90,45 @@ } .modal-image { + background: none; + border: none; + cursor: default; height: 100%; + margin: 0; + padding: 0; width: 100%; - cursor: default; - background: none !important; - border: none; - margin: 0 !important; - padding: 0 !important; div { - margin: 0 !important; display: flex; + margin: 0; } img { + margin: auto; max-height: 100%; max-width: 100%; - margin: auto; } &.full-size { img { max-height: initial; - max-width: initial; } } } .close-button { - position: absolute; - bottom: 15px; - background-color: #2e2e2e; - right: 15px; + background-color: var(--color-dark-gray); border: none; + bottom: 15px; + color: var(--color-white); line-height: 1em; - color: white !important; + position: absolute; + right: 15px; svg, span { - vertical-align: middle; display: inline-block; + vertical-align: middle; } svg { @@ -133,41 +138,43 @@ } } -.banner-card-picture-container{ - position: relative; +.banner-card-picture-container { display: flex; + justify-content: center; + position: relative; + + @include media-min-xs { + height: 200px; + } - .offline-overlay{ + .offline-overlay { + color: var(--color-light-gray); + display: flex; + flex-direction: column; font-size: 24px; + height: 100%; + justify-content: center; + left: -20px; line-height: 18px; - color: #DDDDDD; - + pointer-events: none; position: absolute; - left: -20px; top: 0; width: calc(100% + 2 * 20px); - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - - pointer-events: none; - - .offline-overlay-line { - justify-content: center; + .offline-overlay-line { align-items: center; - width: 100%; - height: 3.5em; + background-color: rgba(var(--color-black), 0.7); display: flex; flex-direction: row; - background-color: rgba(0, 0, 0, 0.7); + height: 3.5em; + justify-content: center; + width: 100%; - svg{ + svg { + fill: currentcolor; height: 1em; - width:1em; - fill: currentColor; margin-right: 0.5em; + width: 1em; } } } @@ -179,40 +186,67 @@ } .banner-info-item { + color: var(--color-light-gray); + display: flex; font-size: 14px; line-height: 18px; - color: #DDDDDD; margin-top: 4px; - display: flex; .banner-info-item-icon { width: 20px; .icon { - fill: currentColor; + fill: currentcolor; } } -} -.banner-info-item.warning { - color: @color-blacklist; + &.warning { + color: var(--color-blacklist); + } } - .banner-card-picture { - margin: 20px 0; - display: flex; - max-width: 100%; align-items: center; + display: flex; justify-content: center; + margin: 20px 0; + max-width: 100%; overflow: hidden; + width: 100%; + + @supports (aspect-ratio: 1/1) { /* stylelint-disable-line */ + aspect-ratio: 2 / 1; + } + + @supports not (aspect-ratio: 1/1) { /* stylelint-disable-line */ + /* 160 Pixel is half of width of .banner-card minus padding */ + height: 160px; + } + + @include media-max-md { + aspect-ratio: 2; + height: auto; + } + + &.banner-lines-1 { + @include media-max-md { + aspect-ratio: 6; + } + } + + &.banner-lines-2 { + @include media-max-md { + aspect-ratio: 3; + } + } } .banner-card-picture-inner { + display: table; max-height: 100%; max-width: 100%; - &>img { + & > img { /* stylelint-disable-line */ max-width: 100%; } @@ -238,41 +272,25 @@ } .banner-info-details { + align-items: flex-end; display: flex; flex-direction: column; - align-items: flex-end; - padding: 0px; margin-top: 10px; + padding: 0; a { - border: 1px solid white; - box-sizing: border-box; + border: 1px solid var(--color-white); border-radius: 4px; - padding: 5px 10px; + box-sizing: border-box; + color: var(--color-white); font-size: 14px; - color: white; - } -} - - -// 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 + padding: 5px 10px; } } .banner-card-title, .banner-info-item { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - overflow: hidden; } diff --git a/src/components/banner-card/BannerCard.tsx b/src/components/banner-card/BannerCard.tsx index 66310e52..1d814dc9 100644 --- a/src/components/banner-card/BannerCard.tsx +++ b/src/components/banner-card/BannerCard.tsx @@ -10,11 +10,11 @@ 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' +import './BannerCard.scss' const baseUrl = import.meta.env.VITE_API_BASE_URL 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-edit-tools/banner-edit-tools.less b/src/components/banner-edit-tools/BannerEditTools.scss similarity index 90% rename from src/components/banner-edit-tools/banner-edit-tools.less rename to src/components/banner-edit-tools/BannerEditTools.scss index 38567945..ef836a50 100644 --- a/src/components/banner-edit-tools/banner-edit-tools.less +++ b/src/components/banner-edit-tools/BannerEditTools.scss @@ -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-edit-tools/BannerEditTools.tsx b/src/components/banner-edit-tools/BannerEditTools.tsx index f0e876a3..2155f35c 100644 --- a/src/components/banner-edit-tools/BannerEditTools.tsx +++ b/src/components/banner-edit-tools/BannerEditTools.tsx @@ -8,7 +8,7 @@ import { Banner, deleteBanner } from '../../features/banner' import { useUserLoggedIn } from '../../hooks/UserLoggedIn' import { useCreatorPluginAvailable } from '../../hooks/CreatorPluginAvailable' -import './banner-edit-tools.less' +import './BannerEditTools.scss' export const BannerEditTools: FC = ({ banner }) => { const history = useHistory() @@ -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-image/banner-image.less b/src/components/banner-image/BannerImage.scss similarity index 100% rename from src/components/banner-image/banner-image.less rename to src/components/banner-image/BannerImage.scss diff --git a/src/components/banner-image/BannerImage.tsx b/src/components/banner-image/BannerImage.tsx index 90e1649c..c68a4f3a 100644 --- a/src/components/banner-image/BannerImage.tsx +++ b/src/components/banner-image/BannerImage.tsx @@ -3,7 +3,7 @@ import React, { FC } from 'react' import { Mission } from '../../features/mission' import MissionImage from '../mission-image/MissionImage' -import './banner-image.less' +import './BannerImage.scss' const BannerImage: FC = ({ missions, width, useIndex }) => { const mapMissions = () => { diff --git a/src/components/banner-info-card/banner-info-card.less b/src/components/banner-info-card/BannerInfoCard.scss similarity index 79% rename from src/components/banner-info-card/banner-info-card.less rename to src/components/banner-info-card/BannerInfoCard.scss index 4cb11b7b..993fd617 100644 --- a/src/components/banner-info-card/banner-info-card.less +++ b/src/components/banner-info-card/BannerInfoCard.scss @@ -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-card/BannerInfoCard.tsx b/src/components/banner-info-card/BannerInfoCard.tsx index 8cf29942..7e195455 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,16 +26,16 @@ 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' +import './BannerInfoCard.scss' import { PlainDate } from '../plain-date' const getAgentList = (banner: Banner) => @@ -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-mobile-switch/BannerInfoMobileSwitch.scss b/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.scss new file mode 100644 index 00000000..b2d84d6a --- /dev/null +++ b/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.scss @@ -0,0 +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-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; + } + + .mobile-switch-tabs-row { + align-items: center; + display: flex; + flex-direction: row; + } + + .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 var(--color-active-green) !important; + } + } + + .positive-action-button { + background: var(--color-positive-green); + color: var(--color-white); + padding: 0.5em 2em; + border: none; + border-radius: 5px; + cursor: pointer; + + &:disabled { + background: var(--color-gray); + color: var(--color-light-gray); + cursor: not-allowed; + } + } + + .mobile-switch-submit-button { + flex-grow: 0; + white-space: nowrap; + width: auto; + } +} diff --git a/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx b/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx index 6d29a438..100471ff 100644 --- a/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx +++ b/src/components/banner-info-mobile-switch/BannerInfoMobileSwitch.tsx @@ -2,9 +2,9 @@ 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' +import './BannerInfoMobileSwitch.scss' export type BannerInfoMobileView = 'info' | 'map' | 'missions' 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 deleted file mode 100644 index 230fa9c4..00000000 --- a/src/components/banner-info-mobile-switch/banner-info-mobile-switch.less +++ /dev/null @@ -1,77 +0,0 @@ -.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; - - } - - .mobile-switch-tabs-row { - align-items: center; - display: flex; - flex-direction: row; - } - - .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; - } - } - - .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; - } - } - - .mobile-switch-submit-button { - flex-grow: 0; - white-space:nowrap; - width: auto; - } -} \ No newline at end of file diff --git a/src/components/banner-info-overview/banner-info-overview.less b/src/components/banner-info-overview/BannerInfoOverview.scss similarity index 60% rename from src/components/banner-info-overview/banner-info-overview.less rename to src/components/banner-info-overview/BannerInfoOverview.scss index ab90015c..f0ae5f26 100644 --- a/src/components/banner-info-overview/banner-info-overview.less +++ b/src/components/banner-info-overview/BannerInfoOverview.scss @@ -9,7 +9,6 @@ display: flex; flex-direction: column; flex-grow: 1; - width: 100%; padding-right: 4px; width: 370px; @@ -21,24 +20,24 @@ margin-top: 15px; } - // Hide line below the buttons + /*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 - // If we leave it, the screen flackers when resizing the window - // 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 + /*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*/ + /*make the width of the ant-tabs-nav-list smaller or givt it a right margin*/ + /*or padding*/ .ant-tabs-nav-more { display: none; } .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-overview/BannerInfoOverview.tsx b/src/components/banner-info-overview/BannerInfoOverview.tsx index de1021ec..d19d5aea 100644 --- a/src/components/banner-info-overview/BannerInfoOverview.tsx +++ b/src/components/banner-info-overview/BannerInfoOverview.tsx @@ -15,7 +15,7 @@ import MissionList from '../mission-list' import BannerInfoCard from '../banner-info-card' import IfUserLoggedIn from '../login/if-user-logged-in' -import './banner-info-overview.less' +import './BannerInfoOverview.scss' export type BannerInfoView = 'info' | 'missions' diff --git a/src/components/banner-info-overview/index.ts b/src/components/banner-info-overview/index.ts index bcdfcc67..b680f3de 100644 --- a/src/components/banner-info-overview/index.ts +++ b/src/components/banner-info-overview/index.ts @@ -1,2 +1,2 @@ -export { default as BannerInfoOverview } from './BannerInfoOverview' -export type { BannerInfoView } from './BannerInfoOverview' +export { default as BannerInfoOverview } from './BannerInfoOverview.tsx' +export type { BannerInfoView } from './BannerInfoOverview.tsx' diff --git a/src/components/banner-info-with-map/BannerInfoWithMap.scss b/src/components/banner-info-with-map/BannerInfoWithMap.scss new file mode 100644 index 00000000..c7b13ead --- /dev/null +++ b/src/components/banner-info-with-map/BannerInfoWithMap.scss @@ -0,0 +1,143 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + +.banner-info-with-map { + column-gap: 40px; + display: flex; + flex: 0 1 100%; + flex-direction: row; + min-height: 1px; + + .banner-info { + height: 100%; + max-width: 50vw; + min-height: 320px; + min-width: 370px; + overflow: hidden auto; + scrollbar-width: thin; + } + + .banner-info-additional { + display: flex; + flex-direction: column; + height: 100%; + row-gap: 30px; + width: 100%; + } + + .leaflet-container { + height: 100%; + min-height: 500px; + width: 100%; + } +} + +.banner-info-with-map-container { + display: flex; + flex-direction: column; + height: 100%; + padding-bottom: 40px; + padding-left: 40px; + padding-right: 40px; + + @include media-max-md { + display: flex; + flex-direction: column; + height: inherit; + padding: 0; + } + + .banner-info-with-map { + @include media-max-md { + height: 100%; + justify-content: center; + margin: 0; + overflow-y: auto; + padding: 0; + } + + .banner-info { + @include media-max-md { + height: auto; + max-width: 864px; + min-height: 0; + min-width: 320px; + overflow-y: unset; + padding: 0.6em; + width: 100%; + } + } + + .banner-info-left-pane-missions { + .banner-card { + @include media-max-md { + display: none; + } + } + } + + .mission-card { + @include media-max-md { + max-width: none; + } + } + + .leaflet-container { + @include media-max-md { + height: 100%; + min-height: 0; + width: 100%; + } + } + } + + .banner-info-card { + @include media-max-md { + margin-top: 0; + } + } + + .ant-tabs-nav { + @include media-max-md { + display: none !important; /* stylelint-disable-line */ + margin: 0 !important; /* stylelint-disable-line */ + } + } + + .ant-tabs-tabpane { + @include media-max-md { + margin-top: 0; + } + } +} + +.banner-info-with-map-goback { + align-items: center; + column-gap: 20px; + display: flex; + flex: 0 0 content; + flex-direction: row; + padding-bottom: 20px; + padding-top: 20px; + + .back-button { + background: none; + border: none; + cursor: pointer; + font-size: 2em; + height: 100%; + line-height: 2em; + margin-bottom: -6px; + width: 30px; + } + + h1 { + flex: 1; + line-height: 2em; + margin: auto 0; + vertical-align: middle; + } + + .banner-info-with-map-submit { + width: calc(100% - 370px - 40px); + } +} diff --git a/src/components/banner-info-with-map/BannerInfoWithMap.tsx b/src/components/banner-info-with-map/BannerInfoWithMap.tsx index 596e1381..bba05be1 100644 --- a/src/components/banner-info-with-map/BannerInfoWithMap.tsx +++ b/src/components/banner-info-with-map/BannerInfoWithMap.tsx @@ -12,9 +12,9 @@ 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' +import './BannerInfoWithMap.scss' class BannerInfoWithMap extends React.Component< BannerInfoWithMapProps, @@ -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 deleted file mode 100644 index 725d1b4a..00000000 --- a/src/components/banner-info-with-map/banner-info-with-map.less +++ /dev/null @@ -1,74 +0,0 @@ -.banner-info-with-map-container { - height: 100%; - display: flex; - flex-direction: column; - padding-left: 40px; - padding-bottom: 40px; - padding-right: 40px; -} - -.banner-info-with-map-goback { - display: flex; - flex-direction: row; - flex: 0 0 content; - column-gap: 20px; - align-items: center; - padding-top: 20px; - padding-bottom: 20px; - - .back-button { - width: 30px; - height: 100%; - background: none; - border: none; - font-size: 2em; - line-height: 2em; - cursor: pointer; - margin-bottom: -6px; - } - - h1 { - vertical-align: middle; - line-height: 2em; - margin: auto 0; - flex: 1; - } - - .banner-info-with-map-submit { - width: calc(100% - 370px - 40px); - } -} - -.banner-info-with-map { - display: flex; - flex-grow: 0; - flex-shrink: 1; - flex-basis: 100%; - flex-direction: row; - column-gap: 40px; - min-height: 1px; - - .banner-info { - height: 100%; - min-height: 320px; - overflow-y: auto; - min-width: 370px; - max-width: 50vw; - overflow-x: hidden; - scrollbar-width: thin; - } - - .banner-info-additional { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - row-gap: 30px; - } - - .leaflet-container { - width: 100%; - height: 100%; - min-height: 500px; - } -} diff --git a/src/components/banner-list-type-control/BannerListTypeControl.scss b/src/components/banner-list-type-control/BannerListTypeControl.scss new file mode 100644 index 00000000..f32f53de --- /dev/null +++ b/src/components/banner-list-type-control/BannerListTypeControl.scss @@ -0,0 +1,62 @@ +.banner-list-type-control { + display: flex; + flex-direction: row; + gap: 1em; + padding: 5px; + width: 100%; + + .banner-list-type { + background: none; + border: none; + cursor: pointer; + flex: 1; + outline: none; + text-align: center; + vertical-align: middle; + + svg { + align-self: center; + height: 1.1em; + margin-right: 5px; + position: relative; + top: 0.125em; + width: 1.1em; + } + } + + .banner-list-type-todo { + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--color-todo); + } + } + + &.active { + color: var(--color-todo); + } + } + + .banner-list-type-done { + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--color-done); + } + } + + &.active { + color: var(--color-done); + } + } + + .banner-list-type-blacklist { + @media (hover: hover) and (pointer: fine) { + &:hover { + color: var(--color-blacklist); + } + } + + &.active { + color: var(--color-blacklist); + } + } +} diff --git a/src/components/banner-list-type-control/BannerListTypeControl.tsx b/src/components/banner-list-type-control/BannerListTypeControl.tsx index ba9dee13..2eb62c93 100644 --- a/src/components/banner-list-type-control/BannerListTypeControl.tsx +++ b/src/components/banner-list-type-control/BannerListTypeControl.tsx @@ -2,11 +2,11 @@ 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' +import './BannerListTypeControl.scss' const BannerListTypeControl: FC = ({ bannerListType, 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 deleted file mode 100644 index 9b4e421b..00000000 --- a/src/components/banner-list-type-control/banner-list-type-control.less +++ /dev/null @@ -1,59 +0,0 @@ -@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; - } - } - - .banner-list-type-todo.active { - color: @color-todo; - } - - .banner-list-type-done.active { - color: @color-done; - } - - .banner-list-type-blacklist.active { - color: @color-blacklist; - } - - @media (hover: hover) and (pointer: fine) { - .banner-list-type-todo:hover { - color: @color-todo; - } - - .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/BannerListTypeNavigation.scss similarity index 83% rename from src/components/banner-list-type-navigation/banner-list-type-navigation.less rename to src/components/banner-list-type-navigation/BannerListTypeNavigation.scss index c6e200af..082c726b 100644 --- a/src/components/banner-list-type-navigation/banner-list-type-navigation.less +++ b/src/components/banner-list-type-navigation/BannerListTypeNavigation.scss @@ -1,14 +1,11 @@ -@import '../../App.less'; - .banner-list-type-navigation { - margin-bottom: 20px; button { - border: none; - outline: none; background-color: transparent; + border: none; cursor: pointer; + outline: none; } a:any-link { @@ -21,12 +18,12 @@ } .active { + color: var(--color-white); font-size: 36px; - color: #FFFFFF; } .inactive { + color: var(--color-light-gray); font-size: 28px; - color: #DDDDDD; } -} \ No newline at end of file +} diff --git a/src/components/banner-list-type-navigation/BannerListTypeNavigation.tsx b/src/components/banner-list-type-navigation/BannerListTypeNavigation.tsx index 07b4c466..8575b067 100644 --- a/src/components/banner-list-type-navigation/BannerListTypeNavigation.tsx +++ b/src/components/banner-list-type-navigation/BannerListTypeNavigation.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom' import { BannerListType, getBannerListTypeText } from '../../features/banner' -import './banner-list-type-navigation.less' +import './BannerListTypeNavigation.scss' const BannerListTypeNavigation: FC = ({ bannerListType, diff --git a/src/components/banner-list/banner-list.less b/src/components/banner-list/BannerList.scss similarity index 59% rename from src/components/banner-list/banner-list.less rename to src/components/banner-list/BannerList.scss index 0d514c3f..26f17b94 100644 --- a/src/components/banner-list/banner-list.less +++ b/src/components/banner-list/BannerList.scss @@ -1,23 +1,26 @@ -.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; - } -} +@use "/src/assets/stylesheets/base/mixins" as *; + +.banner-list { + align-items: flex-start; + display: flex; + flex-flow: row wrap; + gap: 20px; + place-content: flex-start center; + + .banner-list-entry { + max-width: 90vw; + + @include media-max-md { + width: 360px; + } + } + + .banner-card-link { + background: none; + border: none; + font-size: initial; + height: 100%; + text-align: initial; + width: 100%; + } +} diff --git a/src/components/banner-list/BannerList.tsx b/src/components/banner-list/BannerList.tsx index 017ac428..719bca59 100644 --- a/src/components/banner-list/BannerList.tsx +++ b/src/components/banner-list/BannerList.tsx @@ -7,7 +7,7 @@ import { Banner } from '../../features/banner' import { useInfiniteScroll } from '../../hooks/InfiniteScroll' import BannerCard from '../banner-card' -import './banner-list.less' +import './BannerList.scss' const BannerList: FC = ({ banners, @@ -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-order-chooser/banner-order-chooser.less b/src/components/banner-order-chooser/BannerOrderChooser.scss similarity index 89% rename from src/components/banner-order-chooser/banner-order-chooser.less rename to src/components/banner-order-chooser/BannerOrderChooser.scss index 3518afc8..2770eb8f 100644 --- a/src/components/banner-order-chooser/banner-order-chooser.less +++ b/src/components/banner-order-chooser/BannerOrderChooser.scss @@ -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/banner-order-chooser/BannerOrderChooser.tsx b/src/components/banner-order-chooser/BannerOrderChooser.tsx index 88463896..b05e8b45 100644 --- a/src/components/banner-order-chooser/BannerOrderChooser.tsx +++ b/src/components/banner-order-chooser/BannerOrderChooser.tsx @@ -2,9 +2,9 @@ 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 './BannerOrderChooser.scss' import { BannerFilter, BannerOrder } from '../../features/banner/filter' import Order, { hasBothDirections } from './Order' import Switch from '../switch/Switch' @@ -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]) /* eslint-disable-line */ 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.less b/src/components/banner-order-chooser/Order.scss similarity index 100% rename from src/components/banner-order-chooser/order.less rename to src/components/banner-order-chooser/Order.scss diff --git a/src/components/banner-order-chooser/Order.tsx b/src/components/banner-order-chooser/Order.tsx index 263b4730..aaec617a 100644 --- a/src/components/banner-order-chooser/Order.tsx +++ b/src/components/banner-order-chooser/Order.tsx @@ -1,14 +1,15 @@ 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 './Order.scss' 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/banners-accordion/banners-accordion.less b/src/components/banners-accordion/BannersAccordion.scss similarity index 95% rename from src/components/banners-accordion/banners-accordion.less rename to src/components/banners-accordion/BannersAccordion.scss index 17d3500c..ac62c812 100644 --- a/src/components/banners-accordion/banners-accordion.less +++ b/src/components/banners-accordion/BannersAccordion.scss @@ -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/banners-accordion/BannersAccordion.tsx b/src/components/banners-accordion/BannersAccordion.tsx index ca0f7e36..9755b4a5 100644 --- a/src/components/banners-accordion/BannersAccordion.tsx +++ b/src/components/banners-accordion/BannersAccordion.tsx @@ -6,9 +6,9 @@ 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 './BannersAccordion.scss' import BannerOrderChooser from '../banner-order-chooser' import { BannerFilter } from '../../features/banner/filter' @@ -78,7 +78,7 @@ const BannerAccordion: FC = ({ hasMoreBanners={hasMoreBanners} onSelectBanner={onSelectBannerCallback} loadMoreBanners={loadMoreBanners} - applyBannerListStlyes + applyBannerListStyles hideBlacklisted showDetailsButton /> diff --git a/src/components/banners-map/banners-map.less b/src/components/banners-map/BannersMap.scss similarity index 100% rename from src/components/banners-map/banners-map.less rename to src/components/banners-map/BannersMap.scss diff --git a/src/components/banners-map/BannersMap.tsx b/src/components/banners-map/BannersMap.tsx index 0e21f299..2009e406 100644 --- a/src/components/banners-map/BannersMap.tsx +++ b/src/components/banners-map/BannersMap.tsx @@ -19,7 +19,7 @@ import { LocateControl } from '../locate' import { MapLoadingControl } from '../map-loading-control' import { MapZoomControl } from '../map-zoom-control' -import './banners-map.less' +import './BannersMap.scss' import 'leaflet/dist/leaflet.css' import i18n from '../../i18n' 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/FaqQuestion.scss similarity index 86% rename from src/components/faq-question/faq-question.less rename to src/components/faq-question/FaqQuestion.scss index 58d9d92f..4e455f04 100644 --- a/src/components/faq-question/faq-question.less +++ b/src/components/faq-question/FaqQuestion.scss @@ -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/faq-question/FaqQuestion.tsx b/src/components/faq-question/FaqQuestion.tsx index c2f8e92c..bc89ecf7 100644 --- a/src/components/faq-question/FaqQuestion.tsx +++ b/src/components/faq-question/FaqQuestion.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' -import './faq-question.less' +import './FaqQuestion.scss' const FaqQuestion: React.FC = ({ title, children }) => { const [selected, setSelected] = useState(false) diff --git a/src/components/footer-main/FooterMain.scss b/src/components/footer-main/FooterMain.scss new file mode 100644 index 00000000..e65b20a7 --- /dev/null +++ b/src/components/footer-main/FooterMain.scss @@ -0,0 +1,28 @@ +.footer-main { + text-align: center; + background-color: var(--color-black); + padding: 1.5rem 0; + width: 100%; + + .footer-links { + padding-bottom: 1em; + display: flex; + flex-direction: row; + column-gap: 25px; + width: 100%; + justify-content: center; + + svg { + width: 20px; + height: 20px; + path { + fill: var(--color-white); + fill-opacity: 1 !important; + } + } + } + + .footer-disclaimers { + font-size: 12px; + } +} diff --git a/src/components/footer-main/FooterMain.tsx b/src/components/footer-main/FooterMain.tsx index bdad4922..f947ab92 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' +import './FooterMain.scss' const FooterMain: React.FC = () => (
- {/* - - - - - */} diff --git a/src/components/footer-main/footer.less b/src/components/footer-main/footer.less deleted file mode 100644 index d0e98943..00000000 --- a/src/components/footer-main/footer.less +++ /dev/null @@ -1,23 +0,0 @@ -.footer-main { - .footer-links { - padding-bottom: 1em; - display: flex; - flex-direction: row; - column-gap: 25px; - width: 100%; - justify-content: center; - svg { - width: 20px; - height: 20px; - path { - fill: 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/loading-overlay.less b/src/components/loading-overlay/LoadingOverlay.scss similarity index 76% rename from src/components/loading-overlay/loading-overlay.less rename to src/components/loading-overlay/LoadingOverlay.scss index a2b68208..203f2c8a 100644 --- a/src/components/loading-overlay/loading-overlay.less +++ b/src/components/loading-overlay/LoadingOverlay.scss @@ -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/loading-overlay/LoadingOverlay.tsx b/src/components/loading-overlay/LoadingOverlay.tsx index 8448c7ac..a17e4ac4 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/locate/locate-control.less b/src/components/locate/LocateControl.scss similarity index 100% rename from src/components/locate/locate-control.less rename to src/components/locate/LocateControl.scss diff --git a/src/components/locate/LocateControl.tsx b/src/components/locate/LocateControl.tsx index 96ce1c77..839424a0 100644 --- a/src/components/locate/LocateControl.tsx +++ b/src/components/locate/LocateControl.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import 'leaflet.locatecontrol' import 'leaflet.locatecontrol/dist/L.Control.Locate.css' -import './locate-control.less' +import './LocateControl.scss' const LocateControl: FC = () => { const context = useLeafletContext() diff --git a/src/components/login/login-in-navbar/Login-in-navbar.less b/src/components/login/login-in-navbar/LoginInNavbar.scss similarity index 79% rename from src/components/login/login-in-navbar/Login-in-navbar.less rename to src/components/login/login-in-navbar/LoginInNavbar.scss index 2d529e7e..fb716579 100644 --- a/src/components/login/login-in-navbar/Login-in-navbar.less +++ b/src/components/login/login-in-navbar/LoginInNavbar.scss @@ -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/login-in-navbar/LoginInNavbar.tsx b/src/components/login/login-in-navbar/LoginInNavbar.tsx index c8a8293a..2988e2e1 100644 --- a/src/components/login/login-in-navbar/LoginInNavbar.tsx +++ b/src/components/login/login-in-navbar/LoginInNavbar.tsx @@ -8,7 +8,7 @@ import IfUserInitializing from '../if-user-initializing' import LoginButton from '../login-button' import MenuUser from '../../menu-user' -import './Login-in-navbar.less' +import './LoginInNavbar.scss' const LoginInNavbar: React.FC = () => { const { keycloak } = useKeycloak() diff --git a/src/components/login/login-required/login-required.less b/src/components/login/login-required/LoginRequired.scss similarity index 100% rename from src/components/login/login-required/login-required.less rename to src/components/login/login-required/LoginRequired.scss diff --git a/src/components/login/login-required/LoginRequired.tsx b/src/components/login/login-required/LoginRequired.tsx index 7c126b96..50fb51d2 100644 --- a/src/components/login/login-required/LoginRequired.tsx +++ b/src/components/login/login-required/LoginRequired.tsx @@ -4,7 +4,7 @@ import { Trans, useTranslation } from 'react-i18next' import LoginInNavbar from '../login-in-navbar' -import './login-required.less' +import './LoginRequired.scss' const LoginRequired: React.FC = (props) => { const { keycloak, initialized: keycloakInitialized } = useKeycloak() 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/map.less b/src/components/map-detail/MapDetail.scss similarity index 55% rename from src/components/map-detail/map.less rename to src/components/map-detail/MapDetail.scss index a343a111..6c6c607a 100644 --- a/src/components/map-detail/map.less +++ b/src/components/map-detail/MapDetail.scss @@ -1,5 +1,3 @@ -@import url(../../App.less); - .leaflet-container { width: 100%; height: 80vh; @@ -10,7 +8,7 @@ z-index: 2000 !important; } -.marker-pin { +@mixin marker-pin { flex-direction: column; min-width: 26px; min-height: 26px; @@ -18,14 +16,14 @@ 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; } @@ -39,8 +37,8 @@ margin-bottom: 0.2em; } -.marker-pin-medium { - .marker-pin; +@mixin marker-pin-medium { + @include marker-pin; width: 26px !important; height: 26px !important; min-height: initial; @@ -51,72 +49,87 @@ } .marker-pin-medium-false { - .marker-pin-medium; + @include marker-pin-medium; } .marker-pin-medium-true { - .marker-pin-medium; - background-color: rgb(22, 212, 178); + @include marker-pin-medium; + background-color: var(--color-positive-green-light); } .marker-pin-false { - .marker-pin; + @include marker-pin; } .marker-pin-true { - .marker-pin; - background-color: rgb(22, 212, 178); + @include marker-pin; + 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 { - .marker-pin; + @include marker-pin; width: 14px !important; height: 14px !important; min-height: initial; @@ -125,4 +138,3 @@ margin-left: -7px; margin-top: -7px; } - diff --git a/src/components/map-detail/MapDetail.tsx b/src/components/map-detail/MapDetail.tsx index 41116796..0b6336f9 100644 --- a/src/components/map-detail/MapDetail.tsx +++ b/src/components/map-detail/MapDetail.tsx @@ -7,7 +7,7 @@ import { getAttributionLayer } from './getAttributionLayer' import MissionStartMarkerList from './MissionStartMarkerList' import MissionPoiMarkerList from './MissionPoiMarkerList' -import './map.less' +import './MapDetail.scss' import { LocateControl } from '../locate' import { MapLoadingControl } from '../map-loading-control' import { MapZoomControl } from '../map-zoom-control' 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/menu-main/MenuMain.scss b/src/components/menu-main/MenuMain.scss new file mode 100644 index 00000000..1a030987 --- /dev/null +++ b/src/components/menu-main/MenuMain.scss @@ -0,0 +1,72 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + +.menu-main { + display: flex; + + .icon { + height: 24px; + width: 24px; + } + + @media (hover: hover) and (pointer: fine) { /* stylelint-disable-line */ + & a:hover { + background: rgb(29 165 122 / 0.3); + color: var(--color-white); + + & > .icon { + fill: var(--color-white); + } + } + } + + & > a { + align-items: center; + color: var(--color-white-light); + display: flex; + flex-direction: column; + justify-content: center; + min-width: 70px; + padding: 5px; + transition: background-color 0.3s; + + & > .icon { + fill: var(--color-white-light); + } + + &.active { + color: var(--color-active-green); + + & > .icon { + fill: var(--color-active-green); + } + } + } +} + +.bottom-menu { + background: var(--color-black); + display: none; + padding: 5px 10px; + + @include media-max-md { + display: block; + } + + .menu-main { + > a { + flex-grow: 1; + } + + a { + @include media-max-md { + flex: 1; + min-width: 0 !important; /* stylelint-disable-line */ + } + } + } + + .brand-menu { + flex: 1; + max-width: 329px; + } +} diff --git a/src/components/menu-main/MenuMain.tsx b/src/components/menu-main/MenuMain.tsx index 360dfcfd..fe9f2f4c 100644 --- a/src/components/menu-main/MenuMain.tsx +++ b/src/components/menu-main/MenuMain.tsx @@ -2,13 +2,13 @@ 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' +import './MenuMain.scss' const MenuMain: React.FC = () => { const { t } = useTranslation(undefined, { keyPrefix: 'menu' }) diff --git a/src/components/menu-main/menu-main.less b/src/components/menu-main/menu-main.less deleted file mode 100644 index 1a81c18f..00000000 --- a/src/components/menu-main/menu-main.less +++ /dev/null @@ -1,47 +0,0 @@ -.menu-main { - display: flex; - - & > a { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: #eaeaea; - padding: 5px; - min-width: 70px; - transition: background-color .3s; - - & > .icon { - fill: #eaeaea; - } - } - - & > a.active { - color: #0ca589; - - & > .icon { - fill: #0ca589; - } - } - - @media (hover: hover) and (pointer: fine) { - & a:hover { - background: rgba(29,165,122,.3); - color: white; - - & > .icon { - fill: white; - } - } - } - - .icon { - width: 24px; - height: 24px; - } - -} - -.bottom-menu .menu-main > a { - flex-grow: 1; -} diff --git a/src/components/menu-user/menu-user.less b/src/components/menu-user/MenuUser.scss similarity index 100% rename from src/components/menu-user/menu-user.less rename to src/components/menu-user/MenuUser.scss diff --git a/src/components/menu-user/MenuUser.tsx b/src/components/menu-user/MenuUser.tsx index 4a24feeb..c7559bd0 100644 --- a/src/components/menu-user/MenuUser.tsx +++ b/src/components/menu-user/MenuUser.tsx @@ -4,9 +4,9 @@ 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' +import './MenuUser.scss' const MenuUser: React.FC = ({ logout }) => { const history = useHistory() diff --git a/src/components/mission-card/mission-card.less b/src/components/mission-card/MissionCard.scss similarity index 94% rename from src/components/mission-card/mission-card.less rename to src/components/mission-card/MissionCard.scss index 01cb708d..cf356bb8 100644 --- a/src/components/mission-card/mission-card.less +++ b/src/components/mission-card/MissionCard.scss @@ -1,14 +1,12 @@ -@import "../../index"; - .mission-card { &:first-child { margin-top: 0; } margin-top: 1rem; - background: #2e2e2e; + background: var(--color-dark-gray); border-radius: 5px; border: none; - // max-width: 400px; + max-width: 400px; padding: 20px; color: white; @@ -25,7 +23,6 @@ column-gap: 10px; } - .mission-circle { display: flex; align-items: center; diff --git a/src/components/mission-card/MissionCard.tsx b/src/components/mission-card/MissionCard.tsx index a3c031ac..d69ae58f 100644 --- a/src/components/mission-card/MissionCard.tsx +++ b/src/components/mission-card/MissionCard.tsx @@ -4,9 +4,9 @@ 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' +import './MissionCard.scss' const MissionCard: React.FC = ({ mission, diff --git a/src/components/mission-image/MissionImage.scss b/src/components/mission-image/MissionImage.scss new file mode 100644 index 00000000..45d900b2 --- /dev/null +++ b/src/components/mission-image/MissionImage.scss @@ -0,0 +1,11 @@ +.mission-image { + 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-image/MissionImage.tsx b/src/components/mission-image/MissionImage.tsx index 73e8786e..6f0d784d 100644 --- a/src/components/mission-image/MissionImage.tsx +++ b/src/components/mission-image/MissionImage.tsx @@ -3,7 +3,7 @@ import React, { FC } from 'react' import { isPlaceholder, Mission } from '../../features/mission' import { getSizedImageUrl } from '../../features/utils' -import './mission-image.less' +import './MissionImage.scss' const MissionImage: FC = ({ mission }) => { return ( diff --git a/src/components/mission-image/mission-image.less b/src/components/mission-image/mission-image.less deleted file mode 100644 index eb335734..00000000 --- a/src/components/mission-image/mission-image.less +++ /dev/null @@ -1,11 +0,0 @@ -.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; -} diff --git a/src/components/mission-info/mission-info.less b/src/components/mission-info/MissionInfo.scss similarity index 86% rename from src/components/mission-info/mission-info.less rename to src/components/mission-info/MissionInfo.scss index fa1e52bf..2601d96b 100644 --- a/src/components/mission-info/mission-info.less +++ b/src/components/mission-info/MissionInfo.scss @@ -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/mission-info/MissionInfo.tsx b/src/components/mission-info/MissionInfo.tsx index 197c0f27..df263b0c 100644 --- a/src/components/mission-info/MissionInfo.tsx +++ b/src/components/mission-info/MissionInfo.tsx @@ -10,11 +10,11 @@ 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 './MissionInfo.scss' import { isMobile } from '../../features/utils/os' const MissionList: React.FC = ({ mission }) => { diff --git a/src/components/mission-list/mission-list.less b/src/components/mission-list/MissionList.scss similarity index 100% rename from src/components/mission-list/mission-list.less rename to src/components/mission-list/MissionList.scss diff --git a/src/components/mission-list/MissionList.tsx b/src/components/mission-list/MissionList.tsx index 3dbae074..043fc942 100644 --- a/src/components/mission-list/MissionList.tsx +++ b/src/components/mission-list/MissionList.tsx @@ -6,7 +6,7 @@ import { NumDictionary } from '../../features/banner' import { mapMissions, Mission } from '../../features/mission' import MissionCard from '../mission-card' -import './mission-list.less' +import './MissionList.scss' const MissionList: React.FC = ({ missions, diff --git a/src/components/navbar/Navbar.less b/src/components/navbar/Navbar.scss similarity index 62% rename from src/components/navbar/Navbar.less rename to src/components/navbar/Navbar.scss index fa1db540..255dd810 100644 --- a/src/components/navbar/Navbar.less +++ b/src/components/navbar/Navbar.scss @@ -1,80 +1,94 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + .top-menu { + align-items: center; + background: var(--color-black); display: flex; flex-direction: row; justify-content: space-between; - align-items: center; + padding: 5px 10px; > div { - display: flex; align-items: center; + display: flex; .search-bar { margin-right: 45px; width: 250px; } } + + .menu-main { + @include media-max-md { + display: none; + } + } + + .brand-menu { + flex: 1; + max-width: 329px; + } } .brand-logo { + background-position: left center; + background-repeat: no-repeat; + background-size: contain; display: flex; - height: 57px; flex-grow: 1; + height: 57px; min-width: 150px; - background-size: contain; - background-repeat: no-repeat; - background-position: left center; + + @include media-max-md { + width: 50vw; + } } .mobile-search-bar { - padding-top: 0.5em; - padding-bottom: 0.5em; - padding-left: 2em; - padding-right: 2em; display: flex; + padding: 0.5em 2em; } -.mobile-search-button-container{ +.mobile-search-button-container { display: flex; - min-width: 70px; height: 60px; - transition: background-color .3s; + min-width: 70px; + transition: background-color 0.3s; .mobile-search-button { + align-items: center; background: none; - color: inherit; border: none; - padding: 0; - font: inherit; + color: inherit; cursor: pointer; - outline: inherit; - display: flex; - justify-content: center; - align-items: center; - + font: inherit; height: 100%; + justify-content: center; + outline: inherit; + padding: 0; width: 100%; - - .search-button-icon - { - width: 24px; + + .search-button-icon { + fill: var(--color-white-light); height: 24px; - fill: #eaeaea; - } - } + width: 24px; + } + } } .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: rgb(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..5d5d6af6 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 './Navbar.less' - -export class Navbar extends React.Component { - constructor(props: NavBarProps) { - super(props) - - this.state = { - mobileSearchBarActive: false, +import Logo from '../../assets/img/logo/logo64.png' +import SVGSearch from '../../assets/img/icons/search.svg?react' + +import './Navbar.scss' + +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/PlaceAccordion.scss b/src/components/place-accordion/PlaceAccordion.scss new file mode 100644 index 00000000..84c75690 --- /dev/null +++ b/src/components/place-accordion/PlaceAccordion.scss @@ -0,0 +1,3 @@ +.place-accordion { + background: var(--color-dark-gray); +} diff --git a/src/components/place-accordion/PlaceAccordion.tsx b/src/components/place-accordion/PlaceAccordion.tsx index 0d8b91bf..fdf279c8 100644 --- a/src/components/place-accordion/PlaceAccordion.tsx +++ b/src/components/place-accordion/PlaceAccordion.tsx @@ -3,7 +3,7 @@ import { Place, PlaceSortOrder } from '../../features/place' import { PlaceAccordionPage } from './PlaceAccordionPage' -import './place-accordion.less' +import './PlaceAccordion.scss' export const PlaceAccordion: FC = ({ selectedPlaces, @@ -22,7 +22,7 @@ export const PlaceAccordion: FC = ({ const parentPlace = hierarchy[index - 1] return ( = ({ place, diff --git a/src/components/place-accordion/place-accordion-page.less b/src/components/place-accordion/PlaceAccordionPage.scss similarity index 100% rename from src/components/place-accordion/place-accordion-page.less rename to src/components/place-accordion/PlaceAccordionPage.scss diff --git a/src/components/place-accordion/PlaceAccordionPage.tsx b/src/components/place-accordion/PlaceAccordionPage.tsx index 63f60572..452c7c10 100644 --- a/src/components/place-accordion/PlaceAccordionPage.tsx +++ b/src/components/place-accordion/PlaceAccordionPage.tsx @@ -6,10 +6,10 @@ 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' +import './PlaceAccordionPage.scss' export const PlaceAccordionPage: FC = ({ parentPlace, diff --git a/src/components/place-accordion/place-accordion.less b/src/components/place-accordion/place-accordion.less deleted file mode 100644 index 3cf227bf..00000000 --- a/src/components/place-accordion/place-accordion.less +++ /dev/null @@ -1,3 +0,0 @@ -.place-accordion { - background: #2E2E2E; -} diff --git a/src/components/place-list-flat/place-card.less b/src/components/place-list-flat/PlaceCard.scss similarity index 81% rename from src/components/place-list-flat/place-card.less rename to src/components/place-list-flat/PlaceCard.scss index 4fbbad98..c7f9b555 100644 --- a/src/components/place-list-flat/place-card.less +++ b/src/components/place-list-flat/PlaceCard.scss @@ -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-flat/PlaceCard.tsx b/src/components/place-list-flat/PlaceCard.tsx index 8775da49..3b153743 100644 --- a/src/components/place-list-flat/PlaceCard.tsx +++ b/src/components/place-list-flat/PlaceCard.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { Place, createBrowseUri } from '../../features/place' import PlaceEntry from '../place-list/PlaceEntry' -import './place-card.less' +import './PlaceCard.scss' const PlaceCard: FC = ({ place }) => { const { t } = useTranslation(undefined, { keyPrefix: 'places.types' }) diff --git a/src/components/place-list-flat/place-list-flat.less b/src/components/place-list-flat/PlaceListFlat.scss similarity index 100% rename from src/components/place-list-flat/place-list-flat.less rename to src/components/place-list-flat/PlaceListFlat.scss diff --git a/src/components/place-list-flat/PlaceListFlat.tsx b/src/components/place-list-flat/PlaceListFlat.tsx index b13e492a..e31c9bb4 100644 --- a/src/components/place-list-flat/PlaceListFlat.tsx +++ b/src/components/place-list-flat/PlaceListFlat.tsx @@ -6,7 +6,7 @@ import { useInfiniteScroll } from '../../hooks/InfiniteScroll' import { Place } from '../../features/place' import PlaceCard from './PlaceCard' -import './place-list-flat.less' +import './PlaceListFlat.scss' const PlaceListFlat: FC = ({ places, diff --git a/src/components/place-list/place-entry.less b/src/components/place-list/PlaceEntry.scss similarity index 53% rename from src/components/place-list/place-entry.less rename to src/components/place-list/PlaceEntry.scss index f2045042..4f5b1024 100644 --- a/src/components/place-list/place-entry.less +++ b/src/components/place-list/PlaceEntry.scss @@ -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/PlaceEntry.tsx b/src/components/place-list/PlaceEntry.tsx index a93bc917..86c3b3b0 100644 --- a/src/components/place-list/PlaceEntry.tsx +++ b/src/components/place-list/PlaceEntry.tsx @@ -2,10 +2,10 @@ import React, { FC } from 'react' import { Place } from '../../features/place' -import './place-entry.less' +import './PlaceEntry.scss' -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/PlaceList.scss b/src/components/place-list/PlaceList.scss new file mode 100644 index 00000000..74f2eddc --- /dev/null +++ b/src/components/place-list/PlaceList.scss @@ -0,0 +1,41 @@ +.places-list-item { + column-gap: 0.3em; + cursor: pointer; + overflow: hidden; + 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/place-list/PlaceList.tsx b/src/components/place-list/PlaceList.tsx index 73fe0594..2d972b39 100644 --- a/src/components/place-list/PlaceList.tsx +++ b/src/components/place-list/PlaceList.tsx @@ -5,7 +5,7 @@ import { Trans, useTranslation } from 'react-i18next' import { Place, PlaceSortOrder, sortPlaces } from '../../features/place' import PlaceEntry from './PlaceEntry' -import './place-list.less' +import './PlaceList.scss' const PlaceList: FC = ({ title, diff --git a/src/components/place-list/place-list.less b/src/components/place-list/place-list.less deleted file mode 100644 index bfc5c75f..00000000 --- a/src/components/place-list/place-list.less +++ /dev/null @@ -1,24 +0,0 @@ -.places-list-item { - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - overflow: hidden; - display: flex; - align-items: baseline; - column-gap: 0.3em; -} - -.places-list { - padding-top: 5em; - padding-left: 2em; - padding-right: 1em; -} - -.places-list-child { - margin-left: 1em; - - .place-name { - overflow: hidden; - text-overflow: ellipsis; - } -} diff --git a/src/components/recent-banners/recent-banners.less b/src/components/recent-banners/RecentBanners.scss similarity index 100% rename from src/components/recent-banners/recent-banners.less rename to src/components/recent-banners/RecentBanners.scss diff --git a/src/components/recent-banners/RecentBanners.tsx b/src/components/recent-banners/RecentBanners.tsx index ad7c38ab..ff9dab5a 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 './RecentBanners.scss' +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.scss b/src/components/search-input/SearchInput.scss new file mode 100644 index 00000000..92959d39 --- /dev/null +++ b/src/components/search-input/SearchInput.scss @@ -0,0 +1,65 @@ +@mixin search-input-button-highlighted { + background: var(--color-positive-green); + fill: white; +} + + +.search-input-form { + display: flex; + flex-direction: row; + width: 100%; + + .search-input { + padding: 5px; + padding-left: 10px; + background: var(--color-panel-bg); + border-radius: 4px 0px 0px 4px; + border: none; + width: 100%; + + &:focus { + background: var(--color-gray); + outline: none; + color: var(--color-white); + } + + &:focus + .search-input-button { + @include search-input-button-highlighted; + } + + &::placeholder { + color: var(--color-light-gray); + font-size: 12px; + } + } + + .search-input-button { + display: flex; + flex-direction: column; + background: var(--color-gray); + border-radius: 0px 4px 4px 0px; + border: none; + cursor: pointer; + justify-content: center; + align-items: center; + padding-left: 12px; + padding-right: 12px; + fill: var(--color-light-black); + + @media (hover: hover) and (pointer: fine) { + &:hover { + @include search-input-button-highlighted; + } + } + + &:focus { + @include search-input-button-highlighted; + } + } + + .search-input-icon { + fill: inherit; + width: 17.49px; + height: 17.49px; + } +} diff --git a/src/components/search-input/SearchInput.tsx b/src/components/search-input/SearchInput.tsx index cb40f91e..8b62056b 100644 --- a/src/components/search-input/SearchInput.tsx +++ b/src/components/search-input/SearchInput.tsx @@ -2,9 +2,9 @@ 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' +import './SearchInput.scss' const SearchInput: FC = ({ autoFocus, onSearch }) => { const textInput = useRef(null) diff --git a/src/components/search-input/search-input.less b/src/components/search-input/search-input.less deleted file mode 100644 index d0dc2670..00000000 --- a/src/components/search-input/search-input.less +++ /dev/null @@ -1,65 +0,0 @@ -.search-input-button-highlighted() { - background: #077561; - fill: white; -} - -.search-input-form { - display: flex; - flex-direction: row; - width: 100%; - - .search-input { - padding:5px; - padding-left: 10px; - background: #404040; - border-radius: 4px 0px 0px 4px; - border: none; - width: 100%; - - &:focus { - background: #6A6A6A; - outline: none; - color: white; - } - - &:focus + .search-input-button { - .search-input-button-highlighted() - } - - &::placeholder { - color: #AAAAAA; - font-size: 12px; - } - } - - .search-input-button { - display: flex; - flex-direction: column; - background: #888888; - border-radius: 0px 4px 4px 0px; - border: none; - cursor: pointer; - justify-content: center; - align-items: center; - padding-left: 12px; - padding-right: 12px; - fill: #1B1B1B; - - @media (hover: hover) and (pointer: fine) { - &:hover { - .search-input-button-highlighted() - } - } - - &:focus { - .search-input-button-highlighted() - } - } - - .search-input-icon - { - fill: inherit; - width: 17.49px; - height: 17.49px; - } -} \ No newline at end of file diff --git a/src/components/search-mission-card/search-mission-card.less b/src/components/search-mission-card/SearchMissionCard.scss similarity index 84% rename from src/components/search-mission-card/search-mission-card.less rename to src/components/search-mission-card/SearchMissionCard.scss index cc94e04f..e6b9af5e 100644 --- a/src/components/search-mission-card/search-mission-card.less +++ b/src/components/search-mission-card/SearchMissionCard.scss @@ -1,10 +1,6 @@ -@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 +21,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 +57,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-card/SearchMissionCard.tsx b/src/components/search-mission-card/SearchMissionCard.tsx index ef7d18e9..e283a174 100644 --- a/src/components/search-mission-card/SearchMissionCard.tsx +++ b/src/components/search-mission-card/SearchMissionCard.tsx @@ -6,9 +6,9 @@ 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' +import './SearchMissionCard.scss' const getMissionIntelLink = (mission: Mission) => { if (mission.id) { 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/StepCard.scss similarity index 94% rename from src/components/step-card/step-card.less rename to src/components/step-card/StepCard.scss index a0d55eb9..be3e9609 100644 --- a/src/components/step-card/step-card.less +++ b/src/components/step-card/StepCard.scss @@ -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-card/StepCard.tsx b/src/components/step-card/StepCard.tsx index 76e746f2..3ecc5a66 100644 --- a/src/components/step-card/StepCard.tsx +++ b/src/components/step-card/StepCard.tsx @@ -7,7 +7,7 @@ import { getExternalLinkAttributes, } from '../../features/utils' -import './step-card.less' +import './StepCard.scss' const getStepNameLink = (title: string, poi?: POI) => { if (poi && poi.type !== 'unavailable') { diff --git a/src/components/step-list/StepList.scss b/src/components/step-list/StepList.scss new file mode 100644 index 00000000..661e2691 --- /dev/null +++ b/src/components/step-list/StepList.scss @@ -0,0 +1,7 @@ +@use "../../index.scss"; + +.step-list { + .ant-layout { + background-color: var(--color-panel-bg) !important; + } +} diff --git a/src/components/step-list/StepList.tsx b/src/components/step-list/StepList.tsx index 1daa1d46..1210c33a 100644 --- a/src/components/step-list/StepList.tsx +++ b/src/components/step-list/StepList.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next' import { Step } from '../../features/mission' import StepCard from '../step-card' -import './step-list.less' +import './StepList.scss' const StepList: FC = ({ steps }) => { const { t } = useTranslation() diff --git a/src/components/step-list/step-list.less b/src/components/step-list/step-list.less deleted file mode 100644 index 58d7a11a..00000000 --- a/src/components/step-list/step-list.less +++ /dev/null @@ -1,7 +0,0 @@ -@import "../../index"; - -.step-list { - .ant-layout { - background-color: @color-panel-bg !important; - } -} diff --git a/src/components/switch/switch.less b/src/components/switch/Switch.scss similarity index 58% rename from src/components/switch/switch.less rename to src/components/switch/Switch.scss index 4d86c986..54813bea 100644 --- a/src/components/switch/switch.less +++ b/src/components/switch/Switch.scss @@ -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/switch/Switch.tsx b/src/components/switch/Switch.tsx index b9969d3c..d3627de2 100644 --- a/src/components/switch/Switch.tsx +++ b/src/components/switch/Switch.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react' import { Switch as AntdSwitch } from 'antd' -import './switch.less' +import './Switch.scss' const Switch: FC = ({ checked, onChange }) => (
diff --git a/src/components/user-banner-list-preview/user-banner-list-preview.less b/src/components/user-banner-list-preview/UserBannerListPreview.scss similarity index 66% rename from src/components/user-banner-list-preview/user-banner-list-preview.less rename to src/components/user-banner-list-preview/UserBannerListPreview.scss index b9ac4541..6c6857b7 100644 --- a/src/components/user-banner-list-preview/user-banner-list-preview.less +++ b/src/components/user-banner-list-preview/UserBannerListPreview.scss @@ -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/user-banner-list-preview/UserBannerListPreview.tsx b/src/components/user-banner-list-preview/UserBannerListPreview.tsx index f9e011f4..5df99bdc 100644 --- a/src/components/user-banner-list-preview/UserBannerListPreview.tsx +++ b/src/components/user-banner-list-preview/UserBannerListPreview.tsx @@ -18,7 +18,7 @@ import BannerListTypeNavigation from '../banner-list-type-navigation' import BannerList from '../banner-list' import IfUserLoggedIn from '../login/if-user-logged-in' -import './user-banner-list-preview.less' +import './UserBannerListPreview.scss' import { BannerFilter } from '../../features/banner/filter' class UserBannerListPreview extends React.Component< @@ -96,7 +96,7 @@ class UserBannerListPreview extends React.Component< 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/verify-account.less b/src/components/verify-account/VerifyAccount.scss similarity index 82% rename from src/components/verify-account/verify-account.less rename to src/components/verify-account/VerifyAccount.scss index 365462fc..35081c6f 100644 --- a/src/components/verify-account/verify-account.less +++ b/src/components/verify-account/VerifyAccount.scss @@ -1,54 +1,54 @@ -@import '../../index.less'; +@use '/src/assets/stylesheets/components/button' as *; .account-linking { - width: 100%; + align-items: flex-start; display: flex; flex-direction: column; - align-items: left; margin-top: 20px; row-gap: 10px; + width: 100%; - >div { + > div { max-width: 500px; } p { - margin: 0; font-size: 16px; + margin: 0; } .change-verification-buttons { + column-gap: 10px; display: flex; flex-direction: row; - column-gap: 10px; margin-top: 10px; } .input-agent-name, .input-agent-token { + column-gap: 10px; display: flex; flex-direction: row; - column-gap: 10px; margin-top: 10px; input { - width: 100%; height: 36px; + width: 100%; } } .forum-link { - .positive-action-button; + @include positive-action-button; display: flex; flex-direction: column; justify-content: center; } .verify-steps-buttons { - margin-top: 20px; display: flex; flex-direction: row; justify-content: space-between; + margin-top: 20px; } .verify-account-agent { @@ -56,6 +56,6 @@ } .claim-button { - .positive-action-button; + @include positive-action-button; } -} \ No newline at end of file +} diff --git a/src/components/verify-account/VerifyAccount.tsx b/src/components/verify-account/VerifyAccount.tsx index 6d214d3a..213a5565 100644 --- a/src/components/verify-account/VerifyAccount.tsx +++ b/src/components/verify-account/VerifyAccount.tsx @@ -1,189 +1,122 @@ -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 { Agent } from '../agent' +import LoadingOverlay from '../loading-overlay' + +import './VerifyAccount.scss' +import { Step1 } from './Step1' +import { Step2 } from './Step2' +import { User } from '../../features/user' +import { ApiResponse } from '../../api' import { abortClaimUser, claimUser, + getUser, 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' - -type AppDispatch = ThunkDispatch -type AppAction = ThunkAction, RootState, any, UserActionTypes> +} from '../../features/user/api' +import { Step3 } from './Step3' -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]) + + useEffect(() => { + if (verificationMessage && isCopied) { + const id = setInterval(async () => { + try { + await handleApiRequest(getUser()) + } catch (e) {} + }, 120_000) + return () => clearTimeout(id) + } + }, [verificationMessage, isCopied, handleApiRequest]) - const onCopyToken = useCallback(() => { - navigator.clipboard.writeText(currentUser.verificationToken) - onNext() - }, [currentUser.verificationToken, onNext]) + 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') + } - const getCarousel = useCallback( - () => ( + let actions + if (isClaiming) { + actions = ( { slider.current = c @@ -192,131 +125,53 @@ 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/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..c2a3dcfe 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -2,7 +2,7 @@ import i18n from 'i18next' import { initReactI18next } from 'react-i18next' import resourcesToBackend from 'i18next-resources-to-backend' -const supportedLngs = { +const supportedLanguages = { de: /^de(?:-.*)?$/, en: /^en(?:-.*)?$/, es: /^es(?:-.*)?$/, @@ -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)) { + for (const pair of Object.entries(supportedLanguages)) { 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 deleted file mode 100644 index 221ea247..00000000 --- a/src/index.less +++ /dev/null @@ -1,102 +0,0 @@ -body { - margin: 0; - 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.scss b/src/index.scss new file mode 100644 index 00000000..3aa6d06e --- /dev/null +++ b/src/index.scss @@ -0,0 +1,8 @@ +body { + background: var(--color-light-black); + font-family: Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + font-size: 16px; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + margin: 0; +} diff --git a/src/index.tsx b/src/index.tsx index 56f03a45..5d9657ea 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,12 +5,7 @@ import { PersistGate } from 'redux-persist/integration/react' import App from './App' import { store, persistor } from './store' -import './index.less' -import './mobile.less' - -import '@fontsource/roboto/400.css' -import '@fontsource/roboto/400-italic.css' -import '@fontsource/roboto/700.css' +import './index.scss' const container = document.getElementById('root')! const root = createRoot(container) 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.scss b/src/pages/Home.scss new file mode 100644 index 00000000..1b6d75f0 --- /dev/null +++ b/src/pages/Home.scss @@ -0,0 +1,22 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + +.home { + align-items: center; + + .user-banner-list-preview { + margin-top: 2em; + + .banner-list { + justify-content: center; + } + } +} + +.announcement-and-recent-banners { + margin: 2em; + max-width: 70rem; + + @include media-max-md { + margin: 0.4rem; + } +} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 0ab99ce1..724885c4 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 './Home.scss' 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.less b/src/pages/account/Account.scss similarity index 100% rename from src/pages/account/account.less rename to src/pages/account/Account.scss diff --git a/src/pages/account/Account.tsx b/src/pages/account/Account.tsx new file mode 100644 index 00000000..9af5afbb --- /dev/null +++ b/src/pages/account/Account.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' + +import UserPicture from '../../components/login/user-picture' +import UserName from '../../components/login/user-name' +import { VerifyAccount } from '../../components/verify-account' + +import './Account.scss' + +const Account: FC = () => { + const { t } = useTranslation() + + return ( +
+
+

{t('account.title')}

+
+ +
+ + + +
+
+ +
+
+ ) +} + +export default Account diff --git a/src/pages/account/account.tsx b/src/pages/account/account.tsx deleted file mode 100644 index 59b891f5..00000000 --- a/src/pages/account/account.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { ThunkDispatch } from 'redux-thunk' -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] - ) - - return ( -
-
- -

{t('title')}

-
- -
- - - - {currentUser && currentUser.agent && ( - - - - )} -
-
- -
-
- ) -} - -export default Account diff --git a/src/pages/account/index.ts b/src/pages/account/index.ts index fce2a6aa..fbc0dd1c 100644 --- a/src/pages/account/index.ts +++ b/src/pages/account/index.ts @@ -1 +1 @@ -export { default as Account } from './account' +export { default as Account } from './Account' diff --git a/src/pages/agent/agent.less b/src/pages/agent/Agent.scss similarity index 100% rename from src/pages/agent/agent.less rename to src/pages/agent/Agent.scss diff --git a/src/pages/agent/Agent.tsx b/src/pages/agent/Agent.tsx index 28e5f3b6..e8aef802 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 - - - - )} - - )} - - {(bannersStatus === 'initial' || - bannersStatus === 'loading') && ( - Loading... - )} - -
-
- +import './Agent.scss' + +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.scss b/src/pages/banner-info/BannerInfo.scss new file mode 100644 index 00000000..a0b914d9 --- /dev/null +++ b/src/pages/banner-info/BannerInfo.scss @@ -0,0 +1,24 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + +.banner-info-page { + height: 100%; + + @include media-max-md { + height: 100%; + } + + .banner-card-title { + @include media-max-md { + display: none !important; /* stylelint-disable-line */ + } + } + + .banner-info-overview { + .banner-info-container { + @include media-max-md { + padding-right: unset; + width: 320px; + } + } + } +} diff --git a/src/pages/banner-info/BannerInfo.tsx b/src/pages/banner-info/BannerInfo.tsx index 164fe531..508fb11c 100644 --- a/src/pages/banner-info/BannerInfo.tsx +++ b/src/pages/banner-info/BannerInfo.tsx @@ -18,7 +18,7 @@ import { import LoadingOverlay from '../../components/loading-overlay' import { BannerInfoWithMap } from '../../components/banner-info-with-map' -import './banner-info.less' +import './BannerInfo.scss' class BannerInfo extends React.Component { constructor(props: BannerInfoProps) { @@ -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 deleted file mode 100644 index 680c79d1..00000000 --- a/src/pages/banner-info/banner-info.less +++ /dev/null @@ -1,3 +0,0 @@ -.banner-info-page { - height: 100%; -} \ No newline at end of file diff --git a/src/pages/browser/Browser.scss b/src/pages/browser/Browser.scss new file mode 100644 index 00000000..1745d776 --- /dev/null +++ b/src/pages/browser/Browser.scss @@ -0,0 +1,34 @@ +@use "/src/assets/stylesheets/base/mixins" as *; + +.browser-icon { + color: var(--color-white-light); + fill: var(--color-white-light); + height: 0.9em; + margin-left: 0.5em; + vertical-align: middle; +} + +.browser { + display: flex; + + .places-list { + flex: 1; + max-width: 400px; + + @include media-max-md { + display: none; + } + } + + .place-accordion { + display: none; + + @include media-max-md { + display: block; + } + } + + .places-banners { + padding: 10px; + } +} diff --git a/src/pages/browser/Browser.tsx b/src/pages/browser/Browser.tsx index 5a9e54c4..179510fa 100644 --- a/src/pages/browser/Browser.tsx +++ b/src/pages/browser/Browser.tsx @@ -26,9 +26,9 @@ 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 './Browser.scss' import LoadingOverlay from '../../components/loading-overlay' import { BannerFilter, @@ -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 deleted file mode 100644 index 104895d2..00000000 --- a/src/pages/browser/browser.less +++ /dev/null @@ -1,24 +0,0 @@ -.browser-icon { - color: #EAEAEA; - fill: #EAEAEA; - vertical-align: middle; - margin-left: 0.5em; - height: 0.9em; - } - -.browser { - display: flex; - - .places-list { - flex: 1; - max-width: 400px; - } - - .place-accordion { - display: none; - } - - .places-banners { - padding: 10px; - } -} diff --git a/src/pages/create-banner/create-banner.less b/src/pages/create-banner/CreateBanner.scss similarity index 56% rename from src/pages/create-banner/create-banner.less rename to src/pages/create-banner/CreateBanner.scss index 445e800f..7587d088 100644 --- a/src/pages/create-banner/create-banner.less +++ b/src/pages/create-banner/CreateBanner.scss @@ -1,75 +1,71 @@ .create-banner { - margin: 0 5%; + align-items: center; display: flex; flex-direction: column; - align-items: center; height: calc(100% - 5px); + margin: 0 5%; - &>h1 { + & > h1 { width: 100%; } .create-banner-steps { display: flex; flex-wrap: wrap; - column-gap: 50px; - row-gap: 20px; - width: 100%; + gap: 20px 50px; height: 100%; + width: 100%; h1 { - vertical-align: middle; - display: flex; column-gap: 10px; + display: flex; font-size: 36px; line-height: 40px; + vertical-align: middle; .ellipse { + border: 2px solid var(--color-white); border-radius: 50%; - width: 36px; - height: 36px; - padding: 0; - border: 2px solid white; - text-align: center; display: block; - line-height: 30px; font-size: 24px; + height: 36px; + line-height: 30px; margin-top: 3px; + padding: 0; + text-align: center; + width: 36px; } } h3 { margin-top: 10px; + + svg { + path { + fill: var(--color-white); + } + } } - &>div { - flex-grow: 1; + & > div { flex-basis: 10%; + flex-grow: 1; min-width: 300px; } .missions-search, .missions-arrange { - min-height: 750px; display: flex; flex-direction: column; - - } - - .missions-search .search-mission-list { - height: 100% !important; - } - - .missions-arrange .search-mission-list { - height: 100% !important; + min-height: 750px; } .create-banner-preview { - background: #2e2e2e; + background: var(--color-dark-gray); + border: 1px solid var(--color-white); border-radius: 5px; - padding: 15px; margin: 10px 0 30px; - border: 1px solid white; + padding: 15px; & .banner-image { margin: auto; @@ -77,10 +73,10 @@ } .adv-options-container { - border: 1px solid white; + border: 1px solid var(--color-white); border-radius: 4px; - padding: 5px 20px; margin-top: 10px; + padding: 5px 20px; h4 { margin-top: 10px; @@ -88,8 +84,8 @@ .ant-select, .ant-input-number { - width: 100%; margin-top: 10px; + width: 100%; } } @@ -105,21 +101,20 @@ input, textarea { - background-color: #B9B9B9; - border-radius: 4px; + background-color: var(--color-gray); border: none; - color: #000; + border-radius: 4px; + color: var(--color-black); font-size: 14px; padding: 8px 10px; &::placeholder { - color: #555; + color: var(--color-light-black); } } .missions-search, .create-banner-info { - input, textarea { margin-top: 5px; @@ -138,9 +133,9 @@ } button { - border-color: white; + border-color: var(--color-white); border-radius: 4px; - color: white; + color: var(--color-white); width: 100px; } } @@ -150,7 +145,7 @@ svg { path { - fill: white; + fill: var(--color-white); } } @@ -162,26 +157,34 @@ .incomplete-chooser { margin-bottom: 10px; + // TODO: find if the button exist??? button { - margin: 0 !important; - background-color: #2e2e2e; - border: none; - display: block; + background-clip: content-box, border-box !important; /* stylelint-disable-line */ + background-color: var(--color-dark-gray); + background-image: linear-gradient( + rgb(255 255 255 / 0), + rgb(255 255 255 / 0) + ), + linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)) !important; /* stylelint-disable-line */ + background-origin: border-box !important; /* stylelint-disable-line */ + border: solid 2px transparent !important; /* stylelint-disable-line */ 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; + box-shadow: 2px 1000px 1px var(--color-light-black) inset !important; /* stylelint-disable-line */ + color: var(--color-white); + display: block; + margin: 0 !important; /* stylelint-disable-line */ 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; + 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, #109393, #15D4B2) !important; + background-color: green; + background-image: linear-gradient( + rgb(255 255 255 / 0), + rgb(255 255 255 / 0) + ), + linear-gradient(0deg, var(--color-border-dark), var(--color-border-light)) !important; /* stylelint-disable-line */ } } } @@ -189,52 +192,56 @@ .mission-error { input { - background-color: tomato; + background-color: var(--color-error); } } } .button-review { + color: var(--color-white); width: 100%; - color: white; } .ant-radio-group { - color: white; - display: flex; + color: var(--color-white); column-gap: 5px; + display: flex; label { - color: white !important; - text-align: center; + background-color: var(--color-white); border: none; border-radius: 5px; - background-color: white; - padding: 1px; - display: flex; + color: var(--color-white); cursor: pointer; + display: flex; + padding: 1px; + text-align: center; + > span:nth-child(2) { + background-color: var(--color-light-black); + border-radius: 4px; + flex: 1; + padding: 0 10px; + } &.ant-radio-button-wrapper-checked { - background-image: linear-gradient(180deg, #109393, #15D4B2); + background-image: linear-gradient(180deg, var(--color-border-dark), var(--color-border-light)); padding: 2px; - >span:nth-child(2) { + > span:nth-child(2) { + align-items: center; + display: flex; + justify-content: center; line-height: 24px; } } - - >span:nth-child(2) { - background-color: #1B1B1B; - border-radius: 4px; - padding: 0 10px; - flex: 1; - } } } - .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { - background: none !important; + .ant-radio-button-wrapper-checked:not( + .ant-radio-button-wrapper-disabled + )::before { + background: none !important; /* stylelint-disable-line */ } } @@ -245,7 +252,7 @@ } .placeholder-card { - .ant-input-number { - margin-right: 87px !important; + .ant-input-number { /* stylelint-disable-line */ + margin-right: 87px !important; /* stylelint-disable-line */ } -} \ No newline at end of file +} diff --git a/src/pages/create-banner/CreateBanner.tsx b/src/pages/create-banner/CreateBanner.tsx index bb4ad688..f90bfb73 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,11 +45,12 @@ 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' +import './CreateBanner.scss' import { MissionFilter } from '../../features/mission/filter' class CreateBanner extends React.Component< @@ -775,6 +776,20 @@ class CreateBanner extends React.Component< bannerTitle, detectedLength ) + const markdownHelp = ( + , + code: , + pre:
,
+          p: 

, + ul: