├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .nowignore ├── .prettierignore ├── .prettierrc ├── .vscode └── launch.json ├── History.md ├── LICENSE ├── README.md ├── amplify ├── .config │ └── project-config.json ├── backend │ ├── awscloudformation │ │ └── nested-cloudformation-stack.yml │ ├── backend-config.json │ └── hosting │ │ └── S3AndCloudFront │ │ ├── parameters.json │ │ └── template.json └── team-provider-info.json ├── amplifyPublishIgnore.json ├── apollo.config.js ├── config ├── buildAuth.js ├── site.js └── theme.js ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── launch.json ├── now.json ├── package.json ├── src ├── apps │ └── admin │ │ ├── components │ │ ├── affiliation.edit.js │ │ ├── codex.textinput.js │ │ ├── company.edit.js │ │ ├── companylinks.js │ │ ├── companylinks.select.js │ │ ├── profile.edit.js │ │ └── select │ │ │ ├── helpers │ │ │ ├── getSuggestions.js │ │ │ ├── index.js │ │ │ ├── renderInput.js │ │ │ └── renderSuggestions.js │ │ │ └── index.js │ │ ├── config │ │ ├── index.js │ │ └── styles.js │ │ ├── features │ │ ├── afffiliation.create │ │ │ ├── components │ │ │ │ ├── companySearch │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── getSuggestion.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── renderInput.js │ │ │ │ │ │ └── renderSuggestion.js │ │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ └── select.js │ │ │ ├── graphql │ │ │ │ ├── index.js │ │ │ │ ├── mutations.js │ │ │ │ └── queries.js │ │ │ ├── helpers │ │ │ │ ├── checkCanFormSubmit.js │ │ │ │ ├── checkForSectionErrors.js │ │ │ │ ├── getCurrentErrors.js │ │ │ │ ├── getInitialValues.js │ │ │ │ ├── handleCreateAffiliation.js │ │ │ │ ├── index.js │ │ │ │ ├── renderFormHeader.js │ │ │ │ └── validationSchema.js │ │ │ └── index.js │ │ ├── company.create │ │ │ ├── components │ │ │ │ ├── basics.js │ │ │ │ ├── categories │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── formatCategory.js │ │ │ │ │ │ ├── getSuggestions.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── renderInput.js │ │ │ │ │ │ └── renderSuggestions.js │ │ │ │ │ └── index.js │ │ │ │ ├── formErrorMessage.js │ │ │ │ ├── index.js │ │ │ │ ├── links │ │ │ │ │ ├── index.js │ │ │ │ │ └── links.input.js │ │ │ │ ├── location │ │ │ │ │ ├── address.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── locationPaper.js │ │ │ │ ├── logo.js │ │ │ │ ├── preview.js │ │ │ │ └── select.js │ │ │ ├── graphql │ │ │ │ ├── index.js │ │ │ │ ├── mutations.js │ │ │ │ └── queries.js │ │ │ ├── helpers │ │ │ │ ├── checkCanFormSubmit.js │ │ │ │ ├── checkForSectionErrors.js │ │ │ │ ├── getCurrentErrors.js │ │ │ │ ├── getDisplayedErrorMessage.js │ │ │ │ ├── getInitialValues.js │ │ │ │ ├── handleCreateCompany.js │ │ │ │ ├── index.js │ │ │ │ ├── renderExpansionPanel.js │ │ │ │ ├── renderFormHeader.js │ │ │ │ └── validationSchema.js │ │ │ └── index.js │ │ ├── profile.affiliations │ │ │ ├── components │ │ │ │ ├── avatar.js │ │ │ │ ├── content │ │ │ │ │ ├── index.js │ │ │ │ │ ├── primary.js │ │ │ │ │ └── secondary.js │ │ │ │ ├── controls │ │ │ │ │ ├── delete.js │ │ │ │ │ ├── edit.js │ │ │ │ │ └── index.js │ │ │ │ ├── edit.js │ │ │ │ └── index.js │ │ │ ├── controller.js │ │ │ ├── graphql │ │ │ │ ├── index.js │ │ │ │ ├── mutations.js │ │ │ │ └── queries.js │ │ │ ├── helpers │ │ │ │ ├── formatDates.js │ │ │ │ ├── index.js │ │ │ │ └── renderAffiliation.js │ │ │ └── index.js │ │ ├── profile.companies │ │ │ ├── components │ │ │ │ ├── avatar.js │ │ │ │ ├── content │ │ │ │ │ ├── index.js │ │ │ │ │ ├── primary.js │ │ │ │ │ └── secondary.js │ │ │ │ ├── controls │ │ │ │ │ ├── delete.js │ │ │ │ │ ├── edit.js │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── controller.js │ │ │ ├── graphql │ │ │ │ ├── index.js │ │ │ │ ├── mutations.js │ │ │ │ └── queries.js │ │ │ ├── helpers │ │ │ │ ├── formatDates.js │ │ │ │ ├── index.js │ │ │ │ └── renderCompany.js │ │ │ └── index.js │ │ └── profile.navigation │ │ │ ├── components │ │ │ ├── desktop.js │ │ │ └── mobile.js │ │ │ └── index.js │ │ ├── helpers.js │ │ ├── index.js │ │ └── routes │ │ ├── affiliation │ │ └── index.js │ │ ├── auth │ │ ├── company.js │ │ ├── confirm.js │ │ ├── create.js │ │ ├── createwiz.js │ │ ├── index.js │ │ ├── login.js │ │ ├── mocks.js │ │ └── profile.js │ │ ├── company │ │ ├── index.js │ │ └── location.js │ │ └── profile │ │ ├── graphql │ │ ├── index.js │ │ └── queries.js │ │ ├── index.js │ │ ├── listitems.js │ │ ├── profile.js │ │ └── styles.js ├── atoms │ ├── confirm.js │ ├── containers.js │ ├── index.js │ ├── inputs.js │ └── spinner.js ├── bootstrap.js ├── components │ ├── PrivateRoute.js │ ├── header │ │ ├── __mocks__ │ │ │ └── index.js │ │ ├── header.mobile.js │ │ ├── header.one.js │ │ ├── header.secondary.js │ │ ├── header.styles.js │ │ ├── header.three.js │ │ ├── header.two.js │ │ ├── helpers │ │ │ └── renderAvatar.js │ │ └── index.js │ ├── image.js │ ├── input │ │ ├── field.js │ │ ├── index.js │ │ ├── input.js │ │ └── label.js │ ├── layout.js │ ├── navigation │ │ └── navbar.js │ ├── search │ │ ├── helpers │ │ │ ├── getSuggestion.js │ │ │ ├── index.js │ │ │ ├── renderInput.js │ │ │ └── renderSuggestion.js │ │ └── index.js │ ├── seo.js │ └── transition.js ├── constants │ └── transition.js ├── features │ └── landinghero │ │ ├── hero.js │ │ ├── index.js │ │ ├── sidekick.js │ │ ├── sidekickItem.js │ │ └── styles.js ├── graphql │ ├── index.js │ ├── mutations │ │ └── index.js │ └── queries │ │ └── index.js ├── helpers │ ├── callAll.js │ ├── enums.js │ ├── formatPhoneNumber.js │ ├── truncateText.js │ └── wrapPageElement.js ├── html.js ├── images │ └── sls-logo.png ├── molecules │ ├── avatars.js │ └── index.js ├── pages │ ├── 404.js │ ├── about.js │ ├── app.js │ ├── companies │ │ └── index.js │ ├── index.js │ └── tags.js ├── services │ └── hacks.js ├── store │ ├── apollo.js │ ├── auth-context.js │ ├── createContext.js │ ├── modal-context.js │ ├── provider.js │ ├── useModal.js │ ├── user-context.js │ └── utils │ │ ├── auth-client.js │ │ ├── bootstrap.js │ │ ├── const.js │ │ └── useCallbackStatus.js └── templates │ ├── __mocks__ │ ├── listitems.js │ └── styles.js │ ├── company.js │ ├── company │ ├── companyCategories.js │ ├── companyContactLinks.js │ ├── contact.js │ ├── helpers.js │ ├── index.js │ ├── intelligence.js │ ├── location.js │ ├── locationmap.js │ └── news.js │ ├── profile.js │ └── tags.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "babel-preset-gatsby", 5 | { 6 | "targets": { 7 | "browsers": [">0.25%", "not dead"] 8 | } 9 | } 10 | ] 11 | ], 12 | "plugins": ["babel-plugin-styled-components"] 13 | } 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | }, 6 | plugins: ['react'], 7 | globals: { 8 | graphql: false, 9 | }, 10 | parser: 'babel-eslint', 11 | 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 8, 15 | ecmaFeatures: { 16 | experimentalObjectRestSpread: true, 17 | jsx: true, 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | #amplify 72 | amplify/\#current-cloud-backend 73 | amplify/.config/local-* 74 | amplify/backend/amplify-meta.json 75 | aws-exports.js 76 | awsconfiguration.json 77 | 78 | .env 79 | .env.* 80 | codex-tech-index.code-workspace 81 | -------------------------------------------------------------------------------- /.nowignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vscode 3 | node_modules 4 | public 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.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": "Gatsby develop", 9 | "type": "node", 10 | "request": "launch", 11 | "protocol": "inspector", 12 | "program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby", 13 | "args": ["develop"], 14 | "stopOnEntry": false, 15 | "runtimeArgs": ["--nolazy"], 16 | "sourceMaps": false 17 | }, 18 | { 19 | "name": "Gatsby build", 20 | "type": "node", 21 | "request": "launch", 22 | "protocol": "inspector", 23 | "program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby", 24 | "args": ["build"], 25 | "stopOnEntry": false, 26 | "runtimeArgs": ["--nolazy"], 27 | "sourceMaps": false 28 | }, 29 | 30 | { 31 | "type": "node", 32 | "request": "launch", 33 | "name": "Launch Program", 34 | "program": "${workspaceFolder}/develop" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeX LegalTech Index 2 | 3 | Explore a curated list of ~1200 companies changing the way legal is done. 4 | 5 | [Current Website](https://law.haus) vs. [Legacy Website](https://techindex.law.stanford.edu) 6 | 7 | -------------------------------------------------------------------------------- /amplify/.config/project-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "frontendtechlist", 3 | "version": "1.0", 4 | "frontend": "javascript", 5 | "javascript": { 6 | "framework": "react", 7 | "config": { 8 | "SourceDir": "src", 9 | "DistributionDir": "public", 10 | "BuildCommand": "yarn build", 11 | "StartCommand": "npm run-script start" 12 | } 13 | }, 14 | "providers": ["awscloudformation"] 15 | } 16 | -------------------------------------------------------------------------------- /amplify/backend/backend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "S3AndCloudFront": { 4 | "service": "S3AndCloudFront", 5 | "providerPlugin": "awscloudformation" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /amplify/backend/hosting/S3AndCloudFront/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "bucketName": "frontendtechlist-20190209123941-hostingbucket" 3 | } -------------------------------------------------------------------------------- /amplify/team-provider-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "master": { 3 | "awscloudformation": { 4 | "AuthRoleName": "frontendtechlist-20190209123828-authRole", 5 | "UnauthRoleArn": "arn:aws:iam::196309159802:role/frontendtechlist-20190209123828-unauthRole", 6 | "AuthRoleArn": "arn:aws:iam::196309159802:role/frontendtechlist-20190209123828-authRole", 7 | "Region": "us-east-1", 8 | "DeploymentBucketName": "frontendtechlist-20190209123828-deployment", 9 | "UnauthRoleName": "frontendtechlist-20190209123828-unauthRole", 10 | "StackName": "frontendtechlist-20190209123828", 11 | "StackId": "arn:aws:cloudformation:us-east-1:196309159802:stack/frontendtechlist-20190209123828/82e72fe0-2c91-11e9-8109-12b31d3117be" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /amplifyPublishIgnore.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /apollo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: { 3 | name: 'CodeX Tech Index [web]', 4 | includes: ['./src/**/*.js'], 5 | service: { 6 | name: 'stanford-tech-index', 7 | url: 'https://scti.ok8s.net/apollo', 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /config/buildAuth.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | global.fetch = require('node-fetch'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const AWS = require('aws-sdk'); 6 | 7 | const ACCESS_KEY = process.env.ACCESS_KEY; 8 | const SECRET_KEY = process.env.SECRET_KEY; 9 | const REGION = process.env.REGION; 10 | const USER_POOL = process.env.USERPOOL_ID; 11 | const CLIENT_ID = process.env.CLIENT_ID; 12 | const USERNAME = process.env.USERNAME; 13 | const PASSWORD = process.env.PASSWORD; 14 | 15 | AWS.config.update({ 16 | region: 'us-west-2', 17 | accessKeyId: ACCESS_KEY, 18 | secretAccessKey: SECRET_KEY, 19 | credentials: { 20 | accessKeyId: ACCESS_KEY, 21 | secretAccessKey: SECRET_KEY, 22 | }, 23 | }); 24 | 25 | if (ACCESS_KEY === undefined || SECRET_KEY === undefined) { 26 | throw new Error( 27 | 'Pre-build script could not authenticate because `AWS-related` environmental variables were not provided.' 28 | ); 29 | } 30 | 31 | if (USERNAME === undefined || PASSWORD === undefined) { 32 | throw new Error( 33 | '[PRISMA]: Requires authentication. Please supply USERNAME and PASSWORD as ENV' 34 | ); 35 | } 36 | 37 | const Cognito = new AWS.CognitoIdentityServiceProvider({ 38 | apiVersion: '2016-04-18', 39 | }); 40 | 41 | const params = { 42 | AuthFlow: 'ADMIN_NO_SRP_AUTH', 43 | ClientId: CLIENT_ID, 44 | UserPoolId: USER_POOL, 45 | AuthParameters: { 46 | USERNAME: USERNAME, 47 | PASSWORD: PASSWORD, 48 | }, 49 | }; 50 | 51 | async function getTempJWT() { 52 | return new Promise((res, rej) => { 53 | Cognito.adminInitiateAuth(params, (err, data) => { 54 | if (err) { 55 | console.log(err); 56 | rej(err); 57 | } 58 | const { AuthenticationResult } = data; 59 | const { IdToken } = AuthenticationResult; 60 | res({ jwt: IdToken }); 61 | }); 62 | }); 63 | } 64 | 65 | async function exportTempJWT(jwt) { 66 | const destination = path.resolve(__dirname, './tempjwt.json'); 67 | fs.unlink(destination, err => { 68 | fs.appendFile(destination, JSON.stringify(jwt), err => { 69 | if (err) throw err; 70 | console.log(`The data has been written to disk`); 71 | }); 72 | }); 73 | } 74 | 75 | async function getTemporaryAuthCreds() { 76 | const jwt = await getTempJWT(); 77 | return jwt; 78 | } 79 | 80 | module.exports = getTemporaryAuthCreds; 81 | -------------------------------------------------------------------------------- /config/site.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: `.env${ 3 | process.env.NODE_ENV === 'production' || process.env.LOCAL !== 'true' 4 | ? '.production' 5 | : '.local' 6 | }`, 7 | }); 8 | const theme = require('./theme'); 9 | 10 | module.exports = () => ({ 11 | social: { 12 | twitter: 'https://twitter.com/codexstanford', 13 | }, 14 | siteMetadata: { 15 | title: 'CodeX LegalTech Index', 16 | shortTitle: 'LegalTech Index', 17 | description: 18 | 'Explore a curated list of 1129 companies changing the way legal is done', 19 | hostname: 'law.haus', 20 | protocol: 'https', 21 | url: 'https://law.haus', 22 | }, 23 | api: { 24 | graphql: { 25 | endpoint: process.env.GATSBY_GRAPHQL_ENDPOINT, 26 | typeName: 'TechList', 27 | fieldName: 'allTechList', 28 | }, 29 | }, 30 | theme, 31 | }); 32 | -------------------------------------------------------------------------------- /config/theme.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | colors: { 4 | primary: '#b1040e', 5 | link: '#006CB8', 6 | hover: '#00548f', 7 | }, 8 | typography: { 9 | useNextVariants: true, 10 | fontFamily: [ 11 | 'Source Sans Pro', 12 | '-apple-system', 13 | 'BlinkMacSystemFont', 14 | '"Segoe UI"', 15 | 'Roboto', 16 | '"Helvetica Neue"', 17 | 'Arial', 18 | 'sans-serif', 19 | '"Apple Color Emoji"', 20 | '"Segoe UI Emoji"', 21 | '"Segoe UI Symbol"', 22 | ].join(','), 23 | }, 24 | palette: { 25 | white: { 26 | main: '#ffffff', 27 | }, 28 | primary: { 29 | main: '#b1040e', 30 | }, 31 | secondary: { 32 | main: '#04b1a8', 33 | contrastText: '#fff', 34 | }, 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppProvider from 'store/provider'; 3 | 4 | export const registerServiceWorker = () => true; 5 | 6 | export const onRouteUpdate = ({ location, prevLocation }) => { 7 | // console.log('new pathname', location.pathname); 8 | // console.log('old pathname', prevLocation ? prevLocation.pathname : null); 9 | // Track pageview with google analytics 10 | }; 11 | 12 | export const wrapRootElement = ({ element }) => { 13 | return {element}; 14 | }; 15 | 16 | // Page Transitions 17 | export const onPreRouteUpdate = ({ location, prevLocation }) => { 18 | // console.log('Gatsby started to change location to', location.pathname); 19 | // console.log( 20 | // 'Gatsby started to change location from', 21 | // prevLocation ? prevLocation.pathname : null 22 | // ); 23 | }; 24 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderToString } from 'react-dom/server'; 3 | import { ServerStyleSheet } from 'styled-components'; 4 | import AppProvider from 'store/provider'; 5 | 6 | export const replaceRenderer = ({ 7 | bodyComponent, 8 | replaceBodyHTMLString, 9 | setHeadComponents, 10 | }) => { 11 | // React Context in SSR/build 12 | const ConnectedBody = () => {bodyComponent}; 13 | replaceBodyHTMLString(renderToString(bodyComponent)); 14 | 15 | // Add styled-components in SSR/build 16 | const sheet = new ServerStyleSheet(); 17 | const bodyHTML = renderToString(sheet.collectStyles()); 18 | const styleElement = sheet.getStyleElement(); 19 | setHeadComponents(styleElement); 20 | }; 21 | 22 | export const wrapRootElement = ({ element }) => { 23 | return {element}; 24 | }; 25 | -------------------------------------------------------------------------------- /launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Gatsby develop", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby", 10 | "args": ["develop"], 11 | "stopOnEntry": false, 12 | "runtimeArgs": ["--nolazy"], 13 | "sourceMaps": false 14 | }, 15 | { 16 | "name": "Gatsby build", 17 | "type": "node", 18 | "request": "launch", 19 | "protocol": "inspector", 20 | "program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby", 21 | "args": ["build"], 22 | "stopOnEntry": false, 23 | "runtimeArgs": ["--nolazy"], 24 | "sourceMaps": false 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "codex", 4 | "build": { 5 | "env": { 6 | "ACCESS_KEY": "@access_key", 7 | "PORT": "8000", 8 | "SECRET_KEY": "@secret_key", 9 | "REGION": "us-west-2", 10 | "CLIENT_ID": "181177ggq1ot45s6t791vposkr", 11 | "USERPOOL_ID": "us-west-2_uzyDC8Snl", 12 | "USERNAME": "@username", 13 | "PASSWORD": "@password", 14 | "GATSBY_GOOGLE_MAPS_API_KEY": "@gatsby_google_maps_api_key", 15 | "GATSBY_BING_SEARCH_NEWS_API": "https://api.cognitive.microsoft.com/bing/v7.0/news/search", 16 | "GATSBY_BING_API_KEY": "@gatsby_bing_api_key", 17 | "GATSBY_ENGINE_API_KEY": "@apollo_engine_api_key", 18 | "GATSBY_APPLICATION_NAME": "Gatsby-Production-Now", 19 | "GATSBY_APPLICATION_VERSION": "0.0.1", 20 | "GATSBY_GRAPHQL_ENDPOINT": "@gatsby_graphql_endpoint", 21 | "GATSBY_GRAPHQL_IDE": "playground" 22 | } 23 | }, 24 | "env": { 25 | "ACCESS_KEY": "@access_key", 26 | "PORT": "8000", 27 | "SECRET_KEY": "@secret_key", 28 | "REGION": "us-west-2", 29 | "CLIENT_ID": "181177ggq1ot45s6t791vposkr", 30 | "USERPOOL_ID": "us-west-2_uzyDC8Snl", 31 | "USERNAME": "@username", 32 | "PASSWORD": "@password", 33 | "GATSBY_GOOGLE_MAPS_API_KEY": "@gatsby_google_maps_api_key", 34 | "GATSBY_BING_SEARCH_NEWS_API": "https://api.cognitive.microsoft.com/bing/v7.0/news/search", 35 | "GATSBY_BING_API_KEY": "@gatsby_bing_api_key", 36 | "GATSBY_ENGINE_API_KEY": "@apollo_engine_api_key", 37 | "GATSBY_APPLICATION_NAME": "Gatsby-Production-Now", 38 | "GATSBY_APPLICATION_VERSION": "0.0.1", 39 | "GATSBY_GRAPHQL_ENDPOINT": "@gatsby_graphql_endpoint", 40 | "GATSBY_GRAPHQL_IDE": "playground", 41 | "NODE_ENV": "production" 42 | }, 43 | "builds": [ 44 | { 45 | "src": "package.json", 46 | "use": "@now/static-build", 47 | "config": { "distDir": "public" } 48 | } 49 | ], 50 | "routes": [ 51 | { 52 | "src": "^/(.*).html", 53 | "headers": { "cache-control": "public,max-age=0,must-revalidate" }, 54 | "dest": "$1.html" 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /src/apps/admin/components/codex.textinput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Field } from 'formik'; 3 | import { TextField } from 'formik-material-ui'; 4 | 5 | export default function CodeXTextField({ 6 | name, 7 | type = 'text', 8 | label, 9 | component = TextField, 10 | errors, 11 | touched, 12 | InputLabelProps, 13 | ...rest 14 | }) { 15 | return ( 16 | <> 17 | 26 | {/* {fieldErrors && isTouched ?
{fieldErrors}
: null} */} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/apps/admin/components/companylinks.select.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Input from '@material-ui/core/Input'; 4 | import OutlinedInput from '@material-ui/core/OutlinedInput'; 5 | import FilledInput from '@material-ui/core/FilledInput'; 6 | import InputLabel from '@material-ui/core/InputLabel'; 7 | import MenuItem from '@material-ui/core/MenuItem'; 8 | import FormHelperText from '@material-ui/core/FormHelperText'; 9 | import FormControl from '@material-ui/core/FormControl'; 10 | import Select from '@material-ui/core/Select'; 11 | import { TextField } from 'formik-material-ui'; 12 | 13 | const organizationLinkType = [ 14 | { type: 'UrlPrivacyPolicy', niceName: 'Privacy Policy' }, 15 | { type: 'UrlSupport', niceName: 'Support' }, 16 | { type: 'UrlSales', niceName: 'Sales' }, 17 | { type: 'UrlTermsOfService', niceName: 'TOS' }, 18 | { type: 'UrlTwitter', niceName: 'Twitter' }, 19 | { type: 'UrlLinkedIn', niceName: 'LinkedIn' }, 20 | { type: 'UrlFacebook', niceName: 'Facebook' }, 21 | { type: 'UrlCrunchbase', niceName: 'Crunchbase' }, 22 | { type: 'UrlAngellist', niceName: 'Angellist' }, 23 | { type: 'UrlWebsite', niceName: 'Website' }, 24 | { type: 'EmailSales', niceName: 'Sales Email' }, 25 | { type: 'EmailSupport', niceName: 'Support Email' }, 26 | { type: 'Other', niceName: 'Other' }, 27 | ]; 28 | 29 | export default function SimpleSelect({ 30 | classes, 31 | field, 32 | form, 33 | values, 34 | styles, 35 | label = 'Link Type', 36 | options = organizationLinkType, 37 | }) { 38 | const inputLabel = React.useRef(null); 39 | const [labelWidth, setLabelWidth] = React.useState(0); 40 | React.useEffect(() => { 41 | setLabelWidth(inputLabel.current.offsetWidth); 42 | }, []); 43 | 44 | return ( 45 | 46 | 47 | {label} 48 | 49 | 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/apps/admin/components/select/helpers/getSuggestions.js: -------------------------------------------------------------------------------- 1 | import deburr from 'lodash/deburr'; 2 | 3 | export function getSuggestions(value, suggestions, { showEmpty = false } = {}) { 4 | const inputValue = deburr(value.trim()).toLowerCase(); 5 | const inputLength = inputValue.length; 6 | let count = 0; 7 | 8 | return inputLength === 0 && !showEmpty 9 | ? [] 10 | : suggestions.filter(suggestion => { 11 | const keep = 12 | count < 5 && 13 | suggestion.label.slice(0, inputLength).toLowerCase() === inputValue; 14 | 15 | if (keep) { 16 | count += 1; 17 | } 18 | 19 | return keep; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/apps/admin/components/select/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { renderInput } from './renderInput'; 2 | export { renderSuggestion } from './renderSuggestions'; 3 | export { getSuggestions } from './getSuggestions'; 4 | -------------------------------------------------------------------------------- /src/apps/admin/components/select/helpers/renderInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextField from '@material-ui/core/TextField'; 3 | import PropTypes from 'prop-types'; 4 | 5 | export function renderInput(inputProps) { 6 | const { InputProps, classes, ref, ...other } = inputProps; 7 | 8 | return ( 9 | 20 | ); 21 | } 22 | 23 | export default renderInput; 24 | -------------------------------------------------------------------------------- /src/apps/admin/components/select/helpers/renderSuggestions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import MenuItem from '@material-ui/core/MenuItem'; 4 | 5 | export function renderSuggestion(suggestionProps) { 6 | const { 7 | suggestion, 8 | index, 9 | itemProps, 10 | highlightedIndex, 11 | selectedItem, 12 | } = suggestionProps; 13 | const isHighlighted = highlightedIndex === index; 14 | const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1; 15 | 16 | return ( 17 | 27 | {suggestion.label} 28 | 29 | ); 30 | } 31 | renderSuggestion.propTypes = { 32 | highlightedIndex: PropTypes.number, 33 | index: PropTypes.number, 34 | itemProps: PropTypes.object, 35 | selectedItem: PropTypes.string, 36 | suggestion: PropTypes.shape({ label: PropTypes.string }).isRequired, 37 | }; 38 | 39 | export default renderSuggestion; 40 | -------------------------------------------------------------------------------- /src/apps/admin/components/select/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import deburr from 'lodash/deburr'; 4 | import Downshift from 'downshift'; 5 | import { makeStyles } from '@material-ui/styles'; 6 | import Paper from '@material-ui/core/Paper'; 7 | import { renderInput, renderSuggestion, getSuggestions } from './helpers'; 8 | 9 | export function Select({ 10 | setFieldValue, 11 | classes, 12 | options, 13 | name = '', 14 | InputLabelProps, 15 | InputProps, 16 | label = '', 17 | placeholder = '', 18 | ...props 19 | }) { 20 | const [inputValue, setInputValue] = React.useState(''); 21 | const [selectedItem, setSelectedItem] = React.useState([]); 22 | 23 | React.useEffect(() => { 24 | setFieldValue(name, selectedItem, false); 25 | }, [selectedItem]); 26 | 27 | function handleKeyDown(event) { 28 | if ( 29 | selectedItem.length && 30 | !inputValue.length && 31 | event.key === 'Backspace' 32 | ) { 33 | setSelectedItem(selectedItem.slice(0, selectedItem.length - 1)); 34 | } 35 | } 36 | 37 | function handleInputChange(event) { 38 | setInputValue(event.target.value); 39 | } 40 | 41 | function handleChange(item) { 42 | let newSelectedItem = [...selectedItem]; 43 | if (newSelectedItem.indexOf(item) === -1) { 44 | newSelectedItem = [...newSelectedItem, item]; 45 | } 46 | setInputValue(item.label); 47 | setSelectedItem(newSelectedItem); 48 | } 49 | 50 | const handleDelete = item => () => { 51 | const newSelectedItem = [...selectedItem]; 52 | newSelectedItem.splice(newSelectedItem.indexOf(item), 1); 53 | setSelectedItem(newSelectedItem); 54 | }; 55 | 56 | return ( 57 | 62 | {({ 63 | getInputProps, 64 | getItemProps, 65 | getLabelProps, 66 | isOpen, 67 | inputValue, 68 | selectedItem, 69 | highlightedIndex, 70 | }) => { 71 | const { onBlur, onChange, onFocus, ...inputProps } = getInputProps({ 72 | onKeyDown: handleKeyDown, 73 | placeholder: placeholder, 74 | }); 75 | return ( 76 |
77 | {renderInput({ 78 | fullwidth: props.fullwidth || true, 79 | classes, 80 | name, 81 | label, 82 | InputLabelProps: getLabelProps(), 83 | InputProps: { 84 | onBlur, 85 | onChange: event => { 86 | handleInputChange(event); 87 | onChange(event); 88 | }, 89 | onFocus, 90 | }, 91 | inputProps, 92 | })} 93 | {isOpen ? ( 94 | 95 | {getSuggestions(inputValue, options).map((suggestion, index) => 96 | renderSuggestion({ 97 | suggestion, 98 | index, 99 | itemProps: getItemProps({ item: suggestion }), 100 | highlightedIndex, 101 | selectedItem: selectedItem, 102 | }) 103 | )} 104 | 105 | ) : null} 106 |
107 | ); 108 | }} 109 |
110 | ); 111 | } 112 | 113 | export default Select; 114 | -------------------------------------------------------------------------------- /src/apps/admin/config/index.js: -------------------------------------------------------------------------------- 1 | export * from './styles'; 2 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/components/companySearch/helpers/getSuggestion.js: -------------------------------------------------------------------------------- 1 | import deburr from 'lodash/deburr'; 2 | 3 | export function getSuggestions({ value, data }) { 4 | const inputValue = deburr(value.trim()).toLowerCase(); 5 | const inputLength = inputValue.length; 6 | let count = 0; 7 | 8 | return inputLength === 0 9 | ? [] 10 | : data.filter(suggestion => { 11 | const keep = 12 | count < 5 && 13 | suggestion.name && 14 | suggestion.name[0].payload.slice(0, inputLength).toLowerCase() === 15 | inputValue; 16 | 17 | if (keep) { 18 | count += 1; 19 | } 20 | 21 | return keep; 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/components/companySearch/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { renderSuggestion } from './renderSuggestion'; 2 | export { renderInput } from './renderInput'; 3 | export { getSuggestions } from './getSuggestion'; 4 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/components/companySearch/helpers/renderInput.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TextField from '@material-ui/core/TextField'; 3 | 4 | export function renderInput(inputProps) { 5 | const { InputProps, classes, ref, error, ...other } = inputProps; 6 | 7 | return ( 8 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/components/companySearch/helpers/renderSuggestion.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MenuItem from '@material-ui/core/MenuItem'; 3 | 4 | export function renderSuggestion({ 5 | suggestion, 6 | index, 7 | itemProps, 8 | highlightedIndex, 9 | selectedItem, 10 | }) { 11 | const isHighlighted = highlightedIndex === index; 12 | const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1; 13 | 14 | return ( 15 | 24 | {suggestion.name[0].payload} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/components/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codexstanford/techlist-frontend-web/06be56c7f1bbaa34e65ba942a24fe29257ae4b7f/src/apps/admin/features/afffiliation.create/components/index.js -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/components/select.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Input from '@material-ui/core/Input'; 4 | import OutlinedInput from '@material-ui/core/OutlinedInput'; 5 | import FilledInput from '@material-ui/core/FilledInput'; 6 | import InputLabel from '@material-ui/core/InputLabel'; 7 | import MenuItem from '@material-ui/core/MenuItem'; 8 | import FormHelperText from '@material-ui/core/FormHelperText'; 9 | import FormControl from '@material-ui/core/FormControl'; 10 | import Select from '@material-ui/core/Select'; 11 | import { TextField } from 'formik-material-ui'; 12 | import styled from 'styled-components'; 13 | 14 | const organizationLinkType = [ 15 | { type: 'UrlPrivacyPolicy', niceName: 'Privacy Policy' }, 16 | { type: 'UrlSupport', niceName: 'Support' }, 17 | { type: 'UrlSales', niceName: 'Sales' }, 18 | { type: 'UrlTermsOfService', niceName: 'TOS' }, 19 | { type: 'UrlTwitter', niceName: 'Twitter' }, 20 | { type: 'UrlLinkedIn', niceName: 'LinkedIn' }, 21 | { type: 'UrlFacebook', niceName: 'Facebook' }, 22 | { type: 'UrlCrunchbase', niceName: 'Crunchbase' }, 23 | { type: 'UrlAngellist', niceName: 'Angellist' }, 24 | { type: 'UrlWebsite', niceName: 'Website' }, 25 | { type: 'EmailSales', niceName: 'Sales Email' }, 26 | { type: 'EmailSupport', niceName: 'Support Email' }, 27 | { type: 'Other', niceName: 'Other' }, 28 | ]; 29 | 30 | export default function SimpleSelect({ 31 | classes, 32 | field, 33 | form, 34 | values, 35 | styles, 36 | label = 'Link Type', 37 | options = organizationLinkType, 38 | }) { 39 | const inputLabel = React.useRef(null); 40 | const [labelWidth, setLabelWidth] = React.useState(0); 41 | React.useEffect(() => { 42 | setLabelWidth(inputLabel.current.offsetWidth); 43 | }, []); 44 | 45 | return ( 46 | 47 | 48 | {label} 49 | 50 | 64 | 65 | ); 66 | } 67 | 68 | const StyledFormControl = styled(FormControl)` 69 | padding-right: 1rem; 70 | min-width: 150px; 71 | margin-top: 15px; 72 | `; 73 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/graphql/index.js: -------------------------------------------------------------------------------- 1 | export * from './mutations'; 2 | export * from './queries'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const CREATE_AFFILIATION_MUTATION = gql` 4 | mutation CreateAffiliation($data: PersonOrganizationAffiliationCreateInput!) { 5 | createAffiliation(data: $data) { 6 | __typename 7 | id 8 | description 9 | role 10 | fromDate 11 | throughDate 12 | title 13 | updatedAt 14 | organization { 15 | id 16 | name { 17 | payload 18 | fromDate 19 | throughDate 20 | } 21 | } 22 | person { 23 | __typename 24 | id 25 | affiliation { 26 | __typename 27 | id 28 | fromDate 29 | throughDate 30 | title 31 | role 32 | description 33 | organization { 34 | id 35 | name { 36 | payload 37 | } 38 | logo { 39 | payload 40 | } 41 | } 42 | } 43 | } 44 | 45 | metadata { 46 | isDraft 47 | isPublic 48 | isRejected 49 | isUnverified 50 | isVerified 51 | isApproved 52 | isPendingReview 53 | } 54 | } 55 | } 56 | `; 57 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/graphql/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const GET_COMPANY_TARGET_MARKETS = gql` 4 | query GetTargetMarketsQuery { 5 | organizationTargetMarkets { 6 | id 7 | payload 8 | } 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/checkCanFormSubmit.js: -------------------------------------------------------------------------------- 1 | import { getCurrentErrors } from './getCurrentErrors'; 2 | 3 | export const checkCanFormSubmit = (touched, errors) => { 4 | if (Object.keys(touched).length < 1) { 5 | return true; 6 | } 7 | 8 | if (getCurrentErrors(touched, errors).length > 0) { 9 | return true; 10 | } 11 | 12 | return false; 13 | }; 14 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/checkForSectionErrors.js: -------------------------------------------------------------------------------- 1 | import { getCurrentErrors } from './getCurrentErrors'; 2 | 3 | export const checkForSectionErrors = (touched, errors, checkArray) => { 4 | if (getCurrentErrors(touched, errors).length > 0) { 5 | return getCurrentErrors(touched, errors).every(i => { 6 | if (checkArray.includes(i)) { 7 | return true; 8 | } else { 9 | return false; 10 | } 11 | }); 12 | } else { 13 | return false; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/getCurrentErrors.js: -------------------------------------------------------------------------------- 1 | export const getCurrentErrors = (touched, errors) => 2 | Object.keys(touched).filter(element => Object.keys(errors).includes(element)); 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/getInitialValues.js: -------------------------------------------------------------------------------- 1 | export function getInitialValues(initialCompany) { 2 | return { 3 | organizationID: initialCompany.id, 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/handleCreateAffiliation.js: -------------------------------------------------------------------------------- 1 | import { navigate } from '@reach/router'; 2 | 3 | const defaultCreateAffiliationMetadata = { 4 | isDraft: true, 5 | isPublic: false, 6 | isUnverified: false, 7 | isApproved: false, 8 | isPendingReview: false, 9 | }; 10 | 11 | export function handleCreateAffiliation(props) { 12 | const { mutation, user, handleClose, ...rest } = props; 13 | return async (values, { setSubmitting }) => { 14 | try { 15 | const result = await mutation({ 16 | // Reinstate when mutation is in place on backend 17 | variables: { 18 | data: { 19 | fromDate: new Date(), 20 | person: { 21 | connect: { 22 | id: user.person.id, 23 | }, 24 | }, 25 | organization: { 26 | connect: { 27 | id: values.organizationID, 28 | }, 29 | }, 30 | 31 | metadata: { 32 | create: defaultCreateAffiliationMetadata, 33 | }, 34 | }, 35 | }, 36 | }); 37 | setSubmitting(false); 38 | handleClose(); 39 | navigate('/app/profile/'); 40 | } catch (error) { 41 | console.log(error); 42 | } 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from './handleCreateAffiliation'; 2 | export * from './getInitialValues'; 3 | export * from './renderFormHeader'; 4 | export * from './checkCanFormSubmit'; 5 | export * from './getCurrentErrors'; 6 | export * from './checkForSectionErrors'; 7 | export * from './validationSchema'; 8 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/renderFormHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Typography from '@material-ui/core/Typography'; 4 | 5 | export function CodeXFormHeader({ 6 | text, 7 | variant = 'h5', 8 | color = 'primary', 9 | weight = '700', 10 | }) { 11 | return ( 12 | 21 | {text} 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/apps/admin/features/afffiliation.create/helpers/validationSchema.js: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | 3 | export const validationSchema = Yup.object().shape({ 4 | fromDate: Yup.date().required('From Date is required.'), 5 | throughDate: Yup.date().required('Through Date is required.'), 6 | role: Yup.string().required('Role is required.'), 7 | title: Yup.string().required('Title is required.'), 8 | }); 9 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/basics.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CodeXTextField from '../../../components/codex.textinput'; 3 | import CompanyTargetMarketSelect from './select'; 4 | import { Field } from 'formik'; 5 | import styled from 'styled-components'; 6 | import formatCategory from './categories/helpers/formatCategory'; 7 | 8 | export function Basics({ 9 | errors, 10 | touched, 11 | classes, 12 | targetMarkets, 13 | handleBlur, 14 | ...rest 15 | }) { 16 | return ( 17 |
24 | 32 | 41 | 42 | 51 | 52 | 0 60 | ? targetMarkets.organizationTargetMarkets.map(t => ({ 61 | type: t.id, 62 | niceName: formatCategory(t.payload), 63 | })) 64 | : [] 65 | } 66 | label="Target Markets" 67 | /> 68 | 69 | 70 |
71 | ); 72 | } 73 | 74 | const FlexLayoutMobile = styled.div` 75 | display: flex; 76 | @media (max-width: 480px) { 77 | flex-direction: column; 78 | } 79 | `; 80 | 81 | const TargetMarketsWrapper = styled.div` 82 | margin-left: 2rem; 83 | @media (max-width: 480px) { 84 | margin-left: 0; 85 | } 86 | `; 87 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/categories/helpers/formatCategory.js: -------------------------------------------------------------------------------- 1 | const formatCategory = str => { 2 | if (str.toUpperCase() !== str) { 3 | return str.toUpperCase().replace(/ /g, '_'); 4 | } else { 5 | return str 6 | .toLowerCase() 7 | .replace(/[^0-9a-z]/gi, ' ') 8 | .replace(/(?:^|\s)\S/g, function(a) { 9 | return a.toUpperCase(); 10 | }); 11 | } 12 | }; 13 | 14 | export default formatCategory; 15 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/categories/helpers/getSuggestions.js: -------------------------------------------------------------------------------- 1 | import deburr from 'lodash/deburr'; 2 | 3 | export function getSuggestions(value, suggestions, { showEmpty = false } = {}) { 4 | const inputValue = deburr(value.trim()).toLowerCase(); 5 | const inputLength = inputValue.length; 6 | let count = 0; 7 | 8 | return inputLength === 0 && !showEmpty 9 | ? [] 10 | : suggestions.filter(suggestion => { 11 | const keep = 12 | count < 5 && 13 | suggestion.label.slice(0, inputLength).toLowerCase() === inputValue; 14 | 15 | if (keep) { 16 | count += 1; 17 | } 18 | 19 | return keep; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/categories/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { renderInput } from './renderInput'; 2 | export { renderSuggestion } from './renderSuggestions'; 3 | export { getSuggestions } from './getSuggestions'; 4 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/categories/helpers/renderInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextField from '@material-ui/core/TextField'; 3 | import PropTypes from 'prop-types'; 4 | 5 | export function renderInput(inputProps) { 6 | const { InputProps, classes, ref, ...other } = inputProps; 7 | 8 | const { onBlur } = InputProps; 9 | 10 | return ( 11 | 23 | ); 24 | } 25 | 26 | export default renderInput; 27 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/categories/helpers/renderSuggestions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MenuItem from '@material-ui/core/MenuItem'; 3 | import PropTypes from 'prop-types'; 4 | import formatCategory from './formatCategory'; 5 | 6 | export function renderSuggestion(suggestionProps) { 7 | const { 8 | suggestion, 9 | index, 10 | itemProps, 11 | highlightedIndex, 12 | selectedItem, 13 | } = suggestionProps; 14 | const isHighlighted = highlightedIndex === index; 15 | const isSelected = (selectedItem || '').indexOf(suggestion.label) > -1; 16 | 17 | return ( 18 | 28 | {formatCategory(suggestion.label)} 29 | 30 | ); 31 | } 32 | renderSuggestion.propTypes = { 33 | highlightedIndex: PropTypes.number, 34 | index: PropTypes.number, 35 | itemProps: PropTypes.object, 36 | selectedItem: PropTypes.string, 37 | suggestion: PropTypes.shape({ label: PropTypes.string }).isRequired, 38 | }; 39 | 40 | export default renderSuggestion; 41 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/formErrorMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FormHelperText from '@material-ui/core/FormHelperText'; 3 | import { getDisplayedErrorMessage } from '../../../features/company.create/helpers'; 4 | import styled from 'styled-components'; 5 | 6 | const FormErrorMessage = ({ touched, errors }) => { 7 | if (getDisplayedErrorMessage(touched, errors) !== null) { 8 | return ( 9 | 10 | 11 | Error in {getDisplayedErrorMessage(touched, errors).section} section. 12 | 13 | 14 | {getDisplayedErrorMessage(touched, errors).message} 15 | 16 | 17 | ); 18 | } else { 19 | return null; 20 | } 21 | }; 22 | 23 | const ErrorMessageContainer = styled.div` 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: center; 27 | align-items: center; 28 | margin-top: 0; 29 | margin-bottom: 1rem; 30 | `; 31 | 32 | export default FormErrorMessage; 33 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './basics'; 2 | export * from './logo'; 3 | export * from './location/'; 4 | export * from './links/'; 5 | export * from './categories/'; 6 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/links/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Field } from 'formik'; 3 | import CompanyLinksInput from './links.input'; 4 | 5 | export function Links({ setFieldValue, setValues, values, ...rest }) { 6 | return ( 7 |
14 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/location/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Field } from 'formik'; 3 | import CodeXTextField from '../../../../components/codex.textinput'; 4 | import AddressField from './address'; 5 | import { CompanyLocationMap } from '../../../../../../templates/company/locationmap'; 6 | 7 | export function Location({ 8 | setFieldValue, 9 | setValues, 10 | values, 11 | handleBlur, 12 | ...rest 13 | }) { 14 | return ( 15 |
22 | 28 | {values.locationjson && values.locationjson.geometry ? ( 29 | 33 | ) : null} 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/location/locationPaper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Paper from '@material-ui/core/Paper'; 3 | import withStyles from '@material-ui/core/styles/withStyles'; 4 | 5 | const styles = theme => ({ 6 | paper: { 7 | marginTop: 10, 8 | display: 'flex', 9 | flexDirection: 'column', 10 | alignItems: 'center', 11 | padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing( 12 | 3 13 | )}px`, 14 | // [theme.breakpoints.up(450 + theme.spacing.unit * 3 * 2)]: { 15 | // marginTop: theme.spacing.unit * 8, 16 | // }, 17 | }, 18 | }); 19 | 20 | const LocationPaper = ({ 21 | classes, 22 | suggestions, 23 | highlightedIndex, 24 | selectedItem, 25 | getItemProps, 26 | renderSuggestion, 27 | ...props 28 | }) => { 29 | return ( 30 | 35 | {suggestions.map((suggestion, index) => 36 | renderSuggestion({ 37 | suggestion, 38 | index, 39 | itemProps: getItemProps({ 40 | item: suggestion, 41 | }), 42 | highlightedIndex, 43 | selectedItem, 44 | }) 45 | )} 46 | 47 | ); 48 | }; 49 | 50 | export default withStyles(styles)(LocationPaper); 51 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/logo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CodeXTextField from '../../../components/codex.textinput'; 3 | import Fab from '@material-ui/core/Fab'; 4 | import Avatar from '@material-ui/core/Avatar'; 5 | import styled from 'styled-components'; 6 | 7 | export function Logo({ 8 | errors, 9 | touched, 10 | classes, 11 | setImage, 12 | setFieldValue, 13 | handleBlur, 14 | values, 15 | ...rest 16 | }) { 17 | return ( 18 | 19 | { 23 | e.stopPropagation(); 24 | e.preventDefault(); 25 | 26 | const fileReader = new FileReader(); 27 | fileReader.onloadend = e => { 28 | const content = fileReader.result; 29 | setFieldValue('logo', content); 30 | setImage(content); 31 | }; 32 | if (e.target.files.length > 0) { 33 | fileReader.readAsDataURL(e.target.files[0]); 34 | } 35 | }} 36 | /> 37 | 38 | 39 | 56 | 57 | 58 | 59 | ); 60 | } 61 | 62 | const InputContainer = styled.div` 63 | width: 100%; 64 | `; 65 | 66 | const InputLabel = styled.label` 67 | display: flex; 68 | justify-content: center; 69 | min-width: 250px; 70 | min-height: 200px; 71 | `; 72 | 73 | const StyledFab = styled(Fab)` 74 | min-width: 250px; 75 | min-height: 200px; 76 | border-radius: 5px; 77 | `; 78 | 79 | const StyledInput = styled.input.attrs({ 80 | type: 'file', 81 | id: 'logo', 82 | accept: 'image/*', 83 | })` 84 | width: 0.1px; 85 | height: 0.1px; 86 | opacity: 0; 87 | overflow: hidden; 88 | position: absolute; 89 | z-index: -1; 90 | `; 91 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/components/select.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Input from '@material-ui/core/Input'; 4 | import OutlinedInput from '@material-ui/core/OutlinedInput'; 5 | import FilledInput from '@material-ui/core/FilledInput'; 6 | import InputLabel from '@material-ui/core/InputLabel'; 7 | import MenuItem from '@material-ui/core/MenuItem'; 8 | import FormHelperText from '@material-ui/core/FormHelperText'; 9 | import FormControl from '@material-ui/core/FormControl'; 10 | import Select from '@material-ui/core/Select'; 11 | import { TextField } from 'formik-material-ui'; 12 | import styled from 'styled-components'; 13 | 14 | const organizationLinkType = [ 15 | { type: 'UrlPrivacyPolicy', niceName: 'Privacy Policy' }, 16 | { type: 'UrlSupport', niceName: 'Support' }, 17 | { type: 'UrlSales', niceName: 'Sales' }, 18 | { type: 'UrlTermsOfService', niceName: 'TOS' }, 19 | { type: 'UrlTwitter', niceName: 'Twitter' }, 20 | { type: 'UrlLinkedIn', niceName: 'LinkedIn' }, 21 | { type: 'UrlFacebook', niceName: 'Facebook' }, 22 | { type: 'UrlCrunchbase', niceName: 'Crunchbase' }, 23 | { type: 'UrlAngellist', niceName: 'Angellist' }, 24 | { type: 'UrlWebsite', niceName: 'Website' }, 25 | { type: 'EmailSales', niceName: 'Sales Email' }, 26 | { type: 'EmailSupport', niceName: 'Support Email' }, 27 | { type: 'Other', niceName: 'Other' }, 28 | ]; 29 | 30 | export default function SimpleSelect({ 31 | classes, 32 | field, 33 | form, 34 | values, 35 | styles, 36 | label = 'Link Type', 37 | options = organizationLinkType, 38 | }) { 39 | const inputLabel = React.useRef(null); 40 | const [labelWidth, setLabelWidth] = React.useState(0); 41 | React.useEffect(() => { 42 | setLabelWidth(inputLabel.current.offsetWidth); 43 | }, []); 44 | 45 | return ( 46 | 47 | 48 | {label} 49 | 50 | 69 | 70 | ); 71 | } 72 | 73 | const StyledFormControl = styled(FormControl)` 74 | padding-right: 1rem; 75 | min-width: 175px; 76 | margin-top: 15px; 77 | `; 78 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/graphql/index.js: -------------------------------------------------------------------------------- 1 | export * from './mutations'; 2 | export * from './queries'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const CREATE_COMPANY_MUTATION = gql` 4 | mutation CreateCompany($data: OrganizationCreateInput!) { 5 | createOrganization(data: $data) { 6 | __typename 7 | id 8 | name { 9 | payload 10 | fromDate 11 | throughDate 12 | } 13 | description 14 | yearFounded 15 | location { 16 | __typename 17 | id 18 | formatted_address 19 | geometry 20 | } 21 | affiliation { 22 | __typename 23 | id 24 | fromDate 25 | person { 26 | __typename 27 | id 28 | } 29 | } 30 | admins { 31 | id 32 | person { 33 | id 34 | } 35 | } 36 | metadata { 37 | isDraft 38 | isPublic 39 | isRejected 40 | isUnverified 41 | isVerified 42 | isApproved 43 | isPendingReview 44 | } 45 | } 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/graphql/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const GET_COMPANY_TARGET_MARKETS = gql` 4 | query GetTargetMarketsQuery { 5 | organizationTargetMarkets { 6 | id 7 | payload 8 | } 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/checkCanFormSubmit.js: -------------------------------------------------------------------------------- 1 | import { getCurrentErrors } from './getCurrentErrors'; 2 | 3 | export const checkCanFormSubmit = (touched, errors) => { 4 | if (Object.keys(touched).length < 1) { 5 | return true; 6 | } 7 | 8 | if (getCurrentErrors(touched, errors).length > 0) { 9 | return true; 10 | } 11 | 12 | return false; 13 | }; 14 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/checkForSectionErrors.js: -------------------------------------------------------------------------------- 1 | import { getCurrentErrors } from './getCurrentErrors'; 2 | 3 | export const checkForSectionErrors = (touched, errors, checkArray) => { 4 | if (getCurrentErrors(touched, errors).length > 0) { 5 | return getCurrentErrors(touched, errors).every(i => { 6 | if (checkArray.includes(i)) { 7 | return true; 8 | } else { 9 | return false; 10 | } 11 | }); 12 | } else { 13 | return false; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/getCurrentErrors.js: -------------------------------------------------------------------------------- 1 | export const getCurrentErrors = (touched, errors) => 2 | Object.keys(touched).filter(element => Object.keys(errors).includes(element)); 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/getDisplayedErrorMessage.js: -------------------------------------------------------------------------------- 1 | import { getCurrentErrors } from './getCurrentErrors'; 2 | 3 | export const getDisplayedErrorMessage = (touched, errors) => { 4 | let error = { section: null, message: null }; 5 | 6 | switch (getCurrentErrors(touched, errors)[0]) { 7 | case 'name': 8 | error = { section: 'Basics', message: errors.name }; 9 | break; 10 | case 'description': 11 | error = { section: 'Basics', message: errors.description }; 12 | break; 13 | case 'yearFounded': 14 | error = { section: 'Basics', message: errors.yearFounded }; 15 | break; 16 | case 'targetMarkets': 17 | error = { section: 'Basics', message: errors.targetMarkets }; 18 | break; 19 | default: 20 | error = null; 21 | } 22 | return error; 23 | }; 24 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/getInitialValues.js: -------------------------------------------------------------------------------- 1 | export function getInitialValues(props) { 2 | return { 3 | name: '', 4 | yearFounded: new Date().toISOString().split('T')[0], 5 | description: '', 6 | locationjson: {}, 7 | targetMarkets: '', 8 | logo: '', 9 | links: [ 10 | { type: 'UrlWebsite', payload: '' }, 11 | { type: 'UrlTwitter', payload: '' }, 12 | { type: 'UrlCrunchbase', payload: '' }, 13 | ], 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/handleCreateCompany.js: -------------------------------------------------------------------------------- 1 | import { navigate } from '@reach/router'; 2 | 3 | const defaultCreateCompanyMetadata = { 4 | isDraft: true, 5 | isPublic: false, 6 | isUnverified: false, 7 | isApproved: false, 8 | isPendingReview: false, 9 | }; 10 | 11 | export function handleCreateCompany(props) { 12 | const { mutation, user, handleClose, ...rest } = props; 13 | return async ( 14 | { 15 | name, 16 | description, 17 | yearFounded, 18 | location, 19 | locationjson, 20 | links, 21 | logo, 22 | targetMarkets, 23 | categories, 24 | }, 25 | { setSubmitting } 26 | ) => { 27 | const { formatted_address, geometry, place_id } = locationjson; 28 | 29 | try { 30 | const result = await mutation({ 31 | variables: { 32 | data: { 33 | categories: { 34 | connect: categories && categories.map(cat => ({ id: cat.value })), 35 | }, 36 | logo: { 37 | create: { 38 | payload: logo, 39 | fromDate: new Date(), 40 | isPrimary: true, 41 | isPublic: true, 42 | isDefault: true, 43 | }, 44 | }, 45 | name: { 46 | create: { 47 | payload: name, 48 | fromDate: new Date(), 49 | }, 50 | }, 51 | targetMarkets: { 52 | connect: { 53 | id: targetMarkets, 54 | }, 55 | }, 56 | description, 57 | yearFounded, 58 | location: { 59 | create: { 60 | formatted_address, 61 | geometry, 62 | placeId: place_id, 63 | }, 64 | }, 65 | links: { 66 | create: links.map(link => { 67 | return { 68 | fromDate: new Date(), 69 | payload: link.payload, 70 | type: link.type, 71 | }; 72 | }), 73 | }, 74 | affiliation: { 75 | create: { 76 | fromDate: new Date(), 77 | person: { 78 | connect: { 79 | id: user.person.id, 80 | }, 81 | }, 82 | }, 83 | }, 84 | admins: { 85 | connect: { 86 | id: user.id, 87 | }, 88 | }, 89 | metadata: { 90 | create: defaultCreateCompanyMetadata, 91 | }, 92 | }, 93 | }, 94 | }); 95 | setSubmitting(false); 96 | handleClose(); 97 | navigate('/app/profile/'); 98 | } catch (error) { 99 | console.log(error); 100 | } 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from './handleCreateCompany'; 2 | export * from './renderExpansionPanel'; 3 | export * from './getInitialValues'; 4 | export * from './renderFormHeader'; 5 | export * from './checkCanFormSubmit'; 6 | export * from './getDisplayedErrorMessage'; 7 | export * from './getCurrentErrors'; 8 | export * from './checkForSectionErrors'; 9 | export * from './validationSchema'; 10 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/renderExpansionPanel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 4 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; 5 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; 6 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 7 | import Typography from '@material-ui/core/Typography'; 8 | 9 | export function CodeXExpansionPanel({ 10 | children, 11 | defaultExpanded, 12 | expandIcon, 13 | title, 14 | titleColor, 15 | titleComponent, 16 | titleStyle, 17 | titleVariant, 18 | error, 19 | style, 20 | ...props 21 | }) { 22 | return ( 23 | 27 | 28 | 34 | {title} 35 | 36 | 37 | {children} 38 | 39 | ); 40 | } 41 | 42 | CodeXExpansionPanel.propTypes = { 43 | title: PropTypes.string.isRequired, 44 | titleStyle: PropTypes.object, 45 | titleColor: PropTypes.string, 46 | titleVariant: PropTypes.string, 47 | expandIcon: PropTypes.element, 48 | }; 49 | 50 | CodeXExpansionPanel.defaultProps = { 51 | title: '', 52 | titleStyle: { 53 | fontWeight: '800', 54 | letterSpacing: '-.5px', 55 | textDecoration: 'none', 56 | }, 57 | titleColor: 'primary', 58 | titleComponent: 'h6', 59 | titleVariant: 'h6', 60 | expandIcon: , 61 | }; 62 | 63 | export default CodeXExpansionPanel; 64 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/renderFormHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Typography from '@material-ui/core/Typography'; 4 | 5 | export function CodeXFormHeader({ 6 | text, 7 | variant = 'h5', 8 | color = 'primary', 9 | weight = '700', 10 | }) { 11 | return ( 12 | 22 | {text} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/apps/admin/features/company.create/helpers/validationSchema.js: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | 3 | const currentDate = new Date(); 4 | const yesterday = new Date( 5 | currentDate.setDate(currentDate.getDate() - 1) 6 | ).toISOString(); 7 | 8 | export const ValidationSchema = Yup.object().shape({ 9 | name: Yup.string().required('Name is required.'), 10 | description: Yup.string() 11 | .required('Description is required.') 12 | .min(150, 'Description must be at least 150 characters.'), 13 | yearFounded: Yup.date() 14 | .required('Date Founded is required.') 15 | .max(yesterday, 'Date Founded must be before today.'), 16 | targetMarkets: Yup.string().required('Target Markets is required'), 17 | }); 18 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/avatar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import ListItemAvatar from '@material-ui/core/ListItemAvatar'; 4 | import Avatar from '@material-ui/core/Avatar'; 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | iconAvatar: { 8 | margin: theme.spacing(1), 9 | width: 60, 10 | height: 60, 11 | }, 12 | letterAvatar: { 13 | margin: 10, 14 | width: 60, 15 | height: 60, 16 | backgroundColor: '#b1040e', 17 | }, 18 | listItem: { 19 | alignSelf: 'flex-start', 20 | marginRight: '.5rem', 21 | }, 22 | })); 23 | 24 | export function AffiliationAvatar({ affiliation }) { 25 | const { organization } = affiliation; 26 | const { logo, name } = organization; 27 | const firstInitial = name && name[0].payload.slice(0, 1); 28 | const classes = useStyles(); 29 | return ( 30 | <> 31 | {logo && logo.length > 0 ? ( 32 | 33 | ) : ( 34 | {firstInitial} 35 | )} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/content/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import renderPrimaryContent from './primary'; 4 | import renderSecondaryContent from './secondary'; 5 | import styled from 'styled-components'; 6 | 7 | const useStyles = makeStyles(() => ({ 8 | listItem: { 9 | minWidth: '300px', 10 | }, 11 | })); 12 | 13 | export function AffiliationContent({ affiliation, ...props }) { 14 | const classes = useStyles(); 15 | return ( 16 |
17 |
{renderPrimaryContent({ affiliation })}
18 |
{renderSecondaryContent({ affiliation })}
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/content/primary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typography from '@material-ui/core/Typography'; 3 | import { makeStyles } from '@material-ui/styles'; 4 | import Link from '@material-ui/core/Link'; 5 | import { navigate } from 'gatsby'; 6 | import slugify from 'slugify'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | companyName: { 10 | fontWeight: 500, 11 | }, 12 | })); 13 | 14 | function renderAffiliationPrimaryContent({ affiliation }) { 15 | const classes = useStyles(); 16 | return ( 17 | 19 | navigate( 20 | `/companies/${slugify(affiliation.organization.name[0].payload)}` 21 | ) 22 | } 23 | target="_blank" 24 | > 25 | 26 | {affiliation.organization.name[0].payload} 27 | 28 | 29 | ); 30 | } 31 | 32 | export default renderAffiliationPrimaryContent; 33 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/content/secondary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typography from '@material-ui/core/Typography'; 3 | import { makeStyles } from '@material-ui/styles'; 4 | import { formatDateString } from '../../helpers'; 5 | const useStyles = makeStyles(theme => ({ 6 | subtitle1: { 7 | fontSize: '12px', 8 | }, 9 | })); 10 | 11 | function renderAffiliationSecondaryContent({ affiliation }) { 12 | const classes = useStyles(); 13 | return ( 14 | <> 15 | {`${affiliation.title}`} 19 | {`${formatDateString(affiliation.fromDate)} to ${formatDateString( 23 | affiliation.throughDate 24 | )} `} 25 | 26 | ); 27 | } 28 | 29 | export default renderAffiliationSecondaryContent; 30 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/controls/delete.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IconButton from '@material-ui/core/IconButton'; 3 | import DeleteIcon from '@material-ui/icons/Delete'; 4 | import { useMutation } from 'react-apollo-hooks'; 5 | import { makeStyles } from '@material-ui/styles'; 6 | 7 | import Confirm from '../../../../../../atoms/confirm'; 8 | 9 | import { DELETE_AFFILIATION_MUTATION } from '../../graphql'; 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | icon: { 13 | margin: theme.spacing(0.5), 14 | }, 15 | })); 16 | export function DeleteAffiliationControl({ affiliation, refetch, ...props }) { 17 | const [isDeleting, toggleDelete] = React.useState(false); 18 | const classes = useStyles(); 19 | 20 | return ( 21 | <> 22 | toggleDelete(!isDeleting)} 25 | className={classes.icon} 26 | > 27 | 28 | 29 | 35 | 36 | ); 37 | } 38 | 39 | function DeleteAffiliation({ 40 | isDeleting, 41 | toggleDelete, 42 | affiliation, 43 | refetch, 44 | ...props 45 | }) { 46 | const deleteAffiliation = useMutation(DELETE_AFFILIATION_MUTATION, { 47 | variables: { 48 | where: { id: affiliation.id }, 49 | }, 50 | }); 51 | 52 | if (isDeleting === false) { 53 | return null; 54 | } 55 | return ( 56 | { 59 | deleteAffiliation(); 60 | refetch(); 61 | }} 62 | onCancel={() => null} 63 | > 64 | Test 65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/controls/edit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import IconButton from '@material-ui/core/IconButton'; 4 | import EditIcon from '@material-ui/icons/Edit'; 5 | import EditAffiliation from '../../../../components/affiliation.edit'; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | icon: { 9 | margin: theme.spacing(0.5), 10 | }, 11 | })); 12 | 13 | export function EditAffiliationControl({ affiliation, ...props }) { 14 | const [isEditing, toggleEditing] = React.useState(false); 15 | const classes = useStyles(); 16 | return ( 17 | <> 18 | toggleEditing(!isEditing)} 22 | className={classes.icon} 23 | > 24 | 25 | 26 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/controls/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | 4 | import { EditAffiliationControl } from './edit'; 5 | import { DeleteAffiliationControl } from './delete'; 6 | 7 | const useStyles = makeStyles(theme => ({ 8 | listItem: { 9 | display: 'flex', 10 | justifyContent: 'flex-end', 11 | alignSelf: 'flex-start', 12 | flexGrow: 1, 13 | }, 14 | })); 15 | 16 | export function AfilliationControls({ affiliation, refetch, ...props }) { 17 | const classes = useStyles(); 18 | return ( 19 |
20 | 21 | 22 |
23 | ); 24 | } 25 | 26 | export default AfilliationControls; 27 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/edit.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codexstanford/techlist-frontend-web/06be56c7f1bbaa34e65ba942a24fe29257ae4b7f/src/apps/admin/features/profile.affiliations/components/edit.js -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './avatar'; 2 | export * from './content/'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/controller.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import List from '@material-ui/core/List'; 4 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 5 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; 6 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; 7 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 8 | 9 | import { renderAffiliation } from './helpers'; 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | root: { 13 | width: '100%', 14 | backgroundColor: 'white', 15 | }, 16 | expansionPanel: { 17 | boxShadow: 'none', 18 | width: '100%', 19 | }, 20 | })); 21 | 22 | export default function AffiliationsListController({ 23 | affiliations, 24 | first, 25 | second, 26 | rest, 27 | refetch, 28 | ...props 29 | }) { 30 | const classes = useStyles(); 31 | 32 | if (affiliations) { 33 | return ( 34 | <> 35 | {first && 36 | renderAffiliation({ 37 | affiliation: first, 38 | refetch, 39 | hasDivider: false, 40 | })} 41 | {second && 42 | renderAffiliation({ 43 | affiliation: second, 44 | refetch, 45 | hasDivider: false, 46 | })} 47 | {rest && rest.length > 0 ? ( 48 | 49 | } /> 50 | 51 | 52 | {rest.map(affiliation => 53 | renderAffiliation({ affiliation, refetch }) 54 | )} 55 | 56 | 57 | 58 | ) : null} 59 | 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/graphql/index.js: -------------------------------------------------------------------------------- 1 | export * from './mutations'; 2 | export * from './queries'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const DELETE_AFFILIATION_MUTATION = gql` 4 | mutation DeleteAffiliationMutation( 5 | $where: PersonOrganizationAffiliationWhereUniqueInput! 6 | ) { 7 | deleteAffiliation(where: $where) { 8 | __typename 9 | id 10 | createdAt 11 | fromDate 12 | throughDate 13 | title 14 | role 15 | description 16 | organization { 17 | __typename 18 | id 19 | name { 20 | __typename 21 | id 22 | payload 23 | } 24 | description 25 | logo { 26 | __typename 27 | id 28 | payload 29 | } 30 | } 31 | metadata { 32 | __typename 33 | isDraft 34 | isPublic 35 | isRejected 36 | isUnverified 37 | isApproved 38 | isPendingReview 39 | } 40 | } 41 | } 42 | `; 43 | 44 | // PersonOrganizationAffiliationUpdateManyWithoutOrganizationInput 45 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/graphql/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const GET_PERSON_AFFILIATIONS_QUERY = gql` 4 | query GetPersonAffiliationsQuery( 5 | $where: PersonOrganizationAffiliationWhereInput 6 | $orderBy: PersonOrganizationAffiliationOrderByInput 7 | ) { 8 | personOrganizationAffiliations(where: $where, orderBy: $orderBy) { 9 | __typename 10 | id 11 | createdAt 12 | fromDate 13 | throughDate 14 | title 15 | role 16 | description 17 | organization { 18 | __typename 19 | id 20 | name { 21 | __typename 22 | id 23 | payload 24 | } 25 | description 26 | logo { 27 | __typename 28 | id 29 | payload 30 | } 31 | } 32 | metadata { 33 | __typename 34 | isDraft 35 | isPublic 36 | isRejected 37 | isUnverified 38 | isApproved 39 | isPendingReview 40 | } 41 | } 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/helpers/formatDates.js: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon'; 2 | 3 | export function formatDateString(date) { 4 | if (date === null) { 5 | return 'Present'; 6 | } 7 | return DateTime.fromISO(date).toLocaleString(); 8 | } 9 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from './formatDates'; 2 | export * from './renderAffiliation'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/helpers/renderAffiliation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import Divider from '@material-ui/core/Divider'; 4 | import styled from 'styled-components'; 5 | 6 | import AffiliationControls from '../components/controls'; 7 | import { AffiliationAvatar, AffiliationContent } from '../components'; 8 | 9 | const useStyles = makeStyles({ 10 | divider: { 11 | color: 'black', 12 | }, 13 | wrapper: { 14 | display: 'flex', 15 | alignItems: 'center', 16 | }, 17 | }); 18 | 19 | export function renderAffiliation({ affiliation, refetch, hasDivider = true }) { 20 | const classes = useStyles(); 21 | if (affiliation.organization !== null) { 22 | return ( 23 |
24 | 25 | 26 | 27 | 28 | {hasDivider && ( 29 | 30 | )} 31 |
32 | ); 33 | } 34 | return null; 35 | } 36 | 37 | const AvatarAndContentContainer = styled.div` 38 | display: flex; 39 | align-items: center; 40 | `; 41 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.affiliations/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import Controller from './controller'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Media from 'react-media'; 6 | import Card from '@material-ui/core/Card'; 7 | import CardContent from '@material-ui/core/CardContent'; 8 | 9 | import { GET_PERSON_AFFILIATIONS_QUERY } from './graphql'; 10 | import { useQuery } from 'react-apollo-hooks'; 11 | import { CreateCompanyModalContext } from '../../../../store/modal-context'; 12 | 13 | const useStyles = makeStyles(theme => ({ 14 | wrapper: { 15 | width: '100%', 16 | maxWidth: 600, 17 | justifyContent: 'space-between', 18 | [theme.breakpoints.down('sm')]: { 19 | minWidth: '100%', 20 | maxWidth: '100vw', 21 | }, 22 | }, 23 | card: { 24 | width: '100%', 25 | }, 26 | })); 27 | 28 | export default function ProfileAffiliations({ person, style, ...props }) { 29 | const classes = useStyles(); 30 | const { open } = useContext(CreateCompanyModalContext); 31 | 32 | const { loading, error, data, refetch } = useQuery( 33 | GET_PERSON_AFFILIATIONS_QUERY, 34 | { 35 | variables: { 36 | where: { 37 | person: { 38 | id: person.id, 39 | }, 40 | }, 41 | orderBy: 'fromDate_DESC', 42 | }, 43 | } 44 | ); 45 | 46 | useEffect(() => { 47 | refetch(); 48 | }, [open]); 49 | 50 | if (loading) { 51 | return null; 52 | } 53 | 54 | const { personOrganizationAffiliations: affiliations } = data; 55 | 56 | const newAffiliations = []; 57 | 58 | affiliations.map(affiliation => { 59 | if (affiliation.organization !== null) { 60 | newAffiliations.push(affiliation); 61 | } 62 | }); 63 | console.log('DATA', data); 64 | 65 | console.log('newAffiliations', newAffiliations); 66 | 67 | const [first, second, ...rest] = newAffiliations; 68 | 69 | return ( 70 |
71 | 72 | 73 |
74 | 75 | {matches => 76 | matches ? ( 77 | 78 | Affiliations{' '} 79 | 80 | ) : ( 81 | 82 | Affiliations{' '} 83 | 84 | ) 85 | } 86 | 87 | {affiliations && ( 88 | 95 | )} 96 |
97 |
98 |
99 |
100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/avatar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import ListItemAvatar from '@material-ui/core/ListItemAvatar'; 4 | import Avatar from '@material-ui/core/Avatar'; 5 | 6 | const useStyles = makeStyles({ 7 | iconAvatar: { 8 | margin: 10, 9 | width: 60, 10 | height: 60, 11 | }, 12 | letterAvatar: { 13 | margin: 10, 14 | width: 60, 15 | height: 60, 16 | backgroundColor: '#b1040e', 17 | }, 18 | listItem: { 19 | alignSelf: 'flex-start', 20 | marginRight: '.5rem', 21 | }, 22 | }); 23 | 24 | export function CompanyAvatar({ company }) { 25 | const { logo, name } = company; 26 | const firstInitial = name[0].payload.slice(0, 1); 27 | const classes = useStyles(); 28 | return ( 29 | 30 | {logo && logo.length > 0 && logo[0].payload ? ( 31 | 32 | ) : ( 33 | {firstInitial} 34 | )} 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/content/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderPrimaryContent from './primary'; 3 | import renderSecondaryContent from './secondary'; 4 | import styled from 'styled-components'; 5 | import truncateText from '../../../../../../helpers/truncateText'; 6 | 7 | export function CompanyContent({ company, ...props }) { 8 | return ( 9 | 10 | 11 | 12 | {renderPrimaryContent({ company })} 13 | {renderSecondaryContent({ company })} 14 | 15 | 16 | {company.description && truncateText(company.description, 125)} 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | const Container = styled.div` 24 | min-width: 300px; 25 | `; 26 | 27 | const Wrapper = styled.div` 28 | display: flex; 29 | align-items: flex-start; 30 | justify-content: flex-start; 31 | `; 32 | 33 | const ContentWrapper = styled.div` 34 | flex: 1 0 1rem; 35 | `; 36 | 37 | const CompanyDescription = styled.p` 38 | display: none; 39 | margin: 0; 40 | flex: 2 0 24rem; 41 | @media (max-width: 960px) { 42 | display: block; 43 | } 44 | @media (max-width: 800px) { 45 | display: none; 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/content/primary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from '@material-ui/core/Link'; 3 | import { navigate } from 'gatsby'; 4 | import slugify from 'slugify'; 5 | 6 | function renderCompanyPrimaryContent({ classes, company }) { 7 | return ( 8 | navigate(`/companies/${slugify(company.name[0].payload)}`)} 10 | target="_blank" 11 | > 12 | {company.name[0].payload} 13 | 14 | ); 15 | } 16 | 17 | export default renderCompanyPrimaryContent; 18 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/content/secondary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatDateString } from '../../helpers'; 3 | import truncateText from '../../../../../../helpers/truncateText'; 4 | 5 | function renderCompanySecondaryContent({ company }) { 6 | return ( 7 |
8 | {company.title && ( 9 | <> 10 | {`${company.title}`} 11 |
12 | 13 | )} 14 | { 15 | {`${ 16 | company.yearFounded !== null 17 | ? formatDateString(company.yearFounded) 18 | : 'N/A' 19 | }`} 20 | } 21 |
22 | ); 23 | } 24 | 25 | export default renderCompanySecondaryContent; 26 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/controls/delete.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IconButton from '@material-ui/core/IconButton'; 3 | import DeleteIcon from '@material-ui/icons/Delete'; 4 | import Confirm from '../../../../../../atoms/confirm'; 5 | import { useMutation } from 'react-apollo-hooks'; 6 | import { DELETE_COMPANY_MUTATION } from '../../graphql'; 7 | 8 | export function DeleteCompanyControl({ company, ...props }) { 9 | const [isDeleting, toggleDelete] = React.useState(false); 10 | return ( 11 | <> 12 | toggleDelete(!isDeleting)}> 13 | 14 | 15 | 20 | 21 | ); 22 | } 23 | 24 | function DeleteCompany({ isDeleting, toggleDelete, company, ...props }) { 25 | const deleteCompany = useMutation(DELETE_COMPANY_MUTATION); 26 | 27 | // console.log('deleteCompany', deleteCompany); 28 | 29 | return ( 30 | 33 | deleteCompany({ variables: { where: { id: company.id } } }) 34 | } 35 | onClose={() => toggleDelete(false)} 36 | onCancel={() => {}} 37 | > 38 | Test 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/controls/edit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IconButton from '@material-ui/core/IconButton'; 3 | import EditIcon from '@material-ui/icons/Edit'; 4 | import EditCompany from '../../../../components/company.edit'; 5 | 6 | export function EditCompanyControl({ company, ...props }) { 7 | const [isEditing, toggleEditing] = React.useState(false); 8 | return ( 9 | <> 10 | toggleEditing(!isEditing)}> 11 | 12 | 13 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/controls/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; 3 | import ListItem from '@material-ui/core/ListItem'; 4 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import EditIcon from '@material-ui/icons/Edit'; 7 | import EditCompany from '../../../../components/company.edit'; 8 | import { makeStyles } from '@material-ui/styles'; 9 | 10 | import { EditCompanyControl } from './edit'; 11 | import { DeleteCompanyControl } from './delete'; 12 | 13 | const useStyles = makeStyles(theme => ({ 14 | listItem: { 15 | alignSelf: 'flex-start', 16 | minWidth: 103, 17 | }, 18 | })); 19 | 20 | export function AfilliationControls({ company, ...props }) { 21 | const classes = useStyles(); 22 | return ( 23 |
24 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export default AfilliationControls; 31 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './avatar'; 2 | export * from './content/'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/controller.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import List from '@material-ui/core/List'; 4 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 5 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; 6 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; 7 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 8 | 9 | import { renderCompany } from './helpers'; 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | root: { 13 | width: '100%', 14 | backgroundColor: 'white', 15 | }, 16 | expansionPanel: { 17 | boxShadow: 'none', 18 | width: '100%', 19 | }, 20 | })); 21 | 22 | export default function CompaniesListController({ companies, ...props }) { 23 | const classes = useStyles(); 24 | const [first, second, ...rest] = companies; 25 | 26 | return ( 27 | <> 28 | {first && renderCompany({ company: first, hasDivider: false })} 29 | {second && renderCompany({ company: second, hasDivider: false })} 30 | {rest && rest.length > 0 ? ( 31 | 32 | } /> 33 | 34 | 35 | {rest.map(company => { 36 | renderCompany({ company }); 37 | })} 38 | 39 | 40 | 41 | ) : null} 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/graphql/index.js: -------------------------------------------------------------------------------- 1 | export * from './mutations'; 2 | export * from './queries'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/graphql/mutations.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const DELETE_COMPANY_MUTATION = gql` 4 | mutation DeleteCompany($where: OrganizationWhereUniqueInput!) { 5 | deleteOrganization(where: $where) { 6 | __typename 7 | id 8 | } 9 | } 10 | `; 11 | 12 | export const UPDATE_COMPANY_MUATATION = gql` 13 | mutation UpdateCompany( 14 | $where: OrganizationWhereUniqueInput! 15 | $data: OrganizationUpdateInput! 16 | ) { 17 | updateOrganization(where: $where, data: $data) { 18 | __typename 19 | id 20 | name { 21 | payload 22 | } 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/graphql/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const GET_USER_ADMIN_COMPANIES = gql` 4 | query GetUserAdminCompanies($where: PartyAccountWhereUniqueInput!) { 5 | partyAccount(where: $where) { 6 | id 7 | admin { 8 | id 9 | yearFounded 10 | description 11 | name { 12 | id 13 | payload 14 | } 15 | logo { 16 | id 17 | payload 18 | } 19 | description 20 | metadata { 21 | id 22 | isDraft 23 | isPublic 24 | isRejected 25 | isUnverified 26 | isApproved 27 | isPendingReview 28 | } 29 | } 30 | } 31 | } 32 | `; 33 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/helpers/formatDates.js: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon'; 2 | 3 | export function formatDateString(date) { 4 | if (date === null) { 5 | return 'Present'; 6 | } 7 | return DateTime.fromISO(date).toLocaleString(); 8 | } 9 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/helpers/index.js: -------------------------------------------------------------------------------- 1 | export * from './formatDates'; 2 | export * from './renderCompany'; 3 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/helpers/renderCompany.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import ListItem from '@material-ui/core/ListItem'; 4 | import Divider from '@material-ui/core/Divider'; 5 | import styled from 'styled-components'; 6 | 7 | import CompanyControls from '../components/controls'; 8 | import { CompanyAvatar, CompanyContent } from '../components'; 9 | import truncateText from '../../../../../helpers/truncateText'; 10 | 11 | const useStyles = makeStyles({ 12 | divider: { 13 | color: 'black', 14 | }, 15 | listItem: { 16 | diplay: 'flex', 17 | justifyContent: 'space-between', 18 | padding: 0, 19 | }, 20 | }); 21 | 22 | export function renderCompany({ company, hasDivider = true }) { 23 | const classes = useStyles(); 24 | return ( 25 | <> 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {company.description && truncateText(company.description, 125)} 35 | 36 | 37 | {hasDivider && ( 38 | 39 | )} 40 | 41 | ); 42 | } 43 | 44 | const AvatarAndContentContainer = styled.div` 45 | display: flex; 46 | align-items: center; 47 | `; 48 | 49 | const CompanyDescription = styled.p` 50 | padding: 0 14px; 51 | @media (max-width: 960px) { 52 | display: none; 53 | } 54 | @media (max-width: 800px) { 55 | display: block; 56 | } 57 | `; 58 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.companies/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import Controller from './controller'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Media from 'react-media'; 6 | import { GET_USER_ADMIN_COMPANIES } from './graphql'; 7 | import { useQuery } from 'react-apollo-hooks'; 8 | import Card from '@material-ui/core/Card'; 9 | import CardContent from '@material-ui/core/CardContent'; 10 | import { CreateCompanyModalContext } from '../../../../store/modal-context'; 11 | 12 | const useStyles = makeStyles(theme => ({ 13 | root: { 14 | width: '100%', 15 | backgroundColor: 'white', 16 | }, 17 | card: { 18 | width: '100%', 19 | minWidth: `300px`, 20 | }, 21 | wrapper: { 22 | width: '100%', 23 | maxWidth: 600, 24 | justifyContent: 'space-between', 25 | marginBottom: theme.spacing(2), 26 | [theme.breakpoints.down('sm')]: { 27 | minWidth: '100%', 28 | maxWidth: '100vw', 29 | }, 30 | }, 31 | })); 32 | 33 | export default function ProfileCompanies({ user, ...props }) { 34 | const classes = useStyles(); 35 | const { open } = useContext(CreateCompanyModalContext); 36 | 37 | const { loading, error, data, refetch } = useQuery(GET_USER_ADMIN_COMPANIES, { 38 | variables: { 39 | where: { id: user.id }, 40 | orderBy: 'fromDate_DESC', 41 | }, 42 | }); 43 | 44 | useEffect(() => { 45 | console.log('gotta refetch', open); 46 | refetch(); 47 | }, [open]); 48 | 49 | if (loading) { 50 | return null; 51 | } 52 | 53 | const { partyAccount } = data; 54 | return ( 55 |
56 | 57 | 58 |
59 |
60 | 61 | {matches => 62 | matches ? ( 63 | 64 | Companies{' '} 65 | 66 | ) : ( 67 | 68 | Companies{' '} 69 | 70 | ) 71 | } 72 | 73 |
74 | {partyAccount && 75 | partyAccount.admin && 76 | partyAccount.admin.length > 0 && ( 77 |
78 |
79 | 80 |
81 |
82 | )} 83 |
84 |
85 |
86 |
87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.navigation/components/desktop.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import Drawer from '@material-ui/core/Drawer'; 3 | import Divider from '@material-ui/core/Divider'; 4 | import List from '@material-ui/core/List'; 5 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; 6 | import classNames from 'classnames'; 7 | import IconButton from '@material-ui/core/IconButton'; 8 | import ListItem from '@material-ui/core/ListItem'; 9 | import DashboardIcon from '@material-ui/icons/Dashboard'; 10 | import BusinessIcon from '@material-ui/icons/Business'; 11 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 12 | import ListItemText from '@material-ui/core/ListItemText'; 13 | import EditIcon from '@material-ui/icons/Edit'; 14 | import { CreateCompanyModalContext } from '../../../../../store/modal-context'; 15 | 16 | function DesktopProfileNavigation({ 17 | classes, 18 | secondaryListItems, 19 | isOpen, 20 | toggleDrawerVisibility, 21 | toggleEditProfile, 22 | showEditProfile, 23 | logout, 24 | user, 25 | ...props 26 | }) { 27 | const { showModal } = useContext(CreateCompanyModalContext); 28 | const handleLogout = () => { 29 | logout(); 30 | }; 31 | 32 | return ( 33 | <> 34 | 44 |
45 | toggleDrawerVisibility(!isOpen)}> 46 | 47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | toggleEditProfile(!showEditProfile)}> 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {secondaryListItems} 73 |
74 | 75 | ); 76 | } 77 | 78 | export default DesktopProfileNavigation; 79 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.navigation/components/mobile.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import Drawer from '@material-ui/core/Drawer'; 3 | import Divider from '@material-ui/core/Divider'; 4 | import List from '@material-ui/core/List'; 5 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; 6 | import classNames from 'classnames'; 7 | import IconButton from '@material-ui/core/IconButton'; 8 | import ListItem from '@material-ui/core/ListItem'; 9 | import DashboardIcon from '@material-ui/icons/Dashboard'; 10 | import BusinessIcon from '@material-ui/icons/Business'; 11 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 12 | import ListItemText from '@material-ui/core/ListItemText'; 13 | import { CreateCompanyModalContext } from '../../../../../store/modal-context'; 14 | 15 | function DesktopProfileNavigation({ 16 | classes, 17 | MainListItems, 18 | secondaryListItems, 19 | isOpen, 20 | isClosed, 21 | toggleDrawerVisibility, 22 | logout, 23 | user, 24 | }) { 25 | const { showModal } = useContext(CreateCompanyModalContext); 26 | 27 | return ( 28 | <> 29 | 39 |
40 | toggleDrawerVisibility(!isOpen)}> 41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | logout()} /> 57 | 58 | 59 | 60 | {secondaryListItems} 61 |
62 | 63 | ); 64 | } 65 | 66 | export default DesktopProfileNavigation; 67 | -------------------------------------------------------------------------------- /src/apps/admin/features/profile.navigation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MobileNav from './components/mobile'; 3 | import DesktopNav from './components/desktop'; 4 | import Media from 'react-media'; 5 | 6 | // TODO: extend or trash mobile nav. 7 | 8 | function ProfileNav(props) { 9 | return ( 10 | 11 | {matches => 12 | matches ? : 13 | } 14 | 15 | ); 16 | } 17 | 18 | export default ProfileNav; 19 | -------------------------------------------------------------------------------- /src/apps/admin/helpers.js: -------------------------------------------------------------------------------- 1 | export const validateCreateAccountForm = values => { 2 | const errors = {}; 3 | if (!values.email) { 4 | errors.email = 'Required.'; 5 | } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { 6 | errors.email = 'Invalid email address'; 7 | } 8 | 9 | if (!values.password) { 10 | errors.password = 'Required.'; 11 | } else if (values.password.length < 8) { 12 | errors.password = 'Must be at least 8 characters long.'; 13 | } 14 | 15 | if (!values.phone) { 16 | errors.phone = 'Required.'; 17 | } 18 | 19 | if (!values.confirm) { 20 | errors.confirm = 'Required'; 21 | } else if (values.confirm !== values.password) { 22 | errors.confirm = 'Passwords do not match.'; 23 | } 24 | return errors; 25 | }; 26 | 27 | export const validateSignInForm = values => { 28 | const errors = {}; 29 | if (!values.email) { 30 | errors.email = 'Required.'; 31 | } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { 32 | errors.email = 'Invalid email address'; 33 | } 34 | 35 | if (!values.password) { 36 | errors.password = 'Required.'; 37 | } else if (values.password.length < 8) { 38 | errors.password = 'Must be at least 8 characters long.'; 39 | } 40 | 41 | return errors; 42 | }; 43 | -------------------------------------------------------------------------------- /src/apps/admin/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PrivateRoute from '../../components/PrivateRoute'; 3 | import { Router, navigate } from '@reach/router'; 4 | import Login from './routes/auth/login'; 5 | import Create from './routes/auth/createwiz'; 6 | import Profile from './routes/profile/'; 7 | import Layout from '../../components/layout'; 8 | import withStyles from '@material-ui/core/styles/withStyles'; 9 | 10 | import { styles } from './config/styles'; 11 | 12 | import { useUser } from '../../store/user-context'; 13 | import { UseModal } from '../../store/useModal'; 14 | 15 | function App(props) { 16 | const { login, data, logout, register, getUser } = useUser(); 17 | 18 | return ( 19 | 26 | 27 | 28 | 35 | 41 | 42 | 43 | ); 44 | } 45 | 46 | function AppWithStuff({ children, ...props }) { 47 | return ; 48 | } 49 | 50 | export default withStyles(styles)(AppWithStuff); 51 | -------------------------------------------------------------------------------- /src/apps/admin/routes/affiliation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CreateAffiliation from '../../features/afffiliation.create'; 3 | import { useUser } from '../../../../store/user-context'; 4 | 5 | function CreateAffiliationScreen({ 6 | classes, 7 | navigate, 8 | open, 9 | onCancel, 10 | initialCompany, 11 | ...rest 12 | }) { 13 | const { data } = useUser(); 14 | const { user } = data; 15 | 16 | return ( 17 | onCancel(!open)} 21 | open={open} 22 | initialCompany={initialCompany} 23 | /> 24 | ); 25 | } 26 | 27 | export default CreateAffiliationScreen; 28 | -------------------------------------------------------------------------------- /src/apps/admin/routes/auth/createwiz.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Mutation, Query } from 'react-apollo'; 3 | import styled from 'styled-components'; 4 | import { navigate } from '@reach/router'; 5 | 6 | import Paper from '@material-ui/core/Paper'; 7 | import Typography from '@material-ui/core/Typography'; 8 | import Link from '@material-ui/core/Link'; 9 | import Divider from '@material-ui/core/Divider'; 10 | 11 | import CreateAccount from './create'; 12 | import CreateProfile from './profile'; 13 | 14 | import { 15 | GET_CURRENT_USER_QUERY, 16 | UPDATE_CURRENT_USER_MUTATION, 17 | } from '../../../../graphql'; 18 | 19 | function SignUpWizard({ initialStep = 0, classes, ...rest }) { 20 | const [activeStep, setStep] = useState(initialStep); 21 | 22 | return ( 23 | 24 | 25 | 26 | {({ data, loading, error }) => { 27 | if (loading) { 28 | return null; 29 | } 30 | if (error) { 31 | console.log(error); 32 | } 33 | 34 | return ( 35 |
36 | {getStepContent({ 37 | step: activeStep, 38 | props: { activeStep, setStep, classes, hoist: data }, 39 | })} 40 |
41 | ); 42 | }} 43 |
44 | 45 | 46 | 47 | Already have an account?{' '} 48 | navigate('/app/login/')}> 49 | Sign in! 50 | 51 | 52 |
53 |
54 | ); 55 | } 56 | 57 | export default SignUpWizard; 58 | 59 | const Container = styled.div` 60 | display: flex; 61 | justify-content: center; 62 | `; 63 | 64 | function getStepContent({ step, props, hoist }) { 65 | console.log('STEP', step); 66 | console.log('PROPS', props); 67 | switch (step) { 68 | case 0: 69 | return ; 70 | case 1: 71 | return ( 72 | 73 | {({ data, loading, error }) => { 74 | if (loading) { 75 | return null; 76 | } 77 | if (error) { 78 | console.log('Error Case 1', error); 79 | return null; 80 | } 81 | console.log('PROPS Case 1', props); 82 | console.log('DATA Case 1', data); 83 | return ( 84 | 85 | {mutation => { 86 | return ( 87 | 93 | ); 94 | }} 95 | 96 | ); 97 | }} 98 | 99 | ); 100 | 101 | default: 102 | return 'Unknown step'; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/apps/admin/routes/auth/index.js: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup'; 2 | 3 | export const schema = yup.object().shape({ 4 | firstName: yup 5 | .string() 6 | .min(2, 'Too Short!') 7 | .max(50, 'Too Long!') 8 | .required(), 9 | lastName: yup 10 | .string() 11 | .min(2, 'Too Short!') 12 | .max(50, 'Too Long!') 13 | .required(), 14 | handle: yup 15 | .string() 16 | .min(2, 'Too Short!') 17 | .max(50, 'Too Long!') 18 | .required(), 19 | }); 20 | 21 | export const companySchema = yup.object().shape({}); 22 | -------------------------------------------------------------------------------- /src/apps/admin/routes/auth/mocks.js: -------------------------------------------------------------------------------- 1 | export const opts = [ 2 | { value: 'Attorney', label: 'Attorney' }, 3 | { value: 'Developer', label: 'Developer' }, 4 | { value: 'Academic', label: 'Academic' }, 5 | { value: 'Product Desginer', label: 'Product Desginer' }, 6 | ]; 7 | 8 | export const linkOptions = [ 9 | { value: 'LinkedIn', label: 'LinkedIn' }, 10 | { value: 'Twitter', label: 'Twitter' }, 11 | { value: 'Facebook', label: 'Facebook' }, 12 | { value: 'Other', label: 'Other' }, 13 | ]; 14 | -------------------------------------------------------------------------------- /src/apps/admin/routes/company/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dialog from '@material-ui/core/Dialog'; 3 | import DialogContent from '@material-ui/core/DialogContent'; 4 | import Paper from '@material-ui/core/Paper'; 5 | import Slide from '@material-ui/core/Slide'; 6 | import styled from 'styled-components'; 7 | import CreateCompanyNew from '../../features/company.create'; 8 | import Clear from '@material-ui/icons/Clear'; 9 | 10 | const Transition = React.forwardRef(function Transition(props, ref) { 11 | return ( 12 | 18 | ); 19 | }); 20 | 21 | function CreateCompanyScreen({ 22 | classes, 23 | user, 24 | navigate, 25 | open, 26 | onCancel, 27 | ...rest 28 | }) { 29 | return ( 30 | 37 | 38 | 43 | 44 | 45 | 46 | ); 47 | } 48 | 49 | const StyledDialog = styled(Dialog)` 50 | padding: 0; 51 | margin: 0 16px; 52 | @media (max-width: 750px) { 53 | margin: 0; 54 | } 55 | `; 56 | 57 | const StyledDialogContent = styled(DialogContent)` 58 | position: relative; 59 | width: 100%; 60 | display: flex; 61 | justify-content: center; 62 | padding: 0 16px; 63 | 64 | @media (min-width: 750px) { 65 | max-width: 900px; 66 | padding: 3rem 3rem; 67 | } 68 | `; 69 | 70 | const MobileExit = styled(Clear)` 71 | display: none; 72 | @media (max-width: 750px) { 73 | display: block; 74 | position: absolute; 75 | top: 10px; 76 | right: 8px; 77 | font-weight: 500; 78 | font-size: 27px; 79 | color: #b1040e; // no theme in place 80 | } 81 | `; 82 | 83 | const PaperComponent = ({ children, ...props }) => { 84 | return {children}; 85 | }; 86 | 87 | const StyledPaper = styled(Paper)` 88 | width: 100vw; 89 | height: 100vh; 90 | border-radius: 0; 91 | @media (min-width: 750px) { 92 | max-width: 800px; 93 | max-height: 90vh; 94 | overflow: auto; 95 | } 96 | ::-webkit-scrollbar { 97 | width: 0; 98 | } 99 | `; 100 | 101 | export default CreateCompanyScreen; 102 | -------------------------------------------------------------------------------- /src/apps/admin/routes/profile/graphql/index.js: -------------------------------------------------------------------------------- 1 | export * from './queries'; 2 | -------------------------------------------------------------------------------- /src/apps/admin/routes/profile/graphql/queries.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export const GET_USER_ADMIN_COMPANIES = gql` 4 | query GetUserAdminCompanies($where: PartyAccountWhereUniqueInput!) { 5 | partyAccount(where: $where) { 6 | id 7 | admin { 8 | id 9 | yearFounded 10 | description 11 | name { 12 | id 13 | payload 14 | } 15 | logo { 16 | id 17 | payload 18 | } 19 | description 20 | metadata { 21 | id 22 | isDraft 23 | isPublic 24 | isRejected 25 | isUnverified 26 | isApproved 27 | isPendingReview 28 | } 29 | } 30 | } 31 | } 32 | `; 33 | -------------------------------------------------------------------------------- /src/apps/admin/routes/profile/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import UserProfile from './profile'; 3 | import CreateProfile from '../../../admin/routes/auth/profile'; 4 | import { Mutation } from 'react-apollo'; 5 | import { useQuery } from 'react-apollo-hooks'; 6 | 7 | import { 8 | UPDATE_CURRENT_USER_MUTATION, 9 | GET_CURRENT_USER_QUERY, 10 | } from '../../../../graphql'; 11 | 12 | export const UserProfileWithGraphQL = props => { 13 | const { data, loading, error, refetch } = useQuery(GET_CURRENT_USER_QUERY); 14 | if (loading) { 15 | return null; 16 | } 17 | if (error) { 18 | console.log(error); 19 | return null; 20 | } 21 | 22 | const { me } = data; 23 | 24 | while (me === 'undefined' || me === undefined) { 25 | refetch(); 26 | return null; 27 | } 28 | 29 | if (typeof me !== 'undefined') { 30 | const { person } = me; 31 | 32 | if (person.metadata.isDraft === true) { 33 | return ( 34 | 35 | {mutation => { 36 | return ( 37 | 42 | ); 43 | }} 44 | 45 | ); 46 | } else if (person.metadata.isDraft === false) { 47 | return ; 48 | } 49 | } 50 | }; 51 | 52 | export default UserProfileWithGraphQL; 53 | -------------------------------------------------------------------------------- /src/apps/admin/routes/profile/listitems.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ListItem from '@material-ui/core/ListItem'; 3 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 4 | import ListItemText from '@material-ui/core/ListItemText'; 5 | import ListSubheader from '@material-ui/core/ListSubheader'; 6 | import DashboardIcon from '@material-ui/icons/Dashboard'; 7 | import ShoppingCartIcon from '@material-ui/icons/ShoppingCart'; 8 | import PeopleIcon from '@material-ui/icons/People'; 9 | import BarChartIcon from '@material-ui/icons/BarChart'; 10 | import LayersIcon from '@material-ui/icons/Layers'; 11 | import AssignmentIcon from '@material-ui/icons/Assignment'; 12 | import { navigate } from '@reach/router'; 13 | import { useUser } from '../../../../store/user-context'; 14 | 15 | export function MainListItems(props) { 16 | const { logout } = useUser(); 17 | return ( 18 |
19 | 20 | 21 | 22 | 23 | navigate('/')} /> 24 | 25 | 26 | 27 | 28 | 29 | logout()} /> 30 | 31 |
32 | ); 33 | } 34 | 35 | export default MainListItems; 36 | 37 | export const secondaryListItems = ( 38 |
39 | {/* Saved reports 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | */} 58 |
59 | ); 60 | -------------------------------------------------------------------------------- /src/apps/admin/routes/profile/styles.js: -------------------------------------------------------------------------------- 1 | const drawerWidth = 240; 2 | 3 | // TODO: Delete this file it is dead code; 4 | 5 | export const styles = theme => ({ 6 | menuButton: { 7 | marginLeft: 12, 8 | marginRight: 36, 9 | }, 10 | menuButtonHidden: { 11 | display: 'none', 12 | }, 13 | title: { 14 | flexGrow: 1, 15 | }, 16 | 17 | content: { 18 | flexGrow: 1, 19 | padding: theme.spacing(3), 20 | height: '100vh', 21 | overflow: 'auto', 22 | }, 23 | chartContainer: { 24 | marginLeft: -22, 25 | }, 26 | tableContainer: { 27 | height: 320, 28 | }, 29 | h5: { 30 | marginBottom: theme.spacing(2), 31 | }, 32 | paperRoot: { 33 | ...theme.mixins.gutters(), 34 | paddingTop: theme.spacing(2), 35 | paddingBottom: theme.spacing(2), 36 | marginBottom: theme.spacing(2), 37 | }, 38 | card: { 39 | minWidth: 275, 40 | marginBottom: theme.spacing(2), 41 | color: 'black', 42 | }, 43 | bullet: { 44 | display: 'inline-block', 45 | margin: '0 2px', 46 | transform: 'scale(0.8)', 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/atoms/containers.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | export const Container = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | `; 8 | 9 | export const SectionWrapper = styled.div` 10 | display: flex; 11 | flex-direction: column; 12 | margin-top: 1rem; 13 | margin-bottom: 1rem; 14 | `; 15 | -------------------------------------------------------------------------------- /src/atoms/index.js: -------------------------------------------------------------------------------- 1 | export * from './containers'; 2 | export * from './inputs'; 3 | -------------------------------------------------------------------------------- /src/atoms/inputs.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const HiddenFileInput = styled.input.attrs({ 4 | type: 'file', 5 | id: 'avatar', 6 | accept: 'image/*', 7 | })` 8 | width: 0.1px; 9 | height: 0.1px; 10 | opacity: 0; 11 | overflow: hidden; 12 | position: absolute; 13 | z-index: -1; 14 | `; 15 | -------------------------------------------------------------------------------- /src/atoms/spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from 'styled-components'; 3 | import { BounceLoader as ClipLoader } from 'react-spinners'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | const override = css` 8 | display: block; 9 | margin: 0 auto; 10 | border-color: red; 11 | `; 12 | 13 | class AwesomeComponent extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | loading: true, 18 | }; 19 | } 20 | render() { 21 | return ( 22 | 23 | 29 | 30 | ); 31 | } 32 | } 33 | 34 | export default AwesomeComponent; 35 | 36 | const Container = styled.div` 37 | height: 100vh; 38 | width: 100vw; 39 | display: flex; 40 | justify-content: center; 41 | align-items: center; 42 | background-color: #b1040e; 43 | `; 44 | 45 | const Wrapper = styled.div` 46 | height: 100vh; 47 | width: 100vw; 48 | `; 49 | -------------------------------------------------------------------------------- /src/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { install } from '@material-ui/styles'; 2 | 3 | install(); 4 | -------------------------------------------------------------------------------- /src/components/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { navigate } from '@reach/router'; 4 | 5 | const PrivateRoute = ({ component: Component, location, user, ...rest }) => { 6 | if (!user) { 7 | navigate('/app/login/', { 8 | state: { 9 | from: location, 10 | ...rest, 11 | }, 12 | }); 13 | } 14 | 15 | return ; 16 | }; 17 | 18 | PrivateRoute.propTypes = { 19 | component: PropTypes.any.isRequired, 20 | }; 21 | 22 | export default PrivateRoute; 23 | -------------------------------------------------------------------------------- /src/components/header/__mocks__/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | headerLeftSectionMocks: [ 3 | { title: 'Index', to: '/companies' }, 4 | { title: 'About', to: '/about' }, 5 | ], 6 | headerSecondaryMocks: [ 7 | { title: 'Marketplace', to: '/tags/marketplace/' }, 8 | { title: 'Document Automation', to: '/tags/document-automation' }, 9 | { title: 'Practice Management', to: '/tags/practice-management' }, 10 | { title: 'Research', to: '/tags/legal-research' }, 11 | { title: 'Education', to: '/tags/legal-education' }, 12 | 13 | { title: 'Discovery', to: '/tags/discovery' }, 14 | { title: 'Analytics', to: '/tags/analytics' }, 15 | { title: 'Compliance', to: '/tags/compliance' }, 16 | ], 17 | featurePostMocks: [ 18 | { 19 | title: 'Tattooed Unicorn Venmo Dreamcatcher ', 20 | date: 'Feb 16', 21 | description: 22 | 'Prism bespoke authentic, normcore tousled venmo deep v 3 wolf moon snackwave sriracha.', 23 | }, 24 | { 25 | title: 'Hexagon Pok Pok Master Cleanse.', 26 | date: 'Feb 16', 27 | description: 28 | 'Chic brunch, waistcoat freegan craft beer echo park cronut.', 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/header/header.one.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Typography from '@material-ui/core/Typography'; 4 | import { Link as GatsbyLink } from 'gatsby'; 5 | import Link from '@material-ui/core/Link'; 6 | 7 | export function HeaderCenter({ siteTitle, classes, ...props }) { 8 | return ( 9 |
10 | {siteTitle && ( 11 | ( 13 | 14 | ))} 15 | variant="h5" 16 | color="inherit" 17 | style={{ 18 | fontWeight: '700', 19 | letterSpacing: '-.5px', 20 | textDecoration: 'none', 21 | }} 22 | noWrap 23 | > 24 | {siteTitle} 25 | 26 | )} 27 |
28 | ); 29 | } 30 | 31 | HeaderCenter.propTypes = { 32 | siteTitle: PropTypes.string.isRequired, 33 | }; 34 | 35 | export default HeaderCenter; 36 | -------------------------------------------------------------------------------- /src/components/header/header.secondary.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/styles'; 3 | import { Link as GatsbyLink } from 'gatsby'; 4 | import Toolbar from '@material-ui/core/Toolbar'; 5 | import Button from '@material-ui/core/Button'; 6 | 7 | const AdapterLink = React.forwardRef((props, ref) => ( 8 | 9 | )); 10 | 11 | const useStyles = makeStyles(theme => ({ 12 | toolbarSecondary: { 13 | justifyContent: 'space-between', 14 | }, 15 | 16 | sectionDesktop: { 17 | display: 'none', 18 | [theme.breakpoints.up('lg')]: { 19 | display: 'flex', 20 | justifyContent: 'center', 21 | }, 22 | }, 23 | })); 24 | 25 | export function SecondaryHeader(props) { 26 | const classes = useStyles(); 27 | const { sections } = props; 28 | return ( 29 |
30 | 31 | {sections && 32 | sections.map(section => ( 33 | 44 | ))} 45 | 46 |
47 | ); 48 | } 49 | 50 | export default SecondaryHeader; 51 | -------------------------------------------------------------------------------- /src/components/header/header.styles.js: -------------------------------------------------------------------------------- 1 | import { fade } from '@material-ui/core/styles/colorManipulator'; 2 | 3 | export const styles = theme => ({ 4 | layout: { 5 | display: 'flex', 6 | justifyContent: 'space-between', 7 | marginLeft: theme.spacing(3), 8 | marginRight: theme.spacing(3), 9 | }, 10 | headerAppBar: { 11 | display: 'flex', 12 | alignItems: 'space-between', 13 | flexDirection: 'column', 14 | zIndex: 1300, 15 | boxShadow: 'none', 16 | }, 17 | toolbarMain: { 18 | borderBottom: `1px solid ${theme.palette.grey[300]}`, 19 | flexGrow: 1, 20 | display: 'flex', 21 | justifyContent: 'space-between', 22 | }, 23 | inputRoot: { 24 | color: 'primary', 25 | width: '100%', 26 | }, 27 | container: { 28 | flexGrow: 1, 29 | position: 'relative', 30 | }, 31 | paper: { 32 | position: 'absolute', 33 | zIndex: 1, 34 | marginTop: theme.spacing(1), 35 | left: 0, 36 | right: 0, 37 | }, 38 | chip: { 39 | margin: `${theme.spacing(0.5)}px ${theme.spacing(0.25)}px`, 40 | }, 41 | wrapper: { 42 | display: 'flex', 43 | justifyContent: 'center', 44 | alignItems: 'center', 45 | }, 46 | inputInput: { 47 | paddingTop: theme.spacing(1), 48 | paddingRight: theme.spacing(1), 49 | paddingBottom: theme.spacing(1), 50 | paddingLeft: theme.spacing(10), 51 | transition: theme.transitions.create('width'), 52 | width: '100%', 53 | [theme.breakpoints.up('sm')]: { 54 | width: 400, 55 | '&:focus': { 56 | width: 500, 57 | }, 58 | }, 59 | }, 60 | sectionDesktop: { 61 | display: 'none', 62 | [theme.breakpoints.up('md')]: { 63 | display: 'flex', 64 | }, 65 | }, 66 | sectionMobile: { 67 | display: 'flex', 68 | [theme.breakpoints.up('md')]: { 69 | display: 'none', 70 | }, 71 | }, 72 | search: { 73 | position: 'relative', 74 | borderRadius: theme.shape.borderRadius, 75 | backgroundColor: fade(theme.palette.primary.main, 0.15), 76 | '&:hover': { 77 | backgroundColor: fade(theme.palette.primary.main, 0.25), 78 | }, 79 | marginLeft: 0, 80 | width: '100%', 81 | [theme.breakpoints.up('sm')]: { 82 | marginLeft: theme.spacing(1), 83 | width: 'auto', 84 | }, 85 | }, 86 | searchIcon: { 87 | width: theme.spacing(9), 88 | height: '100%', 89 | position: 'absolute', 90 | pointerEvents: 'none', 91 | display: 'flex', 92 | alignItems: 'center', 93 | justifyContent: 'center', 94 | }, 95 | }); 96 | -------------------------------------------------------------------------------- /src/components/header/header.three.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link as GatsbyLink } from 'gatsby'; 4 | import Link from '@material-ui/core/Link'; 5 | import Button from '@material-ui/core/Button'; 6 | import { useUser } from '../../store/user-context'; 7 | import { navigate } from '@reach/router'; 8 | import { CreateCompanyModalContext } from '../../store/modal-context'; 9 | import renderAvatar from './helpers/renderAvatar'; 10 | 11 | export function HeaderLeft({ sections, classes, ...props }) { 12 | const { showModal } = useContext(CreateCompanyModalContext); 13 | const { data, logout } = useUser(); 14 | const { user } = data; 15 | 16 | return ( 17 | 18 |
19 | {sections && 20 | sections.map(section => { 21 | return ( 22 | 37 | ); 38 | })} 39 | 40 | 52 | 53 | 54 | {user ? ( 55 | renderAvatar(user) 56 | ) : ( 57 | 58 | 68 | 69 | )} 70 |
71 |
72 | ); 73 | } 74 | 75 | HeaderLeft.propTypes = { 76 | sections: PropTypes.arrayOf( 77 | PropTypes.shape({ 78 | title: PropTypes.string.isRequired, 79 | }) 80 | ), 81 | }; 82 | 83 | HeaderLeft.defaultProps = { 84 | sections: [], 85 | }; 86 | 87 | export default HeaderLeft; 88 | -------------------------------------------------------------------------------- /src/components/header/header.two.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { styles } from './header.styles'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import MainSearch from '../search'; 6 | 7 | export function HeaderRight(props) { 8 | const { classes, allSitePages, shouldShowSearch = true } = props; 9 | 10 | if (shouldShowSearch === false) { 11 | return null; 12 | } 13 | 14 | return ( 15 |
16 | 21 |
22 | ); 23 | } 24 | 25 | HeaderRight.propTypes = { 26 | classes: PropTypes.object, 27 | allSitePages: PropTypes.any, 28 | }; 29 | 30 | export default withStyles(styles)(HeaderRight); 31 | -------------------------------------------------------------------------------- /src/components/header/helpers/renderAvatar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Avatar from '@material-ui/core/Avatar'; 3 | import PersonIcon from '@material-ui/icons/Person'; 4 | import { navigate } from '@reach/router'; 5 | import PersonIcon from '@material-ui/icons/Person'; 6 | 7 | function renderAvatar(user) { 8 | const { person } = user; 9 | const { avatar, name } = person; 10 | 11 | if (avatar && avatar.length > 0) { 12 | const [userAvatar, ...rest] = avatar; 13 | return ( 14 | navigate('/app/profile/')} 17 | style={{ marginLeft: 10 }} 18 | imgProps={{ 19 | style: { maxWidth: '100%', maxHeight: '100%' }, 20 | }} 21 | /> 22 | ); 23 | } else if (name && name.length > 0) { 24 | const [userName, ...rest] = name; 25 | const { firstName, lastName } = userName; 26 | return ( 27 | navigate('/app/profile/')} 29 | style={{ marginLeft: 10 }} 30 | imgProps={{ 31 | style: { maxWidth: '100%', maxHeight: '100%' }, 32 | }} 33 | > 34 | {`${firstName[0] + lastName[0]}`} 35 | 36 | ); 37 | } 38 | 39 | return ( 40 | navigate('/app/profile/')} 42 | style={{ marginLeft: 10 }} 43 | imgProps={{ 44 | style: { maxWidth: '100%', maxHeight: '100%' }, 45 | }} 46 | > 47 | 48 | 49 | ); 50 | } 51 | 52 | export default renderAvatar; 53 | -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppBar from '@material-ui/core/AppBar'; 3 | import Toolbar from '@material-ui/core/Toolbar'; 4 | import HeaderThree from './header.three'; 5 | import HeaderOne from './header.one'; 6 | import HeaderTwo from './header.two'; 7 | import SecondaryHeader from './header.secondary'; 8 | import Hidden from '@material-ui/core/Hidden'; 9 | import MobileNav from './header.mobile'; 10 | import { styles } from './header.styles'; 11 | import { withStyles } from '@material-ui/core/styles'; 12 | import mocks from './__mocks__'; 13 | 14 | export function Header({ 15 | siteTitle, 16 | classes, 17 | shouldShowSecondaryHeader = true, 18 | allSitePage, 19 | shouldShowSearch, 20 | }) { 21 | return ( 22 | 23 | 28 | 29 | 30 | 31 | 36 | 40 | 41 | 42 | 48 | 49 | 50 | {shouldShowSecondaryHeader && ( 51 | 52 | )} 53 | 54 | 55 | ); 56 | } 57 | 58 | export default withStyles(styles)(Header); 59 | -------------------------------------------------------------------------------- /src/components/image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StaticQuery, graphql } from 'gatsby'; 3 | import Img from 'gatsby-image'; 4 | 5 | /* 6 | * This component is built using `gatsby-image` to automatically serve optimized 7 | * images with lazy loading and reduced file sizes. The image is loaded using a 8 | * `StaticQuery`, which allows us to load the image from directly within this 9 | * component, rather than having to pass the image data down from pages. 10 | * 11 | * For more information, see the docs: 12 | * - `gatsby-image`: https://gatsby.app/gatsby-image 13 | * - `StaticQuery`: https://gatsby.app/staticquery 14 | */ 15 | 16 | const Image = () => ( 17 | } 30 | /> 31 | ); 32 | export default Image; 33 | -------------------------------------------------------------------------------- /src/components/input/field.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | const elevation = [ 6 | 'box-shadow: inset 0 7px 9px -7px rgba(0,0,0, 0.7)', 7 | 'box-shadow: 0 1px 3px rgba(0,0,0, 0.12), 0 1px 2px rgba(0,0,0, 0.24)', 8 | 'box-shadow: 0 3px 6px rgba(0,0,0, 0.16), 0 3px 6px rgba(0,0,0, 0.23)', 9 | ]; 10 | 11 | InputField.propTypes = { 12 | id: PropTypes.string.isRequired, 13 | leftButton: PropTypes.node, 14 | onBlur: PropTypes.func, 15 | onChange: PropTypes.func, 16 | onFocus: PropTypes.func, 17 | password: PropTypes.bool, 18 | rightButton: PropTypes.node, 19 | type: PropTypes.string, 20 | value: PropTypes.string, 21 | }; 22 | 23 | InputField.defaultProps = { 24 | leftButton: undefined, 25 | password: false, 26 | rightButton: undefined, 27 | type: 'text', 28 | }; 29 | 30 | export default InputField; 31 | 32 | /* eslint no-console: off */ 33 | 34 | function InputField({ leftButton, rightButton, onChange, ...props }) { 35 | return ( 36 | 37 | {leftButton && {leftButton}} 38 | 39 | {rightButton && ( 40 | {rightButton} 41 | )} 42 | 43 | ); 44 | } 45 | 46 | /* eslint react/prop-types: off */ 47 | /* eslint no-magic-numbers: off */ 48 | 49 | const InputWrapper = styled.div.attrs(props => ({ 50 | height: props.height || 60, 51 | }))` 52 | max-width: 50vw; 53 | display: flex; 54 | height: 50px; 55 | width: 100%; 56 | position: relative; 57 | `; 58 | 59 | const InputButtonWrapper = styled.div.attrs(props => ({ 60 | height: props.height || 60, 61 | inputWidth: props.width - 0.2 * props.height || 600 - 12, 62 | }))` 63 | width: ${props => props.height * 0.5}px; 64 | height: ${props => props.height * 0.5}px; 65 | position: absolute; 66 | top: ${props => props.height * 0.15}px; 67 | left: ${({ left, inputWidth }) => (left ? '5px' : `${inputWidth * 0.925}px`)}; 68 | z-index: 10; 69 | `; 70 | 71 | const Input = styled.input.attrs(props => ({ 72 | height: props.height || 60, 73 | readOnly: props.readOnly, 74 | width: props.width || 600, 75 | }))` 76 | max-width: ${props => props.width}px; 77 | min-height: ${props => props.height * 0.75}px; 78 | width: 100%; 79 | cursor: text; 80 | outline: none; 81 | overflow: hidden; 82 | font-size: 1rem; 83 | border: none; 84 | border: solid 1px #ccc; 85 | border-radius: 10px; 86 | 87 | padding-left: 19px; 88 | 89 | &:focus { 90 | outline: none !important; 91 | border: 1px solid #f4f4f4; 92 | ${elevation[1]}; 93 | } 94 | `; 95 | -------------------------------------------------------------------------------- /src/components/input/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codexstanford/techlist-frontend-web/06be56c7f1bbaa34e65ba942a24fe29257ae4b7f/src/components/input/index.js -------------------------------------------------------------------------------- /src/components/input/input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Field from './field'; 4 | import Label from './label'; 5 | import styled from 'styled-components'; 6 | 7 | TextInput.propTypes = { 8 | children: PropTypes.node, 9 | id: PropTypes.string, 10 | label: PropTypes.string, 11 | labelModifiers: PropTypes.array, 12 | onChange: PropTypes.func, 13 | width: PropTypes.number, 14 | }; 15 | 16 | function TextInput({ label, id, onChange, width, labelModifiers, ...props }) { 17 | return ( 18 | 19 | {label && ( 20 | 28 | )} 29 | 30 | 31 | ); 32 | } 33 | 34 | export default TextInput; 35 | 36 | const StyledInputWrapper = styled.div` 37 | margin: 1rem 0; 38 | display: flex; 39 | justify-content: center; 40 | position: relative; 41 | font-weight: 700; 42 | width: 100%; 43 | &:focus + label { 44 | font-weight: 900; 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /src/components/input/label.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import { applyStyleModifiers } from 'styled-components-modifiers'; 5 | 6 | /* eslint no-magic-numbers: off */ 7 | 8 | Label.propTypes = { 9 | children: PropTypes.node.isRequired, 10 | htmlFor: PropTypes.string, 11 | id: PropTypes.string, 12 | isFocused: PropTypes.bool, 13 | labelSpacing: PropTypes.number, 14 | modifiers: PropTypes.array, 15 | move: PropTypes.bool, 16 | style: PropTypes.object, 17 | }; 18 | 19 | export default Label; 20 | 21 | function Label({ children, id, style, isFocused, move, ...props }) { 22 | return ( 23 | 24 | 32 | {children} 33 | 34 | 35 | ); 36 | } 37 | 38 | const StyledLabel = styled.label.attrs(props => ({}))` 39 | font-size: 13px; 40 | `; 41 | 42 | const LABEL_MODIFIERS = { 43 | odd: () => ` 44 | background: rgb(243,244,245); 45 | `, 46 | }; 47 | 48 | const StyledLabelWrap = styled.div` 49 | position: absolute; 50 | top: -10px; 51 | left: 15px; 52 | z-index: 10; 53 | background: white; 54 | padding: 0 5px; 55 | 56 | ${applyStyleModifiers(LABEL_MODIFIERS)}; 57 | `; 58 | -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { StaticQuery, graphql } from 'gatsby'; 4 | 5 | import styled from 'styled-components'; 6 | import Header from './header/index'; 7 | 8 | const Layout = ({ 9 | children, 10 | allSitePage, 11 | user, 12 | fullScreen = false, 13 | ...rest 14 | }) => ( 15 | 16 | ( 27 | 28 |
35 | 36 | {fullScreen ? ( 37 | {children} 38 | ) : ( 39 | 40 | {children} 41 | 42 | )} 43 | 44 |