= ({
11 | text,
12 | label,
13 | }) => {
14 | return (
15 |
16 | {label} : {text}
17 |
18 | );
19 | };
20 |
21 | export default SingleHackerField;
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "rulesDirectory": ["tslint-plugin-prettier"],
4 | "rules": {
5 | "prettier": true,
6 | "interface-name": true,
7 | "object-literal-sort-keys": false,
8 | "no-console": false,
9 | "object-literal-shorthand": true
10 | },
11 | "linterOptions": {
12 | "exclude": [
13 | "config/**/*.js",
14 | "node_modules/**/*.ts",
15 | "coverage/lcov-report/*.js"
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/shared/Elements/Image.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export interface IImageProps {
4 | src: any;
5 | imgHeight?: string;
6 | imgWidth?: string;
7 | padding?: string;
8 | }
9 |
10 | export const Image = styled.img`
11 | src: ${(props: IImageProps) => props.src};
12 | height: ${(props: IImageProps) => props.imgHeight || 'auto'};
13 | width: ${(props: IImageProps) => props.imgWidth || 'auto'};
14 | padding: ${(props: IImageProps) => props.padding || 0};
15 | `;
16 | export default Image;
17 |
--------------------------------------------------------------------------------
/src/shared/HOC/withToaster.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { ToastContainer } from 'react-toastify';
3 |
4 | const WithToaster = (Component: React.ComponentType
) =>
5 | class extends React.Component
{
6 | public render() {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | }
14 | };
15 |
16 | export default WithToaster;
17 |
--------------------------------------------------------------------------------
/src/pages/Travel/TravelStatusNone.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { ITravel } from '../../config';
4 |
5 | interface ITravelStatusProps {
6 | travel: ITravel;
7 | }
8 |
9 | const TravelStatusNone: React.FunctionComponent = ({
10 | travel,
11 | }) => {
12 | return (
13 |
14 | Your request to recieve ${travel.request.toFixed(2)} in reimbursement for
15 | travel is still being processed.
16 |
17 | );
18 | };
19 |
20 | export default TravelStatusNone;
21 |
--------------------------------------------------------------------------------
/src/config/gradYears.ts:
--------------------------------------------------------------------------------
1 | // const GradYearList = [2020, 2021, 2022, 2023, 2024, 2025, 2026];
2 | const presentDate = new Date();
3 | const presentYear = presentDate.getFullYear();
4 | const GradYearList = [
5 | presentYear,
6 | presentYear + 1,
7 | presentYear + 2,
8 | presentYear + 3,
9 | presentYear + 4,
10 | presentYear + 5,
11 | presentYear + 6,
12 | presentYear + 7,
13 | presentYear + 8,
14 | presentYear + 9,
15 | presentYear + 10,
16 | ];
17 | export const GradYears = GradYearList.map((v) => ({ label: v, value: v }));
18 |
--------------------------------------------------------------------------------
/src/shared/Form/SubmitBtn.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex } from '@rebass/grid';
2 | import * as React from 'react';
3 |
4 | import { Button, ButtonVariant, IButtonProps } from '../Elements';
5 |
6 | export const SubmitBtn: React.FC<
7 | IButtonProps & React.ButtonHTMLAttributes
8 | > = (props) => (
9 |
10 |
11 |
12 | {props.children}
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/src/features/Onboarding/Content.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Content = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | max-width: 1200px;
7 | margin: auto;
8 | @media only screen and (max-width: 1345px) {
9 | max-width: 1000px;
10 | }
11 | @media only screen and (max-width: 1118px) {
12 | flex-direction: column;
13 | justify-content: center;
14 | padding-left: 30px;
15 | padding-right: 30px;
16 | padding-top: 40px;
17 | }
18 | `;
19 |
20 | export default Content;
21 |
--------------------------------------------------------------------------------
/src/pages/Travel/TravelStatusClaimed.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { ITravel } from '../../config';
4 | import DollarAmount from './DollarAmount';
5 |
6 | interface ITravelStatusProps {
7 | travel: ITravel;
8 | }
9 |
10 | const TravelStatusClaimed: React.FunctionComponent = ({
11 | travel,
12 | }) => {
13 | return (
14 |
15 | We reimbursed you for which you
16 | have already claimed.
17 |
18 | );
19 | };
20 |
21 | export default TravelStatusClaimed;
22 |
--------------------------------------------------------------------------------
/src/features/Onboarding/Text.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Text = styled.div`
4 | margin-bottom: 1rem;
5 | padding: 26px;
6 | h2 {
7 | color: #f2463a;
8 | font-size: 2rem;
9 | font-weight: 400;
10 | margin-bottom: 0.22rem;
11 |
12 | @media only screen and (min-width: ${(props) => props.theme.screens.smUp}) {
13 | font-size: 3rem;
14 | }
15 | }
16 | b {
17 | color: #f2463a;
18 | }
19 | a {
20 | :hover {
21 | color: #f2463a;
22 | }
23 | }
24 | `;
25 |
26 | export default Text;
27 |
--------------------------------------------------------------------------------
/src/shared/Elements/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BackgroundImage';
2 | export * from './Button';
3 | export * from './Card';
4 | export * from './ConfirmModal';
5 | export * from './H1';
6 | export * from './H2';
7 | export * from './Image';
8 | export * from './LeftContainer';
9 | export * from './MaxWidthBox';
10 | export * from './Paragraph';
11 | export * from './StyledModal';
12 | export * from './StyledModalSmall';
13 | export * from './Table';
14 | export * from './Textarea';
15 | export * from './ViewPDF';
16 | export * from './LinkDuo';
17 | export * from './HorizontalSpacer';
18 |
--------------------------------------------------------------------------------
/src/assets/images/fb-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/features/Login/SignUpLink.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FrontendRoute } from '../../config';
3 | import { LinkDuo } from '../../shared/Elements';
4 | import theme from '../../shared/Styles/theme';
5 |
6 | const SignUpLink: React.FC<{}> = (props) => {
7 | return (
8 |
9 | Don't have an account?
10 |
11 | Sign up
12 |
13 |
14 | );
15 | };
16 |
17 | export default SignUpLink;
18 |
--------------------------------------------------------------------------------
/src/config/validationError.ts:
--------------------------------------------------------------------------------
1 | export interface IValidationError {
2 | status: number;
3 | message: string;
4 | data: {
5 | [id: string]: IValidationErrorItem;
6 | };
7 | }
8 |
9 | export interface IValidationErrorItem {
10 | location: string;
11 | param: string;
12 | msg: string;
13 | }
14 |
15 | export function instanceOfIValidationErrorItem(
16 | object: any
17 | ): object is IValidationErrorItem {
18 | if (typeof object === 'object') {
19 | return 'location' in object && 'param' in object && 'msg' in object;
20 | } else {
21 | return false;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/pages/Application/Edit.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet from 'react-helmet';
3 |
4 | import ManageApplicationForm, {
5 | ManageApplicationModes,
6 | } from '../../features/Application/ManageApplicationForm';
7 |
8 | import * as CONSTANTS from '../../config/constants';
9 |
10 | const EditApplicationPage: React.FC = () => (
11 | <>
12 |
13 | Edit Application | {CONSTANTS.HACKATHON_NAME}
14 |
15 |
16 |
17 | >
18 | );
19 |
20 | export default EditApplicationPage;
21 |
--------------------------------------------------------------------------------
/src/shared/Elements/HorizontalSpacer.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface IHorizontalSpacerProps {
4 | paddingLeft?: string;
5 | paddingRight?: string;
6 | }
7 |
8 | // A div with with some horizontal padding
9 | // ---
10 | // Example use case: To indent forms so that they can be centered in space not
11 | // filled by sidebar
12 | export const HorizontalSpacer = styled.div`
13 | padding-left: ${(props) => props.paddingLeft || '0'};
14 | padding-right: ${(props) => props.paddingRight || '0'};
15 | `;
16 |
17 | export default HorizontalSpacer;
18 |
--------------------------------------------------------------------------------
/src/features/Account/AlreadyHaveAccount.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FrontendRoute } from '../../config';
3 | import { LinkDuo } from '../../shared/Elements';
4 | import theme from '../../shared/Styles/theme';
5 |
6 | const AlreadyHaveAccount: React.FC<{}> = (props) => {
7 | return (
8 |
9 | Already have an account?
10 |
11 | Sign in
12 |
13 |
14 | );
15 | };
16 |
17 | export default AlreadyHaveAccount;
18 |
--------------------------------------------------------------------------------
/src/shared/Form/Label.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface ILabelProps {
4 | width?: string;
5 | fontWeight?: string;
6 | }
7 |
8 | export const Label = styled.label`
9 | font-family: ${(props) => props.theme.fonts.header};
10 | font-weight: ${(props) => props.fontWeight || '400'};
11 | color: ${(props) => props.theme.colors.black80};
12 | display: block;
13 | width: ${(props) => props.width || '100%'};
14 |
15 | p {
16 | font-size: 14px;
17 | color: ${(props) => props.theme.colors.black60};
18 | }
19 | `;
20 |
21 | export default Label;
22 |
--------------------------------------------------------------------------------
/src/pages/Application/Create.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet from 'react-helmet';
3 |
4 | import ManageApplicationForm, {
5 | ManageApplicationModes,
6 | } from '../../features/Application/ManageApplicationForm';
7 |
8 | import * as CONSTANTS from '../../config/constants';
9 |
10 | const CreateApplicationPage: React.FC = () => (
11 | <>
12 |
13 | Create Application | {CONSTANTS.HACKATHON_NAME}
14 |
15 |
16 |
17 | >
18 | );
19 |
20 | export default CreateApplicationPage;
21 |
--------------------------------------------------------------------------------
/src/features/SingleHacker/SingleHackerParagraph.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { Box } from '@rebass/grid';
4 | import { Paragraph } from '../../shared/Elements';
5 |
6 | interface IParagraphProps {
7 | text: string | number | undefined;
8 | label: string;
9 | }
10 |
11 | const SingleHackerParagraph: React.FunctionComponent = ({
12 | text,
13 | label,
14 | }) => {
15 | return (
16 |
17 | {label} {' '}
18 | {text || 'N/A'}
19 |
20 | );
21 | };
22 |
23 | export default SingleHackerParagraph;
24 |
--------------------------------------------------------------------------------
/src/features/Team/MemberList/MemberList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { IMemberName } from '../../../config';
4 | import MemberItem from './MemberItem';
5 |
6 | interface IMemberListProps {
7 | members: IMemberName[];
8 | }
9 |
10 | const MemberList: React.FC = (props) => (
11 |
12 | {props.members.map((member, index) => (
13 |
14 | ))}
15 |
16 |
22 |
23 | );
24 |
25 | export default MemberList;
26 |
--------------------------------------------------------------------------------
/src/features/Nav/LoginButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { FrontendRoute } from '../../config';
3 | import { LinkDuo } from '../../shared/Elements';
4 | import Button, { ButtonVariant } from '../../shared/Elements/Button';
5 |
6 | const LoginBtn: React.FunctionComponent = () => {
7 | return (
8 |
15 |
16 | Sign in
17 |
18 |
19 | );
20 | };
21 |
22 | export default LoginBtn;
23 |
--------------------------------------------------------------------------------
/src/shared/HOC/withThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { ThemeProvider } from 'styled-components';
3 | import GlobalStyles from '../Styles/GlobalStyles';
4 | import theme from '../Styles/theme';
5 |
6 | const withThemeProvider = (Component: React.ComponentType
) =>
7 | class extends React.Component
{
8 | public render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 | };
19 |
20 | export default withThemeProvider;
21 |
--------------------------------------------------------------------------------
/src/features/Nav/NavLink.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const NavLink = styled.a`
4 | font-family: 'Brown';
5 | cursor: pointer;
6 | color: ${(props) => props.theme.colors.black60};
7 | text-decoration: none;
8 |
9 | &:focus,
10 | &:hover,
11 | &:active {
12 | color: ${(props) => props.theme.colors.red};
13 | background: transparent;
14 | }
15 |
16 | @media only screen and (min-width: ${(props) => props.theme.screens.smUp}) {
17 | margin-right: 2rem;
18 | margin-top: 1px;
19 | }
20 |
21 | &.active {
22 | color: ${(props) => props.theme.colors.red};
23 | }
24 | `;
25 |
26 | export default NavLink;
27 |
--------------------------------------------------------------------------------
/src/features/SingleHacker/SingleHackerSection.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { H2 } from '../../shared/Elements';
3 | import theme from '../../shared/Styles/theme';
4 |
5 | interface IProps {
6 | title: string;
7 | hidden?: boolean;
8 | children?: React.ReactNode;
9 | }
10 |
11 | const SingleHackerSection: React.FunctionComponent = ({
12 | title,
13 | children,
14 | hidden,
15 | }) => {
16 | return hidden ? (
17 |
18 | ) : (
19 |
20 |
21 | {title}
22 | {children}
23 |
24 | );
25 | };
26 |
27 | export default SingleHackerSection;
28 |
--------------------------------------------------------------------------------
/src/shared/Form/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Autosuggest';
2 | export * from './Checkbox';
3 | export * from './EmailInput';
4 | export * from './FileInput';
5 | export * from './FileUpload';
6 | export * from './Form';
7 | export * from './Input';
8 | export * from './Label';
9 | export * from './LabelText';
10 | export * from './NumberFormatInput';
11 | export * from './PasswordInput';
12 | export * from './RequiredInputLabel';
13 | export * from './SecondaryInfoText';
14 | export * from './StyledCreatableSelect';
15 | export * from './StyledNumberFormat';
16 | export * from './StyledSelect';
17 | export * from './SubmitBtn';
18 | export * from './validationErrorGenerator';
19 |
--------------------------------------------------------------------------------
/src/features/HackPass/Pass.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { IAccount, IHacker } from '../../config';
4 |
5 | interface IPassProps {
6 | account: IAccount;
7 | hacker: IHacker;
8 | qrData: string;
9 | }
10 |
11 | export const Pass: React.FunctionComponent = (
12 | props: IPassProps
13 | ) => {
14 | return (
15 |
16 |
17 |
18 |
{props.account.firstName}
19 | {props.account.pronoun}
20 | {props.hacker.application.general.school}
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/shared/HOC/withBackground.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import theme from '../../shared/Styles/theme';
4 |
5 | const withBackground = (Component: React.ComponentType
) => {
6 | return (props: P) => {
7 | return (
8 |
9 |
10 |
11 | );
12 | };
13 | };
14 |
15 | const styles = {
16 | background: {
17 | width: '100%',
18 | height: '100%',
19 | minHeight: '100vh',
20 | background: `linear-gradient(to bottom, ${theme.colors.white}, ${theme.colors.black5} 100%)`,
21 | zIndex: -1001,
22 | },
23 | };
24 |
25 | export default withBackground;
26 |
--------------------------------------------------------------------------------
/src/config/degrees.ts:
--------------------------------------------------------------------------------
1 | export enum Degrees {
2 | LESS_THAN_SECONDARY = 'Less than Secondary / High School',
3 | SECONDARY = 'Secondary / High School',
4 | CEGEP = 'CEGEP',
5 | COLLEGE = 'Undergraduate University (2 year - community college or similar)',
6 | UNDERGRADUATE = 'Undergraduate University (3+ year)',
7 | GRADUATE = 'Graduate University (Masters, Professional, Doctoral, etc)',
8 | CODE_SCHOOL = 'Code School / Bootcamp',
9 | VOCATIONAL = 'Other Vocational / Trade Program or Apprenticeship',
10 | POSTDOC = 'Post Doctorate',
11 | OTHER = 'Other',
12 | NOT_STUDENT = "I'm not currently a student",
13 | NO_ANSWER = 'Prefer not to answer',
14 | }
15 | export default Degrees;
16 |
--------------------------------------------------------------------------------
/src/features/Nav/Nav.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface INavProps {
4 | hasBorder: boolean;
5 | }
6 |
7 | export const Nav = styled.nav`
8 | z-index: 10;
9 | height: 90px;
10 | position: sticky;
11 | top: 0;
12 | left: 0;
13 | right: 0;
14 | width: 100%;
15 | display: flex;
16 | justify-content: space-between;
17 | background: ${(props) =>
18 | props.hasBorder ? props.theme.colors.white : 'transparent'};
19 | border-bottom: 2px solid transparent;
20 | border-color: ${(props) =>
21 | props.hasBorder ? props.theme.colors.black5 : 'transparent'};
22 | transition: 0.25s border-color ease-in;
23 | `;
24 |
25 | export default Nav;
26 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Chrome",
9 | "type": "chrome",
10 | "request": "launch",
11 | "url": "http://localhost:1337",
12 | "webRoot": "${workspaceRoot}/src",
13 | "userDataDir": "${workspaceRoot}/.vscode/chrome",
14 | "sourceMapPathOverrides": {
15 | "webpack:///src/*": "${webRoot}/*"
16 | }
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/src/api/search.ts:
--------------------------------------------------------------------------------
1 | import { AxiosPromise } from 'axios';
2 | import { APIRoute, ISearchOptions, ISearchParameter } from '../config';
3 | import API from './api';
4 | import APIResponse from './APIResponse';
5 | class SearchAPI {
6 | constructor() {
7 | API.createEntity(APIRoute.SEARCH);
8 | }
9 | public search(
10 | model: string,
11 | parameters: ISearchParameter[],
12 | searchOptions: ISearchOptions
13 | ): AxiosPromise> {
14 | return API.getEndpoint(APIRoute.SEARCH).getAll({
15 | params: {
16 | q: JSON.stringify(parameters),
17 | model,
18 | ...searchOptions,
19 | },
20 | });
21 | }
22 | }
23 | export const Search = new SearchAPI();
24 | export default Search;
25 |
--------------------------------------------------------------------------------
/src/assets/images/passwordReset.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "allowJs": true,
5 | "skipLibCheck": false,
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "module": "esnext",
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "noEmit": true,
14 | "jsx": "preserve",
15 | "lib": [
16 | "es2015",
17 | "dom"
18 | ],
19 | "alwaysStrict": false,
20 | "strictFunctionTypes": false,
21 | "strictPropertyInitialization": false,
22 | "strict": true,
23 | "noImplicitReturns": true,
24 | "noUnusedLocals": false,
25 | },
26 | "include": [
27 | "src"
28 | ],
29 | }
30 |
--------------------------------------------------------------------------------
/src/assets/images/upload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/features/Onboarding/NavLink.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const NavLink = styled.a`
4 | font-family: 'Brown';
5 | cursor: pointer;
6 | color: ${(props) => props.theme.colors.black60};
7 | text-decoration: none;
8 | font-size: 0.8rem;
9 |
10 | &:focus,
11 | &:hover,
12 | &:active {
13 | color: ${(props) => props.theme.colors.red};
14 | background: transparent;
15 | }
16 |
17 | @media only screen and (min-width: ${(props) => props.theme.screens.smUp}) {
18 | margin-right: 2rem;
19 | margin-top: 1px;
20 | font-size: 1rem;
21 | }
22 |
23 | &.active {
24 | border-bottom: 2px solid ${(props) => props.theme.colors.red};
25 | color: ${(props) => props.theme.colors.red};
26 | }
27 | `;
28 |
29 | export default NavLink;
30 |
--------------------------------------------------------------------------------
/src/shared/Elements/GridTwoColumn.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export interface IGridTwoColumnProps {
4 | columnWidth?: string;
5 | columnGap?: string;
6 | rowGap?: string;
7 | margin?: string;
8 | }
9 |
10 | export const GridTwoColumn = styled.div`
11 | display: grid;
12 | grid-template-columns: repeat(
13 | auto-fill,
14 | minmax(
15 | min(
16 | 80vw,
17 | ${(props: IGridTwoColumnProps) => props.columnWidth || '400px'}
18 | ),
19 | 1fr
20 | )
21 | );
22 | column-gap: ${(props: IGridTwoColumnProps) => props.columnGap || '80px'};
23 | row-gap: ${(props: IGridTwoColumnProps) => props.rowGap || '20px'};
24 | margin: ${(props: IGridTwoColumnProps) => props.margin || '0 0 60px 0'};
25 | `;
26 |
27 | export default GridTwoColumn;
28 |
--------------------------------------------------------------------------------
/src/assets/images/sidebar-profile.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/pages/Travel/TravelStatusPolicy.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { ITravel, TRAVEL_POLICY } from '../../config';
4 | import { Button, LinkDuo } from '../../shared/Elements';
5 |
6 | interface ITravelStatusProps {
7 | travel: ITravel;
8 | }
9 |
10 | const TravelStatusPolicy: React.FunctionComponent = () => {
11 | return (
12 |
13 | Your travel reimbursement decision has been released. In order to see how
14 | much you will be reimbursed, you must first agree to our{' '}
15 |
travel policy .
16 |
17 | I agree to McHacks Travel Policy
18 |
19 |
20 | );
21 | };
22 |
23 | export default TravelStatusPolicy;
24 |
--------------------------------------------------------------------------------
/src/assets/images/sidebar-bus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/shared/Elements/H2.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface IH2Props {
4 | color?: string;
5 | fontSize?: string;
6 | fontWeight?: string;
7 | textAlign?: string;
8 | marginLeft?: string;
9 | marginTop?: string;
10 | marginBottom?: string;
11 | display?: string;
12 | }
13 |
14 | export const H2 = styled.h2`
15 | font-size: ${(props) => props.fontSize || '24px'};
16 | text-align: ${(props) => props.textAlign || 'left'};
17 | color: ${(props) => props.color || props.theme.colors.red};
18 | font-weight: ${(props) => props.fontWeight || 'normal'};
19 | margin-left: ${(props) => props.marginLeft || 'initial'};
20 | margin-bottom: ${(props) => props.marginBottom || '12px'};
21 | margin-top: ${(props) => props.marginTop || 'initial'};
22 | display: ${(props) => props.display || ''};
23 | `;
24 |
25 | export default H2;
26 |
--------------------------------------------------------------------------------
/.idea/mongoSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/shared/Form/NumberFormatInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NumberFormatValues, PatternFormatProps } from 'react-number-format';
3 | import { Label, LabelText, StyledNumericFormat, StyledPatternFormat } from '.';
4 |
5 | interface ILabelledNumberFormatProp {
6 | value?: string;
7 | onValueChange: (value: NumberFormatValues) => void;
8 | label: string;
9 | placeholder: string;
10 | required?: boolean;
11 | disabled?: boolean;
12 | }
13 | export const NumberFormatInput: React.FunctionComponent<
14 | ILabelledNumberFormatProp & PatternFormatProps
15 | > = (props) => {
16 | return (
17 |
18 |
19 | {props.format ? (
20 |
21 | ) : (
22 |
23 | )}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/shared/HOC/withNavbar.tsx:
--------------------------------------------------------------------------------
1 | import { Flex } from '@rebass/grid';
2 | import * as React from 'react';
3 | import Navbar from '../../features/Nav/Navbar';
4 |
5 | export interface INavbarOptions {
6 | // The active page
7 | activePage: string;
8 | }
9 |
10 | const defaultOptions = {
11 | activePage: 'home',
12 | };
13 |
14 | const withNavbar = (
15 | Component: React.ComponentType
,
16 | options: INavbarOptions = defaultOptions
17 | ) =>
18 | class extends React.Component
{
19 | public render() {
20 | return (
21 |
25 |
26 |
27 |
28 | );
29 | }
30 | };
31 |
32 | export default withNavbar;
33 |
--------------------------------------------------------------------------------
/src/features/Invite/ExistingInvitesTable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IInviteInfo } from '../../config';
3 | import { StyledTable } from '../../shared/Elements';
4 |
5 | interface IExistingInvitesTableProps {
6 | // Optional function that is called when a new invite is sent.
7 | invites: IInviteInfo[];
8 | isLoading: boolean;
9 | }
10 |
11 | export const ExistingInvitesTable: React.FC = (
12 | props
13 | ) => {
14 | const columns = [
15 | {
16 | Header: 'Email',
17 | accessor: 'email',
18 | },
19 | {
20 | Header: 'Account Type',
21 | accessor: 'accountType',
22 | },
23 | ];
24 | return (
25 |
31 | );
32 | };
33 |
34 | export default ExistingInvitesTable;
35 |
--------------------------------------------------------------------------------
/docs/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Tickets:
2 |
3 | - HCK-
4 |
5 | ### List of changes:
6 |
7 | -
8 |
9 | ### Type of change:
10 |
11 | Please delete options that aren't relevant.
12 |
13 | - [ ] Bug fix (non-breaking change which fixes an issue)
14 | - [ ] New feature (non-breaking change which adds functionality)
15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
16 | - [ ] This change requires a documentation update
17 |
18 | ### How did you do this?
19 |
20 | ### How to test:
21 |
22 | ### Questions:
23 |
24 | ### PR Checklist:
25 |
26 | - [ ] Merged `develop` branch (before testing)
27 | - [ ] Linted my code locally
28 | - [ ] Listed change(s) in the Changelog
29 | - [ ] Tested all links in project relevant browsers
30 | - [ ] Tested all links on different screen sizes
31 | - [ ] Referenced all useful info (issues, tasks, etc)
32 |
33 | ### Screenshots:
34 |
--------------------------------------------------------------------------------
/src/features/SingleHacker/SingleHackerLink.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { Box } from '@rebass/grid';
4 |
5 | interface ILinkProps {
6 | link?: string;
7 | linkText?: string;
8 | label: string;
9 | }
10 |
11 | const SingleHackerLink: React.FunctionComponent = ({
12 | link,
13 | linkText,
14 | label,
15 | }) => {
16 | if (link) {
17 | const url = new URL(link);
18 | const target =
19 | ['https:', 'http:'].indexOf(url.protocol) !== -1 ? '_blank' : '';
20 | return (
21 |
22 | {label} :{' '}
23 |
24 | {linkText ? linkText : link}
25 |
26 |
27 | );
28 | } else {
29 | return (
30 |
31 | {label} : None
32 |
33 | );
34 | }
35 | };
36 |
37 | export default SingleHackerLink;
38 |
--------------------------------------------------------------------------------
/src/shared/Elements/MaxWidthBox.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@rebass/grid';
2 | import styled from 'styled-components';
3 |
4 | export interface IMaxWidthBoxProps {
5 | maxWidth?: string;
6 | textAlign?: string;
7 | paddingLeft?: string;
8 | paddingRight?: string;
9 | float?: string;
10 | minWidth?: string;
11 | left?: string;
12 | position?: string;
13 | right?: string;
14 | }
15 |
16 | export const MaxWidthBox = styled(Box)`
17 | max-width: ${(props) => props.maxWidth || '600px'};
18 | text-align: ${(props) => props.textAlign || 'initial'};
19 | padding-left: ${(props) => props.paddingLeft || '0'};
20 | padding-right: ${(props) => props.paddingRight || '0'};
21 | float: ${(props) => props.float || 'none'};
22 | left: ${(props) => props.left || 'auto'};
23 | right: ${(props) => props.right || 'auto'};
24 | position: ${(props) => props.position || 'static'};
25 | `;
26 |
27 | export default MaxWidthBox;
28 |
--------------------------------------------------------------------------------
/src/config/reviewers.ts:
--------------------------------------------------------------------------------
1 | export const reviewers = [
2 | '',
3 | 'Aditi',
4 | 'Amy',
5 | 'Carolyn',
6 | 'Clara',
7 | 'Debo',
8 | 'Deon',
9 | 'Doaa',
10 | 'Emily',
11 | 'Emma',
12 | 'Ethan',
13 | 'Evan',
14 | 'Finnley',
15 | 'Gabriel',
16 | 'Ian',
17 | 'Inaya',
18 | 'Jake',
19 | 'Jamie',
20 | 'Jane J.',
21 | 'Jane K.',
22 | 'Jeffrey',
23 | 'Joshua',
24 | 'Jyothsna',
25 | 'Khyati',
26 | 'Michael',
27 | 'Mika',
28 | 'Mubeen',
29 | 'Mira',
30 | 'Oishika',
31 | 'Olivia',
32 | 'Qi',
33 | 'Rémi',
34 | 'Sebastian',
35 | 'Shirley',
36 | 'Sihan',
37 | 'Siva',
38 | 'Snigdha',
39 | 'Sonia',
40 | 'Stephanie',
41 | 'Tavi',
42 | 'Tina',
43 | 'Vipul',
44 | 'Yue Qian',
45 | ];
46 |
47 | export const reviewerOptions = reviewers.map((reviewer) => ({
48 | label: reviewer,
49 | value: reviewer,
50 | }));
51 |
--------------------------------------------------------------------------------
/src/config/searchParameter.ts:
--------------------------------------------------------------------------------
1 | export enum StringOperations {
2 | EQUALS = 'equals',
3 | NOT_EQUALS = 'ne',
4 | REGEXP = 'regex',
5 | IN = 'in',
6 | }
7 | export enum NumberOperations {
8 | EQUALS = 'equals',
9 | NOT_EQUALS = 'ne',
10 | GREATER_THAN_OR_EQUAL = 'gte',
11 | LESS_THAN_OR_EQUAL = 'lte',
12 | GREATER_THAN = 'gt',
13 | LESS_THAN = 'lt',
14 | IN = 'in',
15 | }
16 | export enum BooleanOperations {
17 | EQUALS = 'equals',
18 | NOT_EQUALS = 'ne',
19 | }
20 | export interface ISearchParameter {
21 | param: string;
22 | operation: StringOperations | NumberOperations | BooleanOperations;
23 | value: string | boolean | number | string[] | number[];
24 | }
25 |
26 | export function isValidSearchParameter(parameter: any) {
27 | if (!parameter) {
28 | return false;
29 | }
30 | const { param, operation, value } = parameter;
31 | if (param && typeof param === 'string' && operation && value) {
32 | return true;
33 | }
34 | return false;
35 | }
36 |
--------------------------------------------------------------------------------
/src/shared/Styles/style.d.ts:
--------------------------------------------------------------------------------
1 | import 'styled-components';
2 |
3 | declare module 'styled-components' {
4 | export interface DefaultTheme {
5 | colors: {
6 | red: string;
7 | redMed: string;
8 | redLight: string;
9 | blue: string;
10 | blueLight: string;
11 | yellow: string;
12 | yellowLight: string;
13 | teal: string;
14 | tealLight: string;
15 | purple: string;
16 | purpleLight: string;
17 | greyLight: string;
18 | white: string;
19 | black: string;
20 | black80: string;
21 | black70: string;
22 | black60: string;
23 | black40: string;
24 | black30: string;
25 | black20: string;
26 | black10: string;
27 | black5: string;
28 | };
29 | fonts: {
30 | header: string;
31 | body: string;
32 | };
33 | inputBorderRadius: string;
34 | screens: {
35 | smUp: string;
36 | mdUp: string;
37 | lgUp: string;
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/shared/Form/FormikElements/Input.tsx:
--------------------------------------------------------------------------------
1 | import { FieldProps } from 'formik';
2 | import React from 'react';
3 | import { Input, Label, LabelText } from '..';
4 |
5 | interface IInputFormikComponentProp {
6 | label: string;
7 | inputType: string;
8 | placeholder?: string;
9 | required?: boolean;
10 | disabled?: boolean;
11 | showOptionalLabel?: boolean;
12 | }
13 | const InputFormikComponent: React.FC<
14 | IInputFormikComponentProp & FieldProps
15 | > = ({
16 | placeholder,
17 | label,
18 | required,
19 | inputType,
20 | field,
21 | disabled,
22 | showOptionalLabel,
23 | }) => {
24 | return (
25 |
26 |
31 |
37 |
38 | );
39 | };
40 |
41 | export { InputFormikComponent as Input };
42 |
--------------------------------------------------------------------------------
/src/shared/HOC/withContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Sponsor } from '../../api';
3 | import { ISponsor } from '../../config';
4 | import NomineeContext from '../../features/Search/Context';
5 |
6 | interface IState {
7 | sponsor?: ISponsor;
8 | }
9 |
10 | const withContext = (Component: React.ComponentType
) =>
11 | class extends React.Component
{
12 | constructor(props: any) {
13 | super(props);
14 | this.state = {
15 | sponsor: undefined,
16 | };
17 | }
18 |
19 | public async componentDidMount() {
20 | try {
21 | const sponsor = (await Sponsor.getSelf()).data.data;
22 | this.setState({ sponsor });
23 | } catch (e) {
24 | return;
25 | }
26 | }
27 |
28 | public render() {
29 | return (
30 |
31 |
32 |
33 | );
34 | }
35 | };
36 |
37 | export default withContext;
38 |
--------------------------------------------------------------------------------
/src/assets/images/sidebar-team.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/shared/Elements/Card.tsx:
--------------------------------------------------------------------------------
1 | import { Flex } from '@rebass/grid';
2 | import styled from 'styled-components';
3 |
4 | interface ICardProps {
5 | disabled?: boolean;
6 | }
7 |
8 | export const Card = styled(Flex)`
9 | height: 300px;
10 | max-width: 250px;
11 | margin: 15px;
12 | background-color: ${(props) => props.theme.colors.black5};
13 | position: relative;
14 | padding: 20px;
15 | box-shadow: 5px 5px 20px ${(props) => props.theme.colors.black30};
16 |
17 | ${(props) =>
18 | props.disabled
19 | ? `
20 | cursor: not-allowed;
21 | filter: grayscale(100%);
22 | &:after {
23 | content: '';
24 | width: 100%;
25 | height: 100%;
26 | position: absolute;
27 | background: rgba(0,0,0,0.1);
28 | top: 0;
29 | left: 0;
30 | }
31 | `
32 | : `
33 | &:hover {
34 | transform: translate(-2px, -2px);
35 | box-shadow: 5px 5px 20px ${props.theme.colors.black60};
36 | transition: 0.1s all ease-in;
37 | }
38 | `}
39 | `;
40 |
41 | export default Card;
42 |
--------------------------------------------------------------------------------
/src/assets/images/twitter-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/config/frontendRoutes.ts:
--------------------------------------------------------------------------------
1 | export enum FrontendRoute {
2 | ADMIN_SEARCH_PAGE = '/admin/search',
3 | CHECKIN_HACKER_PAGE = '/hacker/checkin',
4 | PASS_HACKER_PAGE = '/hacker/pass',
5 | CONFIRM_ACCOUNT_PAGE = '/account/confirm',
6 | CREATE_ACCOUNT_PAGE = '/account/create',
7 | CREATE_APPLICATION_PAGE = '/application/create',
8 | CONFIRM_HACKER_PAGE = '/application/confirm',
9 | VIEW_HACKER_PAGE = '/application/view/:id',
10 | EDIT_ACCOUNT_PAGE = '/account/edit',
11 | EDIT_APPLICATION_PAGE = '/application/edit',
12 | FORGOT_PASSWORD_PAGE = '/password/forgot',
13 | WELL_KNOWN_PASSWORD_CHANGE = '/.well-known/change-password',
14 | INVITE_PAGE = '/invite',
15 | HOME_PAGE = '/',
16 | LOGIN_PAGE = '/login',
17 | RESET_PASSWORD_PAGE = '/password/reset',
18 | SETTINGS_PAGE = '/settings',
19 | SPONSOR_SEARCH_PAGE = '/sponsor/search',
20 | SPONSOR_ONBOARDING_PAGE = '/sponsor/onboarding',
21 | TEAM_PAGE = '/team',
22 | // TRAVEL_PAGE = '/travel',
23 |
24 | CREATE_SPONSOR_PAGE = '/sponsor/create',
25 | EDIT_SPONSOR_PAGE = '/sponsor/edit',
26 | }
27 |
28 | export default FrontendRoute;
29 |
--------------------------------------------------------------------------------
/src/shared/Elements/H1.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface IH1Props {
4 | color?: string;
5 | fontSize?: string;
6 | textAlign?: string;
7 | marginLeft?: string;
8 | marginTop?: string;
9 | marginBottom?: string;
10 | display?: string;
11 | fontWeight?: string;
12 | paddingTop?: string;
13 | paddingBottom?: string;
14 | paddingLeft?: string;
15 | }
16 |
17 | export const H1 = styled.h1`
18 | font-size: ${(props) => props.fontSize || '32px'};
19 | font-faimly: ${(props) => props.theme.fonts.header};
20 | text-align: ${(props) => props.textAlign || 'left'};
21 | color: ${(props) => props.color || props.theme.colors.red};
22 | margin-left: ${(props) => props.marginLeft || '0'};
23 | margin-bottom: ${(props) => props.marginBottom || '64px'};
24 | margin-top: ${(props) => props.marginTop || '0'};
25 | display: ${(props) => props.display || ''};
26 | font-weight: ${(props) => props.fontWeight || 'normal'};
27 | padding-top: ${(props) => props.paddingTop || '0'};
28 | padding-bottom: ${(props) => props.paddingBottom || '0'};
29 | `;
30 |
31 | export default H1;
32 |
--------------------------------------------------------------------------------
/src/assets/images/github-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 HackMcGill
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './APIRoute';
2 | export * from './attendanceOptions';
3 | export * from './authToken';
4 | export * from './constants';
5 | export * from './countries';
6 | export * from './degrees';
7 | export * from './dietaryRestrictions';
8 | export * from './ethnicity';
9 | export * from './frontendRoutes';
10 | export * from './genders';
11 | export * from './gradYears';
12 | export * from './hackerStatus';
13 | export * from './hackerReviewerStatus';
14 | export * from './inviteInfo';
15 | export * from './jobInterests';
16 | export * from './majors';
17 | export * from './pronouns';
18 | export * from './resumeResponse';
19 | export * from './schools';
20 | export * from './searchOptions';
21 | export * from './searchParameter';
22 | export * from './settings';
23 | export * from './shirtSizes';
24 | export * from './skills';
25 | export * from './previousHackathons';
26 | export * from './statsResponse';
27 | export * from './team';
28 | export * from './travelStatus';
29 | export * from './travel';
30 | export * from './userTypes';
31 | export * from './validationError';
32 | export * from './pageType';
33 | export * from './reviewers'
34 |
--------------------------------------------------------------------------------
/src/shared/Elements/StyledModal.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import Modal from 'react-modal';
5 |
6 | const ReactModalAdapter = ({ className, ...props }: any) => {
7 | const contentClassName = `${className}__content`;
8 | const overlayClassName = `${className}__overlay`;
9 | return (
10 |
16 | );
17 | };
18 |
19 | export const StyledModal = styled(ReactModalAdapter)`
20 | &__overlay {
21 | position: fixed;
22 | top: 0px;
23 | left: 0px;
24 | right: 0px;
25 | bottom: 0px;
26 | background-color: rgba(0, 0, 0, 0.5);
27 | z-index: 20;
28 | }
29 |
30 | &__content {
31 | position: absolute;
32 | top: 40px;
33 | left: 40px;
34 | right: 40px;
35 | bottom: 40px;
36 | background: #fff;
37 | overflow: auto;
38 | -webkit-overflow-scrolling: touch;
39 | border-radius: 4px;
40 | outline: none;
41 | padding: 20px;
42 | }
43 | `;
44 |
45 | export default StyledModal;
46 |
--------------------------------------------------------------------------------
/src/features/Invite/ExistingInvites.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Account } from '../../api';
3 | import { IInviteInfo } from '../../config';
4 | import ValidationErrorGenerator from '../../shared/Form/validationErrorGenerator';
5 | import { ExistingInvitesTable } from './ExistingInvitesTable';
6 |
7 | export const ExistingInvites: React.FC = () => {
8 | // Are we fetching the invites?
9 | const [isLoading, setIsLoading] = useState(true);
10 | // Invites array
11 | const [invites, setInvites] = useState([]);
12 |
13 | useEffect(() => {
14 | (async () => {
15 | // Load invites
16 | try {
17 | const result = await Account.getInvites();
18 | const allInvites = result.data.data.invites;
19 | console.log(allInvites);
20 | setInvites(allInvites);
21 | } catch (e: any) {
22 | if (e && e.data) {
23 | ValidationErrorGenerator(e);
24 | }
25 | }
26 | setIsLoading(false);
27 | })();
28 | }, []);
29 | return ;
30 | };
31 |
32 | export default ExistingInvites;
33 |
--------------------------------------------------------------------------------
/src/pages/Admin/Invite.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet from 'react-helmet';
3 |
4 | import { HACKATHON_NAME } from '../../config/constants';
5 | import ExistingInvites from '../../features/Invite/ExistingInvites';
6 | import InviteForm from '../../features/Invite/InviteForm';
7 | import { H1, MaxWidthBox } from '../../shared/Elements';
8 | import GridTwoColumn from '../../shared/Elements/GridTwoColumn';
9 | import WithToaster from '../../shared/HOC/withToaster';
10 |
11 | const InvitePage: React.FC = () => (
12 | <>
13 |
14 | Invite User | {HACKATHON_NAME}
15 |
16 |
17 |
26 | User Invites
27 |
28 |
29 |
30 |
31 |
32 |
33 | >
34 | );
35 |
36 | export default WithToaster(InvitePage);
37 |
--------------------------------------------------------------------------------
/src/pages/Travel/TravelStatusBus.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import {
4 | BUS_DEPARTURE_CITY,
5 | BUS_SHOPIFY_PAGE,
6 | BUS_SLACK_CHANNEL,
7 | BUS_SLACK_PAGE,
8 | HACKATHON_NAME,
9 | ITravel,
10 | } from '../../config';
11 | import { LinkDuo } from '../../shared/Elements';
12 |
13 | interface ITravelStatusProps {
14 | travel: ITravel;
15 | }
16 |
17 | const TravelStatusBus: React.FunctionComponent = ({
18 | travel,
19 | }) => {
20 | return (
21 |
22 | Congratulations, you've secured a seat on our {BUS_DEPARTURE_CITY} bus{' '}
23 | to/from {HACKATHON_NAME}!
24 |
25 |
26 |
Bus
27 | Join the {BUS_SLACK_CHANNEL} on our official{' '}
28 | Slack for details and more
29 | information about your bus route.
30 |
31 |
32 | If you can no longer make it to McHacks, please{' '}
33 | contact
34 | us so we can refund your deposit and open the seat up to another hacker.
35 |
36 | );
37 | };
38 |
39 | export default TravelStatusBus;
40 |
--------------------------------------------------------------------------------
/src/shared/Elements/StyledModalSmall.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import Modal from 'react-modal';
5 |
6 | const ReactModalAdapter = ({ className, ...props }: any) => {
7 | const contentClassName = `${className}__content`;
8 | const overlayClassName = `${className}__overlay`;
9 | return (
10 |
16 | );
17 | };
18 |
19 | export const StyledModalSmall = styled(ReactModalAdapter)`
20 | &__overlay {
21 | display: flex;
22 | flex-direction: column;
23 | justify-content: center;
24 | align-items: center;
25 | position: fixed;
26 | top: 0px;
27 | left: 0px;
28 | right: 0px;
29 | bottom: 0px;
30 | background-color: rgba(0, 0, 0, 0.5);
31 | z-index: 20;
32 | }
33 |
34 | &__content {
35 | background: #fff;
36 | overflow: auto;
37 | -webkit-overflow-scrolling: touch;
38 | border-radius: 4px;
39 | outline: none;
40 | padding: 20px;
41 | }
42 | `;
43 |
44 | export default StyledModalSmall;
45 |
--------------------------------------------------------------------------------
/src/features/Application/PaginationHeader/SeperatingBar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import theme from '../../../shared/Styles/theme';
3 |
4 | interface ISeparatingBarProps {
5 | current: boolean;
6 | totalPages?: number;
7 | }
8 | /**
9 | * Dividing bar between NumberBubble components
10 | */
11 | const SeparatingBar: React.FC = (props) => {
12 | const pageNotSelectedBarStyle = {
13 | background: theme.colors.black40,
14 | width: props.totalPages
15 | ? `min(calc(calc(100% - ${props.totalPages * 24}px) / ${
16 | props.totalPages - 1
17 | }), 160px)`
18 | : '160px',
19 | height: '2px',
20 | textAlign: 'center' as 'center',
21 | verticalAlign: 'center',
22 | };
23 |
24 | const pageSelectedBarStyle = {
25 | ...pageNotSelectedBarStyle,
26 | background: theme.colors.purple,
27 | boxShadow: `2px 2px 16px 2px ${theme.colors.blueLight}`,
28 | };
29 |
30 | const barStyle = props.current // The bar before the current page will be purple
31 | ? pageSelectedBarStyle
32 | : pageNotSelectedBarStyle;
33 |
34 | return
;
35 | };
36 |
37 | export default SeparatingBar;
38 |
--------------------------------------------------------------------------------
/src/shared/Elements/TextButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import theme from '../Styles/theme';
3 |
4 | export interface ITextButtonProps {
5 | isGrey?: boolean;
6 | isLoading?: boolean;
7 | onClick?: any;
8 | children?: React.ReactNode;
9 | }
10 |
11 | /**
12 | * Button that displays simmilarly to a link
13 | */
14 | const TextButton: React.FC = (props) => (
15 | <>
16 | {props.isLoading ? (
17 | Loading...
18 | ) : (
19 |
20 | {props.children}
21 |
22 | )}
23 |
40 | >
41 | );
42 |
43 | export default TextButton;
44 |
--------------------------------------------------------------------------------
/src/features/Application/ResumeComponent.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@rebass/grid';
2 | import { FieldProps } from 'formik';
3 | import * as React from 'react';
4 | import ViewPDFComponent from '../../shared/Elements/ViewPDF';
5 | import { FileUpload, Label, LabelText } from '../../shared/Form';
6 | import { ManageApplicationModes } from './ManageApplicationForm';
7 |
8 | export interface IResumeProps {
9 | label: string;
10 | mode: ManageApplicationModes;
11 | hackerId: string;
12 | value?: boolean;
13 | required?: boolean;
14 | }
15 | const ResumeComponent: React.FC = (props) => {
16 | const viewResume = ;
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 | {props.mode === ManageApplicationModes.EDIT ? (
25 |
26 | {viewResume}
27 |
28 | ) : null}
29 |
30 |
31 | );
32 | };
33 | export { ResumeComponent };
34 | export default ResumeComponent;
35 |
--------------------------------------------------------------------------------
/src/api/emails.ts:
--------------------------------------------------------------------------------
1 | import { AxiosPromise } from 'axios';
2 | import API from './api';
3 | import APIResponse from './APIResponse';
4 | import axios from 'axios';
5 |
6 | class EmailsAPI {
7 | constructor() {
8 | API.createEntity('email');
9 | }
10 |
11 | /**
12 | * Trigger automated status emails endpoint
13 | * POST /api/email/automated/status/:status
14 | */
15 | public sendAutomatedStatus(
16 | status: string
17 | ): AxiosPromise> {
18 | return API.getEndpoint('email').create(undefined, {
19 | subURL: `automated/status/${status}`,
20 | config: { withCredentials: true },
21 | });
22 | }
23 |
24 | /**
25 | * Get count of hackers with specified status
26 | * GET /api/email/automated/status/:status/count
27 | */
28 | public getStatusCount(
29 | status: string
30 | ): AxiosPromise> {
31 | const baseURL = API.getEndpoint('email')['resourceURL'];
32 | return axios.get(`${baseURL}/automated/status/${status}/count`, {
33 | withCredentials: true,
34 | });
35 | }
36 | }
37 |
38 | export const Emails = new EmailsAPI();
39 | export default Emails;
40 |
--------------------------------------------------------------------------------
/src/api/settings.ts:
--------------------------------------------------------------------------------
1 | import { AxiosPromise, AxiosResponse } from 'axios';
2 | import { APIResponse } from '.';
3 | import { APIRoute, CACHE_SETTINGS_KEY, ISetting } from '../config';
4 | import LocalCache from '../util/LocalCache';
5 | import API from './api';
6 |
7 | class SettingsAPI {
8 | constructor() {
9 | API.createEntity(APIRoute.SETTINGS);
10 | }
11 | /**
12 | * Update the settings.
13 | * @param setting The settings that you want to update
14 | */
15 | public update(setting: ISetting): AxiosPromise {
16 | return API.getEndpoint(APIRoute.SETTINGS).patch({ id: '' }, setting);
17 | }
18 | /**
19 | * Get the current settings
20 | */
21 | public async get(
22 | overrideCache?: boolean
23 | ): Promise>> {
24 | const cached: any = LocalCache.get(CACHE_SETTINGS_KEY);
25 | if (cached && !overrideCache) {
26 | return cached as AxiosResponse>;
27 | }
28 | const value = await API.getEndpoint(APIRoute.SETTINGS).getAll();
29 | LocalCache.set(CACHE_SETTINGS_KEY, value);
30 | return value;
31 | }
32 | }
33 |
34 | export const Settings = new SettingsAPI();
35 | export default Settings;
36 |
--------------------------------------------------------------------------------
/src/shared/Form/StyledSelect.tsx:
--------------------------------------------------------------------------------
1 | import Select from 'react-select';
2 | import styled from 'styled-components';
3 | import inputStyles, { IInputProps } from '../Styles/inputStyles';
4 |
5 | export const StyledSelect = styled(Select)`
6 | font-family: ${(props) => props.theme.fonts.body};
7 |
8 | .react-select__control {
9 | ${inputStyles}
10 | display: flex;
11 | cursor: pointer;
12 | &--is-focused {
13 | box-shadow: 2px 2px 16px 0px
14 | ${(props: any) => props.theme.colors.blueLight};
15 | }
16 | }
17 |
18 | .react-select__option {
19 | font-weight: normal;
20 | color: ${(props) => props.theme.colors.black70};
21 | padding-left: 18px;
22 | cursor: pointer;
23 | &--is-selected {
24 | background-color: ${(props) => props.theme.colors.white};
25 | color: ${(props) => props.theme.colors.black70};
26 | }
27 | &--is-focused,
28 | &:hover {
29 | background-color: ${(props) => props.theme.colors.black10};
30 | color: ${(props) => props.theme.colors.black70};
31 | }
32 | }
33 |
34 | .react-select__value-container {
35 | padding-left: 0;
36 | }
37 | `;
38 |
39 | export default StyledSelect;
40 |
--------------------------------------------------------------------------------
/src/features/Nav/LogoutButton.tsx:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios';
2 | import React from 'react';
3 | import { NavigateFunction, useNavigate } from 'react-router-dom';
4 | import { APIResponse, Auth } from '../../api';
5 | import { FrontendRoute, IValidationError } from '../../config';
6 | import Button, { ButtonVariant } from '../../shared/Elements/Button';
7 | import ValidationErrorGenerator from '../../shared/Form/validationErrorGenerator';
8 |
9 | const LogoutBtn: React.FunctionComponent = () => {
10 | const navigate = useNavigate();
11 | return (
12 |
17 | Sign out
18 |
19 | );
20 | };
21 |
22 | function handleLogout(navigate: NavigateFunction): () => void {
23 | return () => {
24 | Auth.logout()
25 | .then(() => {
26 | navigate(FrontendRoute.LOGIN_PAGE);
27 | })
28 | .catch((response: AxiosResponse>) => {
29 | if (response && response.data) {
30 | ValidationErrorGenerator(response.data);
31 | }
32 | });
33 | };
34 | }
35 |
36 | export default LogoutBtn;
37 |
--------------------------------------------------------------------------------
/src/shared/Form/validationErrorGenerator.tsx:
--------------------------------------------------------------------------------
1 | import { toast } from 'react-toastify';
2 | import 'react-toastify/dist/ReactToastify.css';
3 | import { APIResponse } from '../../api';
4 | import {
5 | instanceOfIValidationErrorItem,
6 | IValidationError,
7 | IValidationErrorItem,
8 | } from '../../config';
9 |
10 | export default function ValidationErrorGenerator(
11 | response: APIResponse,
12 | autoClose: boolean = true
13 | ) {
14 | if (!response) {
15 | return;
16 | }
17 | const errors: any = response.data;
18 | toast.error(response.message, {
19 | position: toast.POSITION.TOP_RIGHT,
20 | autoClose: autoClose ? 5000 : false,
21 | });
22 |
23 | for (const key in errors) {
24 | // check if the property/key is defined in the object itself, not in parent
25 | if (
26 | errors.hasOwnProperty(key) &&
27 | instanceOfIValidationErrorItem(errors[key])
28 | ) {
29 | toast.error(validationErrorItem(key, errors[key]), {
30 | position: toast.POSITION.TOP_RIGHT,
31 | autoClose: false,
32 | });
33 | }
34 | }
35 | }
36 | function validationErrorItem(key: string, errorItem: IValidationErrorItem) {
37 | return `Invalid ${key}: ${errorItem.msg}`;
38 | }
39 |
--------------------------------------------------------------------------------
/src/assets/images/share-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/shared/Elements/Paragraph.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export interface IParagraphProps {
4 | fontSize?: string;
5 | textAlign?: string;
6 | italic?: boolean;
7 | paddingBottom?: string;
8 | maxWidth?: string;
9 | marginTop?: string;
10 | marginBottom?: string;
11 | marginLeft?: string;
12 | marginRight?: string;
13 | color?: string;
14 | }
15 |
16 | export const Paragraph = styled.p`
17 | ${(props) => props.italic && 'font-style: italic;'}
18 | font-size: ${(props) => props.fontSize || '24px'};
19 | color: ${(props) => props.color || props.theme.colors.black80};
20 | text-align: ${(props) => props.textAlign || 'left'};
21 | padding-bottom: ${(props) => props.paddingBottom || '0px'};
22 | max-width: ${(props) => props.maxWidth || '600px'};
23 | margin-bottom: ${(props) => props.marginBottom || '18px'};
24 | margin-left: ${(props) => props.marginLeft || 0};
25 | margin-right: ${(props) => props.marginRight || 0};
26 | margin-top: ${(props) => props.marginTop || '18px'};
27 | `;
28 |
29 | export const FormDescription = styled(Paragraph)`
30 | font-size: 14px;
31 | margin-top: 4px;
32 | color: ${(props) => props.color || props.theme.colors.black60};
33 | `;
34 | export default Paragraph;
35 |
--------------------------------------------------------------------------------
/src/pages/Travel/TravelStatusOffered.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { ITravel, TRAVEL_RECEIPTS_FORM } from '../../config';
4 | import { LinkDuo } from '../../shared/Elements';
5 | import DollarAmount from './DollarAmount';
6 |
7 | interface ITravelStatusProps {
8 | travel: ITravel;
9 | }
10 |
11 | const TravelStatusOffered: React.FunctionComponent = ({
12 | travel,
13 | }) => {
14 | if (travel.offer > 0) {
15 | return (
16 |
17 | We're happy to offer an amount to subsidize your travel to McHacks. We
18 | can reimburse you up to:
19 |
20 |
27 | Please{' '}
28 | upload your receipts .
29 |
30 |
31 | );
32 | } else {
33 | return (
34 |
35 | Unfortunately, we’re unable to offer you any travel reimbursement to
36 | McHacks.
37 |
38 |
39 | );
40 | }
41 | };
42 |
43 | export default TravelStatusOffered;
44 |
--------------------------------------------------------------------------------
/src/shared/Styles/theme.ts:
--------------------------------------------------------------------------------
1 | import { DefaultTheme } from 'styled-components';
2 |
3 | const theme: DefaultTheme = {
4 | colors: {
5 | red: '#F2463A',
6 | redMed: '#F56F65',
7 | redLight: '#F89790',
8 |
9 | blue: '#0069FF',
10 | blueLight: '#0069FF30',
11 |
12 | yellow: '#FFD081',
13 | yellowLight: '#FFEFB6',
14 |
15 | teal: '#48DEE2',
16 | tealLight: '#88FCFF',
17 |
18 | purple: '#5C63AB',
19 | purpleLight: '#5C63AB19',
20 |
21 | greyLight: '#10143712',
22 |
23 | white: '#FFFFFF',
24 | black: '#202020',
25 | black80: '#4D4D4D',
26 | black70: '#636363',
27 | black60: '#797979',
28 | black40: '#A6A6A6',
29 | black30: '#BCBCBC',
30 | black20: '#D2D2D2',
31 | black10: '#E9E9E9',
32 | black5: '#F4F4F4',
33 | },
34 | inputBorderRadius: '20px',
35 | fonts: {
36 | header:
37 | 'Brown, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
38 | body: 'Hind Siliguri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif',
39 | },
40 | screens: {
41 | smUp: '768px',
42 | mdUp: '992px',
43 | lgUp: '1200px',
44 | },
45 | };
46 |
47 | export default theme;
48 |
--------------------------------------------------------------------------------
/src/shared/Elements/BackgroundImage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface IBackgroundImageProps {
4 | top?: string | number;
5 | bottom?: string | number;
6 | left?: string | number;
7 | right?: string | number;
8 | minHeight?: string | number;
9 | imgHeight?: string | number;
10 | minWidth?: string | number;
11 | imgWidth?: string | number;
12 | position?: string;
13 | zIndex?: number;
14 | }
15 |
16 | export const BackgroundImage = styled.img`
17 | position: ${(props: IBackgroundImageProps) => props.position || 'absolute'};
18 | z-index: ${(props: IBackgroundImageProps) => props.zIndex || '1'};
19 | user-select: none;
20 | top: ${(props: IBackgroundImageProps) => props.top || ''};
21 | left: ${(props: IBackgroundImageProps) => props.left || ''};
22 | bottom: ${(props: IBackgroundImageProps) => props.bottom || ''};
23 | right: ${(props: IBackgroundImageProps) => props.right || ''};
24 | min-height: ${(props: IBackgroundImageProps) => props.minHeight || ''};
25 | min-width: ${(props: IBackgroundImageProps) => props.minWidth || ''};
26 | height: ${(props: IBackgroundImageProps) => props.imgHeight || 'auto'};
27 | width: ${(props) => props.imgWidth || 'auto'};
28 | src: ${(props) => props.src};
29 | `;
30 |
31 | export default BackgroundImage;
32 |
--------------------------------------------------------------------------------
/src/config/statsResponse.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AttendenceOptions,
3 | DietaryRestriction,
4 | HackerStatus,
5 | HackerReviewerStatus,
6 | JobInterest,
7 | ShirtSize,
8 | } from '.';
9 |
10 | export interface IStatsResponse {
11 | stats: {
12 | total: number;
13 | status: { [key in HackerStatus]: number };
14 | reviewerStatus: { [key in HackerReviewerStatus]: number };
15 | reviewerStatus2: { [key in HackerReviewerStatus]: number };
16 | reviewerName: { [key: string]: number };
17 | reviewerName2: { [key: string]: number };
18 | reviewerComments: { [key: string]: number };
19 | reviewerComments2: { [key: string]: number };
20 | school: { [key: string]: number };
21 | degree: { [key: string]: number };
22 | gender: { [key: string]: number };
23 | travel: { true: number; false: number };
24 | ethnicity: { [key: string]: number };
25 | country: { [key: string]: number };
26 | jobInterest: { [key in JobInterest]: number };
27 | major: { [key: string]: number };
28 | graduationYear: { [key: string]: number };
29 | dietaryRestriction: { [key in DietaryRestriction & string]: number };
30 | ShirtSize: { [key in ShirtSize]: number };
31 | attendancePreference: { [key in AttendenceOptions]: number };
32 | age: { [key: string]: number };
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/shared/HOC/withTokenRedirect.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Navigate } from 'react-router-dom';
3 | import { FrontendRoute, getTokenFromQuery } from '../../config';
4 |
5 | enum authStates {
6 | authorized,
7 | unauthorized,
8 | undefined,
9 | }
10 |
11 | const withTokenRedirect = (Component: React.ComponentType
) =>
12 | class extends React.Component
{
13 | constructor(props: any) {
14 | super(props);
15 | this.state = {
16 | authState: authStates.undefined,
17 | };
18 | }
19 |
20 | public async componentDidMount() {
21 | try {
22 | getTokenFromQuery();
23 | this.setState({
24 | authState: authStates.authorized,
25 | });
26 | } catch (e) {
27 | this.setState({
28 | authState: authStates.unauthorized,
29 | });
30 | }
31 | }
32 |
33 | public render() {
34 | const { authState } = this.state;
35 | switch (authState) {
36 | case authStates.authorized:
37 | return ;
38 | case authStates.unauthorized:
39 | return ;
40 | default:
41 | return
;
42 | }
43 | }
44 | };
45 |
46 | export default withTokenRedirect;
47 |
--------------------------------------------------------------------------------
/src/shared/Styles/inputStyles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | export interface IInputProps {
4 | isTight?: boolean;
5 | fontWeight?: string;
6 | }
7 |
8 | export const inputStyles = css`
9 | border-radius: 8px;
10 | font-weight: ${(props) => props.fontWeight || 'normal'};
11 | border: none;
12 | box-shadow: 2px 4px 16px 0px ${(props) => props.theme.colors.greyLight};
13 | box-sizing: border-box;
14 | display: block;
15 | font-size: 16px;
16 | margin: auto;
17 | margin-top: 12px;
18 | margin-bottom: ${(props) => (props.isTight ? '24px' : '32px')};
19 | min-height: 40px;
20 | padding-left: 18px;
21 | width: 100%;
22 | transition: 0.25s border ease-in, 0.25s box-shadow ease-in;
23 | color: ${(props) => props.theme.colors.black80};
24 | font-family: ${(props) => props.theme.fonts.header};
25 |
26 | &::placeholder {
27 | color: ${(props) => props.theme.colors.black40};
28 | }
29 |
30 | &:focus {
31 | outline: none;
32 | box-shadow: 2px 2px 16px 2px ${(props) => props.theme.colors.blueLight};
33 | }
34 |
35 | &:disabled {
36 | border-color: ${(props) => props.theme.colors.black5} !important;
37 | background-color: ${(props) => props.theme.colors.black5} !important;
38 | cursor: not-allowed;
39 | box-shadow: none;
40 | }
41 | `;
42 |
43 | export default inputStyles;
44 |
--------------------------------------------------------------------------------
/src/pages/_error.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet from 'react-helmet';
3 | import { LinkDuo } from '../shared/Elements';
4 |
5 | import { FrontendRoute, HACKATHON_NAME } from '../config';
6 |
7 | import Button, { ButtonVariant } from '../shared/Elements/Button';
8 | import H1 from '../shared/Elements/H1';
9 | import Paragraph from '../shared/Elements/Paragraph';
10 |
11 | /**
12 | * Container that renders 404 not found page.
13 | */
14 | const NotFoundPage: React.FC = () => (
15 | <>
16 |
17 | Page not found | {HACKATHON_NAME}
18 |
19 |
20 |
21 |
404: Page not found
22 |
23 | The page you're looking for doesn't exists or has been moved
24 |
25 |
26 |
27 |
32 | Click to go home
33 |
34 |
35 |
36 |
37 |
45 | >
46 | );
47 |
48 | export default NotFoundPage;
49 |
--------------------------------------------------------------------------------
/src/features/Application/PaginationHeader/NumberPageText.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import done from '../../../assets/images/done.svg';
3 | import { Image } from '../../../shared/Elements';
4 | import theme from '../../../shared/Styles/theme';
5 |
6 | interface INumberPageText {
7 | pageNumber: number;
8 | fill: boolean;
9 | check: boolean;
10 | }
11 |
12 | /**
13 | * Displays either a checkmark or page number depending on props
14 | * Should be children prop of NumberBubble
15 | */
16 | const NumberPageText: React.FC = (props) => {
17 | if (!props.check) {
18 | // Number only displays if we are not displaying a checkmark
19 | const notSelectedTextStyle = {
20 | position: 'relative' as 'relative',
21 | fontSize: '12px',
22 | color: theme.colors.black40,
23 | textAlign: 'center' as 'center',
24 | fontFamily: theme.fonts.header,
25 | lineHeight: '24px',
26 | };
27 |
28 | const selectedTextStyle = {
29 | ...notSelectedTextStyle,
30 | color: 'white',
31 | };
32 | const textStyle = props.fill ? selectedTextStyle : notSelectedTextStyle; // Style changes depending on props.fill
33 |
34 | return {props.pageNumber} ;
35 | } else {
36 | // Checkmark svg
37 | return ;
38 | }
39 | };
40 |
41 | export default NumberPageText;
42 |
--------------------------------------------------------------------------------
/src/shared/Form/EmailInput.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Input, Label, LabelText } from './';
3 |
4 | interface IEmailInputProp {
5 | onEmailChanged: (email: string) => void;
6 | disabled?: boolean;
7 | value?: string;
8 | placeholder?: string;
9 | isTight?: boolean;
10 | label: string;
11 | required?: boolean;
12 | }
13 |
14 | export const EmailInput: React.FC = (
15 | props: IEmailInputProp
16 | ) => {
17 | const placeholder = props.placeholder ? props.placeholder : '';
18 | return (
19 |
20 |
21 |
29 |
30 | );
31 | };
32 | /**
33 | * Function factory that generates function to handle changes in user's choice.
34 | * @param props The props passed into the EmailInput component.
35 | * @returns the function that handles changes to the choices provided by the user.
36 | */
37 | function handleChange(
38 | props: IEmailInputProp
39 | ): (event: React.ChangeEvent) => void {
40 | return (event: React.ChangeEvent) =>
41 | props.onEmailChanged(event.target.value);
42 | }
43 |
--------------------------------------------------------------------------------
/src/shared/Form/FormikElements/FormattedNumber.tsx:
--------------------------------------------------------------------------------
1 | import { FieldProps } from 'formik';
2 | import * as React from 'react';
3 | import { NumberFormatValues } from 'react-number-format';
4 | import { NumberFormatInput } from '..';
5 |
6 | interface INumberFormatFormikComponent {
7 | label: string;
8 | format: string;
9 | placeholder?: string;
10 | value?: string;
11 | required?: boolean;
12 | disabled?: boolean;
13 | }
14 |
15 | const NumberFormatFormikComponent: React.FunctionComponent<
16 | INumberFormatFormikComponent & FieldProps
17 | > = (props) => {
18 | const placeholder = props.placeholder ? props.placeholder : '';
19 |
20 | return (
21 |
30 | );
31 | };
32 | /**
33 | * Function factory that generates function to handle changes in user's choice.
34 | * @param props The props passed into the Textarea component.
35 | * @returns the function that handles changes to the choices provided by the user.
36 | */
37 | function handleChange({ field, form }: FieldProps) {
38 | return (value: NumberFormatValues) => {
39 | form.setFieldValue(field.name, value.value);
40 | };
41 | }
42 |
43 | export { NumberFormatFormikComponent as FormattedNumber };
44 |
--------------------------------------------------------------------------------
/src/config/skills.ts:
--------------------------------------------------------------------------------
1 | export enum Skills {
2 | ANDROID = 'Android',
3 | ANGULAR = 'Angular',
4 | AI = 'Artificial Intelligence',
5 | BACKEND = 'Backend',
6 | BASH = 'Bash',
7 | C = 'C',
8 | C_PLUS_PLUS = 'C++',
9 | C_SHARP = 'C#',
10 | CSS = 'CSS',
11 | DATA_SCIENCE = 'Data Science',
12 | DESKTOP_APPS = 'Desktop Apps',
13 | DJANGO = 'Django',
14 | EXCEL = 'Excel',
15 | FIGMA = 'Figma',
16 | FPGA = 'FPGA',
17 | FRONTEND = 'Frontend',
18 | GAMEDEV = 'Game Development',
19 | GATSBY = 'Gatsby',
20 | HTML = 'HTML',
21 | IOS = 'iOS',
22 | JAVA = 'Java',
23 | JAVASCRIPT = 'Javascript',
24 | JIRA = 'Jira',
25 | JS = 'JS',
26 | ML = 'Machine Learning',
27 | MOBILE_APPS = 'Mobile Apps',
28 | MONGODB = 'MongoDB',
29 | NLP = 'Natural Language Processing',
30 | NEURAL_NETWORKS = 'Neural Networks',
31 | NEXTJS = 'Next JS',
32 | NODEJS = 'Node JS',
33 | PHP = 'PHP',
34 | PRODUCT_MANAGEMENT = 'Product Management',
35 | PYTHON = 'Python',
36 | REACT = 'React',
37 | RNN = 'RNN',
38 | ROBOTICS = 'Robotics',
39 | RUBY = 'Ruby',
40 | RUBY_ON_RAILS = 'Ruby on Rails',
41 | SKETCH = 'Sketch',
42 | SWIFT = 'Swift',
43 | TS = 'TS',
44 | TURING = 'Turing',
45 | TYPESCRIPT = 'Typescript',
46 | UI_DESIGN = 'UI Design',
47 | UNITY = 'Unity',
48 | UNREAL_ENGINE = 'Unreal Engine',
49 | UX_DESIGN = 'UX Design',
50 | VUE = 'Vue',
51 | WEB_DEV = 'Web Development',
52 | }
53 | export default Skills;
54 |
--------------------------------------------------------------------------------
/src/shared/Styles/GlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | export const GlobalStyles = createGlobalStyle`
4 | @font-face {
5 | font-family: 'Brown';
6 | font-style: normal;
7 | font-weight: regular;
8 | src: url('/fonts/lineto-brown-regular.ttf');
9 | font-display: swap;
10 | }
11 |
12 | @font-face {
13 | font-family: 'Brown';
14 | font-style: normal;
15 | font-weight: bold;
16 | src: url("/fonts/lineto-brown-bold.ttf");
17 | font-display: swap;
18 | }
19 |
20 | body {
21 | font-family: ${(props) => props.theme.fonts.body};
22 | margin: 0;
23 | padding: 0;
24 |
25 | & > #root {
26 | min-height: 100vh;
27 | }
28 | }
29 |
30 | a {
31 | color: ${(props) => props.theme.colors.purple};
32 | text-decoration: none;
33 |
34 | &:hover {
35 | color: ${(props) => props.theme.colors.yellow};
36 |
37 | }
38 |
39 | transition: 0.15s color ease-in-out;
40 | }
41 |
42 | h1, h2, h3, h4, h5, h6 {
43 | font-family: ${(props) => props.theme.fonts.header};
44 | }
45 |
46 | input, textarea, select {
47 | font-family: inherit;
48 | }
49 |
50 | .toast-notification {
51 | z-index: 100000;
52 | }
53 |
54 | @media screen and (min-width: ${(props) => props.theme.screens.smUp}) {
55 | .bm-burger-button, .bm-menu-wrap {
56 | display: none;
57 | }
58 | }
59 | `;
60 |
61 | export default GlobalStyles;
62 |
--------------------------------------------------------------------------------
/src/features/Account/validationSchema.ts:
--------------------------------------------------------------------------------
1 | import { array, number, object, string } from 'yup';
2 |
3 | const getValidationSchema = (isCreate: boolean) => {
4 | const password = isCreate
5 | ? string().min(6, 'Must be at least 6 characters').required('Required')
6 | : string().when('newPassword', {
7 | is: (pass: string) => pass,
8 | then: (schema) => schema.required('Required to change password'),
9 | otherwise: (schema) => schema,
10 | });
11 |
12 | return object().shape({
13 | firstName: string().required('Required'),
14 | lastName: string().required('Required'),
15 | email: string().required('Required').email('Must be a valid email'),
16 | password,
17 | newPassword: string().min(6, 'Must be at least 6 characters'),
18 | pronoun: string(),
19 | gender: string(),
20 | dietaryRestrictions: array().of(string()),
21 | phoneNumber: string().test(
22 | 'validPhone',
23 | 'Must be a valid phone number',
24 | (value) => {
25 | const parsedValue = value?.replace(/\D/g, '');
26 | return (
27 | !parsedValue || (parsedValue.length > 10 && parsedValue.length < 14)
28 | );
29 | }
30 | ),
31 | age: number()
32 | .required('Required')
33 | .min(0, 'Age must be a positive number') // Minimum age
34 | .max(100, 'Age must be less than or equal to 100'), // Maximum age
35 | });
36 | };
37 |
38 | export default getValidationSchema;
39 |
--------------------------------------------------------------------------------
/src/features/Checkin/Reader.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useZxing } from 'react-zxing';
3 | import styled from 'styled-components';
4 |
5 | interface IReaderProps {
6 | onScan: (data: string | null) => void;
7 | onError: (err: any) => void;
8 | }
9 |
10 | const Viewport = styled.div`
11 | top: 0;
12 | left: 0;
13 | z-index: 1;
14 | box-sizing: border-box;
15 | border: 50px solid rgba(0, 0, 0, 0.3);
16 | box-shadow: inset 0 0 0 5px rgba(255, 0, 0, 0.5);
17 | position: absolute;
18 | width: 100%;
19 | height: 100%;
20 | `;
21 |
22 | const Video = styled.video`
23 | top: 0;
24 | left: 0;
25 | display: block;
26 | position: absolute;
27 | overflow: hidden;
28 | width: 100%;
29 | height: 100%;
30 | object-fit: cover;
31 | `;
32 |
33 | export const Reader: React.FunctionComponent = (
34 | props: IReaderProps
35 | ) => {
36 | const { ref } = useZxing({
37 | onDecodeResult: (result) => props.onScan(result.getText()),
38 | onError: props.onError,
39 | onDecodeError: props.onError,
40 | timeBetweenDecodingAttempts: 500,
41 | });
42 |
43 | return (
44 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/shared/Elements/ConfirmModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex } from '@rebass/grid';
2 | import React from 'react';
3 | import { Button, ButtonVariant, StyledModalSmall } from '.';
4 |
5 | interface IConfirmModalProps {
6 | isOpen: boolean;
7 | onConfirmed: () => void;
8 | onCanceled: () => void;
9 | children?: React.ReactNode;
10 | }
11 |
12 | export const ConfirmModal: React.FC = (
13 | props: IConfirmModalProps
14 | ) => {
15 | return (
16 |
23 |
24 | {props.children}
25 |
26 |
27 |
32 | No
33 |
34 |
35 |
36 |
41 | Yes
42 |
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default ConfirmModal;
51 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Before contributing to the McHacks website, please review our [Code of Conduct](https://github.com/hackmcgill/mchacks7/blob/develop/docs/CODE_OF_CONDUCT.md).
4 |
5 | ## Branches
6 |
7 | - Create a new branch from `develop`
8 | - Name branches like `topic/ticket#-short-description`, i.e. `feature/9-styling`
9 | - Some topics: `feature`, `bug`, `refactor`
10 |
11 | ## Commits
12 |
13 | - Use imperative form when writing commit messages, i.e. "Fix margins in..."
14 | - Use sentence case (capitalize the first letter)
15 | - Try to communicate what the change does without having to look at the source code
16 | - List your main change in the unreleased section of the Changelog
17 |
18 | ## Pull Requests
19 |
20 | - Name the PR with a summary of proposed changes
21 | - Complete the entire PR template
22 | - Satisfy the PR checklist before asking for review
23 | - Set `develop` as the base branch unless it's a release (then set base to `master`)
24 | - Squash commits to merge
25 |
26 | ## Releases
27 |
28 | - Create a new branch from `develop` to merge into `master`
29 | - Name the branch like `release/version-number`, i.e. `release/1.4.0`
30 | - For version numbers, we follow [semantic versioning](https://semver.org/) with MAJOR.MINOR.PATCH.
31 | - Create a pull request to merge the release branch into `master`
32 | - Satisfy the entire PR template with a good description for reference
33 | - Add a release tag in the [releases tab](https://github.com/hackmcgill/mchacks7/releases).
34 |
--------------------------------------------------------------------------------
/src/features/Application/PaginationHeader/NumberBubble.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import theme from '../../../shared/Styles/theme';
3 |
4 | interface INumberBubble {
5 | fill: boolean;
6 | current: boolean;
7 | children?: React.ReactNode;
8 | }
9 |
10 | /**
11 | * Page number Bubble that displays child component
12 | */
13 | const NumberBubble: React.FC = (props) => {
14 | const notSelectedBubble = {
15 | width: '24px',
16 | height: '24px',
17 | borderRadius: '50%',
18 | background: 'none',
19 | border: `2px solid ${theme.colors.black40}`,
20 | boxSizing: 'border-box' as 'border-box',
21 | display: 'flex',
22 | alignItems: 'center',
23 | justifyContent: 'center',
24 | verticalAlign: 'center',
25 | };
26 | const selectedBubble = {
27 | ...notSelectedBubble,
28 | background: theme.colors.purple,
29 | width: '24px',
30 | height: '24px',
31 | borderRadius: '50%',
32 | border: 'none',
33 | };
34 | const currentBubble = {
35 | ...selectedBubble,
36 | boxShadow: `2px 2px 16px ${theme.colors.purpleLight}`,
37 | };
38 |
39 | const bubbleStyle = props.fill
40 | ? props.current
41 | ? currentBubble // Display purple bubble with shadow if page is the current one the user is on
42 | : selectedBubble // Display purple bubble if the bubble should be filled but is not current
43 | : notSelectedBubble; // Display grey bubble if no fill
44 |
45 | return {props.children}
;
46 | };
47 |
48 | export default NumberBubble;
49 |
--------------------------------------------------------------------------------
/src/features/Team/MemberList/MemberItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { HackerStatus, IMemberName } from '../../../config';
4 | import theme from '../../../shared/Styles/theme';
5 |
6 | const MemberItem: React.FC = (props) => {
7 | const applied = props.status === HackerStatus.HACKER_STATUS_NONE;
8 |
9 | return (
10 |
11 |
12 |
13 | {props.firstName} {props.lastName}
14 |
15 |
{props.school}
16 |
17 |
18 | {applied ? 'Incomplete Application' : 'Applied'}
19 |
20 |
21 |
49 |
50 | );
51 | };
52 |
53 | export default MemberItem;
54 |
--------------------------------------------------------------------------------
/src/assets/images/dashboard-confirm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/shared/Form/StyledCreatableSelect.tsx:
--------------------------------------------------------------------------------
1 | import CreatableSelect from 'react-select/creatable';
2 | import styled from 'styled-components';
3 | import { inputStyles } from '../Styles/inputStyles';
4 |
5 | export const StyledCreatableSelect = styled(CreatableSelect)`
6 | font-family: ${(props) => props.theme.fonts.body};
7 |
8 | .react-select__control {
9 | ${inputStyles}
10 | display: flex;
11 | cursor: text;
12 | &--is-focused {
13 | box-shadow: 2px 2px 16px 0px
14 | ${(props: any) => props.theme.colors.blueLight};
15 | }
16 | }
17 |
18 | .react-select__indicators {
19 | cursor: pointer;
20 | }
21 |
22 | .react-select__option {
23 | font-weight: normal;
24 | color: ${(props) => props.theme.colors.black70};
25 | padding-left: 18px;
26 | cursor: pointer;
27 | &--is-selected {
28 | background-color: ${(props) => props.theme.colors.white};
29 | color: ${(props) => props.theme.colors.black70};
30 | }
31 | &--is-focused,
32 | &:hover {
33 | background-color: ${(props) => props.theme.colors.black10};
34 | color: ${(props) => props.theme.colors.black70};
35 | }
36 | }
37 |
38 | .react-select__value-container {
39 | padding-left: 0;
40 | }
41 |
42 | .react-select__multi-value__label {
43 | background-color: ${(props) => props.theme.colors.black10};
44 | color: ${(props) => props.theme.colors.purple};
45 | }
46 |
47 | .react-select__multi-value__remove {
48 | background-color: ${(props) => props.theme.colors.black10};
49 | }
50 | `;
51 |
52 | export default StyledCreatableSelect;
53 |
--------------------------------------------------------------------------------
/src/config/APIRoute.ts:
--------------------------------------------------------------------------------
1 | export enum APIRoute {
2 | // Auth routes
3 | AUTH_LOGIN = 'auth/login',
4 | AUTH_LOGOUT = 'auth/logout',
5 | AUTH_FORGOT_PASS = 'auth/password/forgot',
6 | AUTH_RESET_PASS = 'auth/password/reset',
7 | AUTH_CONFIRM_ACCT = 'auth/confirm',
8 | AUTH_RESEND_CONF_EMAIL = 'auth/confirm/resend',
9 | AUTH_CHANGE_PASS = 'auth/password/change',
10 |
11 | // Account routes
12 | ACCOUNT = 'account',
13 | ACCOUNT_SELF = 'account/self',
14 | ACCOUNT_INVITE = 'account/invite',
15 | // Hacker routes
16 | HACKER = 'hacker',
17 | HACKER_EMAIL = 'hacker/email',
18 | HACKER_CHECKIN = 'hacker/checkin',
19 | HACKER_CONFIRMATION = 'hacker/confirmation',
20 | HACKER_RESUME = 'hacker/resume',
21 | HACKER_SELF = 'hacker/self',
22 | HACKER_STATS = 'hacker/stats',
23 | HACKER_STATUS = 'hacker/status',
24 | HACKER_REVIEWER_STATUS = 'hacker/reviewerStatus',
25 | HACKER_REVIEWER_STATUS2 = 'hacker/reviewerStatus2',
26 | HACKER_REVIEWER_NAME = 'hacker/reviewerName',
27 | HACKER_REVIEWER_NAME2 = 'hacker/reviewerName2',
28 | HACKER_REVIEWER_COMMENTS = 'hacker/reviewerComments',
29 | HACKER_REVIEWER_COMMENTS2 = 'hacker/reviewerComments2',
30 | // Travel routes
31 | TRAVEL = 'travel',
32 | TRAVEL_EMAIL = 'travel/email',
33 | TRAVEL_SELF = 'travel/self',
34 | // Search routes
35 | SEARCH = 'search',
36 | // Settings routes
37 | SETTINGS = 'settings',
38 | // Sponsor routes
39 | SPONSOR = 'sponsor',
40 | SPONSOR_SELF = 'sponsor/self',
41 |
42 | TEAM = 'team',
43 | TEAM_JOIN = 'team/join',
44 | TEAM_LEAVE = 'team/leave',
45 | }
46 | export default APIRoute;
47 |
--------------------------------------------------------------------------------
/src/api/team.ts:
--------------------------------------------------------------------------------
1 | import { AxiosPromise } from 'axios';
2 | import { APIRoute, CACHE_HACKER_KEY, ITeam } from '../config';
3 | import { ITeamResponse } from '../config/teamGETResponse';
4 | import LocalCache from '../util/LocalCache';
5 | import API from './api';
6 | import APIResponse from './APIResponse';
7 |
8 | class TeamAPI {
9 | constructor() {
10 | API.createEntity(APIRoute.TEAM);
11 | API.createEntity(APIRoute.TEAM_JOIN);
12 | API.createEntity(APIRoute.TEAM_LEAVE);
13 | }
14 | /**
15 | * create a team.
16 | * @param team The team you want to create.
17 | */
18 | public create(team: ITeam): AxiosPromise> {
19 | LocalCache.remove(CACHE_HACKER_KEY);
20 | return API.getEndpoint(APIRoute.TEAM).create(team);
21 | }
22 | /**
23 | * Join an existing team
24 | * @param name the team name
25 | */
26 | public join(name: string): AxiosPromise> {
27 | LocalCache.remove(CACHE_HACKER_KEY);
28 | return API.getEndpoint(APIRoute.TEAM_JOIN).patch({ id: '' }, { name });
29 | }
30 | /**
31 | * Get information about a team
32 | * @param id the ID of the hacker in a team
33 | */
34 | public get(id: string): AxiosPromise> {
35 | return API.getEndpoint(APIRoute.TEAM).getOne({ id });
36 | }
37 | /**
38 | * Current hacker leaves their team
39 | */
40 | public leave(): AxiosPromise> {
41 | LocalCache.remove(CACHE_HACKER_KEY);
42 | return API.getEndpoint(APIRoute.TEAM_LEAVE).patch({ id: '' }, {});
43 | }
44 | }
45 |
46 | export const Team = new TeamAPI();
47 | export default Team;
48 |
--------------------------------------------------------------------------------
/src/assets/images/sidebar-home.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/features/Nav/Burger.tsx:
--------------------------------------------------------------------------------
1 | import theme from '../../shared/Styles/theme';
2 |
3 | export const Burger = {
4 | bmBurgerButton: {
5 | position: 'fixed',
6 | width: '30px',
7 | height: '25px',
8 | top: '22px',
9 | right: '30px',
10 | },
11 | bmBurgerBars: {
12 | background: theme.colors.black40,
13 | borderRadius: '30px',
14 | height: '3.5px',
15 | },
16 | bmBurgerBarsHover: {
17 | background: theme.colors.red,
18 | },
19 | bmCrossButton: {
20 | height: '30px',
21 | width: '30px',
22 | top: '22px',
23 | right: '30px',
24 | outline: 'none',
25 | },
26 | bmCrossButtonHover: {
27 | background: theme.colors.red,
28 | },
29 | bmCross: {
30 | background: theme.colors.black40,
31 | height: '5px',
32 | width: '30px',
33 | top: '5px',
34 | left: '-15px',
35 | borderRadius: '5px',
36 | },
37 | bmMenuWrap: {
38 | position: 'fixed',
39 | height: '100%',
40 | },
41 | bmMenu: {
42 | background: theme.colors.black5,
43 | padding: '2.5em 1.5em 0',
44 | },
45 | bmMorphShape: {
46 | fill: '#373a47',
47 | },
48 | bmItemList: {
49 | color: theme.colors.red,
50 | padding: '2rem',
51 | top: '8em',
52 | buttom: '8em',
53 | display: 'grid',
54 | height: '250px',
55 | textAlign: 'center',
56 | fontSize: '24px',
57 | marginTop: '60px',
58 | },
59 | bmItem: {
60 | textAlign: 'center',
61 | lineHeight: '7rem',
62 | fontSize: '36px',
63 | padding: '40px',
64 | },
65 | bmOverlay: {
66 | background: 'rgba(0, 0, 0, 0.3)',
67 | },
68 | };
69 |
70 | export default Burger;
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [McHacks Dashboard](https://app.mchacks.ca)
2 |
3 | This repository contains the code for the hackathon dashboard of [McHacks](https://mchacks.ca), Canada's favourite hackathon hosted annually at McGill University. It connects with our [API](https://github.com/hackmcgill/hackerAPI) and is hosted at [app.mchacks.ca](https://app.mchacks.ca).
4 |
5 | ## Folder Structure
6 |
7 | ```
8 | .
9 | ├── .github
10 | ├── .netlify
11 | ├── .vscode
12 | ├── docs
13 | ├── public
14 | | ├── favicon
15 | | ├── fonts
16 | ├── src
17 | | ├── api
18 | | ├── assets
19 | | ├── config
20 | | ├── features
21 | | ├── shared
22 | | └── util
23 | ```
24 |
25 |
26 |
27 | ## Screenshots
28 |
29 | ### Create / edit your account
30 |
31 | 
32 |
33 | ### Create / edit your application
34 |
35 | 
36 |
37 | ### Hacker Dashboard
38 |
39 | 
40 |
41 | ### Team Viewing
42 |
43 | 
44 |
45 | ### HackPass QR code for check-in
46 |
47 | 
48 |
49 | ### Staff Dashboard
50 |
51 | 
52 |
53 | ### Search with filters, fuzzy search
54 |
55 | 
56 |
57 | ### Single hacker view modal
58 |
59 | 
60 |
61 | ### Single hacker view page
62 |
63 | 
64 |
65 | ### Check-in via QR code or email
66 |
67 | 
68 |
--------------------------------------------------------------------------------
/src/assets/images/key.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/pages/Account/Edit.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet from 'react-helmet';
3 | import ManageAccountContainer, {
4 | ManageAccountModes,
5 | } from '../../features/Account/ManageAccountForm';
6 |
7 | import * as CONSTANTS from '../../config/constants';
8 | import { H1, MaxWidthBox } from '../../shared/Elements';
9 |
10 | import Gears from '../../assets/images/hacker2-dots.svg';
11 |
12 | const EditAccountPage: React.FC = () => (
13 | <>
14 |
15 | Edit Profile | {CONSTANTS.HACKATHON_NAME}
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
37 | Your Account
38 |
39 |
40 |
41 |
42 |
58 | >
59 | );
60 |
61 | export default EditAccountPage;
62 |
--------------------------------------------------------------------------------
/src/shared/Form/LabelText.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex } from '@rebass/grid';
2 | import React from 'react';
3 | import { OPTIONAL_INPUT, REQUIRED_INPUT } from '../../config';
4 | import theme from '../Styles/theme';
5 | import { RequiredInputLabel, SecondaryInfoText } from './';
6 |
7 | interface ILabelTextProps {
8 | // Label text
9 | label: any;
10 |
11 | // Is this a required field
12 | required?: boolean;
13 |
14 | // Should this field display a * to let user know it's required
15 | // (a field can be required, but still have showRequiredLabel set to false)
16 | showRequiredLabel?: boolean;
17 |
18 | // Should this field display a (optional) message to let user know
19 | // it's safe to skip this field?
20 | showOptionalLabel?: boolean;
21 |
22 | // Subtext underlabel, explaining in more detail
23 | secondaryInfo?: any;
24 | }
25 |
26 | export const LabelText: React.FC = (
27 | props: ILabelTextProps
28 | ) => {
29 | const requiredText = props.showRequiredLabel ? (
30 | {REQUIRED_INPUT}
31 | ) : props.showOptionalLabel ? (
32 |
39 | {OPTIONAL_INPUT}
40 |
41 | ) : null;
42 |
43 | const secondaryInfo = (
44 | {props.secondaryInfo}
45 | );
46 | return (
47 |
48 |
49 | {props.label}
50 | {requiredText}
51 |
52 | {props.secondaryInfo && {secondaryInfo} }
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/features/Search/HackerSelect.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Sponsor } from '../../api';
3 | import { Checkbox } from '../../shared/Form';
4 | import NomineeContext from './Context';
5 |
6 | interface IProps {
7 | hackerId: string;
8 | }
9 |
10 | interface IState {
11 | isChanging: boolean;
12 | }
13 |
14 | class HackerSelect extends React.Component {
15 | public static contextType = NomineeContext;
16 | public context: React.ContextType;
17 |
18 | constructor(props: IProps) {
19 | super(props);
20 | this.state = {
21 | isChanging: false,
22 | };
23 |
24 | this.handleChange = this.handleChange.bind(this);
25 | }
26 | public render() {
27 | const { hackerId } = this.props;
28 | if (!this.context || !this.context.nominees) {
29 | return
;
30 | }
31 | const isChecked = this.context.nominees.indexOf(hackerId) > -1;
32 | return (
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | private async handleChange(event: React.ChangeEvent) {
40 | const isChecked = event.target.checked;
41 | const { isChanging } = this.state;
42 | const { hackerId } = this.props;
43 |
44 | if (isChanging || !this.context) {
45 | return;
46 | }
47 |
48 | if (isChecked) {
49 | this.context.nominees.push(hackerId);
50 | } else {
51 | this.context.nominees = this.context.nominees.filter(
52 | (n: string) => n !== hackerId
53 | );
54 | }
55 |
56 | this.setState({ isChanging: true });
57 | await Sponsor.update(this.context);
58 | this.setState({ isChanging: false });
59 | }
60 | }
61 |
62 | export default HackerSelect;
63 |
--------------------------------------------------------------------------------
/src/shared/Form/PasswordInput.tsx:
--------------------------------------------------------------------------------
1 | import { Flex } from '@rebass/grid';
2 | import React from 'react';
3 | import ForgotPasswordLinkComponent from '../../features/Login/ForgotPasswordLink';
4 | import { Input, Label, LabelText } from './';
5 |
6 | interface IPasswordInputProp {
7 | onPasswordChanged: (email: string) => void;
8 | label?: string;
9 | required?: boolean;
10 | id?: string;
11 | isTight?: boolean;
12 | value?: string;
13 | placeholder?: string;
14 | hasResetLink?: boolean;
15 | }
16 |
17 | /**
18 | * A password field in the form
19 | * @prop {boolean} hasResetLink should a reset password link be displayed
20 | */
21 | export const PasswordInput: React.FC = (props) => {
22 | const placeholder = props.placeholder ? props.placeholder : '';
23 | return (
24 |
25 |
26 |
27 | {props.hasResetLink && }
28 |
29 |
30 |
38 |
39 | );
40 | };
41 | /**
42 | * Function factory that generates function to handle changes in user's choice.
43 | * @param props The props passed into the PasswordInput component.
44 | * @returns the function that handles changes to the choices provided by the user.
45 | */
46 | function handleChange(
47 | props: IPasswordInputProp
48 | ): (event: React.ChangeEvent) => void {
49 | return (event: React.ChangeEvent) =>
50 | props.onPasswordChanged(event.target.value);
51 | }
52 |
--------------------------------------------------------------------------------
/src/shared/Form/FormikElements/LongTextInput.tsx:
--------------------------------------------------------------------------------
1 | import { FieldProps } from 'formik';
2 | import React from 'react';
3 | import { Label, LabelText } from '../';
4 | import { Textarea } from '../../Elements';
5 |
6 | export interface ITextAreaProp {
7 | label: string;
8 | placeholder?: string;
9 | value?: string;
10 | required?: boolean;
11 | maxLength?: number;
12 | style?: object;
13 | showOptionalLabel?: boolean;
14 | }
15 |
16 | export const LongTextInput: React.FC = (props) => {
17 | const placeholder = props.placeholder ? props.placeholder : '';
18 | const charLeft =
19 | props.maxLength && props.value
20 | ? `${props.value.length}/${props.maxLength} characters`
21 | : '';
22 | return (
23 |
24 |
30 |
37 |
38 | );
39 | };
40 | /**
41 | * Function factory that generates function to handle changes in user's choice.
42 | * @param props The props passed into the Textarea component.
43 | * @returns the function that handles changes to the choices provided by the user.
44 | */
45 | function handleChange(
46 | props: ITextAreaProp & FieldProps
47 | ): (event: React.ChangeEvent) => void {
48 | return (event: React.ChangeEvent) => {
49 | const field = props.field;
50 | const form = props.form;
51 | form.setFieldValue(field.name, event.target.value);
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/src/util/LocalCache.ts:
--------------------------------------------------------------------------------
1 | class LocalCache {
2 | private expiry: { [key: string]: number };
3 | private expiryTime: number;
4 |
5 | /**
6 | * Creates a Cache object.
7 | * @param expiryTime number of milliseconds after creation you want an item to expire in.
8 | */
9 | constructor(expiryTime: number = 60 * 60 * 500) {
10 | this.expiry = {};
11 | this.expiryTime = expiryTime;
12 | }
13 |
14 | /**
15 | * Gets an item in the cache, or null if it has expired / does not exist
16 | * @param key the key of the item you want to retrieve
17 | */
18 | public get(key: string): any {
19 | if (!this.expiry[key] || this.expiry[key] < Date.now()) {
20 | return null;
21 | } else {
22 | return this._get(key);
23 | }
24 | }
25 |
26 | /**
27 | * Set an item in the cache.
28 | * @param key key of the item you want to store in the cache
29 | * @param item value of the item you want to store in the cache
30 | * @param customExpiry Custom time you want this cache to expire at.
31 | */
32 | public set(key: string, item: any, customExpiry?: Date) {
33 | this.expiry[key] = customExpiry
34 | ? customExpiry.getTime()
35 | : Date.now() + this.expiryTime;
36 | this._set(key, item);
37 | }
38 |
39 | public remove(key: string) {
40 | delete this.expiry[key];
41 | this._remove(key);
42 | }
43 |
44 | private _get(key: string): any {
45 | const stored = window.localStorage.getItem(key);
46 | if (!stored) {
47 | return null;
48 | }
49 | return JSON.parse(stored);
50 | }
51 | private _set(key: string, item: any) {
52 | window.localStorage.setItem(key, JSON.stringify(item));
53 | }
54 | private _remove(key: string) {
55 | window.localStorage.removeItem(key);
56 | }
57 | }
58 | export default new LocalCache();
59 |
--------------------------------------------------------------------------------
/src/api/api.ts:
--------------------------------------------------------------------------------
1 | import { DEV_API_URL, LOCAL_API_URL, PROD_API_URL } from '../config';
2 | import Endpoint from './endpoint';
3 | /**
4 | * Inspired by https://github.com/FrancescoSaverioZuppichini/API-Class
5 | */
6 |
7 | class API {
8 | private url: string;
9 | private endpoints: { [id: string]: Endpoint } = {};
10 |
11 | constructor(url: string) {
12 | this.url = url;
13 | this.endpoints = {};
14 | }
15 | /**
16 | * Create and store a single entity's endpoints
17 | * @param {string} name name of the resource
18 | */
19 | public createEntity(name: string): void {
20 | this.endpoints[name] = this.createBasicCRUDEndpoints(name);
21 | }
22 | /**
23 | * Create and store multiple entities' endpoints.
24 | * @param arrayOfEntity names of the resources.
25 | */
26 | public createEntities(arrayOfEntity: string[]): void {
27 | arrayOfEntity.forEach(this.createEntity.bind(this));
28 | }
29 | /**
30 | * Get the endpoint object as created by the name
31 | * @param {String} name name of the entity
32 | */
33 | public getEndpoint(name: string): Endpoint {
34 | return this.endpoints[name];
35 | }
36 | /**
37 | * Create the basic endpoints handlers for CRUD operations
38 | */
39 | private createBasicCRUDEndpoints(name: string): Endpoint {
40 | const endpoints = new Endpoint(name, `${this.url}/${name}`);
41 | return endpoints;
42 | }
43 | }
44 |
45 | // TODO: Can we extract this logic to an environment variable?
46 | let API_URL;
47 | if (
48 | window.location.hostname === 'localhost' ||
49 | window.location.hostname === '127.0.0.1' ||
50 | window.location.hostname === ''
51 | ) {
52 | API_URL = LOCAL_API_URL;
53 | } else if (window.location.hostname === 'app.mchacks.ca') {
54 | API_URL = PROD_API_URL;
55 | } else {
56 | API_URL = DEV_API_URL;
57 | }
58 |
59 | export default new API(API_URL);
60 |
--------------------------------------------------------------------------------
/src/features/Checkin/Email.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ErrorMessage,
3 | FastField,
4 | Formik,
5 | FormikProps,
6 | FormikValues,
7 | } from 'formik';
8 | import * as React from 'react';
9 | import { object, string } from 'yup';
10 |
11 | import { EMAIL_LABEL } from '../../config';
12 | import { Form, SubmitBtn } from '../../shared/Form';
13 | import {
14 | Error as ErrorComponent,
15 | Input,
16 | } from '../../shared/Form/FormikElements';
17 |
18 | interface IEmailProps {
19 | onSubmit: (email: string) => void;
20 | }
21 |
22 | export const Email: React.FunctionComponent = (
23 | props: IEmailProps
24 | ) => {
25 | return (
26 |
31 | {renderFormik}
32 |
33 | );
34 | };
35 |
36 | function handleSubmitFactory({
37 | onSubmit,
38 | }: IEmailProps): (
39 | values: FormikValues,
40 | { setSubmitting }: FormikProps
41 | ) => Promise {
42 | return async (values, { setSubmitting }: FormikProps) => {
43 | await onSubmit(values.email);
44 | setSubmitting(false);
45 | };
46 | }
47 |
48 | function renderFormik(fp: FormikProps) {
49 | return (
50 |
63 | );
64 | }
65 |
66 | function getValidationSchema() {
67 | return object().shape({
68 | email: string().required('Required').email('Must be a valid email'),
69 | });
70 | }
71 |
--------------------------------------------------------------------------------
/src/api/sponsor.ts:
--------------------------------------------------------------------------------
1 | import { AxiosPromise, AxiosResponse } from 'axios';
2 | import { APIResponse } from '.';
3 | import { APIRoute, CACHE_SPONSOR_KEY, ISponsor } from '../config';
4 | import LocalCache from '../util/LocalCache';
5 | import API from './api';
6 |
7 | class SponsorAPI {
8 | constructor() {
9 | API.createEntity(APIRoute.SPONSOR);
10 | API.createEntity(APIRoute.SPONSOR_SELF);
11 | }
12 | /**
13 | * Create an account.
14 | * @param sponsor The sponsor that you want to create
15 | */
16 | public create(sponsor: ISponsor): AxiosPromise {
17 | return API.getEndpoint(APIRoute.SPONSOR).create(sponsor);
18 | }
19 | /**
20 | * Get the logged-in user's sponsor information, if they have a sponsor info.
21 | */
22 | public async getSelf(
23 | overrideCache?: boolean
24 | ): Promise>> {
25 | const cached: any = LocalCache.get(CACHE_SPONSOR_KEY);
26 | if (cached && !overrideCache) {
27 | return cached as AxiosResponse>;
28 | }
29 | const value = await API.getEndpoint(APIRoute.SPONSOR_SELF).getAll();
30 | LocalCache.set(CACHE_SPONSOR_KEY, value);
31 | return value;
32 | }
33 | /**
34 | * Get information about a sponsor
35 | * @param id the ID of the sponsor
36 | */
37 | public get(id: string): AxiosPromise {
38 | return API.getEndpoint(APIRoute.SPONSOR).getOne({ id });
39 | }
40 |
41 | /**
42 | * Update sponsor information
43 | * @param sponsor The sponsor object with an id
44 | */
45 | public update(sponsor: ISponsor): AxiosPromise {
46 | const key = CACHE_SPONSOR_KEY + '-' + sponsor.id;
47 | const value = API.getEndpoint(APIRoute.SPONSOR).patch(sponsor, sponsor);
48 | LocalCache.remove(CACHE_SPONSOR_KEY);
49 | LocalCache.remove(key);
50 | return value;
51 | }
52 | }
53 | export const Sponsor = new SponsorAPI();
54 | export default Sponsor;
55 |
--------------------------------------------------------------------------------
/src/features/Dashboard/DashboardText.tsx:
--------------------------------------------------------------------------------
1 | export const Application: string = 'Application';
2 |
3 | export const Profile: string = 'Profile';
4 |
5 | // Dashboard
6 | export const Confirmation: string = 'Confirmation';
7 |
8 | export const Team: string = 'Team';
9 |
10 | export const HackPass: string = 'HackPass';
11 |
12 | export const BusDeposit: string = 'Bus Deposit';
13 |
14 | export const Search: string = 'Search';
15 |
16 | export const SponsorOnboarding: string = 'Onboarding';
17 |
18 | export const Checkin: string = 'Check In';
19 |
20 | // Confirm Attendence
21 | export const ConfirmPresence: string = 'Confirm presence | McHacks';
22 |
23 | export const ConfirmHeader: string = 'Confirm your Attendance';
24 |
25 | export const ConfirmDeadLine: string =
26 | 'Please confirm your attendance before Friday, January 25, 2019.';
27 |
28 | export const ConfirmParagraph: string =
29 | 'We are excited to offer you a spot at McHacks.';
30 |
31 | export const Liability: string = 'Liability and Photo Release';
32 |
33 | export const LegalReview: string =
34 | '*Students under the age of 18 must have their parent or legal guardian review the following document.';
35 |
36 | // Confirm Account
37 |
38 | export const ConfirmAccount: string = 'Account Confirmed';
39 |
40 | export const ConfirmMessage: string =
41 | 'Your account was successfully created! Click continue to update your profile and manage your application.';
42 |
43 | export const Continue: string = 'Continue';
44 |
45 | export const UnableConfirm: string = 'Unable to confirm account';
46 |
47 | export const Error: string =
48 | 'Something went wrong when we made your account. Please try again later.';
49 |
50 | export const CreateAccount: string = 'Create account';
51 |
52 | export const Confirming: string = 'Confirming...';
53 |
54 | export const ConfirmLink: string = '/';
55 |
56 | export const AttemptingLink: string = '/account/create';
57 |
--------------------------------------------------------------------------------
/src/features/Onboarding/Onboarding.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Helmet from 'react-helmet';
3 | import DiscordOnboarding from './DiscordOnboarding';
4 | import GeneralOnboarding from './GeneralOnboarding';
5 | import NavContainer from './NavContainer';
6 | import NavLink from './NavLink';
7 | import Nav from './OnboardingNav';
8 |
9 | import * as CONSTANTS from '../../config/constants';
10 |
11 | export default class OnboardingContainer extends React.Component {
12 | constructor(props: any) {
13 | super(props);
14 | this.state = {
15 | activeSection: 'general',
16 | };
17 | }
18 |
19 | public async componentDidMount() {
20 | document.addEventListener('scroll', () => {
21 | this.setState({ scrollY: window.scrollY });
22 | });
23 | }
24 |
25 | public render() {
26 | return (
27 | <>
28 |
29 | Onboarding | {CONSTANTS.HACKATHON_NAME}
30 |
31 |
32 |
33 |
34 | this.setState({ activeSection: 'general' })}
37 | >
38 | General Onboarding
39 |
40 | this.setState({ activeSection: 'discord' })}
43 | >
44 | Discord Onboarding
45 |
46 |
47 |
48 |
49 | {this.state.activeSection === 'general' ? (
50 | <>
51 |
52 | >
53 | ) : null}
54 |
55 | {this.state.activeSection === 'discord' ? (
56 | <>
57 |
58 | >
59 | ) : null}
60 | >
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/api/travel.ts:
--------------------------------------------------------------------------------
1 | import { AxiosResponse } from 'axios';
2 | import { APIRoute, CACHE_TRAVEL_KEY, ITravel } from '../config';
3 | import LocalCache from '../util/LocalCache';
4 | import API from './api';
5 | import APIResponse from './APIResponse';
6 |
7 | class TravelAPI {
8 | constructor() {
9 | API.createEntity(APIRoute.TRAVEL_SELF);
10 | API.createEntity(APIRoute.TRAVEL_EMAIL);
11 | API.createEntity(APIRoute.TRAVEL);
12 | }
13 | /**
14 | * Get the logged-in user's travel information, if they have a travel info.
15 | */
16 | public async getSelf(
17 | overrideCache?: boolean
18 | ): Promise>> {
19 | const cached: any = LocalCache.get(CACHE_TRAVEL_KEY);
20 | if (cached && !overrideCache) {
21 | return cached as AxiosResponse>;
22 | }
23 | const value = await API.getEndpoint(APIRoute.TRAVEL_SELF).getAll();
24 | LocalCache.set(CACHE_TRAVEL_KEY, value);
25 | return value;
26 | }
27 | /**
28 | * Get information about a travel
29 | * @param id the ID of the travel
30 | */
31 | public async get(
32 | id: string,
33 | overrideCache?: boolean
34 | ): Promise>> {
35 | const key = CACHE_TRAVEL_KEY + '-' + id;
36 | const cached: any = LocalCache.get(key);
37 | if (cached && !overrideCache) {
38 | return cached as Promise>>;
39 | }
40 | const value = await API.getEndpoint(APIRoute.TRAVEL).getOne({ id });
41 | LocalCache.set(key, value);
42 | return value;
43 | }
44 |
45 | /**
46 | * Get information about a travel
47 | * @param id the ID of the travel
48 | */
49 | public async getByEmail(
50 | email: string,
51 | overrideCache?: boolean
52 | ): Promise>> {
53 | const value = await API.getEndpoint(APIRoute.TRAVEL_EMAIL).getOne({
54 | id: email,
55 | });
56 | return value;
57 | }
58 | }
59 |
60 | export const Travel = new TravelAPI();
61 |
62 | export default Travel;
63 |
--------------------------------------------------------------------------------
/src/shared/Form/FormikElements/PhoneNumberInput.tsx:
--------------------------------------------------------------------------------
1 | import { FieldProps } from 'formik';
2 | import { parsePhoneNumberFromString } from 'libphonenumber-js';
3 | import React from 'react';
4 | import PhoneInput from 'react-phone-number-input';
5 | import 'react-phone-number-input/style.css';
6 | import { Label, LabelText } from '..';
7 | import PhoneNumberContainer from './PhoneNumberContainer';
8 |
9 | interface IPhoneNumberInputProps {
10 | label: string;
11 | placeholder?: string;
12 | required?: boolean;
13 | disabled?: boolean;
14 | showOptionalLabel?: boolean;
15 | }
16 |
17 | const PhoneNumberInput: React.FC = ({
18 | field,
19 | form,
20 | label,
21 | placeholder,
22 | required,
23 | disabled,
24 | showOptionalLabel,
25 | }) => {
26 | const handleChange = (value?: string) => {
27 | if (value) {
28 | const phoneNumber = parsePhoneNumberFromString(value);
29 | if (phoneNumber) {
30 | const countryCode = phoneNumber.countryCallingCode;
31 | const nationalNumber = phoneNumber.nationalNumber;
32 | const formattedValue = `+${countryCode}${nationalNumber}`;
33 | form.setFieldValue(field.name, formattedValue);
34 | } else {
35 | form.setFieldValue(field.name, value);
36 | }
37 | } else {
38 | form.setFieldValue(field.name, '');
39 | }
40 | };
41 |
42 | return (
43 |
44 |
45 |
50 |
60 |
61 |
62 | );
63 | };
64 |
65 | export { PhoneNumberInput as PhoneNumberInput };
66 |
--------------------------------------------------------------------------------
/src/features/Dashboard/SponsorDashboard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { FrontendRoute as routes, UserType } from '../../config';
4 |
5 | import WithToasterContainer from '../../shared/HOC/withToaster';
6 | import DashboardView, { IDashboardCard } from './View';
7 |
8 | import AccountIcon from '../../assets/images/dashboard-account.svg';
9 | import SearchIcon from '../../assets/images/dashboard-search.svg';
10 | import SponsorIcon from '../../assets/images/dashboard-sponsor.svg';
11 |
12 | import { getSponsorInfo } from '../../util/UserInfoHelperFunctions';
13 |
14 | import * as DashboardText from './DashboardText';
15 |
16 | interface ISponsorDashboardProps {
17 | userType: UserType;
18 | }
19 |
20 | interface ISponsorDashboardState {
21 | hasSponsor: boolean;
22 | }
23 |
24 | class SponsorDashboard extends React.Component<
25 | ISponsorDashboardProps,
26 | ISponsorDashboardState
27 | > {
28 | constructor(props: ISponsorDashboardProps) {
29 | super(props);
30 | this.state = {
31 | hasSponsor: false,
32 | };
33 | }
34 |
35 | public async componentDidMount() {
36 | const sponsorResponse = await getSponsorInfo();
37 | if (sponsorResponse !== null) {
38 | this.setState({ hasSponsor: true });
39 | }
40 | }
41 |
42 | public render() {
43 | return (
44 |
45 | );
46 | }
47 |
48 | private generateCards() {
49 | const cards: IDashboardCard[] = [
50 | {
51 | title: DashboardText.Search,
52 | route: routes.SPONSOR_SEARCH_PAGE,
53 | imageSrc: SearchIcon,
54 | },
55 | {
56 | title: DashboardText.Profile,
57 | route: routes.EDIT_ACCOUNT_PAGE,
58 | imageSrc: AccountIcon,
59 | },
60 | {
61 | title: DashboardText.SponsorOnboarding,
62 | route: routes.SPONSOR_ONBOARDING_PAGE,
63 | imageSrc: SponsorIcon,
64 | },
65 | ];
66 | return cards;
67 | }
68 | }
69 | export default WithToasterContainer(SponsorDashboard);
70 |
--------------------------------------------------------------------------------
/src/shared/Form/Autosuggest.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as Autosuggest from 'react-autosuggest';
3 | import styled from 'styled-components';
4 | import inputStyles from '../Styles/inputStyles';
5 |
6 | // Confused? https://github.com/DefinitelyTyped/DefinitelyTyped/issues/14224#issuecomment-428814136
7 | const StringAutosuggester =
8 | Autosuggest as unknown as new () => Autosuggest;
9 |
10 | const AutosuggestWrapper = styled.div`
11 | .react-autosuggest__container {
12 | position: relative;
13 | }
14 |
15 | .react-autosuggest__input {
16 | ${inputStyles};
17 | }
18 |
19 | .react-autosuggest__input--focused {
20 | outline: none;
21 | }
22 |
23 | .react-autosuggest__suggestions-container {
24 | display: none;
25 | }
26 |
27 | .react-autosuggest__suggestions-container--open {
28 | display: block;
29 | padding-top: 5px;
30 | padding-bottom: 5px;
31 | position: absolute;
32 | margin-top: -26px;
33 | width: 100%;
34 | align-self: center;
35 | background: ${(props) => props.theme.colors.white};
36 | overflow-y: auto;
37 | max-height: 200px;
38 | border: 1px solid ${(props) => props.theme.colors.black30};
39 | font-family: 'Hind Siliguri', sans-serif;
40 | border-radius: 2px;
41 | z-index: 10;
42 | }
43 |
44 | .react-autosuggest__suggestions-list {
45 | margin: 0;
46 | padding: 0;
47 | list-style-type: none;
48 | }
49 |
50 | .react-autosuggest__suggestion {
51 | font-weight: normal;
52 | cursor: pointer;
53 | padding: 10px 20px;
54 | }
55 |
56 | .react-autosuggest__suggestion--highlighted {
57 | font-weight: normal;
58 | background-color: ${(props) => props.theme.colors.black10};
59 | color: ${(props) => props.theme.colors.black70};
60 | }
61 |
62 | .react-autosuggest__suggestion--selected {
63 | font-weight: normal;
64 | background-color: ${(props) => props.theme.colors.black10};
65 | color: ${(props) => props.theme.colors.black70};
66 | }
67 | `;
68 |
69 | export const StyledAutosuggest = (props: any) => (
70 |
71 |
72 |
73 | );
74 |
75 | export const AutosuggestItem = styled.div``;
76 |
--------------------------------------------------------------------------------
/src/features/Sponsor/SocialMediaBar.tsx:
--------------------------------------------------------------------------------
1 | import { Flex } from '@rebass/grid';
2 | import React from 'react';
3 | import { Image } from '../../shared/Elements';
4 |
5 | import Facebook from '../../assets/images/fb-logo.svg';
6 | import GitHub from '../../assets/images/github-logo.svg';
7 | import Instagram from '../../assets/images/ig-logo.svg';
8 | import Twitter from '../../assets/images/twitter-logo.svg';
9 |
10 | const SocialMediaBar: React.FC = () => {
11 | return (
12 |
13 |
22 |
23 |
32 |
33 |
42 |
43 |
52 |
69 |
70 | );
71 | };
72 |
73 | export default SocialMediaBar;
74 |
--------------------------------------------------------------------------------
/src/assets/images/dashboard-hackpass.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/pages/Account/Create.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Helmet from 'react-helmet';
3 | import ManageAccountForm, {
4 | ManageAccountModes,
5 | } from '../../features/Account/ManageAccountForm';
6 |
7 | import * as CONSTANTS from '../../config/constants';
8 | import { H1 } from '../../shared/Elements';
9 |
10 | import GirlAtHome from '../../assets/images/girl-at-home.svg';
11 |
12 | const CreateAccountPage: React.FC = () => (
13 | <>
14 |
15 | Create Account | {CONSTANTS.HACKATHON_NAME}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
Create your account
25 |
26 |
27 |
28 |
84 |
85 | >
86 | );
87 |
88 | export default CreateAccountPage;
89 |
--------------------------------------------------------------------------------
/src/config/majors.ts:
--------------------------------------------------------------------------------
1 | const MajorsList = [
2 | 'Accounting',
3 | 'Actuarial Science',
4 | 'Aerospace Engineering',
5 | 'Anthropology',
6 | 'Applied Math',
7 | 'Applied Science',
8 | 'Architecture',
9 | 'Biochemistry',
10 | 'Bioengineering',
11 | 'Biology',
12 | 'Biotechnology',
13 | 'Business',
14 | 'Business Analytics',
15 | 'Chemical Engineering',
16 | 'Chemistry',
17 | 'Civil Engineering',
18 | 'Cognitive Science',
19 | 'Communications',
20 | 'Computer Engineering',
21 | 'Computer Science',
22 | 'Criminology',
23 | 'Cultural Studies',
24 | 'Data Science',
25 | 'Dentistry',
26 | 'Earth Sciences',
27 | 'East Asian Studies',
28 | 'Economics',
29 | 'Education',
30 | 'Electrical Engineering',
31 | 'Engineering',
32 | 'Engineering Chemistry',
33 | 'Engineering Physics',
34 | 'English',
35 | 'Entrepreneurship',
36 | 'Environment',
37 | 'Environmental Engineering',
38 | 'Film',
39 | 'Finance',
40 | 'Fine Arts',
41 | 'Food Science',
42 | 'Geography',
43 | 'Geological Engineering',
44 | 'Gender Studies',
45 | 'Graphic Design',
46 | 'Health Science',
47 | 'History',
48 | 'Industrial Relations',
49 | 'Information Systems',
50 | 'International Development',
51 | 'Journalism',
52 | 'Kinesiology',
53 | 'Law',
54 | 'Liberal Arts',
55 | 'Life Sciences',
56 | 'Linguistics',
57 | 'Literature',
58 | 'Management',
59 | 'Materials Engineering',
60 | 'Mathematics',
61 | 'Marketing',
62 | 'Mechanical Engineering',
63 | 'Mechatronics Engineering',
64 | 'Medicine',
65 | 'Mineral Engineering',
66 | 'Mining Engineering',
67 | 'Music',
68 | 'Nanotechnology Engineering',
69 | 'Neuroscience',
70 | 'Nuclear Engineering',
71 | 'Nursing',
72 | 'Nutrition',
73 | 'Information Systems',
74 | 'Petroleum Engineering',
75 | 'Pharmacology',
76 | 'Philosophy',
77 | 'Photography',
78 | 'Physics',
79 | 'Physiology',
80 | 'Political Science',
81 | 'Public Relations',
82 | 'Psychology',
83 | 'Religious Studies',
84 | 'Retail Management',
85 | 'Social Work',
86 | 'Sociology',
87 | 'Software Engineering',
88 | 'Sustainability',
89 | 'Systems Design Engineering',
90 | 'Statistics',
91 | 'Theatre',
92 | 'Urban Planning',
93 | 'Urban Systems',
94 | 'Other',
95 | ];
96 |
97 | export const Majors = MajorsList.map((v) => ({ label: v, value: v }));
98 | export default Majors;
99 |
--------------------------------------------------------------------------------
/src/shared/Form/FormikElements/PhoneNumberContainer.tsx:
--------------------------------------------------------------------------------
1 | // PhoneNumberContainer.tsx
2 |
3 | import styled, { css } from 'styled-components';
4 | import { inputStyles } from '../../Styles/inputStyles';
5 |
6 | export const PhoneNumberContainer = styled.div`
7 | .phone-number-input {
8 | display: flex;
9 | align-items: center;
10 | width: 100%;
11 | }
12 |
13 | .phone-number-input .PhoneInputInput {
14 | ${inputStyles}
15 | border-radius: 8px;
16 | padding: 0 12px;
17 | font-size: 16px;
18 | color: ${(props) => props.theme.colors.black80};
19 |
20 | &::placeholder {
21 | color: ${(props) => props.theme.colors.black40};
22 | }
23 |
24 | &:focus {
25 | outline: none;
26 | box-shadow: 0 0 0 2px ${(props) => props.theme.colors.blueLight};
27 | }
28 | }
29 |
30 | .phone-number-input .PhoneInputCountry {
31 | background-color: ${(props) => props.theme.colors.white};
32 | border: 1px solid ${(props) => props.theme.colors.greyLight};
33 | border-radius: 8px;
34 | padding: 4px 8px;
35 | cursor: pointer;
36 | display: flex;
37 | align-items: center;
38 | height: 35px;
39 | margin-top: 10px;
40 |
41 | svg {
42 | width: 16px;
43 | height: 16px;
44 | fill: ${(props) => props.theme.colors.black80};
45 | }
46 |
47 | &:hover {
48 | background-color: ${(props) => props.theme.colors.greyLight};
49 | }
50 | }
51 |
52 | .phone-number-input .PhoneInputCountrySelectDropdown {
53 | background-color: ${(props) => props.theme.colors.white};
54 | border: 1px solid ${(props) => props.theme.colors.black};
55 | border-radius: 4px;
56 | padding: 8px;
57 | max-height: 200px;
58 | overflow-y: auto;
59 | z-index: 1000;
60 | }
61 |
62 | .phone-number-input .PhoneInputCountrySelectOption {
63 | padding: 8px;
64 | border-radius: 4px;
65 | cursor: pointer;
66 | color: ${(props) => props.theme.colors.black80};
67 | font-family: ${(props) => props.theme.fonts.header};
68 |
69 | &:hover {
70 | background-color: ${(props) => props.theme.colors.greyLight};
71 | color: ${(props) => props.theme.colors.black};
72 | }
73 |
74 | &.PhoneInputCountrySelectOption--selected {
75 | background-color: ${(props) => props.theme.colors.blueLight};
76 | color: ${(props) => props.theme.colors.white};
77 | }
78 | }
79 | `;
80 |
81 | export default PhoneNumberContainer;
82 |
--------------------------------------------------------------------------------
/src/features/Dashboard/View.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex } from '@rebass/grid';
2 | import * as React from 'react';
3 |
4 | import { Card, H1, H2, Image, LinkDuo } from '../../shared/Elements';
5 |
6 | import theme from '../../shared/Styles/theme';
7 |
8 | interface IDashboardView {
9 | cards: IDashboardCard[];
10 | title: string;
11 | subtitle?: string;
12 | }
13 |
14 | export interface IDashboardCard {
15 | title: string;
16 | route: string;
17 | imageSrc: any;
18 | validation?: () => boolean;
19 | hidden?: boolean;
20 | disabled?: boolean;
21 | }
22 |
23 | const DashboardView: React.FunctionComponent = ({
24 | cards,
25 | title,
26 | subtitle,
27 | }) => {
28 | return (
29 |
30 |
31 |
32 | {title}
33 |
34 | {subtitle ? (
35 |
40 | {subtitle}
41 |
42 | ) : (
43 | ''
44 | )}
45 |
46 | {cards.map((card) => (
47 |
54 |
59 |
64 | {card.title}
65 |
66 |
67 |
68 |
69 | ))}
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | function eventHandleWrapperFactory(card: IDashboardCard) {
77 | return (e: React.MouseEvent) => {
78 | if (card.disabled || (card.validation && !card.validation())) {
79 | e.preventDefault();
80 | }
81 | };
82 | }
83 |
84 | export default DashboardView;
85 |
--------------------------------------------------------------------------------
/src/shared/Elements/ViewPDF.tsx:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'buffer';
2 | import * as React from 'react';
3 | import { Hacker } from '../../api';
4 | import Button, { ButtonVariant } from './Button';
5 |
6 | interface IViewPDFProps {
7 | hackerId: string;
8 | }
9 | interface IViewPDFState {
10 | isLoading: boolean;
11 | }
12 | class ViewPDFComponent extends React.Component {
13 | constructor(props: IViewPDFProps) {
14 | super(props);
15 | this.state = {
16 | isLoading: false,
17 | };
18 | this.handleClick = this.handleClick.bind(this);
19 | }
20 |
21 | public render() {
22 | return (
23 |
30 | View Current Resume
31 |
32 | );
33 | }
34 |
35 | private handleClick(
36 | props: IViewPDFProps
37 | ): (event: React.MouseEvent) => void {
38 | return () => {
39 | this.setState({
40 | isLoading: true,
41 | });
42 | const pdfWindow = window.open('');
43 | if (pdfWindow) {
44 | pdfWindow.document.write('Loading PDF... ');
45 | }
46 | Hacker.downloadResume(props.hackerId)
47 | .then((response) => {
48 | const resume = response.data.data.resume;
49 | const bufferObj = Buffer.from(resume[0].data);
50 | const pdf = bufferObj.toString('base64');
51 | if (pdfWindow) {
52 | pdfWindow.document.body.innerHTML = '';
53 | pdfWindow.document.write(
54 | ""
57 | );
58 | }
59 | this.setState({
60 | isLoading: false,
61 | });
62 | })
63 | .catch((error) => {
64 | console.error(error);
65 | this.setState({
66 | isLoading: false,
67 | });
68 | if (pdfWindow) {
69 | pdfWindow.document.body.innerHTML = '';
70 | pdfWindow.document.write(
71 | 'Could not find Resume. If you are the hacker, please upload it again. '
72 | );
73 | }
74 | });
75 | };
76 | }
77 | }
78 |
79 | export default ViewPDFComponent;
80 |
--------------------------------------------------------------------------------
/src/assets/images/ig-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/shared/Elements/Clipboard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { Box, Flex } from '@rebass/grid';
4 | import Clipboard from 'clipboard';
5 |
6 | import CopyImage from '../../assets/images/copy-icon.svg';
7 | import Image from './Image';
8 |
9 | interface IClipboardProps {
10 | value: string;
11 | onSuccess?: (e: any) => void;
12 | onError?: (e: any) => void;
13 | }
14 |
15 | class ClipboardComponent extends React.Component {
16 | private clipboard: Clipboard;
17 | private copy: Element | null;
18 | private text: Element | null;
19 | constructor(props: IClipboardProps) {
20 | super(props);
21 | this.state = {};
22 | }
23 |
24 | public render() {
25 | const { value } = this.props;
26 | return (
27 |
28 | {
30 | this.text = element;
31 | }}
32 | >
33 | {value}
34 |
35 |
36 |
37 | {
41 | this.copy = element;
42 | }}
43 | />
44 |
45 |
46 |
47 |
60 |
61 | );
62 | }
63 |
64 | public componentDidMount() {
65 | const button = this.copy;
66 | const input = this.text;
67 | if (button && input) {
68 | this.clipboard = new Clipboard(button, {
69 | target: () => input,
70 | });
71 | const success = this.props.onSuccess
72 | ? this.props.onSuccess
73 | : (e: any) => null;
74 | const error = this.props.onError ? this.props.onError : (e: any) => null;
75 |
76 | this.clipboard.on('success', success);
77 | this.clipboard.on('error', error);
78 | }
79 | }
80 |
81 | public componentWillUnmount() {
82 | this.clipboard.destroy();
83 | }
84 | }
85 |
86 | export default ClipboardComponent;
87 |
--------------------------------------------------------------------------------
/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/features/Application/PaginationHeader/PaginationHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NumberBubble from './NumberBubble';
3 | import NumberPageText from './NumberPageText';
4 | import SeparatingBar from './SeperatingBar';
5 |
6 | /**
7 | * Determines whether NumberBubble or NumberPageText should be purple-filled
8 | * @param i - Page number to check
9 | * @param pageNumber - Page number that the user is currently on
10 | */
11 | function isFilled(i: number, pageNumber: number): boolean {
12 | return i <= pageNumber;
13 | }
14 |
15 | /**
16 | * Determines whether NumberBubble or NumberPageText should be purple-filled
17 | * @param i - Page number to check
18 | * @param pageNumber - Page number that the user is currently on
19 | * @param lastCompletedPage - Last application page the user has successfully completed
20 | */
21 | function isCheck(
22 | i: number,
23 | pageNumber: number,
24 | lastCompletedPage: number
25 | ): boolean {
26 | return i < lastCompletedPage && i !== pageNumber;
27 | }
28 |
29 | interface IPaginationHeaderProps {
30 | pageNumber: number;
31 | totalPages: number;
32 | lastCompletedPage: number;
33 | }
34 |
35 | /**
36 | * Component that displays page numbers for Applications
37 | */
38 | const PaginationHeader: React.FC = (props) => {
39 | const elements = [];
40 |
41 | for (let i = 0; i < props.totalPages; i++) {
42 | const pageNumber = i + 1;
43 | const isCurrentPage = pageNumber === props.pageNumber - 1;
44 | const beforeCurrentPage = pageNumber <= props.pageNumber - 1;
45 | const fill = isFilled(pageNumber, props.pageNumber);
46 | const checked = isCheck(
47 | pageNumber,
48 | props.pageNumber,
49 | props.lastCompletedPage
50 | );
51 |
52 | elements.push(
53 |
54 |
55 |
56 | );
57 |
58 | if (i !== props.totalPages - 1) {
59 | // Only display the separating bar if not the last element
60 | elements.push(
61 |
66 | );
67 | }
68 | }
69 |
70 | return (
71 |
80 | {elements}
81 |
82 | );
83 | };
84 |
85 | export default PaginationHeader;
86 |
--------------------------------------------------------------------------------
/src/features/Team/TeamDescription.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { IMemberName, ITeam } from '../../config';
4 | import theme from '../../shared/Styles/theme';
5 |
6 | import { toast } from 'react-toastify';
7 | import ClipboardComponent from '../../shared/Elements/Clipboard';
8 | import TextButton from '../../shared/Elements/TextButton';
9 | import WithToasterContainer from '../../shared/HOC/withToaster';
10 | import MemberList from './MemberList/MemberList';
11 |
12 | interface ITeamDescriptionProps {
13 | team: ITeam;
14 | members: IMemberName[];
15 | onLeaveTeam: () => void;
16 | isLeavingTeam: boolean;
17 | }
18 |
19 | const TeamDescription: React.FC = (
20 | props: ITeamDescriptionProps
21 | ) => {
22 | return (
23 | <>
24 |
25 |
Team code
26 |
27 | Share this code with your team members to let them join:
28 |
29 |
30 |
35 |
36 |
37 |
38 |
42 |
43 |
48 | Leave team
49 |
50 |
51 |
78 | >
79 | );
80 | };
81 |
82 | function onCopied(e: any) {
83 | toast.success('Copied!');
84 | }
85 |
86 | function onCopyFailed(e: any) {
87 | toast.error('Error.');
88 | }
89 |
90 | export default WithToasterContainer(TeamDescription);
91 |
--------------------------------------------------------------------------------
/src/shared/HOC/withSponsorRedirect.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Navigate } from 'react-router-dom';
3 | import { FrontendRoute, ISponsor } from '../../config';
4 | import { getSponsorInfo } from '../../util/UserInfoHelperFunctions';
5 |
6 | enum authStates {
7 | authorized,
8 | unauthorized,
9 | undefined,
10 | }
11 |
12 | export interface ISponsorDirectOptions {
13 | // True, if user must be a sponsor, or False if user must not be sponsor
14 | requiredAuthState?: boolean;
15 | // Function that is called when user is a sponsor. This is used for further state verifications
16 | AuthVerification?: (hacker: ISponsor) => boolean;
17 | }
18 |
19 | const defaultOptions = {
20 | requiredAuthState: true,
21 | AuthVerification: (sponsor: ISponsor) => true,
22 | redirOnSuccess: false,
23 | };
24 |
25 | const withSponsorRedirect = (
26 | Component: React.ComponentType
,
27 | options: ISponsorDirectOptions = defaultOptions
28 | ) =>
29 | class extends React.Component
{
30 | private verification: (sponsor: ISponsor) => boolean;
31 |
32 | constructor(props: P) {
33 | super(props);
34 | this.state = {
35 | authState: authStates.undefined,
36 | };
37 | this.verification =
38 | options.AuthVerification || defaultOptions.AuthVerification;
39 | options.requiredAuthState =
40 | options.requiredAuthState !== undefined
41 | ? options.requiredAuthState
42 | : defaultOptions.requiredAuthState;
43 | }
44 |
45 | public async componentDidMount() {
46 | const selfInfo = await getSponsorInfo();
47 | if (selfInfo) {
48 | const verified = this.verification(selfInfo);
49 | this.setState({
50 | authState: verified ? authStates.authorized : authStates.unauthorized,
51 | });
52 | } else {
53 | this.setState({
54 | authState: authStates.unauthorized,
55 | });
56 | }
57 | }
58 |
59 | public render() {
60 | const { authState } = this.state;
61 | switch (authState) {
62 | case authStates.authorized:
63 | return options.requiredAuthState ? (
64 |
65 | ) : (
66 |
67 | );
68 | case authStates.unauthorized:
69 | return options.requiredAuthState ? (
70 |
71 | ) : (
72 |
73 | );
74 | default:
75 | return
;
76 | }
77 | }
78 | };
79 |
80 | export default withSponsorRedirect;
81 |
--------------------------------------------------------------------------------
/src/assets/images/dashboard-application.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Helmet from 'react-helmet';
3 | import { Navigate } from 'react-router';
4 | import { HACKATHON_NAME, UserType } from '../config';
5 | import HackerDashboard from '../features/Dashboard/HackerDashboard';
6 | import SponsorDashboard from '../features/Dashboard/SponsorDashboard';
7 | import StaffDashboardContainer from '../features/Dashboard/StaffDashboard';
8 | import { H1 } from '../shared/Elements';
9 | import { getUserInfo } from '../util';
10 |
11 | const DashboardPage: React.FC = () => {
12 | // Until we figure out what type of user account we are dealing with, store as UNKNOWN
13 | const [accountType, setAccountType] = useState(UserType.UNKNOWN);
14 |
15 | // Track if we are still loading data or not
16 | const [isLoading, setIsLoading] = useState(true);
17 |
18 | // When component mounts, figure out user's account type
19 | useEffect(() => {
20 | (async () => {
21 | try {
22 | const userInfo = await getUserInfo();
23 | if (userInfo) {
24 | setAccountType(userInfo.accountType);
25 | }
26 | } catch (e) {
27 | console.error(e);
28 | } finally {
29 | setIsLoading(false);
30 | }
31 | })();
32 | }, []);
33 |
34 | // If done loading and still don't know account type
35 | const dashboard = getDashboard(accountType);
36 |
37 | // If page is still loading, display loading message
38 | if (isLoading) {
39 | return Loading... ;
40 | } else if (!dashboard) {
41 | // If page is done and no dashboard to display, return 404
42 | return ;
43 | }
44 |
45 | return (
46 | <>
47 |
48 | Dashboard | {HACKATHON_NAME}
49 |
50 | {dashboard}
51 | >
52 | );
53 | };
54 |
55 | // Get the special type of dashboard for a specific user AccountType
56 | const getDashboard = (accountType: UserType) => {
57 | switch (accountType) {
58 | case UserType.HACKER:
59 | return ;
60 | case UserType.STAFF:
61 | return ;
62 | case UserType.HACKBOARD:
63 | return ;
64 | case UserType.SPONSOR_T1:
65 | return ;
66 | case UserType.SPONSOR_T2:
67 | return ;
68 | case UserType.SPONSOR_T3:
69 | return ;
70 | case UserType.SPONSOR_T4:
71 | return ;
72 | case UserType.SPONSOR_T5:
73 | return ;
74 | }
75 |
76 | return null;
77 | };
78 |
79 | export default DashboardPage;
80 |
--------------------------------------------------------------------------------
/src/shared/HOC/withHackerRedirect.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Navigate } from 'react-router-dom';
3 | import { FrontendRoute, IHacker, ISetting } from '../../config';
4 | import { getHackerInfo, getSettings } from '../../util/UserInfoHelperFunctions';
5 |
6 | enum authStates {
7 | authorized,
8 | unauthorized,
9 | undefined,
10 | }
11 |
12 | export interface IHackerDirectOptions {
13 | // True, if user must be a hacker, or False if user must not be hacker
14 | requiredAuthState?: boolean;
15 | // Function that is called when user is a hacker. This is used for further state verifications
16 | AuthVerification?: (hacker: IHacker, settings?: ISetting) => boolean;
17 | }
18 |
19 | const defaultOptions = {
20 | requiredAuthState: true,
21 | AuthVerification: (hacker: IHacker, settings: ISetting) => true,
22 | redirOnSuccess: false,
23 | };
24 |
25 | const withHackerRedirect = (
26 | Component: React.ComponentType
,
27 | options: IHackerDirectOptions = defaultOptions
28 | ) =>
29 | class extends React.Component
{
30 | private verification: (hacker: IHacker, settings: ISetting) => boolean;
31 |
32 | constructor(props: any) {
33 | super(props);
34 | this.state = {
35 | authState: authStates.undefined,
36 | };
37 | this.verification =
38 | options.AuthVerification || defaultOptions.AuthVerification;
39 | options.requiredAuthState =
40 | options.requiredAuthState !== undefined
41 | ? options.requiredAuthState
42 | : defaultOptions.requiredAuthState;
43 | }
44 |
45 | public async componentDidMount() {
46 | const selfInfo: IHacker | null = await getHackerInfo();
47 | const settings: ISetting | null = await getSettings();
48 | if (selfInfo && settings) {
49 | const verified = this.verification(selfInfo, settings);
50 | console.log(verified);
51 | this.setState({
52 | authState: verified ? authStates.authorized : authStates.unauthorized,
53 | });
54 | } else {
55 | this.setState({
56 | authState: authStates.unauthorized,
57 | });
58 | }
59 | }
60 |
61 | public render() {
62 | const { authState } = this.state;
63 | switch (authState) {
64 | case authStates.authorized:
65 | return options.requiredAuthState ? (
66 |
67 | ) : (
68 |
69 | );
70 | case authStates.unauthorized:
71 | return options.requiredAuthState ? (
72 |
73 | ) : (
74 |
75 | );
76 | default:
77 | return
;
78 | }
79 | }
80 | };
81 |
82 | export default withHackerRedirect;
83 |
--------------------------------------------------------------------------------
/src/assets/images/dashboard-account.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------