├── .nvmrc ├── app ├── components │ ├── Title │ │ ├── title.svg │ │ ├── messages.ts │ │ └── index.tsx │ ├── LoadableButton │ │ └── ShowMoreButton.ts │ ├── TopBarButton │ │ └── Tab.tsx │ ├── CategoriesFilters │ │ ├── CategoryFilterButton │ │ │ ├── filter.svg │ │ │ ├── Wrapper.tsx │ │ │ ├── categories.svg │ │ │ ├── Section.tsx │ │ │ └── index.tsx │ │ ├── types.d.ts │ │ ├── Option │ │ │ ├── ToggleSwitchSelectOption.tsx │ │ │ └── SelectOptionContainer.tsx │ │ ├── Categories.tsx │ │ └── Filters.tsx │ ├── NavBar │ │ ├── index.tsx │ │ └── Wrapper.ts │ ├── ComponentBackground │ │ ├── index.tsx │ │ └── isaBackground.tsx │ ├── Logo │ │ ├── index.tsx │ │ ├── rankingsLogo.tsx │ │ └── sportsLogo.tsx │ ├── MainTableSection │ │ └── index.tsx │ ├── TableWrapper │ │ └── Group.ts │ ├── Icons │ │ ├── categories │ │ │ ├── YearIcon.tsx │ │ │ ├── AgeIcon.tsx │ │ │ ├── PeopleIcon.tsx │ │ │ ├── WorldIcon.tsx │ │ │ └── CategoryIcon.tsx │ │ ├── rankNoChangeIcon.tsx │ │ ├── IconClose.tsx │ │ ├── rankUpIcon.tsx │ │ ├── rankDownIcon.tsx │ │ └── LeftArrowIcon.tsx │ ├── Footer │ │ ├── Link.ts │ │ └── Wrapper.ts │ ├── Divider │ │ └── index.ts │ ├── Avatars │ │ ├── isaLogo.svg │ │ ├── CountryAvatar.tsx │ │ ├── ProfileAvatar.tsx │ │ ├── silhoutte.svg │ │ └── ContestAvatar.tsx │ ├── Containers │ │ └── index.ts │ ├── AutosuggestWrapper │ │ └── index.tsx │ ├── InfoField │ │ └── index.tsx │ └── TabPanel │ │ └── index.tsx ├── translations │ └── en.json ├── images │ ├── favicon.ico │ ├── isa-sport.png │ ├── favicon-old.ico │ ├── apple-touch-icon.png │ └── android-chrome-512x512.png ├── containers │ ├── AdminContest │ │ ├── Loadable.ts │ │ ├── inputs │ │ │ ├── ErrorLabel.ts │ │ │ ├── Wrapper.ts │ │ │ ├── DateInput.tsx │ │ │ ├── ContestGenderInput.tsx │ │ │ ├── ContestTypeInput.tsx │ │ │ └── TextInput.tsx │ │ ├── Wrapper.ts │ │ ├── constants.ts │ │ └── selectors.ts │ ├── AdminTopBarTabs │ │ ├── constants.ts │ │ ├── actions.ts │ │ ├── Link.tsx │ │ ├── types.d.ts │ │ ├── saga.ts │ │ ├── selectors.ts │ │ ├── Tabs.tsx │ │ ├── Wrapper.ts │ │ ├── reducer.ts │ │ └── Background.tsx │ ├── LanguageProvider │ │ ├── constants.ts │ │ ├── actions.ts │ │ ├── selectors.ts │ │ ├── reducer.ts │ │ └── index.tsx │ ├── AdminAthlete │ │ ├── Loadable.ts │ │ ├── inputs │ │ │ ├── ErrorLabel.ts │ │ │ ├── Wrapper.ts │ │ │ ├── DateInput.tsx │ │ │ └── GenderInput.tsx │ │ ├── Header.ts │ │ ├── Wrapper.ts │ │ ├── constants.ts │ │ ├── selectors.ts │ │ ├── actions.ts │ │ └── types.d.ts │ ├── Homepage │ │ ├── MainSection │ │ │ └── SponsorLogos │ │ │ │ ├── tttm.png │ │ │ │ └── blackroll.png │ │ ├── Loadable.ts │ │ └── Wrapper.ts │ ├── AdminLogin │ │ ├── Loadable.ts │ │ ├── constants.ts │ │ ├── Wrapper.ts │ │ ├── TextField.tsx │ │ ├── selectors.ts │ │ ├── types.d.ts │ │ ├── actions.ts │ │ ├── saga.ts │ │ └── reducer.ts │ ├── AdminResults │ │ ├── Loadable.ts │ │ ├── Wrapper.ts │ │ ├── selectors.ts │ │ ├── api.ts │ │ ├── types.d.ts │ │ └── constants.ts │ ├── Athlete │ │ ├── Header.tsx │ │ ├── Loadable.ts │ │ ├── Info │ │ │ └── Title.ts │ │ ├── constants.ts │ │ ├── api.ts │ │ ├── actions.ts │ │ └── selectors.ts │ ├── Contest │ │ ├── Header.tsx │ │ ├── Loadable.ts │ │ ├── Info │ │ │ └── Title.ts │ │ ├── constants.ts │ │ ├── actions.ts │ │ ├── api.ts │ │ └── selectors.ts │ ├── Contests │ │ ├── Loadable.ts │ │ ├── MainTable │ │ │ └── StackedGroup.tsx │ │ ├── constants.ts │ │ ├── api.ts │ │ ├── selectors.ts │ │ └── actions.ts │ ├── Rankings │ │ ├── Loadable.ts │ │ ├── MainTable │ │ │ └── DivGroup.ts │ │ ├── constants.ts │ │ ├── selectors.ts │ │ └── api.ts │ ├── NotFoundPage │ │ ├── Loadable.ts │ │ └── index.tsx │ ├── TopBarTabs │ │ ├── constants.ts │ │ ├── messages.ts │ │ ├── actions.ts │ │ ├── saga.ts │ │ ├── selectors.ts │ │ ├── types.d.ts │ │ ├── Tabs.tsx │ │ └── Background.tsx │ ├── App │ │ ├── selectors.ts │ │ ├── reducer.ts │ │ ├── constants.ts │ │ └── GlobalStyle.ts │ └── GenericTabContent │ │ ├── FilterItem.ts │ │ └── types.d.ts ├── utils │ ├── history.ts │ ├── constants.ts │ ├── error.ts │ ├── checkStore.ts │ ├── index.ts │ ├── reducerInjectors.ts │ ├── injectReducer.tsx │ └── request.ts ├── styles │ ├── fonts │ │ ├── metropolis-bold-webfont.woff │ │ ├── metropolis-bold-webfont.woff2 │ │ ├── metropolis-light-webfont.woff │ │ ├── metropolis-light-webfont.woff2 │ │ ├── metropolis-medium-webfont.woff │ │ ├── metropolis-medium-webfont.woff2 │ │ ├── metropolis-regular-webfont.woff │ │ ├── metropolis-regular-webfont.woff2 │ │ ├── metropolis-semibold-webfont.woff │ │ └── metropolis-semibold-webfont.woff2 │ ├── zIndex.ts │ ├── breakpoints.ts │ ├── icons.css │ ├── media.ts │ ├── AppConstants.ts │ ├── font-face.css │ └── mixins.ts ├── api │ ├── types.d.ts │ ├── amplify │ │ ├── user.ts │ │ ├── options.ts │ │ ├── configure.ts │ │ └── signin.ts │ ├── admin │ │ ├── results │ │ │ ├── __mocks__ │ │ │ │ ├── submit_contest_results_mock.ts │ │ │ │ └── contest_results_mock.ts │ │ │ ├── index.ts │ │ │ └── submit.ts │ │ ├── athlete │ │ │ ├── __mocks__ │ │ │ │ ├── submit_athlete_mock.ts │ │ │ │ └── athlete_mock.ts │ │ │ ├── submit.ts │ │ │ ├── submit_picture.ts │ │ │ └── index.ts │ │ └── contest │ │ │ ├── __mocks__ │ │ │ ├── submit_contest_mock.ts │ │ │ ├── categories_mock.ts │ │ │ ├── disciplines_mock.ts │ │ │ └── contest_mock.ts │ │ │ ├── genders.ts │ │ │ ├── categories.ts │ │ │ ├── disciplines.ts │ │ │ └── submit_picture.ts │ ├── athlete │ │ ├── __mocks__ │ │ │ ├── athlete_mock.ts │ │ │ └── athlete_suggestions_mock.ts │ │ ├── categories.ts │ │ ├── suggestions.ts │ │ ├── index.ts │ │ └── athlete-contests.ts │ ├── rankings │ │ ├── __mocks__ │ │ │ └── countrySuggestions_mock.ts │ │ ├── countrySuggestions.ts │ │ ├── categories.ts │ │ └── results.ts │ ├── contests │ │ ├── __mocks__ │ │ │ ├── contest_mock.ts │ │ │ └── contest_suggestions_mock.ts │ │ ├── discipline-categories.ts │ │ ├── suggestions.ts │ │ └── contest.ts │ └── axios.ts ├── types │ ├── enums.ts │ └── application.d.ts ├── utiltypes.d.ts ├── reducers.ts └── i18n.ts ├── internals ├── generators │ ├── language │ │ ├── translations-json.hbs │ │ ├── app-locale.hbs │ │ ├── add-locale-data.hbs │ │ ├── polyfill-intl-locale.hbs │ │ ├── intl-locale-data.hbs │ │ ├── translation-messages.hbs │ │ └── format-translation-messages.hbs │ ├── component │ │ ├── loadable.js.hbs │ │ ├── test.js.hbs │ │ ├── messages.js.hbs │ │ ├── stateless.js.hbs │ │ └── class.js.hbs │ ├── container │ │ ├── constants.js.hbs │ │ ├── saga.js.hbs │ │ ├── actions.js.hbs │ │ ├── selectors.test.js.hbs │ │ ├── reducer.test.js.hbs │ │ ├── test.js.hbs │ │ ├── actions.test.js.hbs │ │ ├── messages.js.hbs │ │ ├── saga.test.js.hbs │ │ ├── types.js.hbs │ │ ├── selectors.js.hbs │ │ └── reducer.js.hbs │ ├── utils │ │ └── componentExists.js │ └── index.js ├── mocks │ ├── cssModule.js │ └── image.js ├── testing │ ├── test-bundler.js │ └── enzyme-setup.js ├── scripts │ ├── helpers │ │ ├── xmark.js │ │ ├── checkmark.js │ │ └── progress.js │ ├── npmcheckversion.js │ └── analyze.js └── deployToS3.sh ├── .vscode └── settings.json ├── server ├── argv.js ├── port.js ├── middlewares │ ├── frontendMiddleware.js │ ├── addProdMiddlewares.js │ └── addDevMiddlewares.js └── logger.js ├── typings └── tslint.json ├── .prettierignore ├── .prettierrc ├── .stylelintrc ├── .gitignore ├── .editorconfig ├── .env.example ├── babel.config.js ├── jest.config.js ├── tslint.json ├── tsconfig.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/carbon 2 | -------------------------------------------------------------------------------- /app/components/Title/title.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/translations/en.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /internals/generators/language/translations-json.hbs: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } 4 | -------------------------------------------------------------------------------- /internals/mocks/cssModule.js: -------------------------------------------------------------------------------- 1 | module.exports = 'CSS_MODULE'; 2 | -------------------------------------------------------------------------------- /internals/mocks/image.js: -------------------------------------------------------------------------------- 1 | module.exports = 'IMAGE_MOCK'; 2 | -------------------------------------------------------------------------------- /internals/generators/language/app-locale.hbs: -------------------------------------------------------------------------------- 1 | $1 '{{language}}', 2 | -------------------------------------------------------------------------------- /server/argv.js: -------------------------------------------------------------------------------- 1 | module.exports = require('minimist')(process.argv.slice(2)); 2 | -------------------------------------------------------------------------------- /internals/generators/language/add-locale-data.hbs: -------------------------------------------------------------------------------- 1 | $1addLocaleData({{language}}LocaleData); 2 | -------------------------------------------------------------------------------- /typings/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "note": "dummy file to suppress errors on intellij products" 3 | } 4 | -------------------------------------------------------------------------------- /internals/generators/language/polyfill-intl-locale.hbs: -------------------------------------------------------------------------------- 1 | $1 import('intl/locale-data/jsonp/{{language}}.js'), 2 | -------------------------------------------------------------------------------- /app/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/images/favicon.ico -------------------------------------------------------------------------------- /app/images/isa-sport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/images/isa-sport.png -------------------------------------------------------------------------------- /server/port.js: -------------------------------------------------------------------------------- 1 | const argv = require('./argv'); 2 | 3 | module.exports = parseInt(argv.port || process.env.PORT || '3001', 10); 4 | -------------------------------------------------------------------------------- /app/images/favicon-old.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/images/favicon-old.ico -------------------------------------------------------------------------------- /internals/generators/language/intl-locale-data.hbs: -------------------------------------------------------------------------------- 1 | $&const {{language}}LocaleData = require('react-intl/locale-data/{{language}}'); 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | internals/generators/ 4 | internals/scripts/ 5 | package-lock.json 6 | yarn.lock 7 | package.json 8 | -------------------------------------------------------------------------------- /app/containers/AdminContest/Loadable.ts: -------------------------------------------------------------------------------- 1 | import loadable from 'loadable-components'; 2 | 3 | export default loadable(() => import('./index')); 4 | -------------------------------------------------------------------------------- /app/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/images/apple-touch-icon.png -------------------------------------------------------------------------------- /app/utils/history.ts: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory'; 2 | const history = createHistory(); 3 | export default history; 4 | -------------------------------------------------------------------------------- /internals/generators/language/translation-messages.hbs: -------------------------------------------------------------------------------- 1 | $1const {{language}}TranslationMessages = require('./translations/{{language}}.json'); 2 | -------------------------------------------------------------------------------- /app/images/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/images/android-chrome-512x512.png -------------------------------------------------------------------------------- /internals/testing/test-bundler.js: -------------------------------------------------------------------------------- 1 | // needed for regenerator-runtime 2 | // (ES7 generator support is required by redux-saga) 3 | import '@babel/polyfill'; 4 | -------------------------------------------------------------------------------- /internals/generators/language/format-translation-messages.hbs: -------------------------------------------------------------------------------- 1 | $1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages), 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | CHANGE_TOPBAR_INDEX = 'app/TopBar/CHANGE_TOPBAR_INDEX', 3 | } 4 | 5 | export default ActionTypes; 6 | -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-bold-webfont.woff -------------------------------------------------------------------------------- /app/styles/zIndex.ts: -------------------------------------------------------------------------------- 1 | const Elements = ['NavBarOverlay', 'NavBar', 'TopBarTabs', 'PopUps']; 2 | 3 | export default (element: string) => Elements.indexOf(element) + 1; 4 | -------------------------------------------------------------------------------- /internals/testing/enzyme-setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-bold-webfont.woff2 -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-light-webfont.woff -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-light-webfont.woff2 -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-medium-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-medium-webfont.woff -------------------------------------------------------------------------------- /app/containers/LanguageProvider/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider constants 4 | * 5 | */ 6 | 7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 8 | -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-medium-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-medium-webfont.woff2 -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-regular-webfont.woff -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-regular-webfont.woff2 -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-semibold-webfont.woff -------------------------------------------------------------------------------- /app/styles/fonts/metropolis-semibold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/styles/fonts/metropolis-semibold-webfont.woff2 -------------------------------------------------------------------------------- /app/containers/AdminAthlete/Loadable.ts: -------------------------------------------------------------------------------- 1 | import loadable from 'loadable-components'; 2 | 3 | export default loadable(() => import('./index'), { 4 | LoadingComponent: undefined, 5 | }); 6 | -------------------------------------------------------------------------------- /app/containers/Homepage/MainSection/SponsorLogos/tttm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/containers/Homepage/MainSection/SponsorLogos/tttm.png -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-styled-components"], 3 | "extends": [ 4 | "stylelint-config-recommended", 5 | "stylelint-config-styled-components" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/inputs/ErrorLabel.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const ErrorLabel = styled.span` 4 | color: red; 5 | `; 6 | 7 | export default ErrorLabel; 8 | -------------------------------------------------------------------------------- /app/containers/AdminContest/inputs/ErrorLabel.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const ErrorLabel = styled.span` 4 | color: red; 5 | `; 6 | 7 | export default ErrorLabel; 8 | -------------------------------------------------------------------------------- /app/api/types.d.ts: -------------------------------------------------------------------------------- 1 | import { UISelectOption } from 'types/application'; 2 | 3 | export interface CategoryItem { 4 | title: string; 5 | options: UISelectOption[]; 6 | selectedValue: string; 7 | } 8 | -------------------------------------------------------------------------------- /app/containers/Homepage/MainSection/SponsorLogos/blackroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/International-Slackline-Association/Rankings-UI/HEAD/app/containers/Homepage/MainSection/SponsorLogos/blackroll.png -------------------------------------------------------------------------------- /app/api/amplify/user.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from 'aws-amplify'; 2 | 3 | import './configure'; 4 | 5 | async function cognitoUser() { 6 | return Auth.currentUserInfo(); 7 | } 8 | 9 | export default cognitoUser; 10 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/Header.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const Header = styled.h3` 4 | /* align-self: center; */ 5 | /* margin-bottom: 16px; */ 6 | `; 7 | 8 | export default Header; 9 | -------------------------------------------------------------------------------- /app/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount'; 2 | export const DAEMON = '@@saga-injector/daemon'; 3 | export const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount'; 4 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for AdminLogin 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index')); 10 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/actions.ts: -------------------------------------------------------------------------------- 1 | import { action } from 'typesafe-actions'; 2 | 3 | import ActionTypes from './constants'; 4 | 5 | export const changeTopBarIndex = (id: string) => 6 | action(ActionTypes.CHANGE_TOPBAR_INDEX, id); 7 | -------------------------------------------------------------------------------- /app/containers/AdminResults/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for AdminResults 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index')); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | build 4 | node_modules 5 | stats.json 6 | 7 | # Cruft 8 | .DS_Store 9 | npm-debug.log 10 | .idea 11 | .awcache 12 | .env.development 13 | .env.production 14 | .env.test 15 | -------------------------------------------------------------------------------- /app/api/admin/results/__mocks__/submit_contest_results_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminSubmitContestResultsResponse } from '../submit'; 2 | 3 | const generator = (): APIAdminSubmitContestResultsResponse => { 4 | return {}; 5 | }; 6 | 7 | export default generator; 8 | -------------------------------------------------------------------------------- /app/containers/Athlete/Header.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface OwnProps {} 4 | 5 | const Header = styled('h1')` 6 | font-size: 2em; 7 | margin-bottom: 32px; 8 | `; 9 | 10 | export default Header; 11 | -------------------------------------------------------------------------------- /app/containers/Contest/Header.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface OwnProps {} 4 | 5 | const Header = styled('h1')` 6 | font-size: 2em; 7 | margin-bottom: 32px; 8 | `; 9 | 10 | export default Header; 11 | -------------------------------------------------------------------------------- /internals/generators/component/loadable.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for {{ properCase name }} 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index')); 10 | -------------------------------------------------------------------------------- /internals/generators/container/constants.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} constants 4 | * 5 | */ 6 | 7 | enum ActionTypes { 8 | DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION', 9 | } 10 | 11 | export default ActionTypes; 12 | -------------------------------------------------------------------------------- /internals/generators/container/saga.js.hbs: -------------------------------------------------------------------------------- 1 | // import { take, call, put, select } from 'redux-saga/effects'; 2 | 3 | // Individual exports for testing 4 | export default function* {{ camelCase name }}Saga() { 5 | // See example in containers/HomePage/saga.js 6 | } 7 | -------------------------------------------------------------------------------- /app/api/admin/athlete/__mocks__/submit_athlete_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminSubmitAthleteResponse } from '../submit'; 2 | 3 | const generator = (): APIAdminSubmitAthleteResponse => { 4 | return { 5 | id: '1', 6 | }; 7 | }; 8 | 9 | export default generator; 10 | -------------------------------------------------------------------------------- /app/components/LoadableButton/ShowMoreButton.ts: -------------------------------------------------------------------------------- 1 | import LoadableButton from '../LoadableButton'; 2 | import styled from 'styles/styled-components'; 3 | 4 | const ShowMoreButton = styled(LoadableButton)` 5 | margin: 0 auto; 6 | `; 7 | 8 | export default ShowMoreButton; 9 | -------------------------------------------------------------------------------- /app/types/enums.ts: -------------------------------------------------------------------------------- 1 | export enum TopBarTabType { 2 | Static = 'Static', 3 | Dynamic = 'Dynamic', 4 | } 5 | 6 | export enum TopBarTabContentType { 7 | rankings = 'rankings', 8 | contests = 'contests', 9 | athlete = 'athlete', 10 | contest = 'contest', 11 | } 12 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/Link.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | import { Link } from 'react-router-dom'; 4 | 5 | const LinkWrapper = styled(Link)` 6 | color: ${props => props.theme.textPrimary}; 7 | `; 8 | 9 | export default LinkWrapper; 10 | -------------------------------------------------------------------------------- /app/containers/Athlete/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for Rankings 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index'), { 10 | LoadingComponent: undefined, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/Contest/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for Rankings 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index'), { 10 | LoadingComponent: undefined, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/Contests/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for Rankings 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index'), { 10 | LoadingComponent: undefined, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/Homepage/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for Rankings 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index'), { 10 | LoadingComponent: undefined, 11 | }); 12 | -------------------------------------------------------------------------------- /app/containers/Rankings/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for Rankings 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index'), { 10 | LoadingComponent: undefined, 11 | }); 12 | -------------------------------------------------------------------------------- /internals/scripts/helpers/xmark.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark cross symbol 5 | */ 6 | function addXMark(callback) { 7 | process.stdout.write(chalk.red(' ✘')); 8 | if (callback) callback(); 9 | } 10 | 11 | module.exports = addXMark; 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/Loadable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Asynchronously loads the component for Rankings 4 | * 5 | */ 6 | 7 | import loadable from 'loadable-components'; 8 | 9 | export default loadable(() => import('./index'), { 10 | LoadingComponent: undefined, 11 | }); 12 | -------------------------------------------------------------------------------- /app/api/admin/contest/__mocks__/submit_contest_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminSubmitContestResponse } from '../submit'; 2 | 3 | const generator = (): APIAdminSubmitContestResponse => { 4 | return { 5 | id: '1', 6 | discipline: 3, 7 | }; 8 | }; 9 | 10 | export default generator; 11 | -------------------------------------------------------------------------------- /internals/scripts/helpers/checkmark.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | 3 | /** 4 | * Adds mark check symbol 5 | */ 6 | function addCheckMark(callback) { 7 | process.stdout.write(chalk.green(' ✓')); 8 | if (callback) callback(); 9 | } 10 | 11 | module.exports = addCheckMark; 12 | -------------------------------------------------------------------------------- /app/components/TopBarButton/Tab.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from '@material-ui/core'; 2 | import styled from 'styles/styled-components'; 3 | import * as React from 'react'; 4 | 5 | const tab = props => ; 6 | 7 | const CustomTab = styled(tab)``; 8 | 9 | export default CustomTab; 10 | -------------------------------------------------------------------------------- /app/components/Title/messages.ts: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | const scope = 'app.components.Title'; 4 | 5 | export default defineMessages({ 6 | titleAltText: { 7 | id: `${scope}.titleAltText`, 8 | defaultMessage: 'SLACKLINE WORLD RANKING LIST', 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/inputs/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface OwnProps {} 4 | 5 | const Wrapper = styled('div')` 6 | display: flex; 7 | flex-direction: column; 8 | margin: 32px 0px; 9 | `; 10 | 11 | export default Wrapper; 12 | -------------------------------------------------------------------------------- /app/containers/AdminContest/inputs/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface OwnProps {} 4 | 5 | const Wrapper = styled('div')` 6 | display: flex; 7 | flex-direction: column; 8 | margin: 32px 0px; 9 | `; 10 | 11 | export default Wrapper; 12 | -------------------------------------------------------------------------------- /app/utils/error.ts: -------------------------------------------------------------------------------- 1 | export class CustomError extends Error { 2 | public message: string; 3 | public data?: any; 4 | 5 | constructor(params: { message: string; data?: any }) { 6 | super(params.message); 7 | this.message = params.message; 8 | this.data = params.data; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/containers/Athlete/Info/Title.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const Title = styled.span` 4 | font-weight: bold; 5 | font-size: 1.5em; 6 | text-align: center; 7 | color: ${props => props.theme.textInverted}; 8 | margin-top: 24px; 9 | `; 10 | 11 | export default Title; 12 | -------------------------------------------------------------------------------- /app/containers/Contest/Info/Title.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const Title = styled.span` 4 | font-weight: bold; 5 | font-size: 1.5em; 6 | text-align: center; 7 | color: ${props => props.theme.textInverted}; 8 | margin-top: 24px; 9 | `; 10 | 11 | export default Title; 12 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/actions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider actions 4 | * 5 | */ 6 | 7 | import { CHANGE_LOCALE } from './constants'; 8 | 9 | export function changeLocale(languageLocale) { 10 | return { 11 | type: CHANGE_LOCALE, 12 | locale: languageLocale, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | CHANGE_TOPBAR_INDEX = 'app/TopBar/CHANGE_TOPBAR_INDEX', 3 | ADD_TOPBAR_TAB = 'app/TopBar/ADD_TOPBAR_TAB', 4 | SET_TOPBAR_TABS = 'app/TopBar/SET_TOPBAR_TABS', 5 | CHANGE_TOPBAR_NAME = 'app/TopBar/CHANGE_TOPBAR_NAME', 6 | } 7 | 8 | export default ActionTypes; 9 | -------------------------------------------------------------------------------- /internals/generators/container/actions.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} actions 4 | * 5 | */ 6 | 7 | import { action } from 'typesafe-actions'; 8 | import { } from './types'; 9 | 10 | import ActionTypes from './constants'; 11 | 12 | export const defaultAction = () => action(ActionTypes.DEFAULT_ACTION); 13 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.test.js.hbs: -------------------------------------------------------------------------------- 1 | // import { fromJS } from 'immutable'; 2 | // import { select{{ properCase name }}Domain } from '../selectors'; 3 | 4 | describe('select{{ properCase name }}Domain', () => { 5 | it('Expect to have unit tests specified', () => { 6 | expect(true).toEqual(false); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /app/styles/breakpoints.ts: -------------------------------------------------------------------------------- 1 | export interface IBreakpoints { 2 | mobile: number; 3 | tablet: number; 4 | desktop: number; 5 | desktopLarge: number; 6 | } 7 | 8 | const breakpoints: IBreakpoints = { 9 | mobile: 0, 10 | tablet: 640, 11 | desktop: 1024, 12 | desktopLarge: 1140, 13 | }; 14 | 15 | export default breakpoints; 16 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | LOGIN = 'app/AdminLogin/LOGIN', 3 | LOGIN_SUCCESS = 'app/AdminLogin/LOGIN_SUCCESS', 4 | LOGIN_ERROR = 'app/AdminLogin/LOGIN_ERROR', 5 | CLEAR_LOGIN_ERROR = 'app/AdminLogin/CLEAR_LOGIN_ERROR', 6 | CHECK_USER = 'app/AdminLogin/CHECK_USER', 7 | } 8 | 9 | export default ActionTypes; 10 | -------------------------------------------------------------------------------- /app/containers/App/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | 4 | const selectRouter = (state: ApplicationRootState) => state.router; 5 | 6 | const makeSelectLocation = () => 7 | createSelector(selectRouter, routeState => routeState.location); 8 | 9 | export { makeSelectLocation }; 10 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.test.js.hbs: -------------------------------------------------------------------------------- 1 | // import { fromJS } from 'immutable'; 2 | // import {{ camelCase name }}Reducer from '../reducer'; 3 | // 4 | // describe('{{ camelCase name }}Reducer', () => { 5 | // it('returns the initial state', () => { 6 | // expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({})); 7 | // }); 8 | // }); 9 | -------------------------------------------------------------------------------- /app/api/amplify/options.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from 'aws-amplify'; 2 | 3 | import './configure'; 4 | 5 | async function cognitoOptions() { 6 | const currentSession = await Auth.currentSession(); 7 | return { 8 | headers: { 9 | Authorization: currentSession.getIdToken().getJwtToken(), 10 | }, 11 | }; 12 | } 13 | 14 | export default cognitoOptions; 15 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import ComponentBackground from 'components/ComponentBackground'; 3 | 4 | const Wrapper = styled(ComponentBackground)` 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-self: center; 9 | padding: 16px; 10 | `; 11 | 12 | export default Wrapper; 13 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/CategoryFilterButton/filter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internals/scripts/npmcheckversion.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | exec('npm -v', (err, stdout) => { 3 | if (err) throw err; 4 | if (parseFloat(stdout) < 5) { 5 | // NOTE: This can happen if you have a dependency which lists an old version of npm in its own dependencies. 6 | throw new Error(`[ERROR] You need npm version @>=5 but you have ${stdout}`); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/CategoryFilterButton/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import ComponentBackground from 'components/ComponentBackground'; 3 | 4 | interface OwnProps {} 5 | 6 | const Wrapper = styled(ComponentBackground)` 7 | && { 8 | background-color: ${props => props.theme.componentBackground}; 9 | } 10 | `; 11 | 12 | export default Wrapper; 13 | -------------------------------------------------------------------------------- /app/components/NavBar/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Wrapper from './Wrapper'; 3 | import IsaSportsLogo from 'components/Logo/sportsLogo'; 4 | import RankingsLogo from 'components/Logo/rankingsLogo'; 5 | 6 | const NavBar: React.SFC<{}> = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default NavBar; 15 | -------------------------------------------------------------------------------- /internals/generators/component/test.js.hbs: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { mount } from 'enzyme'; 3 | // import { enzymeFind } from 'styled-components/test-utils'; 4 | 5 | // import {{ properCase name }} from '../index'; 6 | 7 | describe('<{{ properCase name }} />', () => { 8 | it('Expect to have unit tests specified', () => { 9 | expect(true).toEqual(false); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /internals/generators/container/test.js.hbs: -------------------------------------------------------------------------------- 1 | // import React from 'react'; 2 | // import { mount } from 'enzyme'; 3 | // import { enzymeFind } from 'styled-components/test-utils'; 4 | 5 | // import { {{ properCase name }} } from '../index'; 6 | 7 | describe('<{{ properCase name }} />', () => { 8 | it('Expect to have unit tests specified', () => { 9 | expect(true).toEqual(false); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /app/components/ComponentBackground/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const ComponentBackground = styled.div` 4 | background-color: ${props => props.theme.componentBackground}; 5 | border-radius: 4px; 6 | box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2), 7 | 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12); 8 | `; 9 | export default ComponentBackground; 10 | -------------------------------------------------------------------------------- /app/api/admin/results/__mocks__/contest_results_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminResultsResponse } from '..'; 2 | 3 | const generator = (): APIAdminResultsResponse => { 4 | return { 5 | items: [ 6 | { 7 | id: 'joshua-leupolz', 8 | name: 'Joshua', 9 | surname: 'Leupolz', 10 | place: 3, 11 | points: 0, 12 | }, 13 | ], 14 | }; 15 | }; 16 | 17 | export default generator; 18 | -------------------------------------------------------------------------------- /app/containers/Homepage/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import media from 'styles/media'; 3 | import { hideScrollBar } from 'styles/mixins'; 4 | 5 | export const Wrapper = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | width: 100%; 9 | min-height: 100%; 10 | ${media.desktop` 11 | flex-direction: row; 12 | width: 100vw; 13 | min-height: 100vh; 14 | `}; 15 | `; 16 | -------------------------------------------------------------------------------- /app/api/amplify/configure.ts: -------------------------------------------------------------------------------- 1 | import Amplify from 'aws-amplify'; 2 | 3 | Amplify.configure({ 4 | Auth: { 5 | identityPoolId: process.env.IDENTITY_POOL_ID, 6 | region: process.env.REGION, 7 | userPoolId: process.env.USER_POOL_ID, 8 | userPoolWebClientId: process.env.USER_POOL_WEB_CLIENT_ID, 9 | }, 10 | Storage: { 11 | bucket: process.env.S3_IMAGES_BUCKET_NAME, 12 | region: process.env.REGION, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import ComponentBackground from 'components/ComponentBackground'; 3 | 4 | interface OwnProps {} 5 | 6 | const Wrapper = styled(ComponentBackground)` 7 | display: flex; 8 | flex-direction: column; 9 | flex-wrap: wrap; 10 | align-items: flex-start; 11 | justify-content: flex-start; 12 | padding: 32px; 13 | `; 14 | 15 | export default Wrapper; 16 | -------------------------------------------------------------------------------- /app/containers/AdminContest/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import ComponentBackground from 'components/ComponentBackground'; 3 | 4 | interface OwnProps {} 5 | 6 | const Wrapper = styled(ComponentBackground)` 7 | display: flex; 8 | flex-direction: column; 9 | flex-wrap: wrap; 10 | align-items: flex-start; 11 | justify-content: flex-start; 12 | padding: 32px; 13 | `; 14 | 15 | export default Wrapper; 16 | -------------------------------------------------------------------------------- /app/containers/AdminResults/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import ComponentBackground from 'components/ComponentBackground'; 3 | 4 | interface OwnProps {} 5 | 6 | const Wrapper = styled(ComponentBackground)` 7 | display: flex; 8 | flex-direction: column; 9 | flex-wrap: wrap; 10 | align-items: flex-start; 11 | justify-content: flex-start; 12 | padding: 32px; 13 | `; 14 | 15 | export default Wrapper; 16 | -------------------------------------------------------------------------------- /app/containers/Contest/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | SET_ID_DISCIPLINE = 'app/Contest/SET_ID_DISCIPLINE', 3 | 4 | LOAD_CONTEST = 'app/Contest/LOAD_CONTEST', 5 | SET_CONTEST = 'app/Contest/SET_CONTEST', 6 | 7 | LOAD_TABLE_ITEMS = 'app/Contest/LOAD_TABLE_ITEMS', 8 | ADD_TABLE_ITEMS = 'app/Contest/ADD_TABLE_ITEMS', 9 | 10 | LOAD_NEXT_TABLE_ITEMS = 'app/Contest/LOAD_NEXT_TABLE_ITEMS', 11 | } 12 | 13 | export default ActionTypes; 14 | -------------------------------------------------------------------------------- /app/utiltypes.d.ts: -------------------------------------------------------------------------------- 1 | // declare module '*.css' { 2 | // const content: string|any; 3 | // export default content; 4 | // } 5 | 6 | declare module '*.svg' { 7 | const content: string | any; 8 | export default content; 9 | } 10 | 11 | declare module '*.svg?file' { 12 | const content: string | any; 13 | export default content; 14 | } 15 | 16 | declare module '*.png' { 17 | const content: string | any; 18 | export default content; 19 | } 20 | -------------------------------------------------------------------------------- /app/api/admin/contest/__mocks__/categories_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminGetCategoriesResponse } from '../categories'; 2 | 3 | const generator = (): APIAdminGetCategoriesResponse => { 4 | return { 5 | categories: [ 6 | { 7 | value: '2', 8 | label: 'World Games', 9 | }, 10 | { 11 | value: '3', 12 | label: 'Open Challenge', 13 | }, 14 | ], 15 | }; 16 | }; 17 | 18 | export default generator; 19 | -------------------------------------------------------------------------------- /app/api/admin/contest/__mocks__/disciplines_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminGetDisciplinesResponse } from '../disciplines'; 2 | 3 | const generator = (): APIAdminGetDisciplinesResponse => { 4 | return { 5 | disciplines: [ 6 | { 7 | value: '2', 8 | label: 'Trickline - Aerial', 9 | }, 10 | { 11 | value: '3', 12 | label: 'Trickline - Jib-Static', 13 | }, 14 | ], 15 | }; 16 | }; 17 | 18 | export default generator; 19 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/TextField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TextField, { TextFieldProps } from '@material-ui/core/TextField/TextField'; 3 | import styled from 'styles/styled-components'; 4 | 5 | interface OwnProps {} 6 | 7 | const textField = (props: any) => ; 8 | 9 | const StyledTextField = styled(textField)` 10 | && { 11 | width: 150px; 12 | margin-bottom: 32px; 13 | } 14 | `; 15 | 16 | export default StyledTextField; 17 | -------------------------------------------------------------------------------- /internals/generators/component/messages.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * {{ properCase name }} Messages 3 | * 4 | * This contains all the text for the {{ properCase name }} component. 5 | */ 6 | 7 | import { defineMessages } from 'react-intl'; 8 | 9 | export const scope = 'app.components.{{ properCase name }}'; 10 | 11 | export default defineMessages({ 12 | header: { 13 | id: `${scope}.header`, 14 | defaultMessage: 'This is the {{ properCase name }} component!', 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /internals/generators/container/actions.test.js.hbs: -------------------------------------------------------------------------------- 1 | // import { defaultAction } from '../actions'; 2 | // import { DEFAULT_ACTION } from '../constants'; 3 | 4 | // describe('{{ properCase name }} actions', () => { 5 | // describe('Default Action', () => { 6 | // it('has a type of DEFAULT_ACTION', () => { 7 | // const expected = { 8 | // type: DEFAULT_ACTION, 9 | // }; 10 | // expect(defaultAction()).toEqual(expected); 11 | // }); 12 | // }); 13 | // }); -------------------------------------------------------------------------------- /internals/generators/container/messages.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * {{ properCase name }} Messages 3 | * 4 | * This contains all the text for the {{ properCase name }} container. 5 | */ 6 | 7 | import { defineMessages } from 'react-intl'; 8 | 9 | export const scope = 'app.containers.{{ properCase name }}'; 10 | 11 | export default defineMessages({ 12 | header: { 13 | id: `${scope}.header`, 14 | defaultMessage: 'This is the {{ properCase name }} container!', 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /internals/generators/container/saga.test.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * Test sagas 3 | */ 4 | 5 | /* eslint-disable redux-saga/yield-effects */ 6 | // import { take, call, put, select } from 'redux-saga/effects'; 7 | // import {{ camelCase name }}Saga from '../saga'; 8 | 9 | // const generator = {{ camelCase name }}Saga(); 10 | 11 | describe('{{ camelCase name }}Saga Saga', () => { 12 | it('Expect to have unit tests specified', () => { 13 | expect(true).toEqual(false); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import isaLogo from './isaLogo.svg?file'; 4 | import styled from 'styles/styled-components'; 5 | 6 | const TitleLogo: React.SFC<{}> = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | const Wrapper = styled.div` 15 | height: 100%; 16 | 17 | img { 18 | width: 100%; 19 | height: 100%; 20 | } 21 | `; 22 | export default TitleLogo; 23 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/messages.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * TopBarTabs Messages 3 | * 4 | * This contains all the text for the TopBarTabs component. 5 | */ 6 | 7 | import { defineMessages } from 'react-intl'; 8 | const scope = 'app.containers.TopBarTabs'; 9 | 10 | export default defineMessages({ 11 | rankings: { 12 | id: `${scope}.rankings`, 13 | defaultMessage: 'Rankings', 14 | }, 15 | contests: { 16 | id: `${scope}.contests`, 17 | defaultMessage: 'Contests', 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /app/styles/icons.css: -------------------------------------------------------------------------------- 1 | .icon:before { 2 | font-family: "cb"; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | font-style: normal; 6 | font-variant: normal; 7 | font-weight: normal; 8 | /* speak: none; only necessary if not using the private unicode range (firstGlyph option) */ 9 | text-decoration: none; 10 | text-transform: none; 11 | } 12 | 13 | 14 | .i-drop:before { 15 | content: "\E001"; 16 | } 17 | 18 | .i-faucet:before { 19 | content: "\E002"; 20 | } 21 | -------------------------------------------------------------------------------- /app/components/MainTableSection/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import ComponentBackground from 'components/ComponentBackground'; 4 | 5 | const MainTableSection: React.SFC<{}> = props => { 6 | return {props.children}; 7 | }; 8 | 9 | const Wrapper = styled(ComponentBackground)` 10 | display: flex; 11 | width: 100%; 12 | /* min-height: 600px; */ 13 | padding-bottom: 32px; 14 | `; 15 | export default MainTableSection; 16 | -------------------------------------------------------------------------------- /app/containers/Contests/MainTable/StackedGroup.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface OwnProps {} 4 | 5 | export const StackedGroup = styled('span')` 6 | display: flex; 7 | flex-direction: column; 8 | justify-content: center; 9 | align-items: flex-start; 10 | text-align: left; 11 | 12 | & a { 13 | text-decoration: none; 14 | 15 | &:hover { 16 | text-decoration: underline; 17 | } 18 | } 19 | `; 20 | 21 | 22 | export default StackedGroup; 23 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | 4 | /** 5 | * Direct selector to the languageToggle state domain 6 | */ 7 | const selectLanguage = (state: ApplicationRootState) => state.language; 8 | 9 | /** 10 | * Select the language locale 11 | */ 12 | 13 | const makeSelectLocale = () => 14 | createSelector(selectLanguage, languageState => languageState.locale); 15 | 16 | export { selectLanguage, makeSelectLocale }; 17 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | /** 6 | * Direct selector to the adminLogin state domain 7 | */ 8 | 9 | const selectAdminLoginDomain = (state: ApplicationRootState) => { 10 | return state.adminLogin ? state.adminLogin : initialState; 11 | }; 12 | 13 | export const selectAdminLogin = () => 14 | createSelector(selectAdminLoginDomain, substate => { 15 | return substate; 16 | }); 17 | -------------------------------------------------------------------------------- /app/containers/App/reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * AppReducer 3 | * 4 | * The reducer takes care of our data. Using actions, we can change our 5 | * application state. 6 | * To add a new action, add it to the switch statement in the reducer function 7 | * 8 | * Example: 9 | * case YOUR_ACTION_CONSTANT: 10 | * return state.set('yourStateVariable', true); 11 | */ 12 | 13 | function appReducer(state = false, action) { 14 | switch (action.type) { 15 | default: 16 | return state; 17 | } 18 | } 19 | 20 | export default appReducer; 21 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider reducer 4 | * 5 | */ 6 | 7 | import { CHANGE_LOCALE } from './constants'; 8 | import { DEFAULT_LOCALE } from 'i18n'; 9 | 10 | export const initialState = { 11 | locale: DEFAULT_LOCALE, 12 | }; 13 | 14 | function languageProviderReducer(state = initialState, action) { 15 | switch (action.type) { 16 | case CHANGE_LOCALE: 17 | return { locale: action.locale }; 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | export default languageProviderReducer; 24 | -------------------------------------------------------------------------------- /app/containers/GenericTabContent/FilterItem.ts: -------------------------------------------------------------------------------- 1 | export class FilterItem { 2 | public category: string; 3 | public name: string; 4 | public isSelected: boolean; 5 | public isSticky: boolean; 6 | 7 | public get id() { 8 | return this.category + '-' + this.name; 9 | } 10 | 11 | constructor( 12 | category: string, 13 | name: string, 14 | isSelected = false, 15 | isSticky = false, 16 | ) { 17 | this.category = category; 18 | this.name = name; 19 | this.isSelected = isSelected; 20 | this.isSticky = isSticky; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/types/application.d.ts: -------------------------------------------------------------------------------- 1 | export interface ISelectOption { 2 | value: string; 3 | label: string; 4 | } 5 | 6 | export interface UISelectOption extends ISelectOption { 7 | isContainerStyle?: boolean; 8 | inlineLevel?: number; 9 | } 10 | export interface Discipline { 11 | id: number; 12 | name: string; 13 | } 14 | 15 | export interface Gender { 16 | id: number; 17 | name: string; 18 | } 19 | 20 | export interface ContestType { 21 | id: number; 22 | name: string; 23 | } 24 | 25 | export interface ContestGender { 26 | id: number; 27 | name: string; 28 | } 29 | -------------------------------------------------------------------------------- /app/api/athlete/__mocks__/athlete_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIGetAthleteResponse } from '..'; 2 | 3 | const generator = (): APIGetAthleteResponse => { 4 | return { 5 | athlete: { 6 | id: 'thomas-buckingham', 7 | name: 'Thomas', 8 | surname: 'Buckingham', 9 | age: 32, 10 | country: 'Switzerland', 11 | overallRank: '2', 12 | profileUrl: 13 | 'http://www.slackattack.ch/wp-content/uploads/2015/11/Vorstand_Tom.jpg', 14 | infoUrl: 'https://www.facebook.com/events/529600260719152', 15 | }, 16 | }; 17 | }; 18 | 19 | export default generator; 20 | -------------------------------------------------------------------------------- /app/components/TableWrapper/Group.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface Props { 4 | alignLeft?: boolean; 5 | } 6 | const Group = styled('span')` 7 | display: flex; 8 | flex-direction: row; 9 | justify-content: ${props => (props.alignLeft ? 'flex-start' : 'center')}; 10 | align-items: center; 11 | text-align: left; 12 | 13 | & div { 14 | margin-right: 8px; 15 | } 16 | 17 | & a { 18 | text-decoration: none; 19 | 20 | &:hover { 21 | text-decoration: underline; 22 | } 23 | } 24 | `; 25 | 26 | export default Group; 27 | -------------------------------------------------------------------------------- /app/api/rankings/__mocks__/countrySuggestions_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIGetCountrySuggestionsResponse } from '../countrySuggestions'; 2 | 3 | const generator = (): APIGetCountrySuggestionsResponse => { 4 | return { 5 | items: [ 6 | { 7 | code: 'tr', 8 | name: 'Turkey', 9 | }, 10 | { 11 | code: 'ch', 12 | name: 'Switzerland', 13 | }, 14 | { 15 | code: 'at', 16 | name: 'Austria', 17 | }, 18 | { 19 | code: 'de', 20 | name: 'Germany', 21 | }, 22 | ], 23 | }; 24 | }; 25 | 26 | export default generator; 27 | -------------------------------------------------------------------------------- /app/containers/Athlete/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | SET_ID = 'app/Athlete/SET_ID', 3 | 4 | LOAD_ATHLETE = 'app/Athlete/LOAD_ATHLETE', 5 | SET_ATHLETE = 'app/Athlete/SET_ATHLETE', 6 | 7 | LOAD_CATEGORIES = 'app/Athlete/LOAD_CATEGORIES', 8 | SET_CATEGORIES = 'app/Athlete/SET_CATEGORIES', 9 | SET_CATEGORY_SELECTED_VALUE = 'app/Athlete/SET_CATEGORY_SELECTED_VALUE', 10 | 11 | LOAD_TABLE_ITEMS = 'app/Athlete/LOAD_TABLE_ITEMS', 12 | ADD_TABLE_ITEMS = 'app/Athlete/ADD_TABLE_ITEMS', 13 | 14 | LOAD_NEXT_TABLE_ITEMS = 'app/Athlete/LOAD_NEXT_TABLE_ITEMS', 15 | } 16 | 17 | export default ActionTypes; 18 | -------------------------------------------------------------------------------- /server/middlewares/frontendMiddleware.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | 3 | /** 4 | * Front-end middleware 5 | */ 6 | module.exports = (app, options) => { 7 | const isProd = process.env.NODE_ENV === 'production'; 8 | 9 | if (isProd) { 10 | const addProdMiddlewares = require('./addProdMiddlewares'); 11 | addProdMiddlewares(app, options); 12 | } else { 13 | const webpackConfig = require('../../internals/webpack/webpack.dev.babel'); 14 | const addDevMiddlewares = require('./addDevMiddlewares'); 15 | addDevMiddlewares(app, webpackConfig); 16 | } 17 | 18 | return app; 19 | }; 20 | -------------------------------------------------------------------------------- /app/components/Icons/categories/YearIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AllYear from './misc/AllYear'; 3 | 4 | interface YearIconProps { 5 | readonly value: string; 6 | } 7 | 8 | /* tslint:disable:max-line-length */ 9 | class YearIcon extends React.PureComponent { 10 | public render() { 11 | const value = this.props.value; 12 | return renderSwitch(value); 13 | } 14 | } 15 | 16 | function renderSwitch(param: string) { 17 | switch (param) { 18 | case '0': 19 | return ; 20 | default: 21 | return
; 22 | } 23 | } 24 | 25 | export default YearIcon; 26 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/actions.ts: -------------------------------------------------------------------------------- 1 | import { action } from 'typesafe-actions'; 2 | import { TopBarTabsItem } from './types'; 3 | 4 | import ActionTypes from './constants'; 5 | 6 | export const changeTopBarIndex = (id: string) => 7 | action(ActionTypes.CHANGE_TOPBAR_INDEX, id); 8 | export const addTopBarTab = (item: TopBarTabsItem) => 9 | action(ActionTypes.ADD_TOPBAR_TAB, item); 10 | export const setTopBarTabs = (items: TopBarTabsItem[]) => 11 | action(ActionTypes.SET_TOPBAR_TABS, items); 12 | export const changeTopBarName = (id: string, name: string) => 13 | action(ActionTypes.CHANGE_TOPBAR_NAME, {id: id, name: name}); 14 | -------------------------------------------------------------------------------- /app/components/Icons/rankNoChangeIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /* tslint:disable:max-line-length */ 4 | class RankNoChangeIcon extends React.PureComponent { 5 | public render() { 6 | return ( 7 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | } 20 | 21 | export default RankNoChangeIcon; 22 | -------------------------------------------------------------------------------- /app/containers/AdminResults/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | const selectAdminResultsDomain = (state: ApplicationRootState) => { 6 | return state.adminResults ? state.adminResults : initialState; 7 | }; 8 | export const selectContestFilter = () => 9 | createSelector(selectAdminResultsDomain, substate => { 10 | return substate.contestFilter; 11 | }); 12 | 13 | export const selectAthleteFilters = () => 14 | createSelector(selectAdminResultsDomain, substate => { 15 | return substate.athleteFilters; 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /internals/generators/container/types.js.hbs: -------------------------------------------------------------------------------- 1 | import { ActionType } from 'typesafe-actions'; 2 | import * as actions from './actions'; 3 | import { ApplicationRootState } from 'types'; 4 | 5 | /* --- STATE --- */ 6 | interface {{ properCase name }}State { 7 | readonly default: any; 8 | } 9 | 10 | 11 | /* --- ACTIONS --- */ 12 | type {{ properCase name }}Actions = ActionType; 13 | 14 | 15 | /* --- EXPORTS --- */ 16 | 17 | type RootState = ApplicationRootState; 18 | type ContainerState = {{ properCase name }}State; 19 | type ContainerActions = {{ properCase name }}Actions; 20 | 21 | export { RootState, ContainerState, ContainerActions }; -------------------------------------------------------------------------------- /app/containers/AdminAthlete/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | LOAD_ATHLETE_SUGGESTIONS = 'app/AdminAthlete/LOAD_ATHLETE_SUGGESTIONS', 3 | SET_ATHLETE_SUGGESTIONS = 'app/AdminAthlete/SET_ATHLETE_SUGGESTIONS', 4 | SET_ATHLETE_FILTER_SELECTED_VALUE = 'app/AdminAthlete/SET_ATHLETE_FILTER_SELECTED_VALUE', 5 | LOAD_ATHLETE = 'app/AdminAthlete/LOAD_ATHLETE', 6 | SET_ATHLETE = 'app/AdminAthlete/SET_ATHLETE', 7 | LOAD_COUNTRY_SUGGESTIONS = 'app/AdminAthlete/LOAD_COUNTRY_SUGGESTIONS', 8 | SET_COUNTRY_SUGGESTIONS = 'app/AdminAthlete/SET_COUNTRY_SUGGESTIONS', 9 | CLEAR_FORM = 'app/AdminAthlete/CLEAR_FORM', 10 | } 11 | 12 | export default ActionTypes; 13 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ## 2 | ## This is an example file. Fill the secrets and rename the file as '.env.development' or ''.env.production'' 3 | ## 4 | 5 | API_ENV=Development 6 | API_BASE_URL='http://localhost:3000' 7 | IDENTITY_POOL_ID='cognito identity pool id' 8 | USER_POOL_ID='cognito user pool id' 9 | REGION='region' 10 | USER_POOL_WEB_CLIENT_ID='cognito user pool app client id' 11 | AWS_PROFILE='your aws cli profile name for uploading files to s3' 12 | S3_BUCKET_NAME='s3 bucket name of the website hosted' 13 | S3_IMAGES_BUCKET_NAME='s3 bucket name of images' 14 | S3_IMAGES_BUCKET_URL='s3 url of images bucket' 15 | CLOUDFRONT_ID='cloudfront distribution id (if any)' 16 | -------------------------------------------------------------------------------- /app/components/Footer/Link.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface OwnProps {} 4 | 5 | const Link = styled.a` 6 | color: ${props => props.theme.textInverted}; 7 | font-size: 1.2em; 8 | font-weight: bold; 9 | text-align: left; 10 | margin: 8px 16px; 11 | text-decoration: none; 12 | &:hover { 13 | text-decoration: underline; 14 | } 15 | `; 16 | 17 | const Info = styled.span` 18 | color: ${props => props.theme.textInverted}; 19 | font-size: 1em; 20 | /* font-weight: bold; */ 21 | text-align: center; 22 | margin: 8px 16px; 23 | text-decoration: none; 24 | `; 25 | export { Info }; 26 | export default Link; 27 | -------------------------------------------------------------------------------- /internals/generators/component/stateless.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import * as React from 'react'; 8 | 9 | // import styled from 'styles/styled-components'; 10 | 11 | {{#if wantMessages}} 12 | import { FormattedMessage } from 'react-intl'; 13 | import messages from './messages'; 14 | {{/if}} 15 | 16 | interface OwnProps {} 17 | 18 | const {{ properCase name }}: React.SFC = (props: OwnProps) => { 19 | return ( 20 |
21 | {{#if wantMessages}} 22 | 23 | {{/if}} 24 |
25 | ); 26 | }; 27 | 28 | export default {{ properCase name }}; 29 | -------------------------------------------------------------------------------- /internals/generators/utils/componentExists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * componentExists 3 | * 4 | * Check whether the given component exist in either the components or containers directory 5 | */ 6 | 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const pageComponents = fs.readdirSync( 10 | path.join(__dirname, '../../../app/components'), 11 | ); 12 | const pageContainers = fs.readdirSync( 13 | path.join(__dirname, '../../../app/containers'), 14 | ); 15 | const components = pageComponents.concat(pageContainers); 16 | 17 | function componentExists(comp) { 18 | return components.indexOf(comp) >= 0; 19 | } 20 | 21 | module.exports = componentExists; 22 | -------------------------------------------------------------------------------- /app/api/admin/athlete/__mocks__/athlete_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminGetAthleteResponse } from '..'; 2 | 3 | const generator = (): APIAdminGetAthleteResponse => { 4 | return { 5 | athlete: { 6 | id: 'thomas-buckingham', 7 | name: 'Thomas', 8 | surname: 'Buckingham', 9 | profileUrl: 10 | 'http://www.slackattack.ch/wp-content/uploads/2015/11/Vorstand_Tom.jpg', 11 | country: 'tr', 12 | gender: 2, 13 | birthdate: '2017-10-25', 14 | email: 'test@test.com', 15 | city: 'Bern', 16 | infoUrl: 'https://www.facebook.com/events/529600260719152/', 17 | }, 18 | }; 19 | }; 20 | 21 | export default generator; 22 | -------------------------------------------------------------------------------- /app/components/Divider/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface Props { 4 | margin?: number; 5 | } 6 | const Divider = styled('hr')` 7 | height: 1px; 8 | width: auto; 9 | margin: auto ${props => (props.margin ? props.margin : '0')}px; 10 | border: none; 11 | background-color: ${props => props.theme.divider}; 12 | `; 13 | 14 | export const VerticalDivider = styled('hr')` 15 | height: auto; 16 | width: 1px; 17 | margin: ${props => (props.margin ? props.margin : '0')}px auto; 18 | border: none; 19 | background-color: ${props => props.theme.divider}; 20 | `; 21 | 22 | export default Divider; 23 | -------------------------------------------------------------------------------- /app/components/Logo/rankingsLogo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import rankingsLogo from './Rankings-Logo-Text.svg'; 4 | import styled from 'styles/styled-components'; 5 | import { clickEffect } from 'styles/mixins'; 6 | 7 | const RankingsLogo: React.SFC<{}> = () => { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | const Wrapper = styled.a` 16 | ${clickEffect}; 17 | height: 100%; 18 | display: flex; 19 | align-items: center; 20 | margin-right: 8px; 21 | img { 22 | max-width: 100%; 23 | max-height: 100%; 24 | } 25 | `; 26 | export default RankingsLogo; 27 | -------------------------------------------------------------------------------- /internals/generators/component/class.js.hbs: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * {{ properCase name }} 4 | * 5 | */ 6 | 7 | import * as React from 'react'; 8 | 9 | // import styled from 'styles/styled-components'; 10 | 11 | {{#if wantMessages}} 12 | import { FormattedMessage } from 'react-intl'; 13 | import messages from './messages'; 14 | {{/if}} 15 | 16 | interface OwnProps {} 17 | 18 | class {{ properCase name }} extends {{{ type }}} { 19 | public render() { 20 | return ( 21 |
22 | {{#if wantMessages}} 23 | 24 | {{/if}} 25 |
26 | ); 27 | } 28 | } 29 | 30 | export default {{ properCase name }}; 31 | -------------------------------------------------------------------------------- /app/components/Avatars/isaLogo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/checkStore.ts: -------------------------------------------------------------------------------- 1 | import conformsTo from 'lodash/conformsTo'; 2 | import isFunction from 'lodash/isFunction'; 3 | import isObject from 'lodash/isObject'; 4 | import invariant from 'invariant'; 5 | 6 | /** 7 | * Validate the shape of redux store 8 | */ 9 | export default function checkStore(store) { 10 | const shape = { 11 | dispatch: isFunction, 12 | subscribe: isFunction, 13 | getState: isFunction, 14 | replaceReducer: isFunction, 15 | runSaga: isFunction, 16 | injectedReducers: isObject, 17 | injectedSagas: isObject, 18 | }; 19 | invariant( 20 | conformsTo(store, shape), 21 | '(app/utils...) injectors: Expected a valid redux store', 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/components/Logo/sportsLogo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import isaLogo from './isa-sport.svg?file'; 4 | import styled from 'styles/styled-components'; 5 | 6 | const IsaSportsLogo: React.SFC<{}> = () => { 7 | return ( 8 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | const Link = styled.a` 18 | height: 100%; 19 | display: flex; 20 | align-items: center; 21 | &:hover { 22 | text-decoration: underline; 23 | } 24 | img { 25 | max-width: 100%; 26 | max-height: 100%; 27 | } 28 | `; 29 | export default IsaSportsLogo; 30 | -------------------------------------------------------------------------------- /app/containers/App/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * AppConstants 3 | * Each action has a corresponding type, which the reducer knows and picks up on. 4 | * To avoid weird typos between the reducer and the actions, we save them as 5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid 6 | * reducers accidentally picking up actions they shouldn't. 7 | * 8 | * Follow this format: 9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT'; 10 | */ 11 | 12 | // export const LOAD_REPOS = 'boilerplate/App/LOAD_REPOS'; 13 | // export const LOAD_REPOS_SUCCESS = 'boilerplate/App/LOAD_REPOS_SUCCESS'; 14 | // export const LOAD_REPOS_ERROR = 'boilerplate/App/LOAD_REPOS_ERROR'; 15 | -------------------------------------------------------------------------------- /app/containers/Rankings/MainTable/DivGroup.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface Props { 4 | vertical?: boolean; 5 | centered?: boolean; 6 | } 7 | const DivGroup = styled('div')` 8 | display: flex; 9 | flex-direction: ${props => (props.vertical ? 'column' : 'row')}; 10 | justify-content: ${props => (props.centered ? 'center' : 'flex-start')}; 11 | align-items: ${props => (props.vertical ? 'flex-start' : 'center')}; 12 | text-align: left; 13 | 14 | & div { 15 | margin-right: 8px; 16 | } 17 | 18 | & a { 19 | text-decoration: none; 20 | 21 | &:hover { 22 | text-decoration: underline; 23 | } 24 | } 25 | `; 26 | 27 | export default DivGroup; 28 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/types.d.ts: -------------------------------------------------------------------------------- 1 | import { ActionType } from 'typesafe-actions'; 2 | import * as actions from './actions'; 3 | import { ApplicationRootState } from 'types'; 4 | import { CustomError } from 'utils/error'; 5 | 6 | /* --- STATE --- */ 7 | interface AdminLoginState { 8 | readonly isAuthenticated: boolean; 9 | readonly loginError: CustomError | null; 10 | readonly isLoginLoading: boolean; 11 | } 12 | 13 | /* --- ACTIONS --- */ 14 | type AdminLoginActions = ActionType; 15 | 16 | /* --- EXPORTS --- */ 17 | 18 | type RootState = ApplicationRootState; 19 | type ContainerState = AdminLoginState; 20 | type ContainerActions = AdminLoginActions; 21 | 22 | export { RootState, ContainerState, ContainerActions }; 23 | -------------------------------------------------------------------------------- /app/styles/media.ts: -------------------------------------------------------------------------------- 1 | import { css } from './styled-components'; 2 | import breakpoints from './breakpoints'; 3 | 4 | const media = Object.keys(breakpoints).reduce( 5 | (acc, label) => { 6 | acc[label] = ( 7 | literals: TemplateStringsArray, 8 | // tslint:disable-next-line:trailing-comma 9 | ...placeholders: any[] 10 | ) => css` 11 | @media (min-width: ${breakpoints[label]}px) { 12 | ${css(literals, ...placeholders)}; 13 | } 14 | `; 15 | return acc; 16 | }, 17 | // tslint:disable-next-line:no-object-literal-type-assertion 18 | {} as Record< 19 | keyof typeof breakpoints, 20 | (l: TemplateStringsArray, ...p: any[]) => string 21 | >, 22 | ); 23 | 24 | export default media; 25 | -------------------------------------------------------------------------------- /app/containers/AdminLogin/actions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * AdminLogin actions 4 | * 5 | */ 6 | 7 | import { action } from 'typesafe-actions'; 8 | import {} from './types'; 9 | 10 | import ActionTypes from './constants'; 11 | import { CustomError } from 'utils/error'; 12 | 13 | export const login = (email: string, password: string) => 14 | action(ActionTypes.LOGIN, { email: email, password: password }); 15 | 16 | export const loginSuccess = () => action(ActionTypes.LOGIN_SUCCESS); 17 | 18 | export const checkUser = () => action(ActionTypes.CHECK_USER); 19 | 20 | export const loginError = (error?: CustomError) => 21 | action(ActionTypes.LOGIN_ERROR, error); 22 | 23 | export const clearLoginError = () => action(ActionTypes.CLEAR_LOGIN_ERROR); 24 | -------------------------------------------------------------------------------- /app/api/admin/contest/__mocks__/contest_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIAdminGetContestResponse } from '..'; 2 | 3 | const generator = (): APIAdminGetContestResponse => { 4 | return { 5 | contest: { 6 | id: 'swiss-open_2017', 7 | name: 'Swiss Open', 8 | profileUrl: 9 | 'http://www.slackattack.ch/wp-content/uploads/2015/11/Vorstand_Tom.jpg', 10 | country: 'tr', 11 | date: '2017-10-25', 12 | city: 'Bern', 13 | contestType: { id: 3, name: 'Open' }, 14 | contestGender: {id: 0, name: 'Mixed'}, 15 | discipline: { id: 2, name: 'Trickline Aerial' }, 16 | prize: 1, 17 | infoUrl: 'https://www.facebook.com/events/529600260719152', 18 | }, 19 | }; 20 | }; 21 | 22 | export default generator; 23 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/types.d.ts: -------------------------------------------------------------------------------- 1 | import { UISelectOption } from 'types/application'; 2 | 3 | export interface ICategoryEntity { 4 | readonly title: string; 5 | readonly options: UISelectOption[]; 6 | readonly selectedValue: string; 7 | } 8 | 9 | export interface ICategory extends ICategoryEntity { 10 | categorySelected(value: string): void; 11 | } 12 | 13 | export interface IFilterEntity { 14 | readonly title: string; 15 | readonly placeholder: string; 16 | readonly suggestions?: UISelectOption[]; 17 | readonly selectedOption?: UISelectOption; 18 | } 19 | 20 | export interface IFilter extends IFilterEntity { 21 | loadSuggestions(searchValue: string): void; 22 | suggestionSelected(suggestion: UISelectOption): void; 23 | } 24 | -------------------------------------------------------------------------------- /app/containers/Contests/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | LOAD_TABLE_ITEMS = 'app/Contests/LOAD_TABLE_ITEMS', 3 | ADD_TABLE_ITEMS = 'app/Contests/ADD_TABLE_ITEMS', 4 | LOAD_NEXT_TABLE_ITEMS = 'app/Contests/LOAD_NEXT_TABLE_ITEMS', 5 | LOAD_CATEGORIES = 'app/Contests/LOAD_CATEGORIES', 6 | SET_CATEGORIES = 'app/Contests/SET_CATEGORIES', 7 | SET_CATEGORY_SELECTED_VALUE = 'app/Contests/SET_CATEGORY_SELECTED_VALUE', 8 | LOAD_CONTEST_SUGGESTIONS = 'app/Contests/LOAD_CONTEST_SUGGESTIONS', 9 | SET_CONTEST_SUGGESTIONS = 'app/Contests/SET_CONTEST_SUGGESTIONS', 10 | SET_CONTEST_FILTER_SELECTED_VALUE = 'app/Contests/SET_CONTESTFILTER_SELECTED_VALUE', 11 | // SET_CATEGORIES_STATUS = 'app/Contests/SET_CATEGORIES_STATUS', 12 | } 13 | 14 | export default ActionTypes; 15 | -------------------------------------------------------------------------------- /internals/scripts/helpers/progress.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const readline = require('readline'); 4 | 5 | /** 6 | * Adds an animated progress indicator 7 | * 8 | * @param {string} message The message to write next to the indicator 9 | * @param {number} amountOfDots The amount of dots you want to animate 10 | */ 11 | function animateProgress(message, amountOfDots) { 12 | if (typeof amountOfDots !== 'number') { 13 | amountOfDots = 3; 14 | } 15 | 16 | let i = 0; 17 | return setInterval(function() { 18 | readline.cursorTo(process.stdout, 0); 19 | i = (i + 1) % (amountOfDots + 1); 20 | const dots = new Array(i + 1).join('.'); 21 | process.stdout.write(message + dots); 22 | }, 500); 23 | } 24 | 25 | module.exports = animateProgress; 26 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/types.d.ts: -------------------------------------------------------------------------------- 1 | import { ActionType } from 'typesafe-actions'; 2 | import * as actions from './actions'; 3 | import { ApplicationRootState } from 'types'; 4 | import { TopBarTabType, TopBarTabContentType } from 'types/enums'; 5 | import { LocationChangeAction } from 'connected-react-router'; 6 | 7 | /* --- EXPORTS --- */ 8 | 9 | type RootState = ApplicationRootState; 10 | type ContainerState = AdminTopBarTabsState; 11 | type ContainerActions = TopBarTabsActions; 12 | 13 | export { RootState, ContainerState, ContainerActions }; 14 | 15 | /* --- STATE --- */ 16 | interface AdminTopBarTabsState { 17 | readonly selectedId: string; 18 | } 19 | 20 | /* --- ACTIONS --- */ 21 | type TopBarTabsActions = ActionType | LocationChangeAction; 22 | -------------------------------------------------------------------------------- /app/components/Icons/IconClose.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface IconProps {} 4 | 5 | /* tslint:disable:max-line-length */ 6 | class IconClose extends React.PureComponent { 7 | public render() { 8 | return ( 9 | 10 | 15 | 16 | ); 17 | } 18 | } 19 | 20 | export default IconClose; 21 | -------------------------------------------------------------------------------- /app/components/Icons/categories/AgeIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AllAge from './misc/AllAge'; 3 | import Youth from './misc/Youth'; 4 | import Senior from './misc/Senior'; 5 | 6 | interface AgeIconProps { 7 | readonly value: string; 8 | } 9 | 10 | /* tslint:disable:max-line-length */ 11 | class AgeIcon extends React.PureComponent { 12 | public render() { 13 | const value = this.props.value; 14 | return renderSwitch(value); 15 | } 16 | } 17 | 18 | function renderSwitch(param: string) { 19 | switch (param) { 20 | case '0': 21 | return ; 22 | case '1': 23 | return ; 24 | case '2': 25 | return ; 26 | default: 27 | return
; 28 | } 29 | } 30 | 31 | export default AgeIcon; 32 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/saga.ts: -------------------------------------------------------------------------------- 1 | // import { takeLatest, call, put, select } from 'redux-saga/effects'; 2 | // import ActionTypes from './constants'; 3 | 4 | // import request from 'utils/request'; 5 | 6 | // export function* getSampleData() { 7 | // // TODO: fix return value 8 | // const index = yield select(makeSelectIndex()); 9 | // const requestURL = `https://jsonplaceholder.typicode.com/todos/${index}`; 10 | 11 | // // Call our request helper (see 'app/utils/request') 12 | // const res = yield call(request, requestURL); 13 | 14 | // console.log('Res: ', res); 15 | // } 16 | 17 | /** 18 | * Root saga manages watcher lifecycle 19 | */ 20 | export default function* sagas(): IterableIterator { 21 | // yield takeLatest(ActionTypes.GET_SAMPLE_DATA, getSampleData); 22 | } 23 | -------------------------------------------------------------------------------- /app/api/admin/contest/genders.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig, axiosConfigWithAuthToken } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | // import mockResponse from './__mocks__/categories_mock'; 5 | import { ISelectOption } from 'types/application'; 6 | 7 | export interface APIAdminGetGendersResponse { 8 | genders: ISelectOption[]; 9 | } 10 | 11 | const requestURL = 'admin/api/contest/genders'; 12 | export const adminGetGenders = async (): Promise< 13 | APIAdminGetGendersResponse 14 | > => { 15 | return axios 16 | .get( 17 | requestURL, 18 | axiosConfig(undefined, 1000, false, await axiosConfigWithAuthToken()), 19 | ) 20 | .then(resp => { 21 | const result = resp.data as APIAdminGetGendersResponse; 22 | return result; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | const selectAdminAthleteDomain = (state: ApplicationRootState) => { 6 | return state.adminAthlete ? state.adminAthlete : initialState; 7 | }; 8 | 9 | export const selectAthleteFilter = () => 10 | createSelector(selectAdminAthleteDomain, substate => { 11 | return substate.athleteFilter; 12 | }); 13 | 14 | export const selectAthlete = () => 15 | createSelector(selectAdminAthleteDomain, substate => { 16 | return substate.athlete; 17 | }); 18 | 19 | export const selectCountryFilter = () => 20 | createSelector(selectAdminAthleteDomain, substate => { 21 | return substate.countryFilter; 22 | }); 23 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/saga.ts: -------------------------------------------------------------------------------- 1 | // import { takeLatest, call, put, select } from 'redux-saga/effects'; 2 | // import ActionTypes from './constants'; 3 | 4 | // import request from 'utils/request'; 5 | 6 | // export function* getSampleData() { 7 | // // TODO: fix return value 8 | // const index = yield select(makeSelectIndex()); 9 | // const requestURL = `https://jsonplaceholder.typicode.com/todos/${index}`; 10 | 11 | // // Call our request helper (see 'app/utils/request') 12 | // const res = yield call(request, requestURL); 13 | 14 | // console.log('Res: ', res); 15 | // } 16 | 17 | /** 18 | * Root saga manages watcher lifecycle 19 | */ 20 | export default function* sagas(): IterableIterator { 21 | // yield takeLatest(ActionTypes.GET_SAMPLE_DATA, getSampleData); 22 | } 23 | -------------------------------------------------------------------------------- /app/components/Icons/rankUpIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /* tslint:disable:max-line-length */ 4 | class RankUpIcon extends React.PureComponent { 5 | public render() { 6 | return ( 7 | 13 | 14 | 18 | 23 | 24 | 25 | ); 26 | } 27 | } 28 | 29 | export default RankUpIcon; 30 | -------------------------------------------------------------------------------- /internals/generators/container/selectors.js.hbs: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | 6 | /** 7 | * Direct selector to the {{ camelCase name }} state domain 8 | */ 9 | 10 | const select{{ properCase name }}Domain = (state: ApplicationRootState) => { 11 | return state ? state : initialState; 12 | }; 13 | 14 | /** 15 | * Other specific selectors 16 | */ 17 | 18 | /** 19 | * Default selector used by {{ properCase name }} 20 | */ 21 | 22 | const select{{ properCase name }} = () => 23 | createSelector(select{{ properCase name }}Domain, substate => { 24 | return substate; 25 | }); 26 | 27 | export default select{{ properCase name }}; 28 | export { select{{ properCase name }}Domain }; 29 | -------------------------------------------------------------------------------- /app/api/amplify/signin.ts: -------------------------------------------------------------------------------- 1 | import { Auth } from 'aws-amplify'; 2 | import { CognitoUser } from 'amazon-cognito-identity-js'; 3 | import { CustomError } from 'utils/error'; 4 | 5 | import './configure'; 6 | async function signin(email: string, password: string) { 7 | return Auth.signIn(email, password) 8 | .then((user: CognitoUser) => ({ 9 | username: user.getUsername(), 10 | })) 11 | .catch(error => { 12 | throw new CustomError({ 13 | message: error.message, 14 | data: error.code, 15 | }); 16 | }); 17 | } 18 | 19 | // Auth.signUp({ 20 | // username: '', 21 | // password: '', 22 | // attributes: { 23 | // email: '', 24 | // }, 25 | // }) 26 | // .then(data => console.log(data)) 27 | // .catch(err => console.log(err)); 28 | export default signin; 29 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/CategoryFilterButton/categories.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/components/Icons/rankDownIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /* tslint:disable:max-line-length */ 4 | class RankDownIcon extends React.PureComponent { 5 | public render() { 6 | return ( 7 | 13 | 14 | 19 | 23 | 24 | 25 | ); 26 | } 27 | } 28 | 29 | export default RankDownIcon; 30 | -------------------------------------------------------------------------------- /server/middlewares/addProdMiddlewares.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const compression = require('compression'); 4 | 5 | module.exports = function addProdMiddlewares(app, options) { 6 | const publicPath = options.publicPath || '/'; 7 | const outputPath = options.outputPath || path.resolve(process.cwd(), 'build'); 8 | 9 | // compression middleware compresses your server responses which makes them 10 | // smaller (applies also to assets). You can read more about that technique 11 | // and other good practices on official Express.js docs http://mxs.is/googmy 12 | app.use(compression()); 13 | app.use(publicPath, express.static(outputPath)); 14 | 15 | app.get('*', (req, res) => 16 | res.sendFile(path.resolve(outputPath, 'index.html')), 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /app/api/athlete/__mocks__/athlete_suggestions_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIGetAthleteSuggestionsResponse } from '../suggestions'; 2 | 3 | const generator = (): APIGetAthleteSuggestionsResponse => { 4 | return { 5 | items: [ 6 | { 7 | id: '1', 8 | name: 'Can', 9 | surname: 'Sahin', 10 | email: 'email', 11 | }, 12 | { 13 | id: '2', 14 | name: 'Thomas', 15 | surname: 'Buckingham', 16 | email: 'email', 17 | }, 18 | { 19 | id: '3', 20 | name: 'Samuel', 21 | surname: 'Volery', 22 | email: 'email', 23 | }, 24 | { 25 | id: '4', 26 | name: 'Lukas', 27 | surname: 'Irmler', 28 | email: 'email', 29 | }, 30 | ], 31 | }; 32 | }; 33 | 34 | export default generator; 35 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | modules: false, 7 | }, 8 | ], 9 | '@babel/preset-react', 10 | ], 11 | plugins: [ 12 | 'styled-components', 13 | '@babel/plugin-proposal-class-properties', 14 | '@babel/plugin-syntax-dynamic-import', 15 | ], 16 | env: { 17 | production: { 18 | only: ['app'], 19 | plugins: [ 20 | 'lodash', 21 | 'transform-react-remove-prop-types', 22 | '@babel/plugin-transform-react-inline-elements', 23 | '@babel/plugin-transform-react-constant-elements', 24 | ], 25 | }, 26 | test: { 27 | plugins: [ 28 | '@babel/plugin-transform-modules-commonjs', 29 | 'dynamic-import-node', 30 | ], 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /app/reducers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Combine all reducers in this file and export the combined reducers. 3 | */ 4 | 5 | import { combineReducers } from 'redux'; 6 | import { connectRouter } from 'connected-react-router'; 7 | 8 | import history from 'utils/history'; 9 | import languageProviderReducer from 'containers/LanguageProvider/reducer'; 10 | 11 | /** 12 | * Merges the main reducer with the router state and dynamically injected reducers 13 | */ 14 | export default function createReducer(injectedReducers = {}) { 15 | const rootReducer = combineReducers({ 16 | language: languageProviderReducer, 17 | ...injectedReducers, 18 | }); 19 | 20 | // Wrap the root reducer and return a new root reducer with router state 21 | const mergeWithRouterState = connectRouter(history); 22 | return mergeWithRouterState(rootReducer); 23 | } 24 | -------------------------------------------------------------------------------- /app/components/Icons/LeftArrowIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | /* tslint:disable:max-line-length */ 4 | class LeftArrowIcon extends React.PureComponent { 5 | public render() { 6 | return ( 7 | 8 | 9 | 10 | 14 | 15 | 16 | ); 17 | } 18 | } 19 | 20 | export default LeftArrowIcon; 21 | -------------------------------------------------------------------------------- /app/api/contests/__mocks__/contest_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIGetContestResponse } from '../contest'; 2 | 3 | const generator = (): APIGetContestResponse => { 4 | return { 5 | contest: { 6 | id: 'swiss-open_2018', 7 | name: 'Swiss Open', 8 | date: '25/10/2018', 9 | city: 'Bern', 10 | country: 'Switzerland', 11 | prize: '400€', 12 | contestType: { id: 4, name: 'Open' }, 13 | contestGender: { id: 0, name: 'Mixed' }, 14 | discipline: { id: 2, name: 'Aerial' }, 15 | infoUrl: 'https://www.facebook.com/events/529600260719152/', 16 | profileUrl: 17 | // tslint:disable-next-line:max-line-length 18 | 'https://scontent-mxp1-1.xx.fbcdn.net/v/t1.0-9/18922028_1381229651962030_8607224956463366700_n.jpg?_nc_cat=107&oh=94312c53ae0b5745939e6e91ecbc1856&oe=5C4A3E47', 19 | }, 20 | }; 21 | }; 22 | 23 | export default generator; 24 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | const selectTopBarDomain = (state: ApplicationRootState) => { 6 | return state.topBarTabs ? state.topBarTabs : initialState; 7 | }; 8 | 9 | export const selectRouterDomain = (state: ApplicationRootState) => { 10 | return state.router; 11 | }; 12 | 13 | export const selectTabItems = () => 14 | createSelector(selectTopBarDomain, state => { 15 | return state.items; 16 | }); 17 | 18 | export const selectSelectedId = () => 19 | createSelector(selectTopBarDomain, state => { 20 | return state.selectedId; 21 | }); 22 | 23 | export const selectLocationPath = () => 24 | createSelector(selectRouterDomain, state => { 25 | return state.location ? state.location.pathname : ''; 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /app/components/Icons/categories/PeopleIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AllPeople from './misc/AllPeople'; 3 | import Male from './misc/Male'; 4 | import Female from './misc/Female'; 5 | 6 | interface PeopleIconProps { 7 | readonly value: string; 8 | readonly className?: string; 9 | } 10 | 11 | /* tslint:disable:max-line-length */ 12 | class PeopleIcon extends React.PureComponent { 13 | public render() { 14 | const value = this.props.value; 15 | return
{renderSwitch(value)}
; 16 | } 17 | } 18 | 19 | function renderSwitch(param: string) { 20 | switch (param) { 21 | case '0': 22 | return ; 23 | case '1': 24 | return ; 25 | case '2': 26 | return ; 27 | default: 28 | return
; 29 | } 30 | } 31 | 32 | export default PeopleIcon; 33 | -------------------------------------------------------------------------------- /app/utils/index.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { isNil as _isNil } from 'lodash'; 3 | 4 | // tslint:disable-next-line:no-namespace 5 | export namespace Utils { 6 | export function currentYear() { 7 | return moment() 8 | .utc() 9 | .year(); 10 | } 11 | 12 | export function isNil(value: any) { 13 | return _isNil(value); 14 | } 15 | 16 | export function getUrlQueryVariable(queryString: string, variable: string) { 17 | const query = queryString.substring(1); 18 | const vars = query.split('&'); 19 | // tslint:disable-next-line:prefer-for-of 20 | for (let i = 0; i < vars.length; i++) { 21 | const pair = vars[i].split('='); 22 | if (decodeURIComponent(pair[0]) === variable) { 23 | return decodeURIComponent(pair[1]); 24 | } 25 | } 26 | // console.log('Query variable %s not found', variable); 27 | return ''; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/components/Icons/categories/WorldIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PointScore from './misc/PointScoreIcon'; 3 | import TopScore from './misc/TopScoreIcon'; 4 | import styled from 'styles/styled-components'; 5 | 6 | interface WorldIconProps { 7 | readonly value: string; 8 | } 9 | 10 | /* tslint:disable:max-line-length */ 11 | class WorldIcon extends React.PureComponent { 12 | public render() { 13 | const value = this.props.value; 14 | return {renderSwitch(value)}; 15 | } 16 | } 17 | 18 | function renderSwitch(param: string) { 19 | switch (param) { 20 | case '1': 21 | return ; 22 | case '2': 23 | return ; 24 | default: 25 | return
; 26 | } 27 | } 28 | 29 | const Wrapper = styled.div` 30 | max-width: 30px; 31 | max-height: 30px; 32 | display: flex; 33 | `; 34 | 35 | export default WorldIcon; 36 | -------------------------------------------------------------------------------- /app/containers/Rankings/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | LOAD_TABLE_ITEMS = 'app/Rankings/LOAD_TABLE_ITEMS', 3 | ADD_TABLE_ITEMS = 'app/Rankings/ADD_TABLE_ITEMS', 4 | LOAD_NEXT_TABLE_ITEMS = 'app/Rankings/LOAD_NEXT_TABLE_ITEMS', 5 | LOAD_CATEGORIES = 'app/Rankings/LOAD_CATEGORIES', 6 | SET_CATEGORIES = 'app/Rankings/SET_CATEGORIES', 7 | SET_CATEGORY_SELECTED_VALUE = 'app/Rankings/SET_CATEGORY_SELECTED_VALUE', 8 | LOAD_ATHLETE_SUGGESTIONS = 'app/Rankings/LOAD_ATHLETE_SUGGESTIONS', 9 | LOAD_COUNTRY_SUGGESTIONS = 'app/Rankings/LOAD_COUNTRY_SUGGESTIONS', 10 | SET_COUNTRY_SUGGESTIONS = 'app/Rankings/SET_COUNTRY_SUGGESTIONS', 11 | SET_ATHLETE_SUGGESTIONS = 'app/Rankings/SET_ATHLETE_SUGGESTIONS', 12 | SET_ATHLETE_FILTER_SELECTED_VALUE = 'app/Rankings/SET_ATHLETE_FILTER_SELECTED_VALUE', 13 | SET_COUNTRY_FILTER_SELECTED_VALUE = 'app/Rankings/SET_COUNTRY_FILTER_SELECTED_VALUE', 14 | } 15 | 16 | export default ActionTypes; 17 | -------------------------------------------------------------------------------- /app/containers/App/GlobalStyle.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styles/styled-components'; 2 | import '../../styles/font-face.css'; 3 | 4 | /* tslint:disable:max-line-length */ 5 | const GlobalStyle = createGlobalStyle` 6 | html { 7 | font-size: 75%; 8 | background-color: ${props => props.theme.appBackground}; 9 | } 10 | body { 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | body { 16 | font-family: 'metropolis', 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | } 18 | 19 | /* body.fontLoaded { 20 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 21 | } */ 22 | 23 | #app { 24 | background-color: #fafafa; 25 | min-height: 100%; 26 | min-width: 100%; 27 | } 28 | 29 | p, 30 | label { 31 | font-family: 'metropolis', 'Helvetica Neue', Helvetica, Arial, sans-serif; 32 | line-height: 1.5em; 33 | } 34 | `; 35 | 36 | export default GlobalStyle; 37 | -------------------------------------------------------------------------------- /app/containers/Contest/actions.ts: -------------------------------------------------------------------------------- 1 | import { action } from 'typesafe-actions'; 2 | import { TableItemsResult, ContestItem } from './types'; 3 | import ActionTypes from './constants'; 4 | import { changeTopBarName as _changeTopBarName } from 'containers/TopBarTabs/actions'; 5 | 6 | export const setIdDiscipline = (id: string, discipline: string) => 7 | action(ActionTypes.SET_ID_DISCIPLINE, { id: id, discipline: discipline }); 8 | 9 | export const loadContest = () => action(ActionTypes.LOAD_CONTEST); 10 | 11 | export const setContest = (item: ContestItem) => 12 | action(ActionTypes.SET_CONTEST, item); 13 | 14 | export const loadTableItems = () => action(ActionTypes.LOAD_TABLE_ITEMS); 15 | 16 | export const addTableItems = (result: TableItemsResult) => { 17 | return action(ActionTypes.ADD_TABLE_ITEMS, result); 18 | }; 19 | 20 | export const loadNextItems = () => action(ActionTypes.LOAD_NEXT_TABLE_ITEMS); 21 | 22 | export const changeTopBarName = _changeTopBarName; 23 | -------------------------------------------------------------------------------- /app/containers/AdminResults/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | APIAdminSubmitContestResultsRequest, 3 | adminSubmitContestResults, 4 | } from 'api/admin/results/submit'; 5 | 6 | import { 7 | adminGetResults, 8 | APIAdminGetResultsRequest, 9 | APIAdminResultsResponse, 10 | } from 'api/admin/results'; 11 | import { AthleteFilter } from './types'; 12 | 13 | export async function apiSubmitContestResults( 14 | request: APIAdminSubmitContestResultsRequest, 15 | ) { 16 | return adminSubmitContestResults(request); 17 | } 18 | export { APIAdminSubmitContestResultsRequest }; 19 | 20 | export async function apiGetContestResults(request: APIAdminGetResultsRequest) { 21 | const results = await adminGetResults(request); 22 | return results.items.map(i => ({ 23 | orderNumber: i.place, 24 | selectedValue: { value: i.id, label: `${i.name} ${i.surname}` }, 25 | points: i.points, 26 | })); 27 | } 28 | export { APIAdminGetResultsRequest, APIAdminResultsResponse }; 29 | -------------------------------------------------------------------------------- /app/containers/AdminResults/types.d.ts: -------------------------------------------------------------------------------- 1 | import { ActionType } from 'typesafe-actions'; 2 | import * as actions from './actions'; 3 | import { ApplicationRootState } from 'types'; 4 | import { ISelectOption } from 'types/application'; 5 | 6 | /* --- STATE --- */ 7 | interface AdminResultsState { 8 | readonly contestFilter: IFilter; 9 | readonly athleteFilters: AthleteFilter[]; 10 | } 11 | 12 | interface IFilter { 13 | readonly selectedValue?: ISelectOption; 14 | readonly suggestions?: ISelectOption[]; 15 | } 16 | 17 | export interface AthleteFilter extends IFilter { 18 | readonly orderNumber: number; 19 | readonly points?: number; 20 | } 21 | 22 | /* --- ACTIONS --- */ 23 | type AdminResultsActions = ActionType; 24 | 25 | /* --- EXPORTS --- */ 26 | 27 | type RootState = ApplicationRootState; 28 | type ContainerState = AdminResultsState; 29 | type ContainerActions = AdminResultsActions; 30 | 31 | export { RootState, ContainerState, ContainerActions }; 32 | -------------------------------------------------------------------------------- /app/containers/GenericTabContent/types.d.ts: -------------------------------------------------------------------------------- 1 | import { ActionType } from 'typesafe-actions'; 2 | import { ApplicationRootState } from 'types'; 3 | import { FilterItem } from './FilterItem'; 4 | 5 | /* --- EXPORTS --- */ 6 | 7 | export { TabContentState, SearchSuggestion, SelectedFilter, DropdownFilter }; 8 | 9 | /* --- STATE --- */ 10 | 11 | interface TabContentState { 12 | selectedFilters: SelectedFilter[]; 13 | isSuggestionsLoading: boolean | null; 14 | suggestions: SearchSuggestion[] | null; 15 | selectedSearchInput: SearchSuggestion | null; 16 | tableItems: TableItem[] | null; 17 | isTableItemsLoading: boolean | null; 18 | dropdownFilters: DropdownFilter[]; 19 | } 20 | 21 | interface SelectedFilter { 22 | id: string; 23 | category: string; 24 | name: string; 25 | isSticky: boolean; 26 | } 27 | interface SearchSuggestion { 28 | name: string; 29 | } 30 | 31 | interface DropdownFilter { 32 | category: string; 33 | items: FilterItem[]; 34 | } 35 | -------------------------------------------------------------------------------- /app/containers/AdminResults/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | LOAD_ATHLETE_SUGGESTIONS = 'app/AdminResults/LOAD_ATHLETE_SUGGESTIONS', 3 | SET_ATHLETE_SUGGESTIONS = 'app/AdminResults/SET_ATHLETE_SUGGESTIONS', 4 | SET_ATHLETE_FILTER_SELECTED_VALUE = 'app/AdminResults/SET_ATHLETE_FILTER_SELECTED_VALUE', 5 | CHANGE_ATHLETE_FILTER_ORDER = 'app/AdminResults/CHANGE_ATHLETE_FILTER_ORDER', 6 | CHANGE_ATHLETE_FILTER_POINTS = 'app/AdminResults/CHANGE_ATHLETE_FILTER_POINTS', 7 | LOAD_CONTEST_SUGGESTIONS = 'app/AdminResults/LOAD_CONTEST_SUGGESTIONS', 8 | SET_CONTEST_SUGGESTIONS = 'app/AdminResults/SET_CONTEST_SUGGESTIONS', 9 | SET_CONTEST_FILTER_SELECTED_VALUE = 'app/AdminResults/SET_CONTEST_FILTER_SELECTED_VALUE', 10 | LOAD_RESULTS = 'app/AdminResults/LOAD_RESULTS', 11 | SET_RESULTS = 'app/AdminResults/SET_RESULTS', 12 | CLEAR_FORM = 'app/AdminResults/CLEAR_FORM', 13 | ADD_ATHLETE_FILTER = 'app/AdminResults/ADD_ATHLETE_FILTER', 14 | } 15 | 16 | export default ActionTypes; 17 | -------------------------------------------------------------------------------- /app/containers/AdminContest/constants.ts: -------------------------------------------------------------------------------- 1 | enum ActionTypes { 2 | LOAD_CONTEST_SUGGESTIONS = 'app/AdminContest/LOAD_CONTEST_SUGGESTIONS', 3 | SET_CONTEST_SUGGESTIONS = 'app/AdminContest/SET_CONTEST_SUGGESTIONS', 4 | SET_CONTEST_FILTER_SELECTED_VALUE = 'app/AdminContest/SET_CONTEST_FILTER_SELECTED_VALUE', 5 | LOAD_CONTEST = 'app/AdminContest/LOAD_CONTEST', 6 | SET_CONTEST = 'app/AdminContest/SET_CONTEST', 7 | LOAD_COUNTRY_SUGGESTIONS = 'app/AdminContest/LOAD_COUNTRY_SUGGESTIONS', 8 | SET_COUNTRY_SUGGESTIONS = 'app/AdminContest/SET_COUNTRY_SUGGESTIONS', 9 | CLEAR_FORM = 'app/AdminContest/CLEAR_FORM', 10 | LOAD_DISCIPLINES = 'app/AdminContest/LOAD_DISCIPLINES', 11 | SET_DISCIPLINES = 'app/AdminContest/SET_DISCIPLINES', 12 | LOAD_CATEGORIES = 'app/AdminContest/LOAD_CATEGORIES', 13 | SET_CATEGORIES = 'app/AdminContest/SET_CATEGORIES', 14 | LOAD_GENDERS = 'app/AdminContest/LOAD_GENDERS', 15 | SET_GENDERS = 'app/AdminContest/SET_GENDERS', 16 | } 17 | 18 | export default ActionTypes; 19 | -------------------------------------------------------------------------------- /internals/scripts/analyze.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const shelljs = require('shelljs'); 4 | const animateProgress = require('./helpers/progress'); 5 | const chalk = require('chalk'); 6 | const addCheckMark = require('./helpers/checkmark'); 7 | 8 | const progress = animateProgress('Generating stats'); 9 | 10 | // Generate stats.json file with webpack 11 | shelljs.exec( 12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json', 13 | addCheckMark.bind(null, callback), // Output a checkmark on completion 14 | ); 15 | 16 | // Called after webpack has finished generating the stats.json file 17 | function callback() { 18 | clearInterval(progress); 19 | process.stdout.write( 20 | '\n\nOpen ' + 21 | chalk.magenta('http://webpack.github.io/analyse/') + 22 | ' in your browser and upload the stats.json file!' + 23 | chalk.blue( 24 | '\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n', 25 | ), 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /internals/generators/container/reducer.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * {{ properCase name }} reducer 4 | * 5 | */ 6 | 7 | import { combineReducers } from 'redux'; 8 | 9 | import ActionTypes from './constants'; 10 | import { ContainerState, ContainerActions } from './types'; 11 | 12 | export const initialState: ContainerState = { 13 | default: null, 14 | }; 15 | 16 | // function {{ camelCase name }}Reducer(state: ContainerState = initialState, action: ContainerActions ) { 17 | // switch (action.type) { 18 | // case ActionTypes.DEFAULT_ACTION: 19 | // return state; 20 | // default: 21 | // return state; 22 | // } 23 | // } 24 | 25 | // export default {{ camelCase name }}Reducer; 26 | 27 | export default combineReducers({ 28 | default: (state = initialState, action) => { 29 | switch (action.type) { 30 | case ActionTypes.DEFAULT_ACTION: 31 | return state; 32 | default: 33 | return state; 34 | } 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | const selectTopBarDomain = (state: ApplicationRootState) => { 6 | return state.adminTopBarTabs ? state.adminTopBarTabs : initialState; 7 | }; 8 | 9 | const selectAdminDomain = (state: ApplicationRootState) => { 10 | return state.adminLogin; 11 | }; 12 | const selectRouterDomain = (state: ApplicationRootState) => { 13 | return state.router; 14 | }; 15 | export const selectSelectedId = () => 16 | createSelector(selectTopBarDomain, state => { 17 | return state.selectedId; 18 | }); 19 | 20 | export const selectLocationPath = () => 21 | createSelector(selectRouterDomain, state => { 22 | return state.location ? state.location.pathname : ''; 23 | }); 24 | 25 | export const selectIsAuthenticated = () => 26 | createSelector(selectAdminDomain, state => { 27 | return state && state.isAuthenticated; 28 | }); 29 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: [ 3 | 'app/**/*.{js,jsx}', 4 | '!app/**/*.test.{js,jsx}', 5 | '!app/*/RbGenerated*/*.{js,jsx}', 6 | '!app/app.js', 7 | '!app/global-styles.js', 8 | '!app/*/*/Loadable.{js,jsx}', 9 | ], 10 | coverageThreshold: { 11 | global: { 12 | statements: 98, 13 | branches: 91, 14 | functions: 98, 15 | lines: 98, 16 | }, 17 | }, 18 | moduleDirectories: ['node_modules', 'app'], 19 | moduleNameMapper: { 20 | '.*\\.(css|less|styl|scss|sass)$': '/internals/mocks/cssModule.js', 21 | '.*\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 22 | '/internals/mocks/image.js', 23 | }, 24 | setupTestFrameworkScriptFile: '/internals/testing/test-bundler.js', 25 | setupFiles: ['raf/polyfill', '/internals/testing/enzyme-setup.js'], 26 | testRegex: 'tests/.*\\.test\\.js$', 27 | snapshotSerializers: ['enzyme-to-json/serializer'], 28 | }; 29 | -------------------------------------------------------------------------------- /app/api/athlete/categories.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/categories_mock'; 5 | import { CategoryItem } from 'api/types'; 6 | 7 | export interface APIAthleteContestsCategoriesResponse { 8 | items: CategoryItem[]; 9 | } 10 | 11 | const requestURL = 'api/athlete/categories'; 12 | export async function getAthleteContestsCategories(): Promise< 13 | APIAthleteContestsCategoriesResponse 14 | > { 15 | return axios 16 | .get(requestURL, axiosConfig(dummyResponse, 1000, false)) 17 | .then(resp => { 18 | const result = resp.data as APIAthleteContestsCategoriesResponse; 19 | return result; 20 | }); 21 | } 22 | 23 | const dummyResponse = (): AxiosResponse< 24 | APIAthleteContestsCategoriesResponse 25 | > => { 26 | return { 27 | data: mockResponse(), 28 | status: 200, 29 | statusText: '', 30 | config: axios.defaults, 31 | headers: undefined, 32 | request: undefined, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:latest", "tslint-react"], 3 | "rules": { 4 | "semicolon": [true, "always", "ignore-bound-class-methods"], 5 | "interface-name": false, 6 | "object-literal-shorthand": [true, "never"], 7 | "indent": [true, "spaces"], 8 | "quotemark": [true, "single", "jsx-double"], 9 | "no-var-requires": false, 10 | "ordered-imports": false, 11 | "no-unused-variable": [false, "react"], 12 | "member-ordering": [false], 13 | "object-literal-sort-keys": false, 14 | "no-shadowed-variable": false, 15 | "no-console": [false], 16 | // "max-line-length": [true, 80], 17 | "no-consecutive-blank-lines": [false], 18 | "no-string-literal": false, 19 | "jsx-no-multiline-js": false, 20 | "jsx-boolean-value": false, 21 | "arrow-parens": [false], 22 | "no-implicit-dependencies": false, 23 | "no-submodule-imports": false, 24 | "jsx-alignment": false, 25 | "no-empty-interface": false 26 | }, 27 | "linterOptions": { 28 | "exclude": ["**/*.js"] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/api/contests/discipline-categories.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/categories_mock'; 5 | import { CategoryItem } from 'api/types'; 6 | 7 | export interface APIContestsDisciplineCategoriesResponse { 8 | items: CategoryItem[]; 9 | } 10 | const requestURL = 'api/contest/categories'; 11 | export async function getContestsDisciplineCategories(): Promise< 12 | APIContestsDisciplineCategoriesResponse 13 | > { 14 | return axios 15 | .get(requestURL, axiosConfig(dummyResponse, 1000, false)) 16 | .then(resp => { 17 | const result = resp.data as APIContestsDisciplineCategoriesResponse; 18 | return result; 19 | }); 20 | } 21 | 22 | const dummyResponse = (): AxiosResponse< 23 | APIContestsDisciplineCategoriesResponse 24 | > => { 25 | return { 26 | data: mockResponse(), 27 | status: 200, 28 | statusText: '', 29 | config: axios.defaults, 30 | headers: undefined, 31 | request: undefined, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/inputs/DateInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import { FieldProps } from 'formik'; 4 | import { AthleteFormValues } from '../types'; 5 | import { isNil } from 'lodash'; 6 | import Wrapper from './Wrapper'; 7 | import ErrorLabel from './ErrorLabel'; 8 | interface Props {} 9 | 10 | class DateInput extends React.PureComponent< 11 | Props & FieldProps 12 | > { 13 | public render() { 14 | const field = this.props.field; 15 | const { touched, errors } = this.props.form; 16 | const isError = touched[field.name] && !isNil(errors[field.name]); 17 | return ( 18 | 19 |
20 | Birthdate: 21 | 22 |
23 | {isError && {errors[field.name]}} 24 |
25 | ); 26 | } 27 | } 28 | 29 | const Span = styled.span` 30 | color: ${props => props.theme.textPrimary}; 31 | `; 32 | 33 | export default DateInput; 34 | -------------------------------------------------------------------------------- /app/containers/AdminContest/inputs/DateInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import { FieldProps } from 'formik'; 4 | import { ContestFormValues } from '../types'; 5 | import { isNil } from 'lodash'; 6 | import Wrapper from './Wrapper'; 7 | import ErrorLabel from './ErrorLabel'; 8 | interface Props {} 9 | 10 | class DateInput extends React.PureComponent< 11 | Props & FieldProps 12 | > { 13 | public render() { 14 | const field = this.props.field; 15 | const { touched, errors } = this.props.form; 16 | const isError = touched[field.name] && !isNil(errors[field.name]); 17 | return ( 18 | 19 |
20 | Start Date: 21 | 22 |
23 | {isError && {errors[field.name]}} 24 |
25 | ); 26 | } 27 | } 28 | 29 | const Span = styled.span` 30 | color: ${props => props.theme.textPrimary}; 31 | `; 32 | 33 | export default DateInput; 34 | -------------------------------------------------------------------------------- /app/components/Title/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Title 4 | * 5 | */ 6 | 7 | import * as React from 'react'; 8 | import { default as styled, colors } from 'styles/styled-components'; 9 | import { FormattedMessage } from 'react-intl'; 10 | 11 | import messages from './messages'; 12 | import media from 'styles/media'; 13 | 14 | // import PropTypes from 'prop-types'; 15 | // import styled from 'styled-components'; 16 | 17 | const Title: React.SFC<{}> = () => { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | const Text = styled.div` 28 | color: ${colors.paleWhite}; 29 | font-size: 1rem; 30 | font-weight: 700; 31 | letter-spacing: -1px; 32 | display: flex; 33 | ${media.tablet` 34 | font-weight: bold; 35 | font-size: 1.66rem; 36 | letter-spacing: -1.75px; 37 | `}; 38 | `; 39 | 40 | const Wrapper = styled.div` 41 | display: flex; 42 | text-align: center; 43 | line-height: 0; 44 | `; 45 | 46 | export default Title; 47 | -------------------------------------------------------------------------------- /app/components/Footer/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import media from 'styles/media'; 3 | import AppConstants from 'styles/AppConstants'; 4 | import breakpoints from 'styles/breakpoints'; 5 | 6 | interface OwnProps {} 7 | 8 | const padding = (size: number) => AppConstants.LeftPadding(size); 9 | 10 | const Wrapper = styled.div` 11 | background-color: ${props => props.theme.componentBackgroundSecondary}; 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: flex-start; 15 | align-items: center; 16 | width: 100%; 17 | padding: 24px ${padding(breakpoints.mobile)}px 8px 18 | ${padding(breakpoints.mobile)}px; 19 | 20 | ${media.tablet` 21 | padding: 24px ${padding(breakpoints.tablet)}px 8px ${padding( 22 | breakpoints.tablet, 23 | )}px; 24 | `}; 25 | 26 | ${media.desktop` 27 | padding: 24px ${padding(breakpoints.desktop)}px 8px ${padding( 28 | breakpoints.desktop, 29 | )}px; 30 | flex-direction: row; 31 | align-items: flex-start; 32 | `}; 33 | `; 34 | 35 | export default Wrapper; 36 | -------------------------------------------------------------------------------- /app/components/Avatars/CountryAvatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import ReactCountryFlag from 'react-country-flag'; 4 | 5 | interface Props { 6 | code: string; 7 | small?: boolean; 8 | } 9 | 10 | function CountryAvatar(props: Props) { 11 | return ( 12 | 13 | 22 | 23 | ); 24 | } 25 | interface WrapperProps { 26 | small?: boolean; 27 | } 28 | const Wrapper = styled('div')` 29 | display: flex; 30 | align-items: center; 31 | width: ${props => (props.small ? '15px' : '25px')}; 32 | height: ${props => (props.small ? '15px' : '25px')}; 33 | flex: none; 34 | overflow: hidden; 35 | border-radius: 50%; 36 | background-size: 100%; 37 | `; 38 | export default CountryAvatar; 39 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/Tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import zIndex from 'styles/zIndex'; 3 | import { Tabs } from '@material-ui/core'; 4 | import styled from 'styles/styled-components'; 5 | import AppConstants from 'styles/AppConstants'; 6 | import breakpoints from 'styles/breakpoints'; 7 | import media from 'styles/media'; 8 | import { TabsProps } from '@material-ui/core/Tabs/Tabs'; 9 | 10 | const tabs = (props: TabsProps) => ( 11 | 12 | ); 13 | 14 | const StyledTabs = styled(tabs)` 15 | && { 16 | display: flex; 17 | align-items: center; 18 | 19 | z-index: ${zIndex('TopBarTabs')}; 20 | justify-content: flex-start; 21 | overflow-x: scroll; 22 | 23 | & .flexContainer { 24 | height: ${AppConstants.TopBarHeight(breakpoints.mobile)}px; 25 | 26 | ${media.tablet` 27 | height: ${AppConstants.TopBarHeight(breakpoints.tablet)}px; 28 | 29 | `}; 30 | 31 | ${media.desktop` 32 | 33 | `}; 34 | } 35 | } 36 | `; 37 | 38 | export default StyledTabs; 39 | -------------------------------------------------------------------------------- /app/api/admin/contest/categories.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig, axiosConfigWithAuthToken } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/categories_mock'; 5 | import { ISelectOption } from 'types/application'; 6 | 7 | export interface APIAdminGetCategoriesResponse { 8 | categories: ISelectOption[]; 9 | } 10 | 11 | const requestURL = 'admin/api/contest/categories'; 12 | export const adminGetCategories = async (): Promise< 13 | APIAdminGetCategoriesResponse 14 | > => { 15 | return axios 16 | .get( 17 | requestURL, 18 | axiosConfig(dummyResponse, 1000, false, await axiosConfigWithAuthToken()), 19 | ) 20 | .then(resp => { 21 | const result = resp.data as APIAdminGetCategoriesResponse; 22 | return result; 23 | }); 24 | }; 25 | 26 | const dummyResponse = (): AxiosResponse => { 27 | return { 28 | data: mockResponse(), 29 | status: 200, 30 | statusText: '', 31 | config: axios.defaults, 32 | headers: undefined, 33 | request: undefined, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/CategoryFilterButton/Section.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import categoriesSvg from './categories.svg?file'; 4 | import filterSvg from './filter.svg?file'; 5 | 6 | interface SectionProps { 7 | type: 'category' | 'filter'; 8 | width?: string; 9 | } 10 | const Section = (props: SectionProps) => { 11 | return ( 12 | 13 | 14 | {props.type === 'category' ? 'Categories' : 'Filters'} 15 | 16 | ); 17 | }; 18 | 19 | const SectionDiv = styled.div` 20 | display: flex; 21 | flex-direction: row; 22 | flex-wrap: nowrap; 23 | align-items: center; 24 | margin: 8px 16px; 25 | & img { 26 | width: 20px; 27 | height: 20px; 28 | } 29 | & span { 30 | margin-left: 8px; 31 | font-size: 1em; 32 | font-weight: inherit; 33 | color: ${props => props.theme.textPrimary}; 34 | } 35 | `; 36 | 37 | export default Section; 38 | -------------------------------------------------------------------------------- /app/components/Containers/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | interface CointainerProps { 4 | minHeight?: string; 5 | } 6 | 7 | export const EmptyContainer = styled('div')` 8 | position: relative; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | font-weight: normal; 13 | color: ${props => props.theme.textSecondary}; 14 | padding: 0 24px; 15 | text-align: center; 16 | min-height: ${props => props.minHeight}; 17 | `; 18 | 19 | export const TinyLoadingContainer = styled.div` 20 | position: absolute; 21 | display: flex; 22 | justify-content: center; 23 | right: 4px; 24 | align-items: center; 25 | align-self: center; 26 | font-weight: normal; 27 | padding: 0 8px; 28 | text-align: center; 29 | `; 30 | 31 | export const SmallLoadingContainer = styled.div` 32 | position: relative; 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | font-weight: normal; 37 | color: ${props => props.theme.textSecondary}; 38 | padding: 0 24px; 39 | text-align: center; 40 | `; 41 | -------------------------------------------------------------------------------- /app/api/contests/__mocks__/contest_suggestions_mock.ts: -------------------------------------------------------------------------------- 1 | import { APIGetContestSuggestionsResponse } from '../suggestions'; 2 | 3 | const generator = (): APIGetContestSuggestionsResponse => { 4 | return { 5 | items: [ 6 | { 7 | id: '1', 8 | name: 'Swiss Slackline Championship', 9 | discipline: { id: 2, name: 'Aerial' }, 10 | year: 2015, 11 | gender: { id: 0, name: 'Mixed' }, 12 | }, 13 | { 14 | id: '2', 15 | name: 'China Master', 16 | discipline: { id: 2, name: 'Aerial' }, 17 | year: 2015, 18 | gender: { id: 0, name: 'Mixed' }, 19 | }, 20 | { 21 | id: '3', 22 | name: 'Swiss Open', 23 | discipline: { id: 2, name: 'Aerial' }, 24 | year: 2015, 25 | gender: { id: 0, name: 'Mixed' }, 26 | }, 27 | { 28 | id: '4', 29 | name: 'Redbull Slackship', 30 | discipline: { id: 2, name: 'Aerial' }, 31 | year: 2015, 32 | gender: { id: 0, name: 'Mixed' }, 33 | }, 34 | ], 35 | }; 36 | }; 37 | 38 | export default generator; 39 | -------------------------------------------------------------------------------- /app/containers/Contest/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getContest, 3 | APIGetContestRequest, 4 | APIGetContestResponse, 5 | } from 'api/contests/contest'; 6 | import { ContestItem } from './types'; 7 | import { 8 | APIGetContestResultsRequest, 9 | getContestResults, 10 | APIContestResultsResponse, 11 | } from 'api/contests/contest-results'; 12 | 13 | interface GetContestResponse { 14 | contest: ContestItem; 15 | } 16 | 17 | export async function apiGetContest(request: APIGetContestRequest) { 18 | const result: APIGetContestResponse = await getContest(request); 19 | if (!result.contest) { 20 | return result; 21 | } 22 | const { city, country, ...rest } = result.contest; 23 | const resp: GetContestResponse = { 24 | contest: { 25 | ...rest, 26 | location: `${city}, ${country}`, 27 | }, 28 | }; 29 | return resp; 30 | } 31 | 32 | export async function apiGetContestResults( 33 | request: APIGetContestResultsRequest, 34 | ) { 35 | const results = await getContestResults(request); 36 | return results; 37 | } 38 | export { GetContestResponse, APIGetContestRequest, APIContestResultsResponse }; 39 | -------------------------------------------------------------------------------- /app/api/admin/contest/disciplines.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig, axiosConfigWithAuthToken } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/disciplines_mock'; 5 | import { ISelectOption } from 'types/application'; 6 | 7 | export interface APIAdminGetDisciplinesResponse { 8 | disciplines: ISelectOption[]; 9 | } 10 | 11 | const requestURL = 'admin/api/contest/disciplines'; 12 | export const adminGetDisciplines = async (): Promise< 13 | APIAdminGetDisciplinesResponse 14 | > => { 15 | return axios 16 | .get(requestURL, axiosConfig( 17 | dummyResponse, 18 | 1000, 19 | false, 20 | await axiosConfigWithAuthToken(), 21 | )) 22 | .then(resp => { 23 | const result = resp.data as APIAdminGetDisciplinesResponse; 24 | return result; 25 | }); 26 | }; 27 | 28 | const dummyResponse = (): AxiosResponse => { 29 | return { 30 | data: mockResponse(), 31 | status: 200, 32 | statusText: '', 33 | config: axios.defaults, 34 | headers: undefined, 35 | request: undefined, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/types.d.ts: -------------------------------------------------------------------------------- 1 | import { ActionType } from 'typesafe-actions'; 2 | import * as actions from './actions'; 3 | import { ApplicationRootState } from 'types'; 4 | import { TopBarTabType, TopBarTabContentType } from 'types/enums'; 5 | import { LocationChangeAction } from 'connected-react-router'; 6 | 7 | /* --- EXPORTS --- */ 8 | 9 | type RootState = ApplicationRootState; 10 | type ContainerState = TopBarTabsState; 11 | type ContainerActions = TopBarTabsActions; 12 | 13 | export { 14 | RootState, 15 | ContainerState, 16 | ContainerActions, 17 | TopBarTabsItem, 18 | TopBarTabType, 19 | }; 20 | 21 | /* --- STATE --- */ 22 | interface TopBarTabsState { 23 | readonly items: TopBarTabsItem[]; 24 | readonly selectedId: string; 25 | readonly selectedDiscipline: string | null; 26 | } 27 | 28 | interface TopBarTabsItem { 29 | readonly id: string; 30 | readonly discipline?: string; 31 | readonly name: string; 32 | readonly type: TopBarTabType; 33 | readonly contentType: TopBarTabContentType; 34 | } 35 | 36 | /* --- ACTIONS --- */ 37 | type TopBarTabsActions = ActionType | LocationChangeAction; 38 | -------------------------------------------------------------------------------- /app/components/ComponentBackground/isaBackground.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { css } from 'styles/styled-components'; 3 | import isaLogo from 'components/Avatars/isaLogo.svg?file'; 4 | import ComponentBackground from '.'; 5 | 6 | interface Props {} 7 | 8 | class IsaBlurBackground extends React.PureComponent { 9 | public render() { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | } 17 | 18 | const Wrapper = styled(ComponentBackground)` 19 | && { 20 | position: absolute; 21 | display: block; 22 | top: 0px; 23 | left: 0px; 24 | right: 0px; 25 | bottom: 0px; 26 | overflow: hidden; 27 | background-color: #333c4e; 28 | } 29 | `; 30 | 31 | const Logo = styled.div` 32 | position: absolute; 33 | display: block; 34 | top: 0px; 35 | left: 0px; 36 | right: 0px; 37 | bottom: 0px; 38 | background-image: url(${isaLogo}); 39 | background-size: 100%; 40 | background-position: center; 41 | background-color: #333c4e; 42 | filter: blur(60px); 43 | opacity: 0.35; 44 | `; 45 | 46 | export default IsaBlurBackground; 47 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/actions.ts: -------------------------------------------------------------------------------- 1 | import { action } from 'typesafe-actions'; 2 | import { Athlete } from './types'; 3 | 4 | import ActionTypes from './constants'; 5 | import { ISelectOption } from 'types/application'; 6 | 7 | export const loadAthleteSuggestions = (value: string) => 8 | action(ActionTypes.LOAD_ATHLETE_SUGGESTIONS, value); 9 | 10 | export const setAthleteSuggestions = (items: ISelectOption[]) => 11 | action(ActionTypes.SET_ATHLETE_SUGGESTIONS, items); 12 | 13 | export const setAthleteFilterSelectedValue = (option: ISelectOption) => 14 | action(ActionTypes.SET_ATHLETE_FILTER_SELECTED_VALUE, option); 15 | 16 | export const loadAthlete = (id: string) => action(ActionTypes.LOAD_ATHLETE, id); 17 | 18 | export const setAthlete = (athlete: Athlete) => 19 | action(ActionTypes.SET_ATHLETE, athlete); 20 | 21 | export const loadCountrySuggestions = (value: string) => 22 | action(ActionTypes.LOAD_COUNTRY_SUGGESTIONS, value); 23 | 24 | export const setCountrySuggestions = (items: ISelectOption[]) => 25 | action(ActionTypes.SET_COUNTRY_SUGGESTIONS, items); 26 | 27 | export const clearForm = () => action(ActionTypes.CLEAR_FORM); 28 | -------------------------------------------------------------------------------- /app/containers/Contests/api.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { 3 | getContests, 4 | APIGetContestsRequest, 5 | APIGetContestsResponse, 6 | } from 'api/contests/contests'; 7 | import { TableItem } from './types'; 8 | import { 9 | getContestSuggestions, 10 | APIGetContestSuggestionsRequest, 11 | APIGetContestSuggestionsResponse, 12 | } from 'api/contests/suggestions'; 13 | import { getContestsDisciplineCategories } from 'api/contests/discipline-categories'; 14 | 15 | export async function apiGetContests(request: APIGetContestsRequest) { 16 | const result: APIGetContestsResponse = await getContests(request); 17 | return result; 18 | } 19 | 20 | export async function apiGetDisciplineCategories() { 21 | const results = await getContestsDisciplineCategories(); 22 | return results; 23 | } 24 | 25 | export async function apiGetContestSuggestions( 26 | request: APIGetContestSuggestionsRequest, 27 | ) { 28 | const results = await getContestSuggestions(request); 29 | return results; 30 | } 31 | export { APIGetContestSuggestionsRequest, APIGetContestSuggestionsResponse }; 32 | export { APIGetContestsResponse, APIGetContestsRequest }; 33 | -------------------------------------------------------------------------------- /app/components/NavBar/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | import media from 'styles/media'; 3 | import zIndex from 'styles/zIndex'; 4 | import breakpoints from 'styles/breakpoints'; 5 | import AppConstants from 'styles/AppConstants'; 6 | 7 | const padding = (size: number) => AppConstants.LeftPadding(size); 8 | 9 | const Wrapper = styled.div` 10 | background-color: ${props => props.theme.componentBackgroundSecondary}; 11 | border-bottom: 1px solid ${props => props.theme.border}; 12 | height: ${AppConstants.NavBarHeight(breakpoints.mobile)}px; 13 | padding: 4px ${padding(breakpoints.mobile)}px; 14 | display: flex; 15 | align-items: center; 16 | width: 100%; 17 | z-index: ${zIndex('NavBar')}; 18 | justify-content: space-between; 19 | 20 | ${media.tablet` 21 | height: ${AppConstants.NavBarHeight(breakpoints.tablet)}px; 22 | padding: 4px ${padding(breakpoints.tablet)}px; 23 | align-items: center; 24 | `}; 25 | 26 | ${media.desktop` 27 | height: ${AppConstants.NavBarHeight(breakpoints.desktop)}px; 28 | padding: 4px ${padding(breakpoints.desktop)}px; 29 | `}; 30 | `; 31 | 32 | export default Wrapper; 33 | -------------------------------------------------------------------------------- /app/api/rankings/countrySuggestions.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/countrySuggestions_mock'; 5 | 6 | export interface APIGetCountrySuggestionsResponse { 7 | readonly items: CountrySuggestionItem[]; 8 | } 9 | 10 | interface CountrySuggestionItem { 11 | readonly code: string; 12 | readonly name: string; 13 | } 14 | 15 | const requestURL = 'api/country/suggestions'; 16 | export async function getCountrySuggestions( 17 | value: string, 18 | ): Promise { 19 | const url = `${requestURL}/${value}`; 20 | return axios 21 | .get( 22 | url, 23 | axiosConfig(dummyResponse, 1000, false), 24 | ) 25 | .then(resp => { 26 | const result = resp.data as APIGetCountrySuggestionsResponse; 27 | return result; 28 | }); 29 | } 30 | 31 | const dummyResponse = (): AxiosResponse => { 32 | return { 33 | data: mockResponse(), 34 | status: 200, 35 | statusText: '', 36 | config: axios.defaults, 37 | headers: undefined, 38 | request: undefined, 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /app/styles/AppConstants.ts: -------------------------------------------------------------------------------- 1 | import breakpoints from './breakpoints'; 2 | 3 | const AppConstants = { 4 | NavBarHeight: (size: number) => { 5 | switch (size) { 6 | case breakpoints.desktopLarge: 7 | case breakpoints.desktop: 8 | return 48; 9 | case breakpoints.tablet: 10 | return 48; 11 | case breakpoints.mobile: 12 | return 36; 13 | default: 14 | return 36; 15 | } 16 | }, 17 | TopBarHeight: (size: number) => { 18 | switch (size) { 19 | case breakpoints.desktopLarge: 20 | case breakpoints.desktop: 21 | return 64; 22 | case breakpoints.tablet: 23 | return 48; 24 | case breakpoints.mobile: 25 | return 36; 26 | default: 27 | return 36; 28 | } 29 | }, 30 | 31 | LeftPadding: (size: number) => { 32 | switch (size) { 33 | case breakpoints.desktopLarge: 34 | case breakpoints.desktop: 35 | return 64; 36 | case breakpoints.tablet: 37 | return 18; 38 | case breakpoints.mobile: 39 | return 16; 40 | default: 41 | return 16; 42 | } 43 | }, 44 | }; 45 | export default AppConstants; 46 | -------------------------------------------------------------------------------- /app/containers/Athlete/api.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | getAthlete, 4 | APIGetAthleteRequest, 5 | APIGetAthleteResponse, 6 | } from 'api/athlete'; 7 | 8 | export async function apiGetAthlete(request: APIGetAthleteRequest) { 9 | const result: APIGetAthleteResponse = await getAthlete(request); 10 | return result; 11 | } 12 | 13 | export { APIGetAthleteRequest, APIGetAthleteResponse }; 14 | 15 | import { 16 | APIGetAthleteContestsRequest, 17 | getAthleteContests, 18 | APIAthleteContestsResponse, 19 | } from 'api/athlete/athlete-contests'; 20 | import { TableItemsResult } from './types'; 21 | import { 22 | getAthleteContestsCategories, 23 | APIAthleteContestsCategoriesResponse, 24 | } from 'api/athlete/categories'; 25 | 26 | export async function apiGetAthleteContests( 27 | request: APIGetAthleteContestsRequest, 28 | ): Promise { 29 | const results = await getAthleteContests(request); 30 | return results; 31 | } 32 | 33 | export { APIGetAthleteContestsRequest }; 34 | 35 | export async function apiGetCategories() { 36 | const results = await getAthleteContestsCategories(); 37 | return results; 38 | } 39 | 40 | export { APIAthleteContestsCategoriesResponse }; 41 | -------------------------------------------------------------------------------- /app/api/athlete/suggestions.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig } from 'api/axios'; 2 | import { AxiosRequestConfig, AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/athlete_suggestions_mock'; 5 | 6 | export interface APIGetAthleteSuggestionsResponse { 7 | items: AthleteSuggestionItem[]; 8 | } 9 | 10 | interface AthleteSuggestionItem { 11 | id: string; 12 | name: string; 13 | surname: string; 14 | email: string; 15 | } 16 | 17 | const requestURL = 'api/athlete/suggestions'; 18 | const getAthleteSuggestions = async ( 19 | value: string, 20 | ): Promise => { 21 | const url = `${requestURL}/${value}`; 22 | return axios.get(url, axiosConfig(dummyResponse, 1000, false)).then(resp => { 23 | const result = resp.data as APIGetAthleteSuggestionsResponse; 24 | return result; 25 | }); 26 | }; 27 | 28 | const dummyResponse = (): AxiosResponse => { 29 | return { 30 | data: mockResponse(), 31 | status: 200, 32 | statusText: '', 33 | config: axios.defaults, 34 | headers: undefined, 35 | request: undefined, 36 | }; 37 | }; 38 | 39 | export default getAthleteSuggestions; 40 | -------------------------------------------------------------------------------- /app/containers/Rankings/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | const selectDomain = (state: ApplicationRootState) => { 6 | return state.rankings ? state.rankings : initialState; 7 | }; 8 | 9 | export const selectCategories = () => 10 | createSelector(selectDomain, substate => { 11 | return substate.categories; 12 | }); 13 | 14 | export const selectAthleteFilter = () => 15 | createSelector(selectDomain, substate => { 16 | return substate.athleteFilter; 17 | }); 18 | export const selectCountryFilter = () => 19 | createSelector(selectDomain, substate => { 20 | return substate.countryFilter; 21 | }); 22 | export const selectTableResult = () => 23 | createSelector(selectDomain, substate => { 24 | return substate.tableResult; 25 | }); 26 | 27 | export const selectIsTableItemsLoading = () => 28 | createSelector(selectDomain, substate => { 29 | return substate.isTableItemsLoading; 30 | }); 31 | 32 | export const selectIsNextTableItemsLoading = () => 33 | createSelector(selectDomain, substate => { 34 | return substate.isNextTableItemsLoading; 35 | }); 36 | -------------------------------------------------------------------------------- /app/components/AutosuggestWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styles/styled-components'; 2 | 3 | const classNames: any = { 4 | container: 'react-autosuggest__container', 5 | input: 'react-autosuggest__input', 6 | input_focused: 'react-autosuggest__input--focused', 7 | input_open: '.react-autosuggest__input--open', 8 | container_open: 'react-autosuggest__suggestions-container--open', 9 | suggestion_list: 'react-autosuggest__suggestions-list', 10 | suggestion: 'react-autosuggest__suggestion', 11 | section_container_first: 'react-autosuggest__section-container--first', 12 | suggestion_highlighted: 'react-autosuggest__suggestion--highlighted', 13 | }; 14 | 15 | export const AutosuggestChildWrapperDiv = styled.div.attrs(classNames)` 16 | display: flex; 17 | align-items: center; 18 | 19 | .${classNames.suggestion_list} { 20 | width: 100%; 21 | margin: 0; 22 | padding: 0; 23 | list-style-type: none; 24 | } 25 | .${classNames.suggestion} { 26 | cursor: pointer; 27 | padding: 10px 20px; 28 | color: ${props => props.theme.textPrimary}; 29 | } 30 | .${classNames.suggestion_highlighted} { 31 | background-color: ${props => props.theme.divider}; 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /app/api/rankings/categories.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/categories_mock'; 5 | import { UISelectOption } from 'types/application'; 6 | import { CategoryItem } from 'api/types'; 7 | 8 | export interface APIRankingCategoriesResponse { 9 | readonly items: CategoryItem[]; 10 | } 11 | 12 | export interface APIRankingCategoriesRequest { 13 | selectedCategories?: number[]; 14 | } 15 | 16 | const requestURL = 'api/rankings/categories'; 17 | export async function getRankingCategories(request: APIRankingCategoriesRequest): Promise< 18 | APIRankingCategoriesResponse 19 | > { 20 | const body = request; 21 | return axios 22 | .post(requestURL, body, axiosConfig(dummyResponse, 1000, false)) 23 | .then(resp => { 24 | const result = resp.data as APIRankingCategoriesResponse; 25 | return result; 26 | }); 27 | } 28 | 29 | const dummyResponse = (): AxiosResponse => { 30 | return { 31 | data: mockResponse(), 32 | status: 200, 33 | statusText: '', 34 | config: axios.defaults, 35 | headers: undefined, 36 | request: undefined, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /app/containers/TopBarTabs/Tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import zIndex from 'styles/zIndex'; 3 | import { Tabs } from '@material-ui/core'; 4 | import styled from 'styles/styled-components'; 5 | import AppConstants from 'styles/AppConstants'; 6 | import breakpoints from 'styles/breakpoints'; 7 | import media from 'styles/media'; 8 | import { TabsProps } from '@material-ui/core/Tabs/Tabs'; 9 | import { hideScrollBar } from 'styles/mixins'; 10 | 11 | const tabs = (props: TabsProps) => ( 12 | 13 | ); 14 | 15 | const StyledTabs = styled(tabs)` 16 | && { 17 | display: flex; 18 | align-items: center; 19 | z-index: ${zIndex('TopBarTabs')}; 20 | justify-content: flex-start; 21 | overflow-x: scroll; 22 | ${hideScrollBar}; 23 | 24 | & .flexContainer { 25 | height: ${AppConstants.TopBarHeight(breakpoints.mobile)}px; 26 | 27 | ${media.tablet` 28 | height: ${AppConstants.TopBarHeight(breakpoints.tablet)}px; 29 | 30 | `}; 31 | 32 | ${media.desktop` 33 | height: ${AppConstants.TopBarHeight(breakpoints.desktop)}px; 34 | `}; 35 | } 36 | } 37 | `; 38 | 39 | export default StyledTabs; 40 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/Option/ToggleSwitchSelectOption.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import DisciplineIcon from 'components/Icons/categories/DisciplineIcon'; 4 | import WorldIcon from 'components/Icons/categories/WorldIcon'; 5 | interface Props { 6 | title: string; 7 | isSelected: boolean; 8 | selectedValue: string; 9 | } 10 | 11 | class ToggleSwitchSelectOption extends React.PureComponent { 12 | public render() { 13 | return ( 14 | 15 | 16 | {this.props.title} 17 | 18 | ); 19 | } 20 | } 21 | interface WrapperProps { 22 | disabled: boolean; 23 | } 24 | const Wrapper = styled('div')` 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | justify-content: center; 29 | margin: 8px; 30 | opacity: ${props => (props.disabled ? 0.5 : 1)}; 31 | 32 | & span { 33 | font-size: 1em; 34 | margin-top: 2px; 35 | color: ${props => props.theme.textPrimary}; 36 | text-align: center; 37 | } 38 | `; 39 | export default ToggleSwitchSelectOption; 40 | -------------------------------------------------------------------------------- /server/middlewares/addDevMiddlewares.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const webpackDevMiddleware = require('webpack-dev-middleware'); 4 | const webpackHotMiddleware = require('webpack-hot-middleware'); 5 | 6 | function createWebpackMiddleware(compiler, publicPath) { 7 | return webpackDevMiddleware(compiler, { 8 | logLevel: 'warn', 9 | publicPath, 10 | silent: true, 11 | stats: 'errors-only', 12 | }); 13 | } 14 | 15 | module.exports = function addDevMiddlewares(app, webpackConfig) { 16 | const compiler = webpack(webpackConfig); 17 | const middleware = createWebpackMiddleware( 18 | compiler, 19 | webpackConfig.output.publicPath, 20 | ); 21 | 22 | app.use(middleware); 23 | app.use(webpackHotMiddleware(compiler)); 24 | 25 | // Since webpackDevMiddleware uses memory-fs internally to store build 26 | // artifacts, we use it instead 27 | const fs = middleware.fileSystem; 28 | 29 | app.get('*', (req, res) => { 30 | fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => { 31 | if (err) { 32 | res.sendStatus(404); 33 | } else { 34 | res.send(file.toString()); 35 | } 36 | }); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /app/api/rankings/results.ts: -------------------------------------------------------------------------------- 1 | import axios, { axiosConfig } from 'api/axios'; 2 | import { AxiosResponse } from 'axios'; 3 | 4 | import mockResponse from './__mocks__/rankings_mock'; 5 | 6 | export interface APIGetRankingsRequest { 7 | readonly selectedCategories?: number[]; 8 | readonly athleteId?: string; 9 | readonly country?: string; 10 | readonly next?: string; 11 | } 12 | 13 | export interface APIRankingsResponse { 14 | readonly items: RankingsItem[]; 15 | readonly next: any; 16 | } 17 | 18 | interface RankingsItem { 19 | readonly id: string; 20 | readonly rank: number; 21 | readonly name: string; 22 | readonly surname: string; 23 | readonly age: number; 24 | readonly country: string; 25 | readonly points: string; 26 | readonly thumbnailUrl: string; 27 | readonly contestCount?: number; 28 | } 29 | 30 | const requestURL = 'api/rankings/list'; 31 | export async function getRankingResults( 32 | request: APIGetRankingsRequest, 33 | ): Promise { 34 | const body = request; 35 | return axios 36 | .post(requestURL, body, axiosConfig(undefined, 1000, false)) 37 | .then(resp => { 38 | const result = resp.data as APIRankingsResponse; 39 | return result; 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /app/containers/AdminAthlete/inputs/GenderInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styles/styled-components'; 3 | import { FieldProps } from 'formik'; 4 | import { AthleteFormValues } from '../types'; 5 | import { isNil } from 'lodash'; 6 | import Wrapper from './Wrapper'; 7 | import ErrorLabel from './ErrorLabel'; 8 | interface Props {} 9 | 10 | class GenderInput extends React.PureComponent< 11 | Props & FieldProps 12 | > { 13 | public render() { 14 | const field = this.props.field; 15 | const { touched, errors } = this.props.form; 16 | const isError = touched[field.name] && !isNil(errors[field.name]); 17 | return ( 18 | 19 |
20 | Gender: 21 | 26 |
27 | {isError && {errors[field.name]}} 28 |
29 | ); 30 | } 31 | } 32 | 33 | const Span = styled.span` 34 | color: ${props => props.theme.textPrimary}; 35 | `; 36 | 37 | export default GenderInput; 38 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/Wrapper.ts: -------------------------------------------------------------------------------- 1 | import AppConstants from 'styles/AppConstants'; 2 | import styled from 'styles/styled-components'; 3 | import breakpoints from 'styles/breakpoints'; 4 | import zIndex from 'styles/zIndex'; 5 | import media from 'styles/media'; 6 | 7 | const padding = (size: number) => AppConstants.LeftPadding(size); 8 | 9 | const Wrapper = styled.div` 10 | background-color: ${props => props.theme.componentBackground}; 11 | border-bottom: 1px solid ${props => props.theme.border}; 12 | height: ${AppConstants.TopBarHeight(breakpoints.mobile)}px; 13 | /* padding-bottom: 2px; */ 14 | display: flex; 15 | align-items: center; 16 | /* top: 0; */ 17 | /* width: 100%; */ 18 | z-index: ${zIndex('TopBarTabs')}; 19 | justify-content: flex-start; 20 | overflow-y: scroll; 21 | 22 | ${media.tablet` 23 | height: ${AppConstants.TopBarHeight(breakpoints.tablet)}px; 24 | padding-left: ${padding(breakpoints.tablet)}px; 25 | padding-right: ${padding(breakpoints.tablet)}px; 26 | align-items: center; 27 | `}; 28 | 29 | ${media.desktop` 30 | padding-left: ${padding(breakpoints.desktop)}px; 31 | padding-right: ${padding(breakpoints.desktop)}px; 32 | `}; 33 | `; 34 | 35 | export default Wrapper; 36 | -------------------------------------------------------------------------------- /app/components/CategoriesFilters/Categories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Section from './CategoryFilterButton/Section'; 3 | import styled from 'styles/styled-components'; 4 | import { VerticalDivider } from 'components/Divider'; 5 | import CategorySelect from './CategorySelect'; 6 | import { ICategory } from './types'; 7 | import { SmallLoading } from 'components/Loading'; 8 | 9 | export interface CategoryProps { 10 | categories: ICategory[]; 11 | } 12 | 13 | interface Props extends CategoryProps {} 14 | 15 | class Categories extends React.PureComponent { 16 | public render() { 17 | const categories = this.props.categories.filter(c => c.title !== 'World'); 18 | 19 | return ( 20 | 21 | {/*
*/} 22 | {/* */} 23 | {categories.length > 0 ? ( 24 | categories.map(category => ( 25 | 26 | )) 27 | ) : ( 28 | 29 | )} 30 | 31 | ); 32 | } 33 | } 34 | 35 | const Wrapper = styled.div` 36 | display: flex; 37 | justify-content: flex-start; 38 | align-self: auto; 39 | `; 40 | 41 | export default Categories; 42 | -------------------------------------------------------------------------------- /app/containers/AdminContest/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { ApplicationRootState } from 'types'; 3 | import { initialState } from './reducer'; 4 | 5 | const selectAdminContestDomain = (state: ApplicationRootState) => { 6 | return state.adminContest ? state.adminContest : initialState; 7 | }; 8 | 9 | export const selectContestFilter = () => 10 | createSelector(selectAdminContestDomain, substate => { 11 | return substate.contestFilter; 12 | }); 13 | 14 | export const selectContest = () => 15 | createSelector(selectAdminContestDomain, substate => { 16 | return substate.contest; 17 | }); 18 | 19 | export const selectCountryFilter = () => 20 | createSelector(selectAdminContestDomain, substate => { 21 | return substate.countryFilter; 22 | }); 23 | 24 | export const selectDisciplines = () => 25 | createSelector(selectAdminContestDomain, substate => { 26 | return substate.disciplines; 27 | }); 28 | 29 | export const selectContestTypes = () => 30 | createSelector(selectAdminContestDomain, substate => { 31 | return substate.contestTypes; 32 | }); 33 | export const selectContestGenders = () => 34 | createSelector(selectAdminContestDomain, substate => { 35 | return substate.contestGenders; 36 | }); 37 | -------------------------------------------------------------------------------- /app/containers/AdminTopBarTabs/reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import ActionTypes from './constants'; 4 | import { ContainerState, ContainerActions } from './types'; 5 | import { LOCATION_CHANGE } from 'connected-react-router'; 6 | 7 | export const initialState: ContainerState = { 8 | selectedId: 'athlete', 9 | }; 10 | 11 | export function findPathAndId(pathname: string) { 12 | if (pathname) { 13 | const [{}, path, id, ...rest] = pathname.split('/'); 14 | if (rest.length > 0) { 15 | return { path: path, id: null }; 16 | } 17 | return { path: path, id: id }; 18 | } 19 | return { path: null, id: null }; 20 | } 21 | 22 | export default combineReducers({ 23 | selectedId: (state = initialState.selectedId, action) => { 24 | switch (action.type) { 25 | case LOCATION_CHANGE: 26 | const { path, id } = findPathAndId(action.payload.location.pathname); 27 | if (id && path != null) { 28 | return id; 29 | } else if (path) { 30 | return ''; 31 | } 32 | return state; 33 | case ActionTypes.CHANGE_TOPBAR_INDEX: 34 | return action.payload; 35 | default: 36 | return state; 37 | } 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /server/logger.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const chalk = require('chalk'); 4 | const ip = require('ip'); 5 | 6 | const divider = chalk.gray('\n-----------------------------------'); 7 | 8 | /** 9 | * Logger middleware, you can customize it to make messages more personal 10 | */ 11 | const logger = { 12 | // Called whenever there's an error on the server we want to print 13 | error: err => { 14 | console.error(chalk.red(err)); 15 | }, 16 | 17 | // Called when express.js app starts on given port w/o errors 18 | appStarted: (port, host, tunnelStarted) => { 19 | console.log(`Server started ! ${chalk.green('✓')}`); 20 | 21 | // If the tunnel started, log that and the URL it's available at 22 | if (tunnelStarted) { 23 | console.log(`Tunnel initialised ${chalk.green('✓')}`); 24 | } 25 | 26 | console.log(` 27 | ${chalk.bold('Access URLs:')}${divider} 28 | Localhost: ${chalk.magenta(`http://${host}:${port}`)} 29 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) + 30 | (tunnelStarted 31 | ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` 32 | : '')}${divider} 33 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)} 34 | `); 35 | }, 36 | }; 37 | 38 | module.exports = logger; 39 | -------------------------------------------------------------------------------- /app/containers/NotFoundPage/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import TabPanel from 'components/TabPanel'; 4 | import Footer from 'components/Footer'; 5 | import styled from 'styles/styled-components'; 6 | 7 | export default class NotFound extends React.PureComponent { 8 | public render() { 9 | return ( 10 | 11 | 12 |
OOPS!
13 |
PAGE NOT FOUND
14 |
15 |