diff --git a/apps/_template/codegen.yml b/apps/_template/codegen.yml
new file mode 100644
index 0000000..aede7bd
--- /dev/null
+++ b/apps/_template/codegen.yml
@@ -0,0 +1,7 @@
+overwrite: true
+schema: "https://graph.codeday.org/"
+documents: "./src/**/*.gql"
+generates:
+ ./src/@types/graphql-modules.d.ts:
+ plugins:
+ - typescript-graphql-files-modules
diff --git a/apps/_template/gql-loader.js b/apps/_template/gql-loader.js
new file mode 100644
index 0000000..0cef9ba
--- /dev/null
+++ b/apps/_template/gql-loader.js
@@ -0,0 +1,108 @@
+"use strict";
+const path = require("path");
+const fs = require("fs");
+const { parse, visit } = require("graphql");
+
+function resolveImports(source, filePath, visited = new Set()) {
+ if (visited.has(filePath)) return [];
+ visited.add(filePath);
+
+ const dir = path.dirname(filePath);
+ const lines = source.split(/\r\n|\r|\n/);
+ let definitions = [];
+ const queryLines = [];
+
+ for (const line of lines) {
+ if (line.trim().startsWith("#import")) {
+ const match = line.match(/#import\s+["']([^"']+)["']/);
+ if (match) {
+ const importPath = path.resolve(dir, match[1]);
+ const importSource = fs.readFileSync(importPath, "utf-8");
+ definitions = definitions.concat(resolveImports(importSource, importPath, visited));
+ }
+ } else {
+ queryLines.push(line);
+ }
+ }
+
+ const cleanSource = queryLines.join("\n").trim();
+ if (cleanSource) {
+ const doc = parse(cleanSource);
+ definitions = definitions.concat(doc.definitions);
+ }
+
+ return definitions;
+}
+
+function deduplicateDefs(defs) {
+ const seen = new Set();
+ return defs
+ .slice()
+ .reverse()
+ .filter((def) => {
+ if (def.kind === "FragmentDefinition") {
+ if (seen.has(def.name.value)) return false;
+ seen.add(def.name.value);
+ }
+ return true;
+ })
+ .reverse();
+}
+
+function collectFragmentRefs(def) {
+ const refs = new Set();
+ visit(def, {
+ FragmentSpread(node) {
+ refs.add(node.name.value);
+ },
+ });
+ return refs;
+}
+
+function getTransitiveDeps(name, definitionMap, seen = new Set()) {
+ if (seen.has(name)) return seen;
+ seen.add(name);
+ const def = definitionMap.get(name);
+ if (def) {
+ for (const ref of collectFragmentRefs(def)) {
+ getTransitiveDeps(ref, definitionMap, seen);
+ }
+ }
+ return seen;
+}
+
+module.exports = function (source) {
+ if (this.cacheable) this.cacheable();
+
+ const allDefs = resolveImports(source, this.resourcePath);
+ const uniqueDefs = deduplicateDefs(allDefs);
+
+ const definitionMap = new Map();
+ for (const def of uniqueDefs) {
+ if (def.name) {
+ definitionMap.set(def.name.value, def);
+ }
+ }
+
+ const namedDefs = uniqueDefs.filter(
+ (d) => (d.kind === "OperationDefinition" || d.kind === "FragmentDefinition") && d.name,
+ );
+
+ function stripLoc(key, val) {
+ return key === "loc" ? undefined : val;
+ }
+
+ const fullDoc = { kind: "Document", definitions: uniqueDefs };
+ let output = `const _doc = ${JSON.stringify(fullDoc, stripLoc)};\n`;
+ output += `export default _doc;\n\n`;
+
+ for (const def of namedDefs) {
+ const name = def.name.value;
+ const deps = getTransitiveDeps(name, definitionMap);
+ const subsetDefs = uniqueDefs.filter((d) => d.name && deps.has(d.name.value));
+ const subsetDoc = { kind: "Document", definitions: subsetDefs };
+ output += `export const ${name} = ${JSON.stringify(subsetDoc, stripLoc)};\n`;
+ }
+
+ return output;
+};
diff --git a/apps/_template/next-env.d.ts b/apps/_template/next-env.d.ts
new file mode 100644
index 0000000..1970904
--- /dev/null
+++ b/apps/_template/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+import "./.next/types/routes.d.ts";
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/apps/_template/next.config.ts b/apps/_template/next.config.ts
new file mode 100644
index 0000000..4ffd0f0
--- /dev/null
+++ b/apps/_template/next.config.ts
@@ -0,0 +1,24 @@
+import type { NextConfig } from "next";
+import path from "path";
+
+const nextConfig: NextConfig = {
+ transpilePackages: ["@codeday/topo", "@codeday/topocons"],
+ turbopack: {
+ rules: {
+ "*.gql": {
+ loaders: [path.resolve("gql-loader.js")],
+ as: "*.js",
+ },
+ },
+ },
+ webpack(config) {
+ config.module.rules.push({
+ test: /\.gql$/,
+ exclude: /node_modules/,
+ loader: path.resolve("gql-loader.js"),
+ });
+ return config;
+ },
+};
+
+export default nextConfig;
diff --git a/apps/_template/package.json b/apps/_template/package.json
new file mode 100644
index 0000000..4ab6e16
--- /dev/null
+++ b/apps/_template/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@codeday/APPNAME",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "codegen": "echo 'Add .gql files to src/ then run: graphql-codegen'"
+ },
+ "dependencies": {
+ "@codeday/topo": "workspace:*",
+ "@codeday/topocons": "workspace:*",
+ "@codeday/utils": "workspace:*",
+ "graphql": "catalog:",
+ "graphql-tag": "^2.12.6",
+ "next": "catalog:",
+ "react": "catalog:",
+ "react-dom": "catalog:"
+ },
+ "devDependencies": {
+ "@codeday/tsconfig": "workspace:*",
+ "@graphql-codegen/cli": "^5.0.6",
+ "@graphql-codegen/typescript-graphql-files-modules": "^3.0.0",
+ "@types/node": "catalog:",
+ "@types/react": "catalog:",
+ "@types/react-dom": "catalog:",
+ "typescript": "catalog:"
+ }
+}
diff --git a/apps/_template/src/pages/_app.tsx b/apps/_template/src/pages/_app.tsx
new file mode 100644
index 0000000..b3786c2
--- /dev/null
+++ b/apps/_template/src/pages/_app.tsx
@@ -0,0 +1,12 @@
+import { ThemeProvider, PageDataProvider } from "@codeday/topo/Theme";
+import type { AppProps } from "next/app";
+
+export default function App({ Component, pageProps }: AppProps) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/_template/src/pages/index.tsx b/apps/_template/src/pages/index.tsx
new file mode 100644
index 0000000..9d65855
--- /dev/null
+++ b/apps/_template/src/pages/index.tsx
@@ -0,0 +1,11 @@
+import { Content } from "@codeday/topo/Molecule";
+import { Heading, Text } from "@codeday/topo/Atom";
+
+export default function Home() {
+ return (
+
+ Hello World
+ Replace this with your app content.
+
+ );
+}
diff --git a/apps/_template/tsconfig.json b/apps/_template/tsconfig.json
new file mode 100644
index 0000000..ffc566c
--- /dev/null
+++ b/apps/_template/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@codeday/tsconfig/nextjs.json",
+ "compilerOptions": {
+ "typeRoots": ["./src/@types"]
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/apps/www/src/components/Calendly.tsx b/apps/www/src/components/Calendly.tsx
index bdf853e..6dfc409 100644
--- a/apps/www/src/components/Calendly.tsx
+++ b/apps/www/src/components/Calendly.tsx
@@ -1,51 +1 @@
-import { Box } from "@codeday/topo/Atom";
-import React, { useEffect, useState, useRef } from "react";
-
-interface CalendlyProps {
- slug: string;
- meeting?: string;
- calendlyURLParams?: string;
- [key: string]: any;
-}
-
-export default function Calendly({ slug, meeting, calendlyURLParams, ...props }: CalendlyProps) {
- const holder = useRef(null);
- const [hasCalendlyLoaded, setHasCalendlyLoaded] = useState(false);
-
- const typeOfWindow = typeof window; // For static analysis
- const windowCalendly = typeOfWindow !== "undefined" && (window as any)?.Calendly;
-
- useEffect(() => {
- if (window && !(window as any).Calendly) {
- const script = document.createElement("script");
- script.src = "https://calendly.com/assets/external/widget.js";
- script.addEventListener("load", () => {
- setHasCalendlyLoaded(true);
- });
- document.head.appendChild(script);
-
- const link = document.createElement("link");
- link.href = "https://calendly.com/assets/external/widget.css";
- link.rel = "stylesheet";
- document.body.appendChild(link);
-
- return () => {
- document.head.removeChild(script);
- document.body.removeChild(link);
- };
- }
- }, [typeOfWindow]);
-
- useEffect(() => {
- if (windowCalendly && holder) {
- (window as any).Calendly.initInlineWidget({
- url: `https://calendly.com/${slug}${meeting ? `/${meeting}` : ""}${calendlyURLParams || ""}`,
- parentElement: holder.current,
- prefill: {},
- utm: {},
- });
- }
- }, [windowCalendly, hasCalendlyLoaded, holder, slug, meeting]);
-
- return ;
-}
+export { CalendlyEmbed as default } from "@codeday/topo/Molecule";
diff --git a/apps/www/src/components/Contact/Employees.tsx b/apps/www/src/components/Contact/Employees.tsx
index c0d3547..2357bcf 100644
--- a/apps/www/src/components/Contact/Employees.tsx
+++ b/apps/www/src/components/Contact/Employees.tsx
@@ -2,7 +2,7 @@ import { Text, Box, Grid, Image, Link } from "@codeday/topo/Atom";
import { Content } from "@codeday/topo/Molecule";
import React from "react";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
const titleContents = ["CEO", "President", "VP", "Chief", "Director", "Head", "Manager", "Lead"];
const titlePrecedence = (title: string) =>
@@ -29,7 +29,7 @@ function dedupeByKey(key: string, arr: any[]) {
export default function Employees(props: any) {
const {
account: { employees, otherTeam, contractors },
- } = useQuery();
+ } = usePageData();
const sortedEmployees = dedupeByKey("username", [
...employees,
diff --git a/apps/www/src/components/EventInfo.gql b/apps/www/src/components/EventInfo/EventInfo.gql
similarity index 100%
rename from apps/www/src/components/EventInfo.gql
rename to apps/www/src/components/EventInfo/EventInfo.gql
diff --git a/apps/www/src/components/EventInfo/SubscribeBox.tsx b/apps/www/src/components/EventInfo/SubscribeBox.tsx
new file mode 100644
index 0000000..96e4ef3
--- /dev/null
+++ b/apps/www/src/components/EventInfo/SubscribeBox.tsx
@@ -0,0 +1,60 @@
+import { Box, Button, TextInput as Input } from "@codeday/topo/Atom";
+import { useToasts, apiFetch } from "@codeday/topo/utils";
+import React, { useState } from "react";
+import { stringify as urlencode } from "urlencode";
+import { DateTime } from "luxon";
+
+import { SubscribeToEvent } from "./EventInfo.gql";
+
+export default function SubscribeBox({ event, ...rest }: { event: any; [key: string]: any }) {
+ const { success, error } = useToasts();
+ const [destination, setDestination] = useState("");
+ const dtFormat = `yyyyLLdd'T'HHmmss`;
+ const start = DateTime.fromISO(event.start).toUTC().toFormat(dtFormat);
+ const end = DateTime.fromISO(event.end).toUTC().toFormat(dtFormat);
+ const addLinkGoogleParams = urlencode({
+ action: "TEMPLATE",
+ text: event.title,
+ dates: `${start}Z/${end}Z`,
+ location: event.location,
+ sf: "true",
+ output: "xml",
+ });
+ const addLinkGoogle = `https://www.google.com/calendar/render?${addLinkGoogleParams}`;
+ return (
+
+ setDestination(e.target.value)}
+ placeholder="Phone Number"
+ display="inline-block"
+ w="sm"
+ borderTopRightRadius={0}
+ borderBottomRightRadius={0}
+ />
+
+
+
+ );
+}
diff --git a/apps/www/src/components/EventInfo.tsx b/apps/www/src/components/EventInfo/index.tsx
similarity index 68%
rename from apps/www/src/components/EventInfo.tsx
rename to apps/www/src/components/EventInfo/index.tsx
index 5ef1747..427b952 100644
--- a/apps/www/src/components/EventInfo.tsx
+++ b/apps/www/src/components/EventInfo/index.tsx
@@ -1,69 +1,14 @@
-import { Box, Grid, Text, Heading, Link, Button, TextInput as Input } from "@codeday/topo/Atom";
+import { Box, Grid, Text, Heading, Link, Button } from "@codeday/topo/Atom";
import { Html } from "@codeday/topo/Molecule";
-import { useToasts, apiFetch } from "@codeday/topo/utils";
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en";
import { DateTime } from "luxon";
import React, { useState, useEffect } from "react";
-import { stringify as urlencode } from "urlencode";
-import { SubscribeToEvent } from "./EventInfo.gql";
+import SubscribeBox from "./SubscribeBox";
const SERVER_TIMEZONE = "America/Los_Angeles";
-function SubscribeBox({ event, ...rest }: { event: any; [key: string]: any }) {
- const { success, error } = useToasts();
- const [destination, setDestination] = useState("");
- const dtFormat = `yyyyLLdd'T'HHmmss`;
- const start = DateTime.fromISO(event.start).toUTC().toFormat(dtFormat);
- const end = DateTime.fromISO(event.end).toUTC().toFormat(dtFormat);
- const addLinkGoogleParams = urlencode({
- action: "TEMPLATE",
- text: event.title,
- dates: `${start}Z/${end}Z`,
- location: event.location,
- sf: "true",
- output: "xml",
- });
- const addLinkGoogle = `https://www.google.com/calendar/render?${addLinkGoogleParams}`;
- return (
-
- setDestination(e.target.value)}
- placeholder="Phone Number"
- display="inline-block"
- w="sm"
- borderTopRightRadius={0}
- borderBottomRightRadius={0}
- />
-
-
-
- );
-}
-
export default function Event({ event, ...rest }: { event: any; [key: string]: any }) {
const [timezone, setTimezone] = useState(SERVER_TIMEZONE);
const [twasStart, setTwasStart] = useState();
diff --git a/apps/www/src/components/Highlight.tsx b/apps/www/src/components/Highlight.tsx
index 8d284a4..3e25a54 100644
--- a/apps/www/src/components/Highlight.tsx
+++ b/apps/www/src/components/Highlight.tsx
@@ -1,16 +1 @@
-import { Text } from "@codeday/topo/Atom";
-import { useColorMode } from "@codeday/topo/Theme";
-
-export default function Highlight(props: any) {
- const { colorMode } = useColorMode();
- return (
-
- );
-}
+export { Highlight as default } from "@codeday/topo/Atom";
diff --git a/apps/www/src/components/Index/Announcement.tsx b/apps/www/src/components/Index/Announcement.tsx
index 757311f..318c989 100644
--- a/apps/www/src/components/Index/Announcement.tsx
+++ b/apps/www/src/components/Index/Announcement.tsx
@@ -1,10 +1,10 @@
import { Box, Text, Button, Grid } from "@codeday/topo/Atom";
import { Content } from "@codeday/topo/Molecule";
-import { useColorMode } from "@codeday/topo/Theme";
+import { useColorMode, usePageData } from "@codeday/topo/Theme";
import { DateTime } from "luxon";
import React from "react";
-import { useQuery } from "../../query";
+
const fromIso = (s: string) => {
var b = s.split(/\D+/).map(Number);
@@ -16,7 +16,7 @@ export default function Announcement(props: any) {
const {
cms: { announcements },
announcementWebinar: { events },
- } = useQuery();
+ } = usePageData();
const now = new Date();
const webinarAnnouncementEndDate = new Date().setDate(now.getDate() + 2);
diff --git a/apps/www/src/components/Index/Community.tsx b/apps/www/src/components/Index/Community.tsx
deleted file mode 100644
index 0f7cfd1..0000000
--- a/apps/www/src/components/Index/Community.tsx
+++ /dev/null
@@ -1,290 +0,0 @@
-import { Box, Grid, Image, Text, Heading } from "@codeday/topo/Atom";
-import { Content } from "@codeday/topo/Molecule";
-import shuffle from "knuth-shuffle-seeded";
-import React, { useState, ReactElement } from "react";
-import { useInView } from "react-intersection-observer";
-import PageVisibility from "react-page-visibility";
-import Ticker from "react-ticker";
-import truncate from "truncate";
-
-import { useQuery } from "../../query";
-
-function PhotoTextCard({ photo, text, authors, wip, href }: any) {
- return (
-
-
-
-
- {authors &&
- authors.length > 0 &&
- (authors.length > 1 ? (
-
- {authors.map((author: any) => (
-
- ))}
-
- ) : (
-
-
-
- {authors[0].name}
-
-
- ))}
-
- {truncate(text, 90)}{" "}
- {wip && (
-
- #work-in-progress
-
- )}
-
-
-
-
- );
-}
-
-function PhotoCard({ photo, authors, wip, eventInfo, projectTitle, href }: any) {
- return (
-
- {authors &&
- authors.length > 0 &&
- (authors.length > 1 ? (
-
- {authors.map((author: any) => (
-
- ))}
-
-
-
-
- ) : (
-
-
-
- {authors[0].name}
-
-
- ))}
- {!(authors && authors.length > 0) && eventInfo && (
-
-
- {eventInfo
- ? [
- eventInfo.event?.program?.name,
- eventInfo.program?.name,
- eventInfo.region?.name,
- ].join(" ")
- : projectTitle}
-
-
- )}
- {projectTitle && (
-
-
- {projectTitle}
-
-
- )}
-
- {wip && (
-
-
- #work-in-progress
-
-
- )}
-
- );
-}
-
-function Card({ photo, text, authors, wip, eventInfo, projectTitle, href }: any) {
- const elem =
- text && text.length > 0 ? (
-
- ) : (
-
- );
-
- return (
-
- {elem}
-
- );
-}
-
-export default function Community({ seed, ...props }: { seed?: any; [key: string]: any }) {
- const [pageIsVisible, setPageIsVisible] = useState(true);
- const { ref, inView } = useInView({ rootMargin: "200px" });
-
- const {
- showcase,
- cms: { indexCommunityPhotos, stats },
- } = useQuery();
-
- const studentCount = stats?.items?.reduce(
- (accum: number, e: any) => accum + e.statStudentCount,
- 0,
- );
- const studentCountRound = Math.round(studentCount / 10000) * 10000;
- const studentCountPrefix = ["More than", "Nearly"][studentCountRound > studentCount ? 1 : 0];
- const showcaseDemos = showcase.projects
- .map((p: any) => ({
- ...p,
- members: p.members && p.members.map((a: any) => a.account).filter((a: any) => a),
- media:
- (p.media && p.media.filter((m: any) => m.type === "IMAGE" && m.topic !== "TEAM")[0]) ||
- null,
- }))
- .filter((p: any) => p.media && p.members && p.members.length > 0);
-
- const cards: ReactElement[] = shuffle(
- [
- ...showcaseDemos.map((d: any) => (
-
- )),
- ...(
- shuffle(indexCommunityPhotos.items, seed).map((p: any) => (
-
- )) || []
- ).slice(0, 25),
- ...(shuffle(showcase.photos, seed).map((p: any) => (
-
- )) || []),
- ],
- seed,
- );
- const rows = [
- cards.slice(0, Math.floor(cards.length / 2)),
- cards.slice(Math.floor(cards.length / 2)),
- ];
-
- return (
-
-
-
- {pageIsVisible && inView ? (
- {({ index }: { index: number }) => rows[0][index % rows[0].length]}
- ) : (
-
- )}
-
-
-
-
- {studentCountPrefix} {studentCountRound.toLocaleString()} students have created amazing
- projects at CodeDay events.
-
-
-
-
- {pageIsVisible && inView ? (
-
- {({ index }: { index: number }) => rows[1][index % rows[0].length]}
-
- ) : (
-
- )}
-
-
-
- );
-}
diff --git a/apps/www/src/components/Index/Community/Card.tsx b/apps/www/src/components/Index/Community/Card.tsx
new file mode 100644
index 0000000..79dd814
--- /dev/null
+++ b/apps/www/src/components/Index/Community/Card.tsx
@@ -0,0 +1,32 @@
+import { Box } from "@codeday/topo/Atom";
+import PhotoTextCard from "./PhotoTextCard";
+import PhotoCard from "./PhotoCard";
+
+export default function Card({ photo, text, authors, wip, eventInfo, projectTitle, href }: any) {
+ const elem =
+ text && text.length > 0 ? (
+
+ ) : (
+
+ );
+
+ return (
+
+ {elem}
+
+ );
+}
diff --git a/apps/www/src/components/Index/Community/PhotoCard.tsx b/apps/www/src/components/Index/Community/PhotoCard.tsx
new file mode 100644
index 0000000..57a4450
--- /dev/null
+++ b/apps/www/src/components/Index/Community/PhotoCard.tsx
@@ -0,0 +1,89 @@
+import { Box, Image, Text } from "@codeday/topo/Atom";
+
+export default function PhotoCard({ photo, authors, wip, eventInfo, projectTitle, href }: any) {
+ return (
+
+ {authors &&
+ authors.length > 0 &&
+ (authors.length > 1 ? (
+
+ {authors.map((author: any) => (
+
+ ))}
+
+
+
+
+ ) : (
+
+
+
+ {authors[0].name}
+
+
+ ))}
+ {!(authors && authors.length > 0) && eventInfo && (
+
+
+ {eventInfo
+ ? [
+ eventInfo.event?.program?.name,
+ eventInfo.program?.name,
+ eventInfo.region?.name,
+ ].join(" ")
+ : projectTitle}
+
+
+ )}
+ {projectTitle && (
+
+
+ {projectTitle}
+
+
+ )}
+
+ {wip && (
+
+
+ #work-in-progress
+
+
+ )}
+
+ );
+}
diff --git a/apps/www/src/components/Index/Community/PhotoTextCard.tsx b/apps/www/src/components/Index/Community/PhotoTextCard.tsx
new file mode 100644
index 0000000..cfe9518
--- /dev/null
+++ b/apps/www/src/components/Index/Community/PhotoTextCard.tsx
@@ -0,0 +1,69 @@
+import { Box, Grid, Image, Text } from "@codeday/topo/Atom";
+import truncate from "truncate";
+
+export default function PhotoTextCard({ photo, text, authors, wip, href }: any) {
+ return (
+
+
+
+
+ {authors &&
+ authors.length > 0 &&
+ (authors.length > 1 ? (
+
+ {authors.map((author: any) => (
+
+ ))}
+
+ ) : (
+
+
+
+ {authors[0].name}
+
+
+ ))}
+
+ {truncate(text, 90)}{" "}
+ {wip && (
+
+ #work-in-progress
+
+ )}
+
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Index/Community/index.tsx b/apps/www/src/components/Index/Community/index.tsx
new file mode 100644
index 0000000..d36472b
--- /dev/null
+++ b/apps/www/src/components/Index/Community/index.tsx
@@ -0,0 +1,106 @@
+import { Box, Heading } from "@codeday/topo/Atom";
+import { Content } from "@codeday/topo/Molecule";
+import shuffle from "knuth-shuffle-seeded";
+import { useState, ReactElement } from "react";
+import { useInView } from "react-intersection-observer";
+import PageVisibility from "react-page-visibility";
+import Ticker from "react-ticker";
+
+import { usePageData } from "@codeday/topo/Theme";
+import Card from "./Card";
+
+export default function Community({ seed, ...props }: { seed?: any; [key: string]: any }) {
+ const [pageIsVisible, setPageIsVisible] = useState(true);
+ const { ref, inView } = useInView({ rootMargin: "200px" });
+
+ const {
+ showcase,
+ cms: { indexCommunityPhotos, stats },
+ } = usePageData();
+
+ const studentCount = stats?.items?.reduce(
+ (accum: number, e: any) => accum + e.statStudentCount,
+ 0,
+ );
+ const studentCountRound = Math.round(studentCount / 10000) * 10000;
+ const studentCountPrefix = ["More than", "Nearly"][studentCountRound > studentCount ? 1 : 0];
+ const showcaseDemos = showcase.projects
+ .map((p: any) => ({
+ ...p,
+ members: p.members && p.members.map((a: any) => a.account).filter((a: any) => a),
+ media:
+ (p.media && p.media.filter((m: any) => m.type === "IMAGE" && m.topic !== "TEAM")[0]) ||
+ null,
+ }))
+ .filter((p: any) => p.media && p.members && p.members.length > 0);
+
+ const cards: ReactElement[] = shuffle(
+ [
+ ...showcaseDemos.map((d: any) => (
+
+ )),
+ ...(
+ shuffle(indexCommunityPhotos.items, seed).map((p: any) => (
+
+ )) || []
+ ).slice(0, 25),
+ ...(shuffle(showcase.photos, seed).map((p: any) => (
+
+ )) || []),
+ ],
+ seed,
+ );
+ const rows = [
+ cards.slice(0, Math.floor(cards.length / 2)),
+ cards.slice(Math.floor(cards.length / 2)),
+ ];
+
+ return (
+
+
+
+ {pageIsVisible && inView ? (
+ {({ index }: { index: number }) => rows[0][index % rows[0].length]}
+ ) : (
+
+ )}
+
+
+
+
+ {studentCountPrefix} {studentCountRound.toLocaleString()} students have created amazing
+ projects at CodeDay events.
+
+
+
+
+ {pageIsVisible && inView ? (
+
+ {({ index }: { index: number }) => rows[1][index % rows[0].length]}
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Index/EcoBox.tsx b/apps/www/src/components/Index/EcoBox.tsx
index 1510367..5c68b94 100644
--- a/apps/www/src/components/Index/EcoBox.tsx
+++ b/apps/www/src/components/Index/EcoBox.tsx
@@ -3,32 +3,29 @@ import { Content } from "@codeday/topo/Molecule";
import { Eco } from "@codeday/topocons";
import React from "react";
-import { useQuery } from "../../query";
-import StaticContent from "../StaticContent";
+import { usePageData } from "@codeday/topo/Theme";
export default function EcoBox() {
- const { eco, learnMore } = useQuery().cms;
+ const { eco, learnMore } = usePageData().cms;
return (
-
-
-
-
-
-
-
- {eco?.items[0]?.value}{" "}
- {learnMore?.items[0]?.value || "Learn more."}
-
-
-
-
+
+
+
+
+
+
+ {eco?.items[0]?.value}{" "}
+ {learnMore?.items[0]?.value || "Learn more."}
+
+
+
);
}
diff --git a/apps/www/src/components/Index/Hero.tsx b/apps/www/src/components/Index/Hero.tsx
index 61ccef8..f958ff4 100644
--- a/apps/www/src/components/Index/Hero.tsx
+++ b/apps/www/src/components/Index/Hero.tsx
@@ -2,16 +2,16 @@ import { Box, Grid, VisibilityCheckBox, Text, Heading, Button } from "@codeday/t
import { MediaPlay as Play, Broadcast } from "@codeday/topocons";
import React from "react";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import useTwitch from "../../useTwitch";
-import VideoLink from "../VideoLink";
+import { VideoLink } from "@codeday/topo/Molecule";
import Live from "./Live";
import Teaser from "./Teaser";
export default function Hero({ ...props }: any) {
const {
cms: { tagline, mission, explainer },
- } = useQuery();
+ } = usePageData();
const twitch = useTwitch();
const taglineBlock = (
diff --git a/apps/www/src/components/Index/Programs/NextEventDate.tsx b/apps/www/src/components/Index/Programs/NextEventDate.tsx
new file mode 100644
index 0000000..36e81db
--- /dev/null
+++ b/apps/www/src/components/Index/Programs/NextEventDate.tsx
@@ -0,0 +1,17 @@
+import { Text } from "@codeday/topo/Atom";
+import React from "react";
+
+import { nextUpcomingEvent, upcomingEvents, formatInterval } from "../../../utils/time";
+
+export default function NextEventDate({ upcoming }: { upcoming: any[] }) {
+ const next = nextUpcomingEvent(upcoming);
+ return next ? (
+
+ {upcomingEvents(upcoming)
+ .map((e) => formatInterval(e.startsAt, e.endsAt))
+ .join("; ")}
+
+ ) : (
+ <>>
+ );
+}
diff --git a/apps/www/src/components/Index/Programs/ProgramCard.tsx b/apps/www/src/components/Index/Programs/ProgramCard.tsx
new file mode 100644
index 0000000..15ac5d8
--- /dev/null
+++ b/apps/www/src/components/Index/Programs/ProgramCard.tsx
@@ -0,0 +1,39 @@
+import { Box, Button, Image, Text } from "@codeday/topo/Atom";
+import React from "react";
+
+import NextEventDate from "./NextEventDate";
+
+interface ProgramCardProps {
+ program: any;
+}
+
+export default function ProgramCard({ program }: ProgramCardProps) {
+ return (
+
+
+
+
+
+
+ {program.name}
+
+
+
+
+ {program.shortDescription}
+
+
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Index/Programs.gql b/apps/www/src/components/Index/Programs/Programs.gql
similarity index 100%
rename from apps/www/src/components/Index/Programs.gql
rename to apps/www/src/components/Index/Programs/Programs.gql
diff --git a/apps/www/src/components/Index/Programs/RegionList.tsx b/apps/www/src/components/Index/Programs/RegionList.tsx
new file mode 100644
index 0000000..6ceec48
--- /dev/null
+++ b/apps/www/src/components/Index/Programs/RegionList.tsx
@@ -0,0 +1,50 @@
+import { Box } from "@codeday/topo/Atom";
+import { UiStar } from "@codeday/topocons";
+import React from "react";
+
+interface RegionListProps {
+ sortedRegions: any[];
+ registrationOpenWebnames: string[];
+ upcomingNameOverrides: Record;
+}
+
+export default function RegionList({
+ sortedRegions,
+ registrationOpenWebnames,
+ upcomingNameOverrides,
+}: RegionListProps) {
+ return (
+ <>
+
+ {sortedRegions.map((region: any) => (
+
+ {upcomingNameOverrides[region.webname] || region.name}
+ {region.upcoming && (
+
+
+
+
+ {registrationOpenWebnames.includes(region.webname) ? `Registrations open!` : ``}
+
+ )}
+
+ ))}
+
+
+
+
+
+ Event planned this season.
+
+ >
+ );
+}
diff --git a/apps/www/src/components/Index/Programs.tsx b/apps/www/src/components/Index/Programs/index.tsx
similarity index 58%
rename from apps/www/src/components/Index/Programs.tsx
rename to apps/www/src/components/Index/Programs/index.tsx
index 30c1d22..10ec0c3 100644
--- a/apps/www/src/components/Index/Programs.tsx
+++ b/apps/www/src/components/Index/Programs/index.tsx
@@ -1,35 +1,22 @@
import { Box, Grid, Button, Image, Text, CodeDay, Link } from "@codeday/topo/Atom";
import { Content } from "@codeday/topo/Molecule";
-import { useColorMode } from "@codeday/topo/Theme";
+import { useColorMode, usePageData } from "@codeday/topo/Theme";
import { apiFetch } from "@codeday/topo/utils";
-import { UiStar } from "@codeday/topocons";
import { print } from "graphql";
import haversine from "haversine-distance";
import React, { useEffect, useState } from "react";
-import { useQuery } from "../../query";
-import { nextUpcomingEvent, upcomingEvents, formatInterval } from "../../utils/time";
import { GetMyLocation } from "./Programs.gql";
-
-function NextEventDate({ upcoming }: { upcoming: any[] }) {
- const next = nextUpcomingEvent(upcoming);
- return next ? (
-
- {upcomingEvents(upcoming)
- .map((e) => formatInterval(e.startsAt, e.endsAt))
- .join("; ")}
-
- ) : (
- <>>
- );
-}
+import NextEventDate from "./NextEventDate";
+import ProgramCard from "./ProgramCard";
+import RegionList from "./RegionList";
export default function Programs() {
const { colorMode } = useColorMode();
const {
cms: { regions, mainPrograms, codeDayProgram, labsProgram },
clear: { events },
- } = useQuery();
+ } = usePageData();
const [geo, setGeo] = useState();
const codeDay = codeDayProgram?.items[0];
const labs = labsProgram?.items[0];
@@ -89,36 +76,11 @@ export default function Programs() {
)
-
- {sortedRegions.map((region: any) => (
-
- {upcomingNameOverrides[region.webname] || region.name}
- {region.upcoming && (
-
-
-
-
- {registrationOpenWebnames.includes(region.webname) ? `Registrations open!` : ``}
-
- )}
-
- ))}
-
-
-
-
-
- Event planned this season.
-
+
{/* More Programs */}
@@ -159,32 +121,7 @@ export default function Programs() {
{mainPrograms?.items?.map((program: any) => (
-
-
-
-
-
-
- {program.name}
-
-
-
-
- {program.shortDescription}
-
-
-
-
-
+
))}
diff --git a/apps/www/src/components/Index/Quotes/index.tsx b/apps/www/src/components/Index/Quotes/index.tsx
index 3bf6a43..65f3d87 100644
--- a/apps/www/src/components/Index/Quotes/index.tsx
+++ b/apps/www/src/components/Index/Quotes/index.tsx
@@ -3,7 +3,7 @@ import { Content } from "@codeday/topo/Molecule";
import shuffle from "knuth-shuffle-seeded";
import React, { useState, useReducer, useEffect } from "react";
-import { useQuery } from "../../../query";
+import { usePageData } from "@codeday/topo/Theme";
import VideoTestimonialThumbnail from "../../VideoTestimonialThumbnail";
import Globe from "./Globe";
import TextQuote from "./TextQuote";
@@ -18,7 +18,7 @@ interface QuotesProps {
export default function Quotes({ seed }: QuotesProps) {
const {
cms: { quoteRegions, quoteTestimonials },
- } = useQuery();
+ } = usePageData();
const textQuotes = shuffle(
quoteTestimonials?.items.filter((q: any) => !q.video),
seed,
diff --git a/apps/www/src/components/Index/Sponsors.tsx b/apps/www/src/components/Index/Sponsors.tsx
index dd98dac..e8db271 100644
--- a/apps/www/src/components/Index/Sponsors.tsx
+++ b/apps/www/src/components/Index/Sponsors.tsx
@@ -1,16 +1,16 @@
import { Grid, Heading, Link, Box, Text, Image } from "@codeday/topo/Atom";
import { Content } from "@codeday/topo/Molecule";
-import { useColorMode } from "@codeday/topo/Theme";
+import { useColorMode, usePageData } from "@codeday/topo/Theme";
import React from "react";
-import { useQuery } from "../../query";
+
import PreviousCoverageLogos from "../PreviousCoverageLogos";
export default function Sponsors(props: any) {
const { colorMode } = useColorMode();
const {
cms: { majorSponsors, minorSponsors },
- } = useQuery();
+ } = usePageData();
return (
diff --git a/apps/www/src/components/Index/Stats.tsx b/apps/www/src/components/Index/Stats.tsx
index c09aa28..838e075 100644
--- a/apps/www/src/components/Index/Stats.tsx
+++ b/apps/www/src/components/Index/Stats.tsx
@@ -1,10 +1,10 @@
import { Text, Grid, Box } from "@codeday/topo/Atom";
import { Content } from "@codeday/topo/Molecule";
-import { useColorMode } from "@codeday/topo/Theme";
+import { useColorMode, usePageData } from "@codeday/topo/Theme";
import React from "react";
import CountUp from "react-countup";
-import { useQuery } from "../../query";
+
function rollup(events: any[]): Record {
const stats: Record = {};
@@ -47,7 +47,7 @@ export default function Stats(props: any) {
const {
cms: { stats, quoteRegions },
labs: { statTotalOutcomes },
- } = useQuery();
+ } = usePageData();
const rollupStats = rollup(stats.items);
const hours = statTotalOutcomes.find((o: any) => o.key === "hoursCount");
diff --git a/apps/www/src/components/Index/Teaser.tsx b/apps/www/src/components/Index/Teaser.tsx
index be76fb6..f91f29e 100644
--- a/apps/www/src/components/Index/Teaser.tsx
+++ b/apps/www/src/components/Index/Teaser.tsx
@@ -1,70 +1,8 @@
-import { RatioBox } from "@codeday/topo/Atom";
-import React, { useState, useReducer, useRef, useEffect } from "react";
-import { useInView } from "react-intersection-observer";
-import PageVisibility from "react-page-visibility";
-import ReactPlayer from "react-player";
+import React from "react";
-// eslint-disable-next-line no-secrets/no-secrets
-const videoId = "hXdlNf3YVpMgfGh7cUF2L00THVfG02YmRP";
-const thumbWidth = 640;
-const thumbHeight = 480;
-const startAt = 4;
+import MuxAutoplayVideo from "../MuxAutoplayVideo";
+// eslint-disable-next-line no-secrets/no-secrets
export default function Teaser() {
- const ref = useRef(null);
- const { ref: viewRef, inView } = useInView({ rootMargin: "200px", initialInView: true });
- const [pageVisible, setPageVisible] = useState(true);
- const [muted, setMuted] = useState(true);
- const [playing, togglePlaying] = useReducer((prev: boolean) => !prev, true);
- const [pageLoaded, setPageLoaded] = useState(false);
-
- useEffect(() => setPageLoaded(true), []);
- const onClick = () => {
- if (ref.current.getInternalPlayer().muted) {
- setMuted(false);
- } else {
- togglePlaying();
- }
- };
-
- const onPlay = () => {
- if (ref.current.getInternalPlayer().currentTime === 0) {
- ref.current.seekTo(startAt);
- }
- };
-
- const bg = `https://image.mux.com/${videoId}/thumbnail.png?width=${thumbWidth}&height=${thumbHeight}&fit_mode=crop&time=${startAt}`;
-
- return (
-
-
- {pageLoaded && (
-
- )}
-
-
- );
+ return ;
}
diff --git a/apps/www/src/components/Index/Workshops.tsx b/apps/www/src/components/Index/Workshops.tsx
index fe00868..f5877b3 100644
--- a/apps/www/src/components/Index/Workshops.tsx
+++ b/apps/www/src/components/Index/Workshops.tsx
@@ -3,7 +3,7 @@ import { Content } from "@codeday/topo/Molecule";
import { create } from "random-seed";
import React from "react";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import { parseIsoString, formatShortDate } from "../../utils/time";
const fixedColors: Record = {
@@ -17,7 +17,7 @@ const fixedColors: Record = {
const colors = ["green", "blue", "orange", "cyan", "purple", "yellow", "indigo"];
export default function Workshops() {
- const { calendar } = useQuery();
+ const { calendar } = usePageData();
if (calendar?.events?.length === 0) return <>>;
diff --git a/apps/www/src/components/Markdown.tsx b/apps/www/src/components/Markdown.tsx
index 3a5dcea..a67e1ea 100644
--- a/apps/www/src/components/Markdown.tsx
+++ b/apps/www/src/components/Markdown.tsx
@@ -1,64 +1 @@
-import { Heading, Text, List, ListItem, Box } from "@codeday/topo/Atom";
-import React from "react";
-import ReactMarkdown from "react-markdown";
-import rehypeRaw from "rehype-raw";
-import RemarkGFM from "remark-gfm";
-
-const HEADING_SIZES = ["4xl", "3xl", "xl", "md", "md", "md"];
-const adjustHeadingLevel = (baseHeadingLevel: number, level: number): number => {
- const newLevel = baseHeadingLevel + level - 1;
- if (newLevel > 6) return 6;
- if (newLevel < 1) return 1;
- return newLevel;
-};
-
-interface MarkdownProps {
- baseHeadingLevel?: number;
- allowHtml?: boolean;
- rehypePlugins?: any[];
- [key: string]: any;
-}
-
-const Markdown = ({ baseHeadingLevel, allowHtml, ...props }: MarkdownProps) => {
- function h(level: number) {
- return (props: any) => {
- const adjustedLevel = adjustHeadingLevel(baseHeadingLevel || 1, level);
- const m = Math.max(6 - adjustedLevel + 1, 2);
- return (
-
- );
- };
- }
-
- const mdTheme = {
- h1: h(1),
- h2: h(2),
- h3: h(3),
- h4: h(4),
- h5: h(5),
- h6: h(6),
- tr: (props: any) => ,
- p: (props: any) => ,
- ol: (props: any) =>
,
- ul: (props: any) =>
,
- li: (props: any) => ,
- };
- return (
-
- );
-};
-
-export default Markdown;
+export { Markdown as default } from "@codeday/topo/Molecule";
diff --git a/apps/www/src/components/MuxAutoplayVideo.tsx b/apps/www/src/components/MuxAutoplayVideo.tsx
new file mode 100644
index 0000000..4f2878d
--- /dev/null
+++ b/apps/www/src/components/MuxAutoplayVideo.tsx
@@ -0,0 +1,107 @@
+import { Box, RatioBox } from "@codeday/topo/Atom";
+import { UiVolume } from "@codeday/topocons";
+import React, { useState, useReducer, useRef, useEffect } from "react";
+import { useInView } from "react-intersection-observer";
+import PageVisibility from "react-page-visibility";
+import ReactPlayer from "react-player";
+
+interface MuxAutoplayVideoProps {
+ videoId: string;
+ startAt?: number;
+ thumbWidth?: number;
+ thumbHeight?: number;
+ showUnmuteOverlay?: boolean;
+ [key: string]: any;
+}
+
+export default function MuxAutoplayVideo({
+ videoId,
+ startAt = 0,
+ thumbWidth = 640,
+ thumbHeight = 480,
+ showUnmuteOverlay = false,
+ ...props
+}: MuxAutoplayVideoProps) {
+ const ref = useRef(null);
+ const { ref: viewRef, inView } = useInView({ rootMargin: "200px", initialInView: true });
+ const [pageVisible, setPageVisible] = useState(true);
+ const [muted, setMuted] = useState(true);
+ const [playing, togglePlaying] = useReducer((prev: boolean) => !prev, true);
+ const [pageLoaded, setPageLoaded] = useState(false);
+
+ useEffect(() => setPageLoaded(true), []);
+ const onClick = () => {
+ if (ref.current.getInternalPlayer().muted) {
+ setMuted(false);
+ if (showUnmuteOverlay) {
+ ref.current.seekTo(0);
+ }
+ } else {
+ togglePlaying();
+ }
+ };
+
+ const onPlay = () => {
+ if (ref.current.getInternalPlayer().currentTime === 0) {
+ ref.current.seekTo(startAt);
+ }
+ };
+
+ // eslint-disable-next-line no-secrets/no-secrets
+ const bg = `https://image.mux.com/${videoId}/thumbnail.png?width=${thumbWidth}&height=${thumbHeight}&fit_mode=crop&time=${startAt}`;
+
+ return (
+
+
+ {showUnmuteOverlay && (
+
+
+
+
+ Unmute
+
+
+
+ )}
+ {pageLoaded && (
+
+ )}
+
+
+ );
+}
diff --git a/apps/www/src/components/Page.tsx b/apps/www/src/components/Page.tsx
deleted file mode 100644
index 8668804..0000000
--- a/apps/www/src/components/Page.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Presence } from "@chakra-ui/react";
-import { Box, CodeDay, Button, Text, Heading } from "@codeday/topo/Atom";
-import { Content } from "@codeday/topo/Molecule";
-import { Header, SiteLogo, Main, Menu, Footer } from "@codeday/topo/Organism";
-import { DefaultSeo } from "next-seo";
-import Head from "next/head";
-import React, { ReactNode } from "react";
-
-import { useFundraise } from "../providers";
-import { useQuery } from "../query";
-
-const DOMAIN = "https://www.codeday.org";
-const FUNDRAISE_UP_BUTTON_ID = "XBSBRRMF";
-
-interface PageProps {
- children?: ReactNode;
- title?: string;
- darkHeader?: boolean;
- slug?: string;
- seo?: any;
- fun?: boolean;
- [key: string]: any;
-}
-
-export default function Page({ children, title, darkHeader, slug, seo }: PageProps) {
- const { cms } = useQuery();
- const { mission } = cms || {};
- const { isFundraiseLoaded } = useFundraise();
- const disclaimerTexts = (cms?.globalSponsors?.items || [])
- .flatMap((sponsor: any) => sponsor.legalDisclaimer.split(`\n`))
- .filter(Boolean);
-
- return (
-
-
-
-
- {seo ?? (
-
- )}
-
-
-
-
-
-
- CodeDay
-
-
-
-
-
- {children}
-
- {disclaimerTexts && disclaimerTexts.length > 0 && (
-
-
-
- Funding Statements and Disclaimers
-
- {disclaimerTexts.map((text: string) => (
-
- {text}
-
- ))}
-
-
- )}
-
-
-
-
- );
-}
diff --git a/apps/www/src/components/Page/DisclaimerFooter.tsx b/apps/www/src/components/Page/DisclaimerFooter.tsx
new file mode 100644
index 0000000..bd77ff3
--- /dev/null
+++ b/apps/www/src/components/Page/DisclaimerFooter.tsx
@@ -0,0 +1,34 @@
+import { Box, Text, Heading } from "@codeday/topo/Atom";
+import { Content } from "@codeday/topo/Molecule";
+import React from "react";
+
+interface DisclaimerFooterProps {
+ disclaimerTexts: string[];
+}
+
+export default function DisclaimerFooter({ disclaimerTexts }: DisclaimerFooterProps) {
+ if (!disclaimerTexts || disclaimerTexts.length === 0) return null;
+
+ return (
+
+
+
+ Funding Statements and Disclaimers
+
+ {disclaimerTexts.map((text: string) => (
+
+ {text}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/www/src/components/Page/NavMenu.tsx b/apps/www/src/components/Page/NavMenu.tsx
new file mode 100644
index 0000000..084894f
--- /dev/null
+++ b/apps/www/src/components/Page/NavMenu.tsx
@@ -0,0 +1,57 @@
+import { Presence } from "@chakra-ui/react";
+import { Box, Button } from "@codeday/topo/Atom";
+import React from "react";
+
+const FUNDRAISE_UP_BUTTON_ID = "XBSBRRMF";
+
+interface NavMenuProps {
+ isFundraiseLoaded: boolean;
+}
+
+export default function NavMenu({ isFundraiseLoaded }: NavMenuProps) {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/www/src/components/Page.gql b/apps/www/src/components/Page/Page.gql
similarity index 100%
rename from apps/www/src/components/Page.gql
rename to apps/www/src/components/Page/Page.gql
diff --git a/apps/www/src/components/Page/index.tsx b/apps/www/src/components/Page/index.tsx
new file mode 100644
index 0000000..839bb0d
--- /dev/null
+++ b/apps/www/src/components/Page/index.tsx
@@ -0,0 +1,79 @@
+import { Box, CodeDay } from "@codeday/topo/Atom";
+import { Header, SiteLogo, Main, Menu, Footer } from "@codeday/topo/Organism";
+import { DefaultSeo } from "next-seo";
+import Head from "next/head";
+import React, { ReactNode } from "react";
+
+import { useFundraise } from "../../providers";
+import { usePageData } from "@codeday/topo/Theme";
+import DisclaimerFooter from "./DisclaimerFooter";
+import NavMenu from "./NavMenu";
+
+const DOMAIN = "https://www.codeday.org";
+
+interface PageProps {
+ children?: ReactNode;
+ title?: string;
+ darkHeader?: boolean;
+ slug?: string;
+ seo?: any;
+ fun?: boolean;
+ [key: string]: any;
+}
+
+export default function Page({ children, title, darkHeader, slug, seo }: PageProps) {
+ const { cms } = usePageData();
+ const { mission } = cms || {};
+ const { isFundraiseLoaded } = useFundraise();
+ const disclaimerTexts = (cms?.globalSponsors?.items || [])
+ .flatMap((sponsor: any) => sponsor.legalDisclaimer.split(`\n`))
+ .filter(Boolean);
+
+ return (
+
+
+
+
+ {seo ?? (
+
+ )}
+
+
+ {children}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Press/PhotoGallery.tsx b/apps/www/src/components/Press/PhotoGallery.tsx
index 0226f19..94fc555 100644
--- a/apps/www/src/components/Press/PhotoGallery.tsx
+++ b/apps/www/src/components/Press/PhotoGallery.tsx
@@ -3,7 +3,7 @@ import { Content } from "@codeday/topo/Molecule";
import shuffle from "knuth-shuffle-seeded";
import React, { useState } from "react";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import Photo from "./Photo";
import PhotoTagPicker from "./PhotoTagPicker";
@@ -16,7 +16,7 @@ export default function PhotoGallery({ seed, ...props }: PhotoGalleryProps) {
const [filter, setFilter] = useState(null);
const {
cms: { pressPhotos },
- } = useQuery();
+ } = usePageData();
const photos = shuffle(
(pressPhotos?.items || []).map((m: any) => m),
seed,
diff --git a/apps/www/src/components/PreviousCoverageLogos.tsx b/apps/www/src/components/PreviousCoverageLogos.tsx
index d57d295..941d72b 100644
--- a/apps/www/src/components/PreviousCoverageLogos.tsx
+++ b/apps/www/src/components/PreviousCoverageLogos.tsx
@@ -1,9 +1,8 @@
import { Link, Image } from "@codeday/topo/Atom";
import React from "react";
-import { useQuery } from "../query";
-import { dedupeFirstByKey } from "../utils/arr";
-import StaticContent from "./StaticContent";
+import { usePageData } from "@codeday/topo/Theme";
+import { dedupeFirstByKey } from "@codeday/utils";
interface PreviousCoverageLogosProps {
num?: number;
@@ -13,14 +12,14 @@ interface PreviousCoverageLogosProps {
export default function PreviousCoverageLogos({ num = 5, ...props }: PreviousCoverageLogosProps) {
const {
cms: { coverageLogos },
- } = useQuery();
+ } = usePageData();
const pubs = dedupeFirstByKey(
coverageLogos.items.filter((pub: any) => pub.publicationLogo),
"publicationName",
).slice(0, num);
return (
-
+ <>
{pubs.map((pub: any) => (
))}
-
+ >
);
}
diff --git a/apps/www/src/components/StaticContent.tsx b/apps/www/src/components/StaticContent.tsx
deleted file mode 100644
index f0fd8dc..0000000
--- a/apps/www/src/components/StaticContent.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createElement, ReactNode, ElementType } from "react";
-
-interface StaticContentProps {
- children?: ReactNode;
- element?: string | ElementType;
- [key: string]: any;
-}
-
-// This no longer works in React 18, but is left in place for compatibiltiy.
-export default function StaticContent({ children, element = "div", ...props }: StaticContentProps) {
- return createElement(element as any, {
- ...props,
- children,
- });
-}
diff --git a/apps/www/src/components/VideoLink.tsx b/apps/www/src/components/VideoLink.tsx
index b719779..fea3932 100644
--- a/apps/www/src/components/VideoLink.tsx
+++ b/apps/www/src/components/VideoLink.tsx
@@ -1,25 +1 @@
-import { Box } from "@codeday/topo/Atom";
-import React, { useState, ReactNode } from "react";
-import { Modal } from "react-responsive-modal";
-
-import VideoPlayer from "./VideoPlayer";
-
-interface VideoLinkProps {
- children?: ReactNode;
- [key: string]: any;
-}
-
-export default function VideoLink({ children, ...props }: VideoLinkProps) {
- const [modalOpen, setModalOpen] = useState(false);
-
- return (
- <>
- setModalOpen(false)}>
-
-
- setModalOpen(true)}>
- {children}
-
- >
- );
-}
+export { VideoLink as default } from "@codeday/topo/Molecule";
diff --git a/apps/www/src/components/VideoPlayer.tsx b/apps/www/src/components/VideoPlayer.tsx
index e4e4ec9..91a83aa 100644
--- a/apps/www/src/components/VideoPlayer.tsx
+++ b/apps/www/src/components/VideoPlayer.tsx
@@ -1,43 +1 @@
-import { Box } from "@codeday/topo/Atom";
-import dynamic from "next/dynamic";
-import React, { useEffect, useRef } from "react";
-
-//@ts-ignore
-const Hls = dynamic(() => import("hls.js/dist/hls.light.js"), { ssr: false });
-
-interface VideoPlayerProps {
- url?: string;
- poster?: string;
- autoPlay?: boolean;
- volume?: number;
- [key: string]: any;
-}
-
-export default function VideoPlayer({ url, poster, autoPlay, volume, ...props }: VideoPlayerProps) {
- const player = useRef(null);
- useEffect(() => {
- if (typeof window === "undefined" || typeof player === "undefined") return;
- if (url?.split(".").pop() !== "m3u8") return;
- if (!(Hls as any).isSupported()) return;
-
- const hls = new (Hls as any)();
- hls.loadSource(url);
- hls.attachMedia(player);
- }, [typeof window, player, url]);
-
- return (
-
-
-
- );
-}
+export { VideoPlayer as default } from "@codeday/topo/Molecule";
diff --git a/apps/www/src/components/VideoTestimonialThumbnail.tsx b/apps/www/src/components/VideoTestimonialThumbnail.tsx
index fe0203e..c87ba7c 100644
--- a/apps/www/src/components/VideoTestimonialThumbnail.tsx
+++ b/apps/www/src/components/VideoTestimonialThumbnail.tsx
@@ -2,7 +2,7 @@ import { Box } from "@codeday/topo/Atom";
import { MediaPlay } from "@codeday/topocons";
import React from "react";
-import VideoLink from "./VideoLink";
+import { VideoLink } from "@codeday/topo/Molecule";
interface VideoTestimonialThumbnailProps {
video: any;
diff --git a/apps/www/src/components/Volunteer/PhotoGallery.tsx b/apps/www/src/components/Volunteer/PhotoGallery.tsx
index ea9841e..6b8e66b 100644
--- a/apps/www/src/components/Volunteer/PhotoGallery.tsx
+++ b/apps/www/src/components/Volunteer/PhotoGallery.tsx
@@ -1,9 +1,9 @@
import { Box, Grid, Text, Image } from "@codeday/topo/Atom";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
export default function PhotoGallery(props: any) {
- const { cms } = useQuery();
+ const { cms } = usePageData();
const volunteerPhotoGallery = cms?.volunteerPhotoGallery?.items || [];
return (
diff --git a/apps/www/src/components/Volunteer/PreviewVideo.tsx b/apps/www/src/components/Volunteer/PreviewVideo.tsx
index 7836682..41f2d1d 100644
--- a/apps/www/src/components/Volunteer/PreviewVideo.tsx
+++ b/apps/www/src/components/Volunteer/PreviewVideo.tsx
@@ -1,93 +1,8 @@
-import { Box, RatioBox } from "@codeday/topo/Atom";
-import { UiVolume } from "@codeday/topocons";
-import React, { useState, useReducer, useRef, useEffect } from "react";
-import { useInView } from "react-intersection-observer";
-import PageVisibility from "react-page-visibility";
-import ReactPlayer from "react-player";
+import React from "react";
-// eslint-disable-next-line no-secrets/no-secrets
-const videoId = "c1BhPbPJvRjeGvUutUIgrCG5bCsgT021q";
-const thumbWidth = 640;
-const thumbHeight = 480;
-const startAt = 14;
+import MuxAutoplayVideo from "../MuxAutoplayVideo";
+// eslint-disable-next-line no-secrets/no-secrets
export default function PreviewVideo(props: any) {
- const ref = useRef(null);
- const { ref: viewRef, inView } = useInView({ rootMargin: "200px", initialInView: true });
- const [pageVisible, setPageVisible] = useState(true);
- const [muted, setMuted] = useState(true);
- const [playing, togglePlaying] = useReducer((prev: boolean) => !prev, true);
- const [pageLoaded, setPageLoaded] = useState(false);
-
- useEffect(() => setPageLoaded(true), []);
- const onClick = () => {
- if (ref.current.getInternalPlayer().muted) {
- setMuted(false);
- ref.current.seekTo(0);
- } else {
- togglePlaying();
- }
- };
-
- const onPlay = () => {
- if (ref.current.getInternalPlayer().currentTime === 0) {
- ref.current.seekTo(startAt);
- }
- };
-
- const bg = `https://image.mux.com/${videoId}/thumbnail.png?width=${thumbWidth}&height=${thumbHeight}&fit_mode=crop&time=${startAt}`;
-
- return (
-
-
-
-
-
-
- Unmute
-
-
-
- {pageLoaded && (
-
- )}
-
-
- );
+ return ;
}
diff --git a/apps/www/src/components/Volunteer/ProgramInfo.tsx b/apps/www/src/components/Volunteer/ProgramInfo.tsx
index 112e178..9a46a38 100644
--- a/apps/www/src/components/Volunteer/ProgramInfo.tsx
+++ b/apps/www/src/components/Volunteer/ProgramInfo.tsx
@@ -2,7 +2,7 @@ import { Box, Grid, Text, Image, List, ListItem, Button } from "@codeday/topo/At
import React from "react";
import { formatInterval } from "../../utils/time";
-import ContentfulRichText from "../ContentfulRichText";
+import { ContentfulRichText } from "@codeday/topo/Molecule";
import ProgramShareBlurb from "./ProgramShareBlurb";
import { VOLUNTEER_ROLES } from "./wizardConfig";
diff --git a/apps/www/src/components/Volunteer/ProgramInfoCheck.tsx b/apps/www/src/components/Volunteer/ProgramInfoCheck.tsx
index 9633399..ac0da44 100644
--- a/apps/www/src/components/Volunteer/ProgramInfoCheck.tsx
+++ b/apps/www/src/components/Volunteer/ProgramInfoCheck.tsx
@@ -2,7 +2,7 @@ import { Box, Text, Image, List, ListItem, Checkbox } from "@codeday/topo/Atom";
import React, { useState } from "react";
import { formatInterval } from "../../utils/time";
-import ContentfulRichText from "../ContentfulRichText";
+import { ContentfulRichText } from "@codeday/topo/Molecule";
interface ProgramInfoCheckProps {
program: any;
diff --git a/apps/www/src/components/Volunteer/ProgramShareBlurb.tsx b/apps/www/src/components/Volunteer/ProgramShareBlurb.tsx
index 449dd38..de7236b 100644
--- a/apps/www/src/components/Volunteer/ProgramShareBlurb.tsx
+++ b/apps/www/src/components/Volunteer/ProgramShareBlurb.tsx
@@ -2,7 +2,7 @@ import { Box, Grid, Text, Heading, Image, Button, Divider, Link } from "@codeday
import { UiArrowDown, UiArrowUp, FilePdf } from "@codeday/topocons";
import React, { useState } from "react";
-import ContentfulRichText from "../ContentfulRichText";
+import { ContentfulRichText } from "@codeday/topo/Molecule";
interface ProgramShareBlurbProps {
program: any;
diff --git a/apps/www/src/components/Volunteer/Testimonials.tsx b/apps/www/src/components/Volunteer/Testimonials.tsx
index 5d48038..6a3eee0 100644
--- a/apps/www/src/components/Volunteer/Testimonials.tsx
+++ b/apps/www/src/components/Volunteer/Testimonials.tsx
@@ -2,7 +2,7 @@ import { Box, Grid, Text, Image } from "@codeday/topo/Atom";
import shuffle from "knuth-shuffle-seeded";
import { useSlideshow } from "../../providers";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
const QUOTE_DURATION = 10000;
@@ -12,7 +12,7 @@ interface TestimonialsProps {
}
export default function Testimonials({ seed, ...props }: TestimonialsProps) {
- const { cms } = useQuery();
+ const { cms } = usePageData();
const testimonials = shuffle(
(cms?.volunteerTestimonials?.items || []).filter(
(t: any) => t.quote.split(" ").length <= 6 * 8,
diff --git a/apps/www/src/components/Volunteer/Wizard.tsx b/apps/www/src/components/Volunteer/Wizard.tsx
deleted file mode 100644
index ce0c9f0..0000000
--- a/apps/www/src/components/Volunteer/Wizard.tsx
+++ /dev/null
@@ -1,409 +0,0 @@
-import {
- Box,
- Button,
- Text,
- Heading,
- HStack,
- VStack,
- TextInput,
- Divider,
- Checkbox,
- Radio,
- Link,
-} from "@codeday/topo/Atom";
-import { Collapse } from "@codeday/topo/Molecule";
-import { DataCollection } from "@codeday/topo/Molecule";
-import { useToasts } from "@codeday/topo/utils";
-import { debug } from "@codeday/utils";
-import { usePostHog } from "@posthog/react";
-import React, { useState, useReducer, useEffect, RefObject } from "react";
-
-import { useMarketing } from "../../providers";
-import { useAfterMountEffect } from "../../utils/useAfterMountEffect";
-
-const DEBUG = debug(["www", "components", "Volunteer", "Wizard"]);
-
-// https://stackoverflow.com/a/48981669
-function groupBy(xs: any[], f: (x: any) => string): Record {
- return xs.reduce(
- (r: any, v: any, i: number, a: any[], k = f(v)) => ((r[k] || (r[k] = [])).push(v), r),
- {},
- );
-}
-
-const emailRe = new RegExp(".+@.+\\..+");
-
-interface WizardProps {
- events: any[];
- formRef: RefObject;
- startBackground?: string;
- startRegion?: string;
- startPage?: number;
- startSelection?: boolean;
- after?: string;
-}
-
-export default function Wizard({
- events,
- formRef,
- startBackground = "",
- startRegion = "",
- startPage = 0,
- startSelection = false,
- after,
-}: WizardProps) {
- const posthog = usePostHog();
- const { error } = useToasts();
- const regions = new Array(
- ...new Set(
- events
- .filter((e) => !e.dontAcceptVolunteers)
- .map((e) =>
- JSON.stringify({
- name: e.region?.name || e.name,
- webname: e.contentfulWebname,
- country: e.region?.countryName || "Other",
- aliases: e.region?.aliases || [],
- }),
- ),
- ),
- ).map((e) => JSON.parse(e)); // json -> string -> json for deduplication
- const regionsByCountry = groupBy(regions, (r) => r.country);
-
- useEffect(() => {
- DEBUG("regions", regions);
- DEBUG("regionsByCountry", regionsByCountry);
- }, [regions, regionsByCountry]);
-
- let resolvedStartRegion = startRegion;
- if (resolvedStartRegion) {
- const webnamesToRegion: Record = {};
- regions.forEach((r) => {
- webnamesToRegion[r.webname] = r.name;
- r.aliases.forEach((alias: string) => {
- webnamesToRegion[alias] = r.name;
- });
- });
- resolvedStartRegion = webnamesToRegion[resolvedStartRegion] || "";
- }
- let resolvedStartPage = startPage;
- let resolvedStartBackground = startBackground;
- let resolvedStartSelection = startSelection;
- if (startRegion && !resolvedStartRegion) {
- resolvedStartPage = 0;
- resolvedStartBackground = "";
- resolvedStartSelection = false;
- }
-
- const [background, setBackground] = useState(resolvedStartBackground);
- const [firstName, setFirstName] = useState("");
- const [lastName, setLastName] = useState("");
- const [email, setEmail] = useState("");
- const [linkedin, setLinkedin] = useState("");
- const { linkedInConversion } = useMarketing();
- const [region, setRegion] = useState(resolvedStartRegion);
- const [isOrganize, setIsOrganize] = useState(false);
- const [commitmentLevel, setCommitmentLevel] = useState(0);
- const [hasSelection, setHasSelection] = useState(resolvedStartSelection);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [submitError, setSubmitError] = useState(false);
-
- useEffect(() => {
- posthog?.group("background", background);
- }, [background]);
-
- const pageBackground = (
-
-
- Are you a student?
-
-
-
-
-
-
- );
- const pageRegion = (
-
-
- Please select a CodeDay City:
-
- {
- // force "Other" to end of list (kind of hacky)
- [
- ...Object.keys(regionsByCountry).filter((k) => k !== "Other"),
- Object.keys(regionsByCountry).includes("Other") ? "Other" : undefined,
- ].map((regionKey) => (
-
- {/* Capitalize first letter of region (this is mostly to fix "the United States" looking weird) */}
-
- {regionKey?.charAt(0).toUpperCase()}
- {regionKey?.substring(1)}
-
- {regionsByCountry[regionKey!]?.map((r) => (
-
- {
- setRegion(r.name);
- setHasSelection(true);
- setIsOrganize(false);
- }}
- >
- {r.name}
-
-
- ))}
-
- ))
- }
-
- {/* Clear region state in case they clicked some other region button before this */}
-
-
-
-
-
- Interested in becoming a CodeDay Organizer?
-
- CodeDay events around the world are organized by students just like you!
-
- You'll manage a team, come up with cool ideas, and help hundreds of students in your
- community discover CS.
-
-
- (No prior event organizing experience is required, CodeDay Staff will support + guide
- you every step of the way)
-
-
- = 0} animateOpacity>
-
- setCommitmentLevel(e.target.checked ? 1 : 0)}>
- I am interested in organizing a CodeDay in my city
-
-
-
-
- = 1} animateOpacity>
-
- What city/region would you like to organize an event in?
- {
- setRegion(e.target.value);
- setHasSelection(true);
- }}
- w="md"
- display="block"
- />
-
-
-
-
-
-
-
- );
- useEffect(() => {
- if (
- firstName &&
- lastName &&
- emailRe.test(email) &&
- (background === "industry" ? linkedin : true)
- )
- setHasSelection(true);
- else setHasSelection(false);
- }, [firstName, lastName, email, linkedin]);
- const pageEmail = (
-
-
- {/*special logic if the only page user sees is contact info*/}
- {resolvedStartPage === 2
- ? background === "industry"
- ? "Apply to volunteer for CodeDay Labs"
- : `Apply to volunteer for CodeDay ${region}`
- : "Let us know how to reach out:"}
-
-
- setFirstName(e.target.value)}
- />
- setLastName(e.target.value)}
- />
- setEmail(e.target.value)}
- />
- {background === "industry" ? (
- {
- setLinkedin(e.target.value);
- }}
- />
- ) : undefined}
-
-
-
- );
-
- const pageConfirmation = submitError ? (
-
-
- ☹️ An Error Ocurred
-
-
- Please email volunteer@codeday.org with
- your application, as well as the following error code:
-
- {submitError}
-
- ) : (
-
-
- ✅ Got it!
-
- We'll be in touch over email in the next few days!
-
- );
-
- const pages = [pageBackground, pageRegion, pageEmail, pageConfirmation];
- const pageIds = ["background", "region", "email", "final"];
-
- // 'last' should really be 'penultimate' but 'last' is shorter
- const [page, navigate] = useReducer(
- (prev: number, action: string) =>
- Math.max(
- 0,
- action === "next" ? prev + 1 : prev - 1,
- action === "last" ? pages.length - 2 : 0,
- ),
- resolvedStartPage,
- );
- const isFinalPage = page === pages.length - 1;
-
- const [hasStarted, setHasStarted] = useState(false);
- useAfterMountEffect(() => {
- if (!hasStarted) {
- posthog?.capture("volunteer.started", { style: "full" });
- }
- setHasStarted(true);
- }, [background, hasStarted]);
-
- useAfterMountEffect(() => {
- if (isFinalPage) {
- posthog?.capture("volunteer.submitted");
- linkedInConversion("volunteer.submitted");
- if (email) posthog?.identify(posthog?.get_distinct_id(), { email });
- } else {
- posthog?.capture("volunteer.partial", {
- volunteerPage: pageIds[page],
- background: background,
- });
- }
- }, [page]);
-
- useEffect(() => setHasSelection(false), [page]);
-
- async function onClickNext() {
- // I wish i could set behavior: 'smooth' here but for some reason
- // When i set that it stops working entirely??????????????????"??"
- // Apparently you can fix it by modifying chrome flags but i dont want
- // it to not work for people who are using the defaults
- formRef.current!.scrollIntoView();
- if (hasSelection) {
- if (background === "industry" && page === 0) {
- // if industry, we want to skip region selection and get them in touch with
- // labs team
- navigate("last");
- } else if (page === pages.length - 2) {
- // if submitting penultimate page, we now have all info
- setIsSubmitting(true);
- try {
- const resp = await fetch("/api/applyAsVolunteer", {
- method: "POST",
- body: JSON.stringify({
- email,
- firstName,
- lastName,
- linkedin,
- region,
- isOrganize,
- background,
- }),
- headers: {},
- });
- if (!resp.ok) {
- DEBUG(resp);
- setSubmitError(`${resp.status}: ${resp.statusText}`);
- } else {
- if (after) window.location.href = after;
- // Do not redirect if there is an error, as otherwise no indication would be shown to the user that their application was not recieved
- }
- navigate("next");
- } catch (ex: any) {
- error(ex.toString());
- }
- setIsSubmitting(false);
- } else {
- navigate("next");
- }
- }
- }
- return (
-
- {pages[page]}
- {!isFinalPage && page !== 0 && (
-
-
-
- )}
-
- );
-}
diff --git a/apps/www/src/components/Volunteer/Wizard/BackgroundStep.tsx b/apps/www/src/components/Volunteer/Wizard/BackgroundStep.tsx
new file mode 100644
index 0000000..a356438
--- /dev/null
+++ b/apps/www/src/components/Volunteer/Wizard/BackgroundStep.tsx
@@ -0,0 +1,39 @@
+import { Box, Button, Heading, HStack } from "@codeday/topo/Atom";
+import React from "react";
+
+interface BackgroundStepProps {
+ onSelectStudent: () => void;
+ onSelectIndustry: () => void;
+ background: string;
+}
+
+export default function BackgroundStep({
+ onSelectStudent,
+ onSelectIndustry,
+ background,
+}: BackgroundStepProps) {
+ return (
+
+
+ Are you a student?
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Volunteer/Wizard/ConfirmationStep.tsx b/apps/www/src/components/Volunteer/Wizard/ConfirmationStep.tsx
new file mode 100644
index 0000000..0468717
--- /dev/null
+++ b/apps/www/src/components/Volunteer/Wizard/ConfirmationStep.tsx
@@ -0,0 +1,32 @@
+import { Box, Text, Heading, Link } from "@codeday/topo/Atom";
+import React from "react";
+
+interface ConfirmationStepProps {
+ submitError: string | false;
+}
+
+export default function ConfirmationStep({ submitError }: ConfirmationStepProps) {
+ if (submitError) {
+ return (
+
+
+ ☹️ An Error Ocurred
+
+
+ Please email volunteer@codeday.org with
+ your application, as well as the following error code:
+
+ {submitError}
+
+ );
+ }
+
+ return (
+
+
+ ✅ Got it!
+
+ We'll be in touch over email in the next few days!
+
+ );
+}
diff --git a/apps/www/src/components/Volunteer/Wizard/ContactStep.tsx b/apps/www/src/components/Volunteer/Wizard/ContactStep.tsx
new file mode 100644
index 0000000..f46941c
--- /dev/null
+++ b/apps/www/src/components/Volunteer/Wizard/ContactStep.tsx
@@ -0,0 +1,75 @@
+import { Box, Heading, VStack, TextInput } from "@codeday/topo/Atom";
+import { DataCollection } from "@codeday/topo/Molecule";
+import React from "react";
+
+interface ContactStepProps {
+ firstName: string;
+ setFirstName: (v: string) => void;
+ lastName: string;
+ setLastName: (v: string) => void;
+ email: string;
+ setEmail: (v: string) => void;
+ linkedin: string;
+ setLinkedin: (v: string) => void;
+ background: string;
+ region: string;
+ resolvedStartPage: number;
+}
+
+export default function ContactStep({
+ firstName,
+ setFirstName,
+ lastName,
+ setLastName,
+ email,
+ setEmail,
+ linkedin,
+ setLinkedin,
+ background,
+ region,
+ resolvedStartPage,
+}: ContactStepProps) {
+ return (
+
+
+ {/*special logic if the only page user sees is contact info*/}
+ {resolvedStartPage === 2
+ ? background === "industry"
+ ? "Apply to volunteer for CodeDay Labs"
+ : `Apply to volunteer for CodeDay ${region}`
+ : "Let us know how to reach out:"}
+
+
+ setFirstName(e.target.value)}
+ />
+ setLastName(e.target.value)}
+ />
+ setEmail(e.target.value)}
+ />
+ {background === "industry" ? (
+ {
+ setLinkedin(e.target.value);
+ }}
+ />
+ ) : undefined}
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Volunteer/Wizard/RegionStep.tsx b/apps/www/src/components/Volunteer/Wizard/RegionStep.tsx
new file mode 100644
index 0000000..453328a
--- /dev/null
+++ b/apps/www/src/components/Volunteer/Wizard/RegionStep.tsx
@@ -0,0 +1,127 @@
+import {
+ Box,
+ Button,
+ Text,
+ Heading,
+ TextInput,
+ Divider,
+ Checkbox,
+ Radio,
+} from "@codeday/topo/Atom";
+import { Collapse } from "@codeday/topo/Molecule";
+import React from "react";
+
+interface RegionStepProps {
+ regions: any[];
+ regionsByCountry: Record;
+ region: string;
+ setRegion: (r: string) => void;
+ isOrganize: boolean;
+ setIsOrganize: (v: boolean) => void;
+ setHasSelection: (v: boolean) => void;
+ commitmentLevel: number;
+ setCommitmentLevel: (v: number) => void;
+}
+
+export default function RegionStep({
+ regions,
+ regionsByCountry,
+ region,
+ setRegion,
+ isOrganize,
+ setIsOrganize,
+ setHasSelection,
+ commitmentLevel,
+ setCommitmentLevel,
+}: RegionStepProps) {
+ return (
+
+
+ Please select a CodeDay City:
+
+ {
+ // force "Other" to end of list (kind of hacky)
+ [
+ ...Object.keys(regionsByCountry).filter((k) => k !== "Other"),
+ Object.keys(regionsByCountry).includes("Other") ? "Other" : undefined,
+ ].map((regionKey) => (
+
+ {/* Capitalize first letter of region (this is mostly to fix "the United States" looking weird) */}
+
+ {regionKey?.charAt(0).toUpperCase()}
+ {regionKey?.substring(1)}
+
+ {regionsByCountry[regionKey!]?.map((r) => (
+
+ {
+ setRegion(r.name);
+ setHasSelection(true);
+ setIsOrganize(false);
+ }}
+ >
+ {r.name}
+
+
+ ))}
+
+ ))
+ }
+
+ {/* Clear region state in case they clicked some other region button before this */}
+
+
+
+
+
+ Interested in becoming a CodeDay Organizer?
+
+ CodeDay events around the world are organized by students just like you!
+
+ You'll manage a team, come up with cool ideas, and help hundreds of students in your
+ community discover CS.
+
+
+ (No prior event organizing experience is required, CodeDay Staff will support + guide
+ you every step of the way)
+
+
+ = 0} animateOpacity>
+
+ setCommitmentLevel(e.target.checked ? 1 : 0)}>
+ I am interested in organizing a CodeDay in my city
+
+
+
+
+ = 1} animateOpacity>
+
+ What city/region would you like to organize an event in?
+ {
+ setRegion(e.target.value);
+ setHasSelection(true);
+ }}
+ w="md"
+ display="block"
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/www/src/components/Volunteer/Wizard/index.tsx b/apps/www/src/components/Volunteer/Wizard/index.tsx
new file mode 100644
index 0000000..668f29b
--- /dev/null
+++ b/apps/www/src/components/Volunteer/Wizard/index.tsx
@@ -0,0 +1,256 @@
+import { Box, Button } from "@codeday/topo/Atom";
+import { useToasts } from "@codeday/topo/utils";
+import { debug } from "@codeday/utils";
+import { usePostHog } from "@posthog/react";
+import React, { useState, useReducer, useEffect, RefObject } from "react";
+
+import { useMarketing } from "../../../providers";
+import { useAfterMountEffect } from "../../../utils/useAfterMountEffect";
+import BackgroundStep from "./BackgroundStep";
+import ConfirmationStep from "./ConfirmationStep";
+import ContactStep from "./ContactStep";
+import RegionStep from "./RegionStep";
+
+const DEBUG = debug(["www", "components", "Volunteer", "Wizard"]);
+
+// https://stackoverflow.com/a/48981669
+function groupBy(xs: any[], f: (x: any) => string): Record {
+ return xs.reduce(
+ (r: any, v: any, i: number, a: any[], k = f(v)) => ((r[k] || (r[k] = [])).push(v), r),
+ {},
+ );
+}
+
+const emailRe = new RegExp(".+@.+\\..+");
+
+const PAGE_COUNT = 4;
+const pageIds = ["background", "region", "email", "final"];
+
+interface WizardProps {
+ events: any[];
+ formRef: RefObject;
+ startBackground?: string;
+ startRegion?: string;
+ startPage?: number;
+ startSelection?: boolean;
+ after?: string;
+}
+
+export default function Wizard({
+ events,
+ formRef,
+ startBackground = "",
+ startRegion = "",
+ startPage = 0,
+ startSelection = false,
+ after,
+}: WizardProps) {
+ const posthog = usePostHog();
+ const { error } = useToasts();
+ const regions = new Array(
+ ...new Set(
+ events
+ .filter((e) => !e.dontAcceptVolunteers)
+ .map((e) =>
+ JSON.stringify({
+ name: e.region?.name || e.name,
+ webname: e.contentfulWebname,
+ country: e.region?.countryName || "Other",
+ aliases: e.region?.aliases || [],
+ }),
+ ),
+ ),
+ ).map((e) => JSON.parse(e)); // json -> string -> json for deduplication
+ const regionsByCountry = groupBy(regions, (r) => r.country);
+
+ useEffect(() => {
+ DEBUG("regions", regions);
+ DEBUG("regionsByCountry", regionsByCountry);
+ }, [regions, regionsByCountry]);
+
+ let resolvedStartRegion = startRegion;
+ if (resolvedStartRegion) {
+ const webnamesToRegion: Record = {};
+ regions.forEach((r) => {
+ webnamesToRegion[r.webname] = r.name;
+ r.aliases.forEach((alias: string) => {
+ webnamesToRegion[alias] = r.name;
+ });
+ });
+ resolvedStartRegion = webnamesToRegion[resolvedStartRegion] || "";
+ }
+ let resolvedStartPage = startPage;
+ let resolvedStartBackground = startBackground;
+ let resolvedStartSelection = startSelection;
+ if (startRegion && !resolvedStartRegion) {
+ resolvedStartPage = 0;
+ resolvedStartBackground = "";
+ resolvedStartSelection = false;
+ }
+
+ const [background, setBackground] = useState(resolvedStartBackground);
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [email, setEmail] = useState("");
+ const [linkedin, setLinkedin] = useState("");
+ const { linkedInConversion } = useMarketing();
+ const [region, setRegion] = useState(resolvedStartRegion);
+ const [isOrganize, setIsOrganize] = useState(false);
+ const [commitmentLevel, setCommitmentLevel] = useState(0);
+ const [hasSelection, setHasSelection] = useState(resolvedStartSelection);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [submitError, setSubmitError] = useState(false);
+
+ useEffect(() => {
+ posthog?.group("background", background);
+ }, [background]);
+
+ // 'last' should really be 'penultimate' but 'last' is shorter
+ const [page, navigate] = useReducer(
+ (prev: number, action: string) =>
+ Math.max(
+ 0,
+ action === "next" ? prev + 1 : prev - 1,
+ action === "last" ? PAGE_COUNT - 2 : 0,
+ ),
+ resolvedStartPage,
+ );
+ const isFinalPage = page === PAGE_COUNT - 1;
+
+ useEffect(() => {
+ if (
+ firstName &&
+ lastName &&
+ emailRe.test(email) &&
+ (background === "industry" ? linkedin : true)
+ )
+ setHasSelection(true);
+ else setHasSelection(false);
+ }, [firstName, lastName, email, linkedin]);
+
+ const [hasStarted, setHasStarted] = useState(false);
+ useAfterMountEffect(() => {
+ if (!hasStarted) {
+ posthog?.capture("volunteer.started", { style: "full" });
+ }
+ setHasStarted(true);
+ }, [background, hasStarted]);
+
+ useAfterMountEffect(() => {
+ if (isFinalPage) {
+ posthog?.capture("volunteer.submitted");
+ linkedInConversion("volunteer.submitted");
+ if (email) posthog?.identify(posthog?.get_distinct_id(), { email });
+ } else {
+ posthog?.capture("volunteer.partial", {
+ volunteerPage: pageIds[page],
+ background: background,
+ });
+ }
+ }, [page]);
+
+ useEffect(() => setHasSelection(false), [page]);
+
+ async function onClickNext() {
+ // I wish i could set behavior: 'smooth' here but for some reason
+ // When i set that it stops working entirely??????????????????"??"
+ // Apparently you can fix it by modifying chrome flags but i dont want
+ // it to not work for people who are using the defaults
+ formRef.current!.scrollIntoView();
+ if (hasSelection) {
+ if (background === "industry" && page === 0) {
+ // if industry, we want to skip region selection and get them in touch with
+ // labs team
+ navigate("last");
+ } else if (page === PAGE_COUNT - 2) {
+ // if submitting penultimate page, we now have all info
+ setIsSubmitting(true);
+ try {
+ const resp = await fetch("/api/applyAsVolunteer", {
+ method: "POST",
+ body: JSON.stringify({
+ email,
+ firstName,
+ lastName,
+ linkedin,
+ region,
+ isOrganize,
+ background,
+ }),
+ headers: {},
+ });
+ if (!resp.ok) {
+ DEBUG(resp);
+ setSubmitError(`${resp.status}: ${resp.statusText}`);
+ } else {
+ if (after) window.location.href = after;
+ // Do not redirect if there is an error, as otherwise no indication would be shown to the user that their application was not recieved
+ }
+ navigate("next");
+ } catch (ex: any) {
+ error(ex.toString());
+ }
+ setIsSubmitting(false);
+ } else {
+ navigate("next");
+ }
+ }
+ }
+
+ const pages = [
+ {
+ setBackground("student");
+ navigate("next");
+ }}
+ onSelectIndustry={() => {
+ setBackground("industry");
+ navigate("last");
+ }}
+ />,
+ ,
+ ,
+ ,
+ ];
+
+ return (
+
+ {pages[page]}
+ {!isFinalPage && page !== 0 && (
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/www/src/pages/404.gql b/apps/www/src/pages/404.gql
index 78a4c79..8b30b73 100644
--- a/apps/www/src/pages/404.gql
+++ b/apps/www/src/pages/404.gql
@@ -1,4 +1,4 @@
-#import "../components/Page.gql"
+#import "../components/Page/Page.gql"
query Error404Query {
...PageComponent
}
diff --git a/apps/www/src/pages/_app.tsx b/apps/www/src/pages/_app.tsx
index b498198..333c0d1 100644
--- a/apps/www/src/pages/_app.tsx
+++ b/apps/www/src/pages/_app.tsx
@@ -1,4 +1,4 @@
-import { ThemeProvider } from "@codeday/topo/Theme";
+import { ThemeProvider, PageDataProvider } from "@codeday/topo/Theme";
import "react-responsive-modal/styles.css";
import { debug } from "@codeday/utils";
@@ -6,7 +6,6 @@ import { AppProps } from "next/app";
import { Fragment, StrictMode, useEffect } from "react";
import { MarketingProvider, FundraiseProvider } from "../providers";
-import { Provider } from "../query";
const DEBUG = debug(["www", "pages", "_app"]);
const STRICT_MODE_OR_FRAGMENT =
@@ -22,9 +21,9 @@ export default function App({ Component, pageProps }: AppProps) {
-
+
-
+
diff --git a/apps/www/src/pages/contact.tsx b/apps/www/src/pages/contact.tsx
index a397a5e..8e1ce21 100644
--- a/apps/www/src/pages/contact.tsx
+++ b/apps/www/src/pages/contact.tsx
@@ -12,7 +12,7 @@ import Employees from "../components/Contact/Employees";
import FullProfile from "../components/Contact/FullProfile";
import TextOnly from "../components/Contact/TextOnly";
import Page from "../components/Page";
-import { useQuery } from "../query";
+import { usePageData } from "@codeday/topo/Theme";
import { ContactQuery } from "./contact.gql";
function nl2br(str: string): string {
@@ -32,7 +32,7 @@ export default function Home({ seed }: { seed: number }) {
account: { employees, otherTeam, volunteers, board, contractors, emeritus, boardEmeritus },
labs,
clear,
- } = useQuery();
+ } = usePageData();
const employeeIds = employees.map((e: any) => e.id);
const otherIds = [...employees, ...otherTeam, ...board, ...contractors, ...emeritus].map(
diff --git a/apps/www/src/pages/data.tsx b/apps/www/src/pages/data.tsx
index 826c2e4..676b02e 100644
--- a/apps/www/src/pages/data.tsx
+++ b/apps/www/src/pages/data.tsx
@@ -6,12 +6,12 @@ import { DateTime } from "luxon";
import { GetStaticProps } from "next";
import Page from "../components/Page";
-import { useQuery } from "../query";
+import { usePageData } from "@codeday/topo/Theme";
import Error404 from "./404";
import { DataListPublicationsQuery } from "./data.gql";
export default function Home() {
- const { cms } = useQuery();
+ const { cms } = usePageData();
if (!cms) {
return (
diff --git a/apps/www/src/pages/doi/[...doi]/index.tsx b/apps/www/src/pages/doi/[...doi]/index.tsx
index f352bef..bb06f0a 100644
--- a/apps/www/src/pages/doi/[...doi]/index.tsx
+++ b/apps/www/src/pages/doi/[...doi]/index.tsx
@@ -30,7 +30,7 @@ import { useRouter } from "next/router";
import Markdown from "react-markdown";
import Page from "../../../components/Page";
-import { useQuery } from "../../../query";
+import { usePageData } from "@codeday/topo/Theme";
import Error404 from "../../404";
import { PublicationQuery, ListPublicationsQuery } from "./index.gql";
@@ -96,7 +96,7 @@ function FileIcon({ file }: FileIconProps) {
}
export default function Home() {
- const { cms } = useQuery();
+ const { cms } = usePageData();
const { query } = useRouter();
if (!cms) {
diff --git a/apps/www/src/pages/doi/crossref/[...doi]/index.tsx b/apps/www/src/pages/doi/crossref/[...doi]/index.tsx
index 455ae18..60756e2 100644
--- a/apps/www/src/pages/doi/crossref/[...doi]/index.tsx
+++ b/apps/www/src/pages/doi/crossref/[...doi]/index.tsx
@@ -10,7 +10,7 @@ import { useMemo } from "react";
import xmlbuilder from "xmlbuilder";
import Page from "../../../../components/Page";
-import { useQuery } from "../../../../query";
+import { usePageData } from "@codeday/topo/Theme";
import { PublicationQuery, ListPublicationsQuery } from "../../[...doi]/index.gql";
function licenseToLink(license: string): string | undefined {
@@ -206,7 +206,7 @@ function getCrossrefXml(publication: any, id: string): string {
}
export default function Crossref() {
- const { cms } = useQuery();
+ const { cms } = usePageData();
const { query } = useRouter();
const id = useMemo(() => `codeday-${Math.random().toString(36).substring(2, 8)}`, []);
diff --git a/apps/www/src/pages/donate.tsx b/apps/www/src/pages/donate.tsx
index 90f108a..a9bc22e 100644
--- a/apps/www/src/pages/donate.tsx
+++ b/apps/www/src/pages/donate.tsx
@@ -5,13 +5,13 @@ import { print } from "graphql";
import { GetStaticProps } from "next";
import Page from "../components/Page";
-import { useQuery } from "../query";
+import { usePageData } from "@codeday/topo/Theme";
import { DonateQuery } from "./donate.gql";
export default function Donate() {
const {
cms: { mission },
- } = useQuery();
+ } = usePageData();
return (
diff --git a/apps/www/src/pages/e/[calendarId]/[eventId]/index.gql b/apps/www/src/pages/e/[calendarId]/[eventId]/index.gql
index 7ca04bf..21e9481 100644
--- a/apps/www/src/pages/e/[calendarId]/[eventId]/index.gql
+++ b/apps/www/src/pages/e/[calendarId]/[eventId]/index.gql
@@ -1,4 +1,4 @@
-#import "../../../../components/EventInfo.gql"
+#import "../../../../components/EventInfo/EventInfo.gql"
query EventByIdQuery($calendarId: String!, $id: ID!) {
calendar {
diff --git a/apps/www/src/pages/eco.tsx b/apps/www/src/pages/eco.tsx
index d8f1351..3ea3e10 100644
--- a/apps/www/src/pages/eco.tsx
+++ b/apps/www/src/pages/eco.tsx
@@ -1,17 +1,16 @@
import { Heading } from "@codeday/topo/Atom";
-import { Content } from "@codeday/topo/Molecule";
+import { Content, ContentfulRichText } from "@codeday/topo/Molecule";
import { apiFetch } from "@codeday/topo/utils";
import { print } from "graphql";
import { GetStaticProps } from "next";
import React from "react";
-import ContentfulRichText from "../components/ContentfulRichText";
import Page from "../components/Page";
-import { useQuery } from "../query";
+import { usePageData } from "@codeday/topo/Theme";
import { EcoQuery } from "./eco.gql";
export default function Eco() {
- const { details } = useQuery().cms;
+ const { details } = usePageData().cms;
return (
diff --git a/apps/www/src/pages/f/[slug].tsx b/apps/www/src/pages/f/[slug].tsx
index c7b4198..6f29257 100644
--- a/apps/www/src/pages/f/[slug].tsx
+++ b/apps/www/src/pages/f/[slug].tsx
@@ -1,19 +1,18 @@
import { Heading, Image, Skelly, Spinner, Box, Grid } from "@codeday/topo/Atom";
-import { Content, CognitoForm } from "@codeday/topo/Molecule";
+import { Content, CognitoForm, ContentfulRichText } from "@codeday/topo/Molecule";
import { apiFetch } from "@codeday/topo/utils";
import { print } from "graphql";
import { GetStaticProps, GetStaticPaths } from "next";
import { useRouter } from "next/router";
import React from "react";
-import ContentfulRichText from "../../components/ContentfulRichText";
import Page from "../../components/Page";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import Error404 from "../404";
import { FormQuery, ListFormsQuery } from "./form.gql";
export default function Home() {
- const { cms } = useQuery();
+ const { cms } = usePageData();
const { query } = useRouter();
if (!cms) {
diff --git a/apps/www/src/pages/help/[program]/[audience].tsx b/apps/www/src/pages/help/[program]/[audience].tsx
index 72f3085..fc42d1e 100644
--- a/apps/www/src/pages/help/[program]/[audience].tsx
+++ b/apps/www/src/pages/help/[program]/[audience].tsx
@@ -1,15 +1,14 @@
import { Box, Grid, Image, Text, Heading } from "@codeday/topo/Atom";
-import { Content } from "@codeday/topo/Molecule";
-import { useColorMode } from "@codeday/topo/Theme";
+import { Content, ContentfulRichText } from "@codeday/topo/Molecule";
+import { useColorMode, usePageData } from "@codeday/topo/Theme";
import { apiFetch } from "@codeday/topo/utils";
import { UiX } from "@codeday/topocons";
import { print } from "graphql";
import { GetStaticProps, GetStaticPaths } from "next";
import React, { useState } from "react";
-import ContentfulRichText from "../../../components/ContentfulRichText";
import Page from "../../../components/Page";
-import { useQuery } from "../../../query";
+
import { HelpProgramAudienceQuery, HelpProgramAudiencePathsQuery } from "./audience.gql";
interface AudienceProps {
@@ -23,7 +22,7 @@ export default function Audience({ programWebname, audience }: AudienceProps) {
if (!programWebname || !audience) return <>>;
- const { programs, faqs, events } = useQuery().cms || {};
+ const { programs, faqs, events } = usePageData().cms || {};
const program = programs?.items[0] || null;
const photos =
diff --git a/apps/www/src/pages/help/[program]/index.tsx b/apps/www/src/pages/help/[program]/index.tsx
index c0e2492..bdb263c 100644
--- a/apps/www/src/pages/help/[program]/index.tsx
+++ b/apps/www/src/pages/help/[program]/index.tsx
@@ -18,7 +18,7 @@ import { GetStaticProps, GetStaticPaths } from "next";
import React from "react";
import Page from "../../../components/Page";
-import { useQuery } from "../../../query";
+import { usePageData } from "@codeday/topo/Theme";
import { HelpProgramIndexQuery, HelpProgramIndexPathsQuery } from "./index.gql";
const icons: Record = {
@@ -34,7 +34,7 @@ interface ProgramProps {
}
export default function Program({ programWebname }: ProgramProps) {
- const { programs, faqs, events } = useQuery().cms || {};
+ const { programs, faqs, events } = usePageData().cms || {};
if (!programWebname) return <>>;
diff --git a/apps/www/src/pages/help/article/[article].tsx b/apps/www/src/pages/help/article/[article].tsx
index 86f5709..73479a6 100644
--- a/apps/www/src/pages/help/article/[article].tsx
+++ b/apps/www/src/pages/help/article/[article].tsx
@@ -1,5 +1,5 @@
import { Text, Heading, Link, Box, List, ListItem } from "@codeday/topo/Atom";
-import { Content } from "@codeday/topo/Molecule";
+import { Content, ContentfulRichText } from "@codeday/topo/Molecule";
import { apiFetch } from "@codeday/topo/utils";
import { UiArrowRight } from "@codeday/topocons";
import { print } from "graphql";
@@ -7,7 +7,6 @@ import { DateTime } from "luxon";
import { GetStaticProps, GetStaticPaths } from "next";
import React from "react";
-import ContentfulRichText from "../../../components/ContentfulRichText";
import Page from "../../../components/Page";
import { HelpArticleQuery, HelpArticlePathsQuery } from "./article.gql";
diff --git a/apps/www/src/pages/help/index.tsx b/apps/www/src/pages/help/index.tsx
index 60897c7..22a86bb 100644
--- a/apps/www/src/pages/help/index.tsx
+++ b/apps/www/src/pages/help/index.tsx
@@ -12,11 +12,11 @@ import { GetStaticProps } from "next";
import React from "react";
import Page from "../../components/Page";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import { HelpIndexQuery } from "./index.gql";
export default function Help() {
- const { programs } = useQuery().cms || {};
+ const { programs } = usePageData().cms || {};
const programsWithFaqs =
programs?.items?.filter((p: any) => p.linkedFrom?.faqs?.items?.length > 0) || [];
diff --git a/apps/www/src/pages/index.gql b/apps/www/src/pages/index.gql
index 0399cca..781bd39 100644
--- a/apps/www/src/pages/index.gql
+++ b/apps/www/src/pages/index.gql
@@ -1,7 +1,7 @@
-#import "../components/Page.gql"
+#import "../components/Page/Page.gql"
#import "../components/Index/Hero.gql"
#import "../components/Index/Stats.gql"
-#import "../components/Index/Programs.gql"
+#import "../components/Index/Programs/Programs.gql"
#import "../components/Index/Sponsors.gql"
#import "../components/Index/Announcement.gql"
#import "../components/Index/Community.gql"
diff --git a/apps/www/src/pages/legal/[policy].tsx b/apps/www/src/pages/legal/[policy].tsx
index e98718c..fa3891f 100644
--- a/apps/www/src/pages/legal/[policy].tsx
+++ b/apps/www/src/pages/legal/[policy].tsx
@@ -5,9 +5,9 @@ import { print } from "graphql";
import { GetStaticProps, GetStaticPaths } from "next";
import React from "react";
-import Markdown from "../../components/Markdown";
+import { Markdown } from "@codeday/topo/Molecule";
import Page from "../../components/Page";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import { LegalPathsQuery, LegalContentQuery, TermageddonLegalContentQuery } from "./policy.gql";
const TERMAGEDDON_POLICIES = ["tos", "privacy", "cookies", "disclaimer"];
@@ -17,7 +17,7 @@ interface PolicyProps {
}
export default function Policy({ slug }: PolicyProps) {
- const { termageddon, notion } = useQuery();
+ const { termageddon, notion } = usePageData();
const page = TERMAGEDDON_POLICIES.includes(slug)
? {
content: termageddon.terms[slug],
diff --git a/apps/www/src/pages/m/[...slug].tsx b/apps/www/src/pages/m/[...slug].tsx
index 4aded6f..187bdfb 100644
--- a/apps/www/src/pages/m/[...slug].tsx
+++ b/apps/www/src/pages/m/[...slug].tsx
@@ -3,7 +3,7 @@ import { Content } from "@codeday/topo/Molecule";
import { GetStaticProps, GetStaticPaths } from "next";
import React from "react";
-import Calendly from "../../components/Calendly";
+import { CalendlyEmbed as Calendly } from "@codeday/topo/Molecule";
import Page from "../../components/Page";
interface CalendlyPageProps {
diff --git a/apps/www/src/pages/press.tsx b/apps/www/src/pages/press.tsx
index 169609f..45ac347 100644
--- a/apps/www/src/pages/press.tsx
+++ b/apps/www/src/pages/press.tsx
@@ -1,16 +1,15 @@
import { Box, Flex, Grid, Text, Heading, Link, Button, Image } from "@codeday/topo/Atom";
-import { Content } from "@codeday/topo/Molecule";
+import { Content, ContentfulRichText } from "@codeday/topo/Molecule";
import { apiFetch } from "@codeday/topo/utils";
import { print } from "graphql";
import { DateTime } from "luxon";
import { GetStaticProps } from "next";
import React from "react";
-import ContentfulRichText from "../components/ContentfulRichText";
import Page from "../components/Page";
import PhotoGallery from "../components/Press/PhotoGallery";
import PreviousCoverageLogos from "../components/PreviousCoverageLogos";
-import { useQuery } from "../query";
+import { usePageData } from "@codeday/topo/Theme";
import { PressQuery } from "./press.gql";
interface PressProps {
@@ -20,7 +19,7 @@ interface PressProps {
export default function Press({ seed }: PressProps) {
const {
cms: { mission, pressContact, pressDetails, programs, previousCoverage },
- } = useQuery();
+ } = usePageData();
return (
diff --git a/apps/www/src/pages/volunteer/index.tsx b/apps/www/src/pages/volunteer/index.tsx
index c381d58..fd8e16c 100644
--- a/apps/www/src/pages/volunteer/index.tsx
+++ b/apps/www/src/pages/volunteer/index.tsx
@@ -1,6 +1,6 @@
import { Box, Grid, Text, Heading, Link, Button, Divider } from "@codeday/topo/Atom";
import { Content } from "@codeday/topo/Molecule";
-import { useColorMode } from "@codeday/topo/Theme";
+import { useColorMode, usePageData } from "@codeday/topo/Theme";
import { apiFetch } from "@codeday/topo/utils";
import { print } from "graphql";
import { DateTime } from "luxon";
@@ -9,14 +9,14 @@ import { NextSeo } from "next-seo";
import { useRouter } from "next/router";
import React, { useState, useRef } from "react";
-import Highlight from "../../components/Highlight";
+import { Highlight } from "@codeday/topo/Atom";
import Page from "../../components/Page";
import PhotoGallery from "../../components/Volunteer/PhotoGallery";
import PreviewVideo from "../../components/Volunteer/PreviewVideo";
import RemindMe from "../../components/Volunteer/RemindMe";
import Testimonials from "../../components/Volunteer/Testimonials";
import Wizard from "../../components/Volunteer/Wizard";
-import { useQuery } from "../../query";
+
import { VolunteerQuery } from "./volunteer.gql";
interface VolunteerProps {
@@ -37,7 +37,7 @@ export default function Volunteer({
const formRef = useRef(null);
const { colorMode } = useColorMode();
const { asPath, query } = useRouter();
- const { clear } = useQuery();
+ const { clear } = usePageData();
const [wizardVisible, setWizardVisible] = useState(false);
const secondText = (
diff --git a/apps/www/src/pages/volunteer/share.tsx b/apps/www/src/pages/volunteer/share.tsx
index f24aa40..2d84e27 100644
--- a/apps/www/src/pages/volunteer/share.tsx
+++ b/apps/www/src/pages/volunteer/share.tsx
@@ -8,7 +8,7 @@ import React from "react";
import Page from "../../components/Page";
import VideoTestimonialThumbnail from "../../components/VideoTestimonialThumbnail";
import ProgramInfo from "../../components/Volunteer/ProgramInfo";
-import { useQuery } from "../../query";
+import { usePageData } from "@codeday/topo/Theme";
import { upcomingEvents } from "../../utils/time";
import { VolunteerQuery } from "./volunteer.gql";
@@ -17,7 +17,7 @@ const PROGRAM_WEIGHT = ["primary", "secondary", "minor"];
export default function Volunteer() {
const {
cms: { volunteerPrograms, testimonials },
- } = useQuery();
+ } = usePageData();
const programsWithUpcoming =
volunteerPrograms?.items
?.map((program: any) => {
diff --git a/apps/www/src/providers/Fundraise.tsx b/apps/www/src/providers/Fundraise.tsx
index 4463b3b..c61bd8a 100644
--- a/apps/www/src/providers/Fundraise.tsx
+++ b/apps/www/src/providers/Fundraise.tsx
@@ -4,6 +4,7 @@ import Head from "next/head";
import Script from "next/script";
import { createContext, useContext, useEffect, useState, ReactNode } from "react";
const DEBUG = debug(["www", "providers", "Fundraise"]);
+const FUNDRAISE_UP_ID = process.env.NEXT_PUBLIC_FUNDRAISE_UP_ID || "AHCSATYN";
interface FundraiseContextType {
isFundraiseLoaded: boolean;
@@ -47,12 +48,12 @@ export function FundraiseProvider({ children }: { children: ReactNode }) {
-
+