├── .nvmrc ├── src ├── functions │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── apple-touch-icon.png │ │ └── apple-touch-icon-precomposed.png │ ├── admin.ts │ ├── tsconfig.json │ ├── content │ │ └── talk-http-functions.ts │ └── util │ │ └── newsletter.ts └── app │ ├── next-env.d.ts │ ├── public │ ├── kostas.png │ ├── social-media-square.png │ ├── favicons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── ms-icon-144x144.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ └── android-icon-192x192.png │ └── stacks │ │ ├── react.svg │ │ ├── material-ui.svg │ │ ├── react-inverse.svg │ │ ├── nuxt.svg │ │ ├── vue.svg │ │ ├── framer-motion.svg │ │ ├── logstash.svg │ │ ├── windows.svg │ │ ├── xstate.svg │ │ ├── serverless.svg │ │ ├── ethereum.svg │ │ ├── elm.svg │ │ ├── haskell.svg │ │ ├── unity.svg │ │ ├── microsoft.svg │ │ ├── expo.svg │ │ ├── angular.svg │ │ ├── pytorch.svg │ │ ├── auth0.svg │ │ ├── momentjs.svg │ │ ├── heroku.svg │ │ ├── sketch.svg │ │ ├── gatsby.svg │ │ ├── reason.svg │ │ ├── apollo.svg │ │ ├── bluetooth.svg │ │ ├── cocoapods.svg │ │ ├── google-play.svg │ │ ├── flutter.svg │ │ ├── webassembly.svg │ │ ├── relay.svg │ │ ├── rabbitmq.svg │ │ ├── apple.svg │ │ ├── ionic.svg │ │ ├── azure.svg │ │ ├── hasura.svg │ │ ├── components.svg │ │ ├── npm.svg │ │ ├── circleci.svg │ │ ├── twitter.svg │ │ ├── webpack.svg │ │ ├── progressive-web-apps.svg │ │ ├── git.svg │ │ ├── javascript.svg │ │ ├── javascript-inverse.svg │ │ ├── css.svg │ │ ├── aws-lambda.svg │ │ ├── aws-cognito.svg │ │ ├── accessibility.svg │ │ ├── markdown.svg │ │ ├── terminal.svg │ │ ├── flux.svg │ │ ├── json.svg │ │ ├── lodash.svg │ │ ├── nginx.svg │ │ ├── kotlin.svg │ │ ├── swift.svg │ │ ├── mobx.svg │ │ ├── graphene.svg │ │ ├── tensorflow.svg │ │ ├── helm.svg │ │ ├── bem.svg │ │ ├── dynamodb.svg │ │ ├── storybook.svg │ │ ├── docker.svg │ │ ├── nodemon.svg │ │ ├── ios.svg │ │ ├── couchdb.svg │ │ ├── preact.svg │ │ ├── react-router.svg │ │ ├── eslint.svg │ │ ├── es6.svg │ │ ├── webcomponents.svg │ │ ├── jasmine.svg │ │ ├── c++.svg │ │ ├── nextjs.svg │ │ ├── design.svg │ │ ├── html5.svg │ │ ├── handlebars.svg │ │ ├── python.svg │ │ └── wordpress.svg │ ├── stores │ ├── useStores.tsx │ └── index.tsx │ ├── constants │ ├── sizes.ts │ ├── initialState.ts │ ├── routes.ts │ ├── talkTypes.ts │ └── categories.ts │ ├── .babelrc │ ├── components │ ├── DistinctiveTooltip.tsx │ ├── hub │ │ ├── HubSection.tsx │ │ ├── HubInterstitialTalk.tsx │ │ ├── HubEditions.tsx │ │ └── HubInterstitialTalkImage.tsx │ ├── HotTalks.tsx │ ├── UpcomingEditions.tsx │ ├── RecentEditions.tsx │ ├── edition │ │ └── EditionCover.tsx │ ├── TalkGrid.tsx │ ├── TalkCover.tsx │ ├── CuratedYears.tsx │ ├── TextExpander.tsx │ ├── Welcome.tsx │ ├── Breadcrumbs.tsx │ ├── EventEditions.tsx │ ├── CuratedCountries.tsx │ ├── LinkPrefetch.tsx │ ├── StackTabs.tsx │ ├── page-talk │ │ └── TalkVideo.tsx │ ├── CuratedTalks.tsx │ ├── user │ │ └── SavedTalks.tsx │ ├── SignIn.tsx │ ├── context-providers │ │ └── StackContextProvider.tsx │ ├── Layout.tsx │ ├── CuratorCard.tsx │ ├── TalkAccordion.tsx │ └── TalkCoverImage.tsx │ ├── tsconfig.json │ ├── pages │ ├── index.tsx │ ├── account.tsx │ ├── saved.tsx │ ├── topic │ │ └── [topicid].tsx │ ├── _app.tsx │ ├── [eventid] │ │ └── [editionid].tsx │ ├── top100.tsx │ └── hero │ │ └── [heroid].tsx │ ├── services │ └── GA.ts │ ├── next.config.js │ └── appTheme.ts ├── .firebaserc ├── .gitignore ├── storage.rules ├── firestore.rules ├── .editorconfig ├── .travis.yml └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /src/functions/public/robots.txt: -------------------------------------------------------------------------------- 1 | Sitemap: https://hero35.com/sitemap.txt 2 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "prod": "heroes-9c313" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /src/app/public/kostas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/kostas.png -------------------------------------------------------------------------------- /src/functions/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/functions/public/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .firebase/ 2 | *.log 3 | node_modules/ 4 | .env 5 | .DS_Store 6 | *.code-workspace 7 | dist/ 8 | bundles/ 9 | -------------------------------------------------------------------------------- /src/app/public/social-media-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/social-media-square.png -------------------------------------------------------------------------------- /src/app/public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /src/app/public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /src/app/public/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /src/functions/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/functions/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /src/app/public/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /src/app/public/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /src/app/public/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/app/public/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /src/functions/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavropalias/Hero35/HEAD/src/functions/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/app/stores/useStores.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storesContext } from "./index"; 3 | 4 | export const useStores = () => React.useContext(storesContext); 5 | -------------------------------------------------------------------------------- /src/app/constants/sizes.ts: -------------------------------------------------------------------------------- 1 | const APP_BAR_HEIGHT = "65px"; 2 | const TALK_PAGE_PRIMARY_CONTENT_HEIGHT = "175px"; 3 | 4 | export { APP_BAR_HEIGHT, TALK_PAGE_PRIMARY_CONTENT_HEIGHT }; 5 | -------------------------------------------------------------------------------- /src/functions/admin.ts: -------------------------------------------------------------------------------- 1 | import * as functions from "firebase-functions"; 2 | import * as admin from "firebase-admin"; 3 | 4 | // Init Firebase 5 | admin.initializeApp(); 6 | export const db = admin.firestore(); 7 | -------------------------------------------------------------------------------- /storage.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | match /{allPaths=**} { 4 | allow read: if true; 5 | allow write: if request.auth.uid == 'OXXDuevPrfbLTaH4etkbUZZri2z1'; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "admin"; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | tab_width = 2 13 | trim_trailing_whitespace = true 14 | -------------------------------------------------------------------------------- /src/app/constants/initialState.ts: -------------------------------------------------------------------------------- 1 | import { UserContextProps } from "../schema"; 2 | 3 | const INITIAL_STATE: UserContextProps = { 4 | dislikedTalks: [], 5 | likedTalks: [], 6 | name: null, 7 | picture: null, 8 | signedIn: false, 9 | savedTalks: [] 10 | }; 11 | 12 | export default INITIAL_STATE; 13 | -------------------------------------------------------------------------------- /src/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [ 4 | [ 5 | "@babel/plugin-proposal-decorators", 6 | { 7 | "legacy": true 8 | } 9 | ], 10 | [ 11 | "@babel/plugin-proposal-class-properties", 12 | { 13 | "loose": true 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/app/constants/routes.ts: -------------------------------------------------------------------------------- 1 | const ACCOUNT = `/account`; 2 | const SAVED_TALKS = `/saved`; 3 | const CURATED = `/curated-conference-talks`; 4 | const HOME = `/`; 5 | const TWITTER = `https://twitter.com/Hero35Official`; 6 | 7 | export default { 8 | ACCOUNT, 9 | CURATED, 10 | HOME, 11 | SAVED_TALKS, 12 | TWITTER 13 | }; 14 | -------------------------------------------------------------------------------- /src/app/stores/index.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import { UserStore } from "./UserStore"; 3 | import { useStaticRendering } from "mobx-react-lite"; 4 | 5 | const isServer = typeof window === "undefined"; 6 | useStaticRendering(isServer); 7 | 8 | export const storesContext = createContext({ 9 | userStore: new UserStore() 10 | }); 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: 5 | directories: 6 | - $HOME/.cache/yarn 7 | - node_modules 8 | - .next/cache 9 | install: 10 | - npm i -g npm 11 | - npm i -g firebase-tools 12 | - npm ci 13 | script: 14 | - firebase deploy -m "$TRAVIS_COMMIT" --token "$FIREBASE_TOKEN" --force 15 | branches: 16 | only: 17 | - master 18 | -------------------------------------------------------------------------------- /src/app/public/stacks/react.svg: -------------------------------------------------------------------------------- 1 | 2 | React Logo 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/public/stacks/material-ui.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/public/stacks/react-inverse.svg: -------------------------------------------------------------------------------- 1 | 2 | React Logo 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/components/DistinctiveTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { withStyles } from "@material-ui/styles"; 2 | import { Tooltip, Theme } from "@material-ui/core"; 3 | 4 | const DistinctiveTooltip = withStyles((theme: Theme) => ({ 5 | tooltip: { 6 | backgroundColor: theme.palette.secondary.main, 7 | color: "rgba(0, 0, 0, 0.87)", 8 | boxShadow: theme.shadows[1], 9 | fontSize: 11 10 | } 11 | }))(Tooltip); 12 | 13 | export default DistinctiveTooltip; 14 | -------------------------------------------------------------------------------- /src/functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "esModuleInterop": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "isolatedModules": false, 7 | "lib": ["es2017"], 8 | "module": "commonjs", 9 | "outDir": "../../dist/functions", 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "strict": false, 13 | "target": "es2017" 14 | }, 15 | "compileOnSave": true 16 | } 17 | -------------------------------------------------------------------------------- /src/app/components/hub/HubSection.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@material-ui/core"; 2 | import { TalkGroupContents } from "../../schema"; 3 | import TalkGroup from "../TalkGroup"; 4 | 5 | interface Props { 6 | content: TalkGroupContents; 7 | } 8 | 9 | const HubSection = ({ content }: Props) => { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default HubSection; 18 | -------------------------------------------------------------------------------- /src/app/components/HotTalks.tsx: -------------------------------------------------------------------------------- 1 | import { Hidden } from "@material-ui/core"; 2 | import { Talk } from "../schema"; 3 | import TalkGrid from "./TalkGrid"; 4 | import TalkList from "./TalkList"; 5 | 6 | interface Props { 7 | talks?: Talk[]; 8 | } 9 | 10 | const HotTalks = ({ talks }: Props) => ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | export default HotTalks; 21 | -------------------------------------------------------------------------------- /src/app/public/stacks/nuxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/functions/content/talk-http-functions.ts: -------------------------------------------------------------------------------- 1 | import * as functions from "firebase-functions"; 2 | import util from "../util/util"; 3 | import { getTalksTopYear } from "./talk-getters"; 4 | import { TalkBasic } from "schema"; 5 | 6 | /** 7 | * Get top Talks 8 | */ 9 | export const talksTop = functions.https.onRequest(async (req, res) => { 10 | const [request, response, approved] = util.middleware(req, res, true, true); 11 | if (!approved) return response.status(403).send(); 12 | const talks: TalkBasic[] = await getTalksTopYear(); 13 | response.json(talks); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/components/UpcomingEditions.tsx: -------------------------------------------------------------------------------- 1 | import { Hidden } from "@material-ui/core"; 2 | import { EventEdition } from "../schema"; 3 | import EditionList from "./EditionList"; 4 | import EditionGrid from "./EditionGrid"; 5 | 6 | interface Props { 7 | editions?: EventEdition[]; 8 | } 9 | 10 | const UpcomingEditions = ({ editions }: Props) => ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | export default UpcomingEditions; 21 | -------------------------------------------------------------------------------- /src/app/components/RecentEditions.tsx: -------------------------------------------------------------------------------- 1 | import { Hidden } from "@material-ui/core"; 2 | import { EventEdition } from "../schema"; 3 | import EditionList from "./EditionList"; 4 | import EditionGrid from "./EditionGrid"; 5 | 6 | interface Props { 7 | editions?: EventEdition[]; 8 | } 9 | 10 | const RecentEditions = ({ editions }: Props) => ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | export default RecentEditions; 21 | -------------------------------------------------------------------------------- /src/app/public/stacks/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "skipLibCheck": true, 6 | "experimentalDecorators": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "allowJs": true, 16 | "strict": false 17 | }, 18 | "exclude": ["node_modules"], 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { HubContent } from "../schema"; 2 | import Database from "../services/Database"; 3 | import { NextPage } from "next"; 4 | import Hub from "../components/hub/Hub"; 5 | import Layout from "../components/Layout"; 6 | 7 | interface Props { 8 | hubContent: HubContent; 9 | } 10 | 11 | const Home: NextPage = ({ hubContent }) => ( 12 | 13 | 14 | 15 | ); 16 | 17 | Home.getInitialProps = async () => { 18 | const hubContent = await Database.getHub(); 19 | return { 20 | hubContent 21 | }; 22 | }; 23 | 24 | export default Home; 25 | -------------------------------------------------------------------------------- /src/app/services/GA.ts: -------------------------------------------------------------------------------- 1 | declare const window: any; 2 | 3 | export const GA_TRACKING_ID = "UA-749229-33"; 4 | 5 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages 6 | export const pageview = url => { 7 | if (window.gtag) { 8 | window.gtag("config", GA_TRACKING_ID, { 9 | page_path: url 10 | }); 11 | } 12 | }; 13 | 14 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events 15 | export const event = ({ action, category, label, value }) => { 16 | if (window.gtag) { 17 | window.gtag("event", action, { 18 | event_category: category, 19 | event_label: label, 20 | value: value 21 | }); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/public/stacks/framer-motion.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/public/stacks/logstash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/public/stacks/windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/public/stacks/xstate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/public/stacks/serverless.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/pages/account.tsx: -------------------------------------------------------------------------------- 1 | import Layout from "../components/Layout"; 2 | import dynamic from "next/dynamic"; 3 | import { makeStyles, createStyles, Theme, Container } from "@material-ui/core"; 4 | import AccountDetails from "../components/user/AccountDetails"; 5 | import { observer } from "mobx-react-lite"; 6 | import { useStores } from "../stores/useStores"; 7 | 8 | const SignIn = dynamic(() => import("../components/SignIn")); 9 | 10 | const useStyles = makeStyles((theme: Theme) => 11 | createStyles({ 12 | container: { 13 | marginTop: theme.spacing(2) 14 | } 15 | }) 16 | ); 17 | 18 | const Account = observer(() => { 19 | const { userStore } = useStores(); 20 | const classes = useStyles({}); 21 | 22 | return ( 23 | 24 | 25 | {userStore.isSignedIn && } 26 | 27 | 28 | ); 29 | }); 30 | 31 | export default Account; 32 | -------------------------------------------------------------------------------- /src/app/pages/saved.tsx: -------------------------------------------------------------------------------- 1 | import Layout from "../components/Layout"; 2 | import dynamic from "next/dynamic"; 3 | import { makeStyles, createStyles, Theme, Container } from "@material-ui/core"; 4 | import SavedTalks from "../components/user/SavedTalks"; 5 | import { observer } from "mobx-react-lite"; 6 | import { useStores } from "../stores/useStores"; 7 | 8 | const SignIn = dynamic(() => import("../components/SignIn")); 9 | 10 | const useStyles = makeStyles((theme: Theme) => 11 | createStyles({ 12 | container: { 13 | marginTop: theme.spacing(6) 14 | } 15 | }) 16 | ); 17 | 18 | const PageSavedTalks = observer(() => { 19 | const { userStore } = useStores(); 20 | const classes = useStyles({}); 21 | return ( 22 | 23 | 24 | {userStore.isSignedIn && } 25 | 26 | 27 | ); 28 | }); 29 | 30 | export default PageSavedTalks; 31 | -------------------------------------------------------------------------------- /src/app/components/edition/EditionCover.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, createStyles, Theme } from "@material-ui/core"; 2 | import { EventEdition } from "../../schema"; 3 | import EditionCoverMedia from "./EditionCoverMedia"; 4 | import EditionCoverText from "./EditionCoverText"; 5 | 6 | const useStyles = makeStyles((theme: Theme) => 7 | createStyles({ 8 | header: { 9 | width: "100%", 10 | position: "relative", 11 | minHeight: `calc(85vh - 48px)`, 12 | display: "flex", 13 | alignItems: "center" 14 | } 15 | }) 16 | ); 17 | 18 | const EditionCover = ({ 19 | edition, 20 | shouldLinkTitle = true 21 | }: { 22 | edition: EventEdition; 23 | shouldLinkTitle?: boolean; 24 | }) => { 25 | const classes = useStyles({}); 26 | return ( 27 |
28 | 29 | 30 |
31 | ); 32 | }; 33 | 34 | export default EditionCover; 35 | -------------------------------------------------------------------------------- /src/app/public/stacks/elm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/public/stacks/haskell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/components/hub/HubInterstitialTalk.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, createStyles, Theme, Divider } from "@material-ui/core"; 2 | import { TalkBasic } from "../../schema"; 3 | import HubInterstitialTalkText from "./HubInterstitialTalkText"; 4 | import HubInterstitialTalkImage from "./HubInterstitialTalkImage"; 5 | 6 | const useStyles = makeStyles((theme: Theme) => 7 | createStyles({ 8 | interstitial: { 9 | width: "100%", 10 | position: "relative", 11 | borderTop: `1px solid ${theme.palette.divider}`, 12 | borderBottom: `1px solid ${theme.palette.divider}` 13 | } 14 | }) 15 | ); 16 | 17 | const HubInterstitialTalk = ({ 18 | talk, 19 | color 20 | }: { 21 | talk: TalkBasic; 22 | color?: string; 23 | }) => { 24 | const classes = useStyles({}); 25 | return ( 26 |
27 | 28 | 29 |
30 | ); 31 | }; 32 | 33 | export default HubInterstitialTalk; 34 | -------------------------------------------------------------------------------- /src/app/next.config.js: -------------------------------------------------------------------------------- 1 | const withCSS = require("@zeit/next-css"); 2 | const withBundleAnalyzer = require("@zeit/next-bundle-analyzer"); 3 | 4 | module.exports = withBundleAnalyzer( 5 | withCSS({ 6 | analyzeServer: ["server", "both"].includes(process.env.BUNDLE_ANALYZE), 7 | analyzeBrowser: ["browser", "both"].includes(process.env.BUNDLE_ANALYZE), 8 | bundleAnalyzerConfig: { 9 | server: { 10 | analyzerMode: "static", 11 | reportFilename: "../../../../bundles/server.html" 12 | }, 13 | browser: { 14 | analyzerMode: "static", 15 | reportFilename: "../../../../bundles/client.html" 16 | } 17 | }, 18 | env: { 19 | API_KEY: process.env.API_KEY, 20 | STORAGE_PATH: 21 | "https://firebasestorage.googleapis.com/v0/b/heroes-9c313.appspot.com/o/" 22 | }, 23 | compress: false, 24 | distDir: "../../dist/functions/next", 25 | poweredByHeader: false, 26 | target: "serverless", 27 | webpack(config) { 28 | return config; 29 | } 30 | }) 31 | ); 32 | -------------------------------------------------------------------------------- /src/app/constants/talkTypes.ts: -------------------------------------------------------------------------------- 1 | import { TalkType } from "../schema"; 2 | 3 | const TALK_TYPES: TalkType[] = [ 4 | { 5 | id: "1", 6 | title: "Keynote", 7 | titlePlural: "Keynotes" 8 | }, 9 | { 10 | id: "2", 11 | title: "Talk", 12 | titlePlural: "Talks" 13 | }, 14 | { 15 | id: "3", 16 | title: "Lightning talk", 17 | titlePlural: "Lightning talks" 18 | }, 19 | { 20 | id: "4", 21 | title: "Panel discussion", 22 | titlePlural: "Panel discussions" 23 | }, 24 | { 25 | id: "5", 26 | title: "Q&A session", 27 | titlePlural: "Q&A sessions" 28 | }, 29 | { 30 | id: "6", 31 | title: "Sponsor talk", 32 | titlePlural: "Sponsor talks" 33 | }, 34 | { 35 | id: "7", 36 | title: "Workshop", 37 | titlePlural: "Workshops" 38 | }, 39 | { 40 | id: "8", 41 | title: "Interview", 42 | titlePlural: "Interviews" 43 | }, 44 | { 45 | id: "9", 46 | title: "Highlights", 47 | titlePlural: "Highlights" 48 | } 49 | ]; 50 | 51 | export default TALK_TYPES; 52 | -------------------------------------------------------------------------------- /src/app/public/stacks/unity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/public/stacks/expo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/components/TalkGrid.tsx: -------------------------------------------------------------------------------- 1 | import { Grid } from "@material-ui/core"; 2 | import { NextPage } from "next"; 3 | import { TalkBasic } from "../schema"; 4 | import TalkCard from "./TalkCard"; 5 | import CuratorCard from "./CuratorCard"; 6 | 7 | interface Props { 8 | talks?: TalkBasic[]; 9 | showCuration?: boolean; 10 | showTopics?: boolean; 11 | showSaveButton?: boolean; 12 | } 13 | 14 | const TalkGrid: NextPage = ({ 15 | talks, 16 | showCuration, 17 | showTopics = true, 18 | showSaveButton = true 19 | }) => ( 20 | 21 | {showCuration && ( 22 | 23 | 24 | 25 | )} 26 | {talks.map(talk => ( 27 | 28 | 34 | 35 | ))} 36 | 37 | ); 38 | 39 | export default TalkGrid; 40 | -------------------------------------------------------------------------------- /src/app/public/stacks/angular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/public/stacks/pytorch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/auth0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/components/TalkCover.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, createStyles, Theme } from "@material-ui/core"; 2 | import { TalkBasic } from "../schema"; 3 | import TalkCoverImage from "./TalkCoverImage"; 4 | import TalkCoverText from "./TalkCoverText"; 5 | 6 | const useStyles = makeStyles((theme: Theme) => 7 | createStyles({ 8 | header: { 9 | width: "100%", 10 | position: "relative", 11 | minHeight: `calc(85vh - 48px)`, 12 | display: "flex", 13 | alignItems: "center" 14 | } 15 | }) 16 | ); 17 | 18 | const TalkCover = ({ 19 | color, 20 | logo, 21 | shouldLinkTitle = true, 22 | talk, 23 | title 24 | }: { 25 | color?: string; 26 | logo?: string; 27 | shouldLinkTitle?: boolean; 28 | talk: TalkBasic; 29 | title?: string; 30 | }) => { 31 | const classes = useStyles({}); 32 | return ( 33 |
34 | 35 | 42 |
43 | ); 44 | }; 45 | 46 | export default TalkCover; 47 | -------------------------------------------------------------------------------- /src/app/public/stacks/momentjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/public/stacks/heroku.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/public/stacks/sketch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/public/stacks/gatsby.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/reason.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/pages/topic/[topicid].tsx: -------------------------------------------------------------------------------- 1 | import Layout from "../../components/Layout"; 2 | import { HubContent } from "../../schema"; 3 | import Database from "../../services/Database"; 4 | import { NextPage, NextPageContext } from "next"; 5 | import STACKS from "../../constants/stacks"; 6 | import Hub from "../../components/hub/Hub"; 7 | 8 | interface Props { 9 | title: string; 10 | content: HubContent; 11 | } 12 | 13 | const TopicDetails: NextPage = ({ title, content }) => { 14 | const stack = STACKS.filter(stack => stack.slug === title)[0]; 15 | return ( 16 | 17 | 23 | 24 | ); 25 | }; 26 | 27 | interface QueryProps { 28 | topicid: string; 29 | stack?: string; 30 | } 31 | TopicDetails.getInitialProps = async (ctx: NextPageContext) => { 32 | const { topicid: title } = (ctx.query as unknown) as QueryProps; 33 | const content: HubContent = await Database.getHub(title); 34 | return { title, content }; 35 | }; 36 | 37 | export default TopicDetails; 38 | -------------------------------------------------------------------------------- /src/app/public/stacks/apollo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/bluetooth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/cocoapods.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/components/CuratedYears.tsx: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles, Theme, Button } from "@material-ui/core"; 2 | import { useContext } from "react"; 3 | import { StackContext } from "./context-providers/StackContextProvider"; 4 | import LinkPrefetch from "./LinkPrefetch"; 5 | 6 | const useStyles = makeStyles((theme: Theme) => 7 | createStyles({ 8 | item: { 9 | margin: theme.spacing(0, 1, 1, 0) 10 | } 11 | }) 12 | ); 13 | 14 | const CuratedYears = () => { 15 | const { state: stateStack } = useContext(StackContext); 16 | const classes = useStyles({}); 17 | const items = ["2020", "2019", "2018", "2017", "2016", "2015"]; 18 | 19 | return ( 20 | <> 21 | {items.map(item => ( 22 | 32 | 35 | 36 | ))} 37 | 38 | ); 39 | }; 40 | 41 | export default CuratedYears; 42 | -------------------------------------------------------------------------------- /src/functions/util/newsletter.ts: -------------------------------------------------------------------------------- 1 | import * as functions from "firebase-functions"; 2 | import { auth } from "firebase-admin"; 3 | import crypto from "crypto"; 4 | import Mailchimp from "mailchimp-api-v3"; 5 | 6 | const mailchimp = new Mailchimp(functions.config().mailchimp.api_key); 7 | 8 | /** 9 | * Subscribe new user to newsletter 10 | */ 11 | export const newsletterSubscribe = functions.auth 12 | .user() 13 | .onCreate(async (user: auth.UserRecord) => { 14 | const { email, displayName } = user; 15 | const hash = crypto 16 | .createHash("md5") 17 | .update(email.toLowerCase()) 18 | .digest("hex"); 19 | try { 20 | await mailchimp.put( 21 | `/lists/${functions.config().mailchimp.list_id}/members/${hash}`, 22 | { 23 | email_address: email, 24 | status_if_new: "subscribed", 25 | status: "subscribed", 26 | merge_fields: { 27 | NAME: displayName || "" 28 | } 29 | } 30 | ); 31 | console.log( 32 | `Successfully added new user ${displayName || 33 | ""} ${email} to newsletter` 34 | ); 35 | } catch (err) { 36 | console.error( 37 | "Mailchimp: Error while attempting to add registered subscriber —", 38 | err 39 | ); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/public/stacks/google-play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/public/stacks/flutter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/public/stacks/webassembly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/appTheme.ts: -------------------------------------------------------------------------------- 1 | import { createMuiTheme, responsiveFontSizes } from "@material-ui/core/styles"; 2 | 3 | const appTheme = responsiveFontSizes( 4 | createMuiTheme({ 5 | palette: { 6 | type: "dark", 7 | primary: { 8 | main: "#00AEFF" 9 | }, 10 | secondary: { 11 | main: "#FFD14A" 12 | }, 13 | background: { 14 | paper: "#191919", 15 | default: "#121212" 16 | } 17 | }, 18 | overrides: { 19 | MuiCardHeader: { 20 | title: { 21 | fontSize: "16px" 22 | }, 23 | subheader: { 24 | fontSize: "14px" 25 | } 26 | } 27 | }, 28 | props: { 29 | MuiTab: { 30 | style: { 31 | minWidth: "50px" 32 | } 33 | }, 34 | MuiTextField: { 35 | variant: "outlined" 36 | } 37 | }, 38 | typography: { 39 | fontFamily: 40 | "-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol", 41 | h1: { fontWeight: 800 }, 42 | h2: { fontWeight: 800 }, 43 | h3: { fontWeight: 800 }, 44 | h4: { 45 | fontWeight: 700 46 | }, 47 | h5: { 48 | fontWeight: 700 49 | }, 50 | h6: { 51 | fontWeight: 700 52 | } 53 | } 54 | }) 55 | ); 56 | 57 | export default appTheme; 58 | -------------------------------------------------------------------------------- /src/app/components/TextExpander.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | makeStyles, 3 | Theme, 4 | createStyles, 5 | Typography, 6 | Box, 7 | Button 8 | } from "@material-ui/core"; 9 | import { useStores } from "../stores/useStores"; 10 | import { useState } from "react"; 11 | 12 | const useStyles = makeStyles((theme: Theme) => 13 | createStyles({ 14 | default: { 15 | display: "block", 16 | maxWidth: theme.breakpoints.values.sm, 17 | maxHeight: "4.5em", 18 | overflow: "hidden" 19 | }, 20 | expanded: { 21 | display: "block", 22 | maxWidth: theme.breakpoints.values.sm, 23 | whiteSpace: "pre-line" 24 | }, 25 | expandButton: { 26 | color: theme.palette.text.secondary, 27 | padding: 0, 28 | marginTop: theme.spacing(0.5) 29 | } 30 | }) 31 | ); 32 | 33 | const TextExpander = ({ text }: { text: string }) => { 34 | const [expanded, setExpanded] = useState(false); 35 | const classes = useStyles({}); 36 | 37 | return ( 38 | <> 39 | 40 | {text} 41 | 42 | {!expanded && ( 43 | 50 | )} 51 | 52 | ); 53 | }; 54 | 55 | export default TextExpander; 56 | -------------------------------------------------------------------------------- /src/app/public/stacks/relay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/public/stacks/rabbitmq.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/components/Welcome.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | makeStyles, 3 | Theme, 4 | createStyles, 5 | Typography, 6 | Box, 7 | Button 8 | } from "@material-ui/core"; 9 | import { useStores } from "../stores/useStores"; 10 | 11 | const useStyles = makeStyles((theme: Theme) => 12 | createStyles({ 13 | buttonSignup: { 14 | padding: theme.spacing(3, 8), 15 | fontSize: theme.typography.fontSize * 2.5, 16 | fontWeight: 700, 17 | lineHeight: 1, 18 | margin: theme.spacing(3, 0, 2) 19 | } 20 | }) 21 | ); 22 | 23 | const Welcome = () => { 24 | const { userStore } = useStores(); 25 | const classes = useStyles({}); 26 | 27 | return ( 28 | <> 29 | 30 | 31 | Watch the best developer talks, 32 |
33 | discover top conferences, 34 |
35 | elevate your skills 36 |
37 | 46 | 47 | It's free. Sign up with your Github/Twitter/Google/Email. 48 | 49 |
50 | 51 | ); 52 | }; 53 | 54 | export default Welcome; 55 | -------------------------------------------------------------------------------- /src/app/components/Breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme, createStyles } from "@material-ui/core"; 2 | 3 | const useStyles = makeStyles((theme: Theme) => 4 | createStyles({ 5 | breadcrumb: { 6 | display: "none" 7 | } 8 | }) 9 | ); 10 | 11 | const Breadcrumbs = ({ 12 | items 13 | }: { 14 | items: { path?: string; title: string }[]; 15 | }) => { 16 | const classes = useStyles({}); 17 | 18 | return ( 19 | <> 20 |
    25 |
  1. 26 | 27 | React 28 | 29 | 30 |
  2. 31 | {items.map((item, index) => ( 32 |
  3. 33 | {item.path ? ( 34 | 39 | {item.title} 40 | 41 | ) : ( 42 | {item.title} 43 | )} 44 | 45 |
  4. 46 | ))} 47 |
48 | 49 | ); 50 | }; 51 | 52 | export default Breadcrumbs; 53 | -------------------------------------------------------------------------------- /src/app/components/EventEditions.tsx: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles, Theme } from "@material-ui/core"; 2 | import { Event } from "../schema"; 3 | import EditionList from "./EditionList"; 4 | 5 | const useStyles = makeStyles((theme: Theme) => 6 | createStyles({ 7 | talkAccordion: { 8 | marginTop: theme.spacing(2) 9 | } 10 | }) 11 | ); 12 | 13 | const EventEditions = ({ event }: { event?: Event }) => { 14 | const classes = useStyles({}); 15 | 16 | const pastEditions = () => { 17 | const currentDate = new Date(); 18 | return event.editions 19 | .filter(edition => { 20 | const editionDate = new Date(edition.endDate); 21 | if (editionDate.getTime() < currentDate.getTime()) return true; 22 | else return false; 23 | }) 24 | .sort((a, b) => (a.startDate < b.startDate ? 1 : -1)); 25 | }; 26 | 27 | const upcomingEditions = () => { 28 | const currentDate = new Date(); 29 | return event.editions.filter(edition => { 30 | const editionDate = new Date(edition.endDate); 31 | if (editionDate.getTime() > currentDate.getTime()) return true; 32 | else return false; 33 | }); 34 | }; 35 | 36 | return ( 37 | <> 38 | {upcomingEditions().length > 0 && ( 39 | 43 | )} 44 | 48 | 49 | ); 50 | }; 51 | 52 | export default EventEditions; 53 | -------------------------------------------------------------------------------- /src/app/public/stacks/apple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/public/stacks/ionic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/components/CuratedCountries.tsx: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles, Theme, Button } from "@material-ui/core"; 2 | import { Flag as ItemIcon } from "@material-ui/icons"; 3 | import { useContext } from "react"; 4 | import { StackContext } from "./context-providers/StackContextProvider"; 5 | import CATEGORIES from "../constants/categories"; 6 | import LinkPrefetch from "./LinkPrefetch"; 7 | 8 | const useStyles = makeStyles((theme: Theme) => 9 | createStyles({ 10 | icon: { 11 | marginRight: theme.spacing(1) 12 | }, 13 | item: { 14 | margin: theme.spacing(0, 1, 1, 0) 15 | } 16 | }) 17 | ); 18 | 19 | const CuratedCountries = () => { 20 | const { state: stateStack } = useContext(StackContext); 21 | const classes = useStyles({}); 22 | 23 | return ( 24 | <> 25 | {CATEGORIES.find(cat => cat.id === stateStack.id).countries.map(item => ( 26 | 36 | 44 | 45 | ))} 46 | 47 | ); 48 | }; 49 | 50 | export default CuratedCountries; 51 | -------------------------------------------------------------------------------- /src/app/public/stacks/azure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/public/stacks/hasura.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/public/stacks/components.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/public/stacks/npm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/components/LinkPrefetch.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Router from "next/router"; 3 | import { format, resolve, parse, UrlObject } from "url"; 4 | 5 | const prefetch = async (href: UrlObject | string, as: UrlObject | string) => { 6 | // if we're running server-side do nothing 7 | if (typeof window === "undefined") return; 8 | 9 | const url: string = typeof href !== "string" ? format(href) : href; 10 | const urlAs = typeof as !== "string" ? format(as) : as; 11 | 12 | // Re-create query params 13 | const query = {}; 14 | const queryParams = url 15 | .split("/") 16 | .filter(param => param.length > 0) 17 | .map(param => param.replace(/[\[\]]/g, "")); 18 | const queryParamsValues = urlAs.split("/").filter(value => value.length > 0); 19 | queryParams.forEach((param, index) => { 20 | query[param] = queryParamsValues[index]; 21 | }); 22 | 23 | const parsedHref = resolve(window.location.pathname, url); 24 | const { pathname } = typeof href !== "string" ? href : parse(url, true); 25 | const Component: any = await Router.prefetch(parsedHref); 26 | 27 | // if Component exists and has getInitialProps 28 | // fetch the component props (the component should save it in cache) 29 | if (Component?.getInitialProps) { 30 | const ctx = { pathname, query, isVirtualCall: true }; 31 | await Component.getInitialProps(ctx); 32 | } 33 | }; 34 | 35 | // Extend default next/link to customize the prefetch behaviour 36 | export default class LinkPrefetch extends Link { 37 | // Always prefetch with getInitialProps 38 | async prefetch() { 39 | prefetch(this.props.href, this.props.as); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/public/stacks/circleci.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/app/components/StackTabs.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | makeStyles, 3 | createStyles, 4 | Theme, 5 | Tabs, 6 | Tab, 7 | LinearProgress 8 | } from "@material-ui/core"; 9 | import { useContext, useState, useEffect } from "react"; 10 | import CATEGORIES from "../constants/categories"; 11 | import { StackContext } from "./context-providers/StackContextProvider"; 12 | 13 | const useStyles = makeStyles((theme: Theme) => 14 | createStyles({ 15 | tabs: { 16 | marginBottom: theme.spacing(2) 17 | } 18 | }) 19 | ); 20 | 21 | const StackTabs = ({ 22 | fetch, 23 | isLoading, 24 | stateMark 25 | }: { 26 | fetch: Function; 27 | isLoading: boolean; 28 | stateMark?: any; 29 | }) => { 30 | const { state: stateStack } = useContext(StackContext); 31 | const [tab, setTab] = useState(stateStack.id); 32 | const classes = useStyles({}); 33 | 34 | useEffect(() => { 35 | setTab(stateStack.id); 36 | }, [stateMark]); 37 | 38 | const handleChange = async (_: React.ChangeEvent<{}>, newValue: string) => { 39 | setTab(newValue); 40 | fetch(newValue); 41 | }; 42 | 43 | return ( 44 | <> 45 | 54 | {CATEGORIES.map(cat => ( 55 | 56 | ))} 57 | 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default StackTabs; 64 | -------------------------------------------------------------------------------- /src/app/components/page-talk/TalkVideo.tsx: -------------------------------------------------------------------------------- 1 | import YouTube, { Options } from "react-youtube"; 2 | import { makeStyles, createStyles, Theme, Box } from "@material-ui/core"; 3 | import { 4 | APP_BAR_HEIGHT, 5 | TALK_PAGE_PRIMARY_CONTENT_HEIGHT 6 | } from "../../constants/sizes"; 7 | 8 | const useStyles = makeStyles((theme: Theme) => 9 | createStyles({ 10 | container: { 11 | position: "relative", 12 | height: "50vh", 13 | width: "100vw", 14 | maxHeight: `calc(100vh - ${APP_BAR_HEIGHT} - ${TALK_PAGE_PRIMARY_CONTENT_HEIGHT})`, 15 | [theme.breakpoints.up("md")]: { 16 | height: "100vh" 17 | } 18 | }, 19 | youtubePlayer: { 20 | position: "absolute", 21 | top: 0, 22 | left: 0, 23 | width: "100%", 24 | height: "100%" 25 | } 26 | }) 27 | ); 28 | 29 | type TalkVideo = { 30 | videoid: string; 31 | start?: number; 32 | end?: number; 33 | }; 34 | const TalkVideo = ({ videoid, start, end }: TalkVideo) => { 35 | const classes = useStyles({}); 36 | 37 | const opts: Options = { 38 | height: "100%", 39 | width: "100%", 40 | playerVars: { end, modestbranding: 1, playsinline: 1, rel: 0, start } 41 | }; 42 | 43 | const onPlayerReady = event => { 44 | event.target.playVideo(); 45 | const player = document.getElementById("widget2"); 46 | }; 47 | 48 | return ( 49 | 50 | 56 | 57 | ); 58 | }; 59 | 60 | export default TalkVideo; 61 | -------------------------------------------------------------------------------- /src/app/public/stacks/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/public/stacks/webpack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/public/stacks/progressive-web-apps.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/public/stacks/git.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/public/stacks/javascript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/public/stacks/javascript-inverse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/public/stacks/css.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/public/stacks/aws-lambda.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/public/stacks/aws-cognito.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/components/CuratedTalks.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, Typography, Link } from "@material-ui/core"; 2 | import { useTheme } from "@material-ui/core/styles"; 3 | import useMediaQuery from "@material-ui/core/useMediaQuery"; 4 | import { NextPage } from "next"; 5 | import { Talk } from "../schema"; 6 | import CuratedTalk from "./CuratedTalk"; 7 | import { useContext } from "react"; 8 | import { StackContext } from "./context-providers/StackContextProvider"; 9 | import LinkPrefetch from "./LinkPrefetch"; 10 | 11 | interface Props { 12 | talks?: Talk[]; 13 | className?: string; 14 | } 15 | 16 | const CuratedTalks: NextPage = ({ talks, className }) => { 17 | const { state: stateStack } = useContext(StackContext); 18 | const theme = useTheme(); 19 | const talkCount = useMediaQuery(theme.breakpoints.only("sm")) ? 4 : 3; 20 | 21 | return ( 22 |
23 | 24 | 30 | Curated {stateStack.contextTitle} talks 31 | 32 | 33 | 34 | Must-watch {stateStack.contextTitle} talks from developer conferences 35 | around the world, hand-picked by our editorial team. 36 | 37 | 38 | {talks.slice(0, talkCount).map(talk => ( 39 | 40 | 41 | 42 | ))} 43 | 44 |
45 | ); 46 | }; 47 | 48 | export default CuratedTalks; 49 | -------------------------------------------------------------------------------- /src/app/public/stacks/accessibility.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/public/stacks/markdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/public/stacks/terminal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/app/components/user/SavedTalks.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Container, 4 | Typography, 5 | makeStyles, 6 | Theme, 7 | createStyles, 8 | Button 9 | } from "@material-ui/core"; 10 | import { 11 | Bookmarks as BookmarksIcon, 12 | BookmarkBorder as BookmarkOutlinedIcon 13 | } from "@material-ui/icons"; 14 | import TalkGrid from "../TalkGrid"; 15 | import { observer } from "mobx-react-lite"; 16 | import { useStores } from "../../stores/useStores"; 17 | 18 | const SavedTalks = observer(() => { 19 | const { userStore } = useStores(); 20 | return ( 21 | <> 22 | 23 | 24 | 25 |   Saved talks 26 | 27 | 28 | 29 | {userStore.savedTalks.length > 0 ? ( 30 | 31 | ) : ( 32 | <> 33 | 34 | You can save talks to watch later. 35 | 36 | 37 | Look for this button, across the site:   38 | 47 | 48 | 49 | Your saved talks will appear on this page. 50 | 51 | 52 | )} 53 | 54 | 55 | ); 56 | }); 57 | 58 | export default SavedTalks; 59 | -------------------------------------------------------------------------------- /src/app/components/hub/HubEditions.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | makeStyles, 3 | createStyles, 4 | Theme, 5 | Typography, 6 | Box 7 | } from "@material-ui/core"; 8 | import { EventEdition } from "../../schema"; 9 | import EditionGroup from "../EditionGroup"; 10 | 11 | const useStyles = makeStyles((theme: Theme) => 12 | createStyles({ 13 | titleContainer: { 14 | margin: theme.spacing(0, 1, 2, 2), 15 | [theme.breakpoints.up("sm")]: { 16 | margin: theme.spacing(0, 1, 2, 3) 17 | }, 18 | [theme.breakpoints.up("md")]: { 19 | margin: theme.spacing(0, 1, 3, 4) 20 | } 21 | }, 22 | title: { 23 | color: theme.palette.text.secondary, 24 | lineHeight: 1 25 | }, 26 | subtitle: { 27 | display: "block", 28 | marginTop: theme.spacing(1) 29 | } 30 | }) 31 | ); 32 | 33 | const HubEditions = ({ 34 | editions, 35 | showDate, 36 | showEditionTitle, 37 | title, 38 | subtitle 39 | }: { 40 | editions: EventEdition[]; 41 | showDate?: boolean; 42 | showEditionTitle?: boolean; 43 | title?: string; 44 | subtitle?: string; 45 | }) => { 46 | const classes = useStyles({}); 47 | return ( 48 | 49 |
50 | 51 | {title} 52 | 53 | {subtitle && ( 54 | 59 | {subtitle} 60 | 61 | )} 62 |
63 | 68 |
69 | ); 70 | }; 71 | 72 | export default HubEditions; 73 | -------------------------------------------------------------------------------- /src/app/components/SignIn.tsx: -------------------------------------------------------------------------------- 1 | import StyledFirebaseAuth from "react-firebaseui/StyledFirebaseAuth"; 2 | import firebase from "firebase/app"; 3 | import { 4 | Dialog, 5 | DialogTitle, 6 | DialogContent, 7 | DialogActions, 8 | Button, 9 | CircularProgress 10 | } from "@material-ui/core"; 11 | import { useStores } from "../stores/useStores"; 12 | import { observer } from "mobx-react-lite"; 13 | 14 | const SignIn = observer(() => { 15 | const { userStore } = useStores(); 16 | 17 | // Configure FirebaseUI. 18 | const uiConfig = { 19 | signInFlow: "popup", 20 | signInOptions: [ 21 | firebase.auth.GithubAuthProvider.PROVIDER_ID, 22 | firebase.auth.TwitterAuthProvider.PROVIDER_ID, 23 | firebase.auth.GoogleAuthProvider.PROVIDER_ID, 24 | firebase.auth.EmailAuthProvider.PROVIDER_ID 25 | ], 26 | callbacks: { 27 | // Do not redirect after sign-in 28 | signInSuccessWithAuthResult: _ => false 29 | }, 30 | tosUrl: "/terms-of-service", 31 | privacyPolicyUrl: "/privacy-policy" 32 | }; 33 | 34 | const handleClose = () => { 35 | userStore.setShouldSignIn(false); 36 | }; 37 | 38 | return ( 39 | 44 | Sign in 45 | 46 | {firebase.auth ? ( 47 | 51 | ) : ( 52 | 53 | )} 54 | 55 | 56 | 59 | 60 | 61 | ); 62 | }); 63 | 64 | export default SignIn; 65 | -------------------------------------------------------------------------------- /src/app/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import App from "next/app"; 3 | import Head from "next/head"; 4 | import Router from "next/router"; 5 | import { ThemeProvider } from "@material-ui/styles"; 6 | import CssBaseline from "@material-ui/core/CssBaseline"; 7 | import theme from "../appTheme"; 8 | import * as ga from "../services/GA"; 9 | import "../style.css"; 10 | import NProgress from "nprogress"; 11 | import Footer from "../components/Footer"; 12 | import { StackContextProvider } from "../components/context-providers/StackContextProvider"; 13 | 14 | NProgress.configure({ showSpinner: false }); 15 | let doneLoading = true; 16 | Router.events.on("routeChangeStart", url => { 17 | doneLoading = false; 18 | setTimeout(() => { 19 | if (!doneLoading) { 20 | NProgress.start(); 21 | } 22 | }, 300); 23 | }); 24 | Router.events.on("routeChangeError", () => { 25 | doneLoading = true; 26 | NProgress.done(); 27 | }); 28 | Router.events.on("routeChangeComplete", url => { 29 | if (window.location.hostname !== "localhost") { 30 | ga.pageview(url); 31 | } 32 | doneLoading = true; 33 | NProgress.done(); 34 | }); 35 | 36 | export default class MyApp extends App { 37 | componentDidMount() { 38 | // Remove the server-side injected CSS. 39 | const jssStyles = document.querySelector("#jss-server-side"); 40 | if (jssStyles && jssStyles.parentNode) { 41 | jssStyles.parentNode.removeChild(jssStyles); 42 | } 43 | } 44 | 45 | render() { 46 | const { Component, pageProps } = this.props; 47 | 48 | return ( 49 | <> 50 | 51 | HERO35 52 | 53 | 54 | 55 | 56 | 57 |