diff --git a/.babelrc b/.babelrc index b02645ca1..eb6d32d93 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,5 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], + "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], "plugins": [ ["babel-plugin-wildcard", { "exts": ["json"], "nostrip": true, "noModifyCase": true }], [ diff --git a/.dockerignore b/.dockerignore index 0634d58ed..990a864bc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,6 @@ .vscode .eslintrc.yml .gitignore -.sass-lint.yml .travis.yml node_modules diff --git a/.gitignore b/.gitignore index 8a30e37c5..c329861ac 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ *.swp *.vi *~ -*.sass-cache # OS or Editor folders .DS_Store diff --git a/.sass-lint.yml b/.sass-lint.yml deleted file mode 100644 index 110a25ab8..000000000 --- a/.sass-lint.yml +++ /dev/null @@ -1,95 +0,0 @@ -options: - formatter: stylish -files: - include: '**/*.s+(a|c)ss' -rules: - # Extends - extends-before-mixins: 1 - extends-before-declarations: 1 - placeholder-in-extend: 1 - - # Mixins - mixins-before-declarations: 1 - - # Line Spacing - one-declaration-per-line: 1 - empty-line-between-blocks: 1 - single-line-per-selector: 1 - - # Disallows - no-attribute-selectors: 0 - no-color-hex: 0 - no-color-keywords: 1 - no-color-literals: 1 - no-combinators: 0 - no-css-comments: 1 - no-debug: 1 - no-disallowed-properties: 0 - no-duplicate-properties: 1 - no-empty-rulesets: 1 - no-extends: 0 - no-ids: 1 - no-important: 1 - no-invalid-hex: 1 - no-mergeable-selectors: 1 - no-misspelled-properties: 1 - no-qualifying-elements: 1 - no-trailing-whitespace: 1 - no-trailing-zero: 1 - no-transition-all: 1 - no-universal-selectors: 0 - no-url-domains: 1 - no-url-protocols: 1 - no-vendor-prefixes: 1 - no-warn: 1 - property-units: 0 - - # Nesting - declarations-before-nesting: 1 - force-attribute-nesting: 1 - force-element-nesting: 1 - force-pseudo-nesting: 1 - - # Name Formats - class-name-format: 1 - function-name-format: 1 - id-name-format: 0 - mixin-name-format: 1 - placeholder-name-format: 1 - variable-name-format: 1 - - # Style Guide - attribute-quotes: 1 - bem-depth: 0 - border-zero: 1 - brace-style: 1 - clean-import-paths: 1 - empty-args: 1 - hex-length: 1 - hex-notation: 1 - indentation: 1 - leading-zero: 0 - max-line-length: 0 - max-file-line-count: 0 - nesting-depth: 1 - property-sort-order: 0 - pseudo-element: 1 - quotes: 1 - shorthand-values: 1 - url-quotes: 1 - variable-for-property: 1 - zero-unit: 1 - - # Inner Spacing - space-after-comma: 1 - space-before-colon: 1 - space-after-colon: 1 - space-before-brace: 1 - space-before-bang: 1 - space-after-bang: 1 - space-between-parens: 1 - space-around-operator: 1 - - # Final Items - trailing-semicolon: 1 - final-newline: 1 diff --git a/README.md b/README.md index 860dc26d4..a1ce2eb25 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ app │   │   └── selectors.js => Re-select selectors to select data in state │   └── ... ├── static => Static assets, directly copied to the public directory -├── styles => Stylesheets in .sass format, all included from application.sass └── router.jsx => Application router and main entry point ``` diff --git a/app/API/no_internet_error.js b/app/API/no_internet_error.js index 9aaf4ae20..137fe4633 100644 --- a/app/API/no_internet_error.js +++ b/app/API/no_internet_error.js @@ -1,14 +1,14 @@ // Used only to show a message when request fail / timeout due to connection problems + import { NO_INTERNET_ERROR } from '../constants' import { flashError } from '../state/flashes/reducer' -import store from '../state/index' export default function noInternetError() { - store.dispatch( - flashError({ - message: NO_INTERNET_ERROR, - timeLeft: 999999999999, - infoText: 'actions.reload', - }), - ) + flashError({ + title: 'TODO', + message: NO_INTERNET_ERROR, + infoText: 'actions.reload', + variant: 'destructive', + duration: 999999999999, + }) } diff --git a/app/App.jsx b/app/App.jsx index 4c51e95f7..f67518c97 100644 --- a/app/App.jsx +++ b/app/App.jsx @@ -1,6 +1,6 @@ // Import polyfills // Import styles -import './styles/application.sass' +import '@/styles/main.css' import { ApolloProvider } from '@apollo/client' // Import libs @@ -8,7 +8,7 @@ import React from 'react' import { I18nextProvider } from 'react-i18next' import { Configure, Index, InstantSearch } from 'react-instantsearch-dom' import { Provider as ReduxProvider } from 'react-redux' -import { polyfill as smoothSrollPolyfill } from 'smoothscroll-polyfill' +import { polyfill as smoothScrollPolyfill } from 'smoothscroll-polyfill' import { ThemeProvider } from 'styled-components' // Import APIs so they can load their configurations @@ -24,7 +24,7 @@ import store from './state' import theme from './styles/theme' // Activate polyfills -smoothSrollPolyfill() +smoothScrollPolyfill() const App = () => ( diff --git a/app/components/App/LanguageSelector.jsx b/app/components/App/LanguageSelector.jsx index 16c63d493..09def923c 100644 --- a/app/components/App/LanguageSelector.jsx +++ b/app/components/App/LanguageSelector.jsx @@ -1,10 +1,17 @@ -import { Box, Flex } from '@rebass/grid' -import classNames from 'classnames' import { Map } from 'immutable' import React from 'react' import { withNamespaces } from 'react-i18next' import { Globe } from 'styled-icons/fa-solid' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { cn } from '@/lib/css-utils' + const defaultLocales = new Map({ en: 'English', fr: 'Français', @@ -21,26 +28,25 @@ export default class LanguageSelector extends React.PureComponent { const options = defaultLocales.merge(this.props.additionalOptions || {}).sortBy((v, k) => k) return ( - + ) } renderLocalesMap(localesMap) { return localesMap.entrySeq().map(([key, value]) => ( - + )) } renderIcon() { - const { value, size } = this.props + const { value } = this.props if (value === 'fr') { return '🇫🇷' } else if (value === 'en') { @@ -56,16 +62,15 @@ export default class LanguageSelector extends React.PureComponent { } else if (value === 'ru') { return '🇷🇺' } - return + return } render() { - const sizeClass = this.props.size ? `is-${this.props.size}` : null return ( - - {this.props.withIcon && {this.renderIcon()}} - {this.renderSelect()} - +
+ {this.props.withIcon &&
{this.renderIcon()}
} + {this.renderSelect()} +
) } } diff --git a/app/components/App/Layout.jsx b/app/components/App/Layout.jsx index f631c3965..fe54ba036 100644 --- a/app/components/App/Layout.jsx +++ b/app/components/App/Layout.jsx @@ -3,8 +3,8 @@ import { Helmet } from 'react-helmet' import { connect } from 'react-redux' import { MainModalContainer } from '../Modal/MainModalContainer' +import { Toaster } from '../ui/toaster' import PublicAchievementUnlocker from '../Users/PublicAchievementUnlocker' -import { FlashMessages } from '../Utils' import BackgroundNotifier from './BackgroundNotifier' import CrashReportPage from './CrashReportPage' import Navbar from './Navbar' @@ -51,21 +51,23 @@ export default class Layout extends React.PureComponent { const mainContainerClass = sidebarExpended ? undefined : 'expended' return ( -
- {this.renderMetadata()} - - - - -
- {!this.state.error ? children : } + +
+ {this.renderMetadata()} + + + +
+ {!this.state.error ? children : } +
+ +
- - -
+ + ) } diff --git a/app/components/App/Navbar.jsx b/app/components/App/Navbar.jsx index 3063d91ab..57c4357fe 100644 --- a/app/components/App/Navbar.jsx +++ b/app/components/App/Navbar.jsx @@ -22,6 +22,7 @@ import Container from '../StyledUtils/Container' import { fadeIn } from '../StyledUtils/Keyframes' import StyledLink from '../StyledUtils/StyledLink' import { Span } from '../StyledUtils/Text' +import { Button } from '../ui/button' import ScoreTag from '../Users/ScoreTag' import UserAppellation from '../Users/UserAppellation' import UserMenu from '../Users/UserMenu' @@ -260,29 +261,14 @@ const Navbar = ({ ) : ( - - - {t('menu.login')} - - - {t('menu.extension')} - - - {t('menu.signup')} +
+ + + + + - +
)} )} diff --git a/app/components/App/Sidebar.jsx b/app/components/App/Sidebar.jsx index 87aaf6d54..3cec5cb77 100644 --- a/app/components/App/Sidebar.jsx +++ b/app/components/App/Sidebar.jsx @@ -1,7 +1,7 @@ import { Query } from '@apollo/client/react/components' -import { Flex } from '@rebass/grid' import classNames from 'classnames' import { capitalize, get } from 'lodash' +import { CircleHelp, Flag, Heart, ListVideo, Puzzle, Users } from 'lucide-react' import React from 'react' import { withNamespaces } from 'react-i18next' import { connect } from 'react-redux' @@ -27,7 +27,6 @@ import UserLanguageSelector from '../LoggedInUser/UserLanguageSelector' import { withLoggedInUser } from '../LoggedInUser/UserProvider' import ExternalLinkNewTab from '../Utils/ExternalLinkNewTab' import ProgressBar from '../Utils/ProgressBar' -import RawIcon from '../Utils/RawIcon' import ReputationGuard from '../Utils/ReputationGuard' import Tag from '../Utils/Tag' @@ -42,6 +41,7 @@ const WhiteStar = styled(Star)` const DailyGainText = styled.p` color: #858585; ` + @connect((state) => ({ sidebarExpended: state.UserPreferences.sidebarExpended }), { toggleSidebar, closeSidebar, @@ -62,15 +62,19 @@ export default class Sidebar extends React.PureComponent { } } - MenuLink({ title, iconName, customLink, className, children, ...props }) { - const classes = classNames(className, { 'link-with-icon': !!iconName }) + MenuLink({ title, className, children, ...props }) { + const classes = classNames( + 'flex items-center px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-md', + { 'bg-gray-100': props.isActive }, + className, + ) return ( - {iconName && } - {customLink ? children : {children}} + {children} ) } @@ -92,82 +95,104 @@ export default class Sidebar extends React.PureComponent { render() { const { sidebarExpended, className, t, isAuthenticated } = this.props return ( - -
-

{t('menu.language')}

- +
+

+ {t('menu.language')} +

+ {isAuthenticated ? ( -

{t('menu.yourProfile')}

+

+ {t('menu.yourProfile')} +

{this.renderMenuProfile()}
) : null} -

{t('menu.factChecking')}

+

+ {t('menu.factChecking')} +

{this.renderMenuContent()} -

{t('menu.other')}

-
    -
  • - +

    + {t('menu.other')} +

    +
      + + + {t('menu.help')} + + + + {t('menu.contact')} + + + {t('menu.extension')} - - {t('menu.forum')}  - + + {t('menu.forum')} + - + {t('menu.donation')}  - + - - {t('menu.help')} - - - - - - {t('menu.contact')} -
    -

    {t('menu.followus')}

    -
    - {' '} -     +

    + {t('menu.followus')} +

    +
    -     + -     + -     + -     + -     +
    - + ) } @@ -184,8 +209,8 @@ export default class Sidebar extends React.PureComponent { const dailyGain = get(data, 'loggedInUser.todayReputationGain', 0) return ( - - +
    +
    - +
    {`${t('menu.dailyGain')} ${dailyGain}/${MAX_DAILY_REPUTATION_GAIN}`} - +
    ) }} @@ -207,7 +232,7 @@ export default class Sidebar extends React.PureComponent { renderMenuProfile() { return ( -
      +
      • {this.renderDailyGainGauge()}
      ) @@ -216,8 +241,9 @@ export default class Sidebar extends React.PureComponent { renderMenuContent() { const t = this.props.t return ( -
        - +
          + + {capitalize(t('entities.videoFactChecking'))} @@ -229,7 +255,8 @@ export default class Sidebar extends React.PureComponent { {({ data }) => { const pendingCount = get(data, 'loggedInUser.actions_pending_moderation', 0) return ( - + + {t('menu.moderation')} {Boolean(pendingCount) && {pendingCount}} diff --git a/app/components/Home/CFSocialProfiles.jsx b/app/components/Home/CFSocialProfiles.jsx index e65ceb326..32ae9b85c 100644 --- a/app/components/Home/CFSocialProfiles.jsx +++ b/app/components/Home/CFSocialProfiles.jsx @@ -1,89 +1,39 @@ -import { Flex } from '@rebass/grid' -import { get } from 'lodash' -import PropTypes from 'prop-types' +import { Facebook, Github, Twitter } from 'lucide-react' import React from 'react' -import styled, { withTheme } from 'styled-components' -import { Discord } from 'styled-icons/fa-brands' -import { Facebook } from 'styled-icons/fa-brands' -import { Twitter } from 'styled-icons/fa-brands' -import { Github } from 'styled-icons/fa-brands' -import { Mastodon } from 'styled-icons/fa-brands' +import { Discord } from 'styled-icons/remix-line' import ExternalLinkNewTab from '../Utils/ExternalLinkNewTab' -const IconLinkContainer = styled(ExternalLinkNewTab)` - margin: 0 0.5em; -` - -/** - * Render a social icon with the proper link and add a popup with the title - * of the social network. - */ -const SocialIconLink = ({ Icon, name, url, size, color }) => { +const SocialIconLink = ({ Icon, size, name, url }) => { return ( - - - + + + ) } -/** - * Render social profiles icons for other CaptainFact profiles on - * Facebook, Twitter... - */ -const CFSocialProfiles = ({ size, color, justifyContent, theme }) => { - const themeColor = get(theme, `colors.${color}`, color) +const CFSocialProfiles = () => { return ( - +
          - - + + - - +
          ) } -CFSocialProfiles.propTypes = { - /** Icons size. Default to 2em */ - size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - /** Passed to Flex */ - justifyContent: PropTypes.string, -} - -CFSocialProfiles.defaultProps = { - size: '2em', - justifyContent: 'center', -} - -export default withTheme(CFSocialProfiles) +export default CFSocialProfiles diff --git a/app/components/Home/Home.jsx b/app/components/Home/Home.jsx index 300699d6c..12eda9630 100644 --- a/app/components/Home/Home.jsx +++ b/app/components/Home/Home.jsx @@ -1,17 +1,15 @@ -import { Flex } from '@rebass/grid' import React from 'react' import { Trans, withNamespaces } from 'react-i18next' import { Link } from 'react-router-dom' import { ExternalLinkAlt } from 'styled-icons/fa-solid' -import * as Matomo from '../../API/matomo' import imgExample1 from '../../assets/example1.jpg' import examplesImg1 from '../../assets/examples/image-1.jpg' import examplesImg2 from '../../assets/examples/image-2.jpg' import examplesImg3 from '../../assets/examples/image-3.jpg' import { withLoggedInUser } from '../LoggedInUser/UserProvider' import Container from '../StyledUtils/Container' -import { Icon } from '../Utils' +import { Button } from '../ui/button' import ExternalLinkNewTab from '../Utils/ExternalLinkNewTab' import CFSocialProfiles from './CFSocialProfiles' import LastVideos from './LastVideos' @@ -23,307 +21,159 @@ export default class Home extends React.PureComponent { const { t } = this.props return ( -
          -
          -
          -
          -
          -

          +
          +
          +
          +
          +
          +

          Captain Fact.


          -

          {t('titleCF')}

          -

          +

          {t('titleCF')}

          +

          To train a critical mind, improve the quality of information and decision-making. -
          -
          - Against fake news, fraud and disinformation

          -

          {t('presentation')}

          -
          -
          -

          {t('presentationTextButton1')}

          -

          - Matomo.registerClick('Home', 'Button', 'ExtensionPage')} - className="button is-medium" - to="/extension" - > - {t('installExtension')} +

          {t('presentation')}

          +
          +
          +

          {t('presentationTextButton1')}

          +
          + + -

          +
          -
          -

          {t('presentationTextButton2')}

          -

          - Matomo.registerClick('Home', 'Button', 'SignUp')} - className="button is-gradient-primary-light is-medium" - to="/signup" - > - {t('registerAndFactCheck')} +

          +

          {t('presentationTextButton2')}

          +
          + + -

          +
          -
          -
          -