From fe0a25b5ee062f7c02b49c418c992835f9a12ca5 Mon Sep 17 00:00:00 2001 From: johnlindquist Date: Thu, 16 Jan 2025 11:30:29 -0700 Subject: [PATCH] feat(bootcamp): adding entire landing page with ugly styles --- src/components/GridBackground.tsx | 5 + src/pages/bootcamp/components/Conclusion.tsx | 24 +++++ src/pages/bootcamp/components/Features.tsx | 92 ++++++++++++++++++ src/pages/bootcamp/components/Hero.tsx | 95 +++++++++++++++++++ src/pages/bootcamp/components/Instructor.tsx | 29 ++++++ src/pages/bootcamp/components/SignUpForm.tsx | 86 +++++++++++++++++ .../bootcamp/components/WorkshopStructure.tsx | 72 ++++++++++++++ src/pages/bootcamp/components/animations.ts | 28 ++++++ src/pages/bootcamp/components/ui/button.tsx | 56 +++++++++++ src/pages/bootcamp/components/ui/input.tsx | 21 ++++ src/pages/bootcamp/components/ui/utils.ts | 6 ++ src/pages/bootcamp/index.tsx | 54 +++++++++++ src/pages/bootcamp/styles.css | 59 ++++++++++++ src/pages/q/[[...all]].tsx | 25 +++-- 14 files changed, 642 insertions(+), 10 deletions(-) create mode 100644 src/components/GridBackground.tsx create mode 100644 src/pages/bootcamp/components/Conclusion.tsx create mode 100644 src/pages/bootcamp/components/Features.tsx create mode 100644 src/pages/bootcamp/components/Hero.tsx create mode 100644 src/pages/bootcamp/components/Instructor.tsx create mode 100644 src/pages/bootcamp/components/SignUpForm.tsx create mode 100644 src/pages/bootcamp/components/WorkshopStructure.tsx create mode 100644 src/pages/bootcamp/components/animations.ts create mode 100644 src/pages/bootcamp/components/ui/button.tsx create mode 100644 src/pages/bootcamp/components/ui/input.tsx create mode 100644 src/pages/bootcamp/components/ui/utils.ts create mode 100644 src/pages/bootcamp/index.tsx create mode 100644 src/pages/bootcamp/styles.css diff --git a/src/components/GridBackground.tsx b/src/components/GridBackground.tsx new file mode 100644 index 0000000000..09f6968132 --- /dev/null +++ b/src/components/GridBackground.tsx @@ -0,0 +1,5 @@ +import {FC} from 'react' + +export const GridBackground: FC = () => ( +
+) diff --git a/src/pages/bootcamp/components/Conclusion.tsx b/src/pages/bootcamp/components/Conclusion.tsx new file mode 100644 index 0000000000..d46331b57d --- /dev/null +++ b/src/pages/bootcamp/components/Conclusion.tsx @@ -0,0 +1,24 @@ +'use client' +import {motion} from 'framer-motion' +import {fadeInUp} from './animations' + +export default function Conclusion() { + return ( +
+
+ +

+ Ready to Join Our AI Mastery Bootcamp? +

+

+ Secure your spot in this unique, 20-day immersive training + experience. You'll learn alongside a focused cohort of + developers, all committed to mastering practical AI implementation. + Enter your email below to be first in line when registration opens + for our next intensive. +

+
+
+
+ ) +} diff --git a/src/pages/bootcamp/components/Features.tsx b/src/pages/bootcamp/components/Features.tsx new file mode 100644 index 0000000000..4bce90938b --- /dev/null +++ b/src/pages/bootcamp/components/Features.tsx @@ -0,0 +1,92 @@ +'use client' +import {motion} from 'framer-motion' +import {Cpu, Database, Users, Calendar, Shield, Cloud} from 'lucide-react' +import {fadeInUp, staggerContainer, staggerItem} from './animations' + +const features = [ + { + id: 'ai-integration', + title: 'Practical AI Integration', + description: + 'Quickly integrate AI capabilities into new or existing applications using modern frameworks and tools.', + icon: Cpu, + }, + { + id: 'data-handling', + title: 'Intelligent Data Handling', + description: + 'Implement vector search and semantic querying to give your users quick, context-aware answers—no matter the data set.', + icon: Database, + }, + { + id: 'multi-agent', + title: 'Multi-Agent Systems', + description: + 'Build specialized agents (e.g., for support, data analysis, or automation) that work in tandem to serve different business needs.', + icon: Users, + }, + { + id: 'accountability', + title: 'Daily Accountability', + description: + 'Participate in 1-hour live workshop sessions plus evening assignments, ensuring steady progress. Get real-time feedback from John and fellow participants in a shared cohort environment.', + icon: Calendar, + }, + { + id: 'security', + title: 'Testing & Security', + description: + 'Learn best practices for automated testing of AI outputs and safeguarding sensitive data. Keep your enterprise environment secure and compliant.', + icon: Shield, + }, + { + id: 'deployment', + title: 'Production-Ready Deployment', + description: + 'Deploy your AI-driven solutions confidently—either to a cloud platform or Docker environment—with built-in logging and analytics for ongoing improvements.', + icon: Cloud, + }, +] + +export default function Features() { + return ( +
+
+ + What You'll Learn + + + {features.map((feature) => { + const Icon = feature.icon + return ( + +
+
+ +
+

+ {feature.title} +

+

{feature.description}

+
+
+ ) + })} +
+
+
+ ) +} diff --git a/src/pages/bootcamp/components/Hero.tsx b/src/pages/bootcamp/components/Hero.tsx new file mode 100644 index 0000000000..c9ae432053 --- /dev/null +++ b/src/pages/bootcamp/components/Hero.tsx @@ -0,0 +1,95 @@ +'use client' +import Link from 'next/link' +import {motion, AnimatePresence} from 'framer-motion' +import {fadeInUp, scaleIn} from './animations' +import {useState, useEffect} from 'react' +import '../styles.css' + +const phrases = [ + 'Level Up Your Skills With', + 'Join Our AI Deep Dive Using', + 'Master Real-World Apps With', + 'Transform Projects Using', + 'Start Your AI Journey With', + 'Join Our Hands-On Lab Using', + 'Build Production Apps With', + 'Power Your Future Using', +] + +const AnimatedPhrase = ({text}: {text: string}) => ( + + {text} + +) + +export default function Hero() { + const [phraseIndex, setPhraseIndex] = useState(0) + + useEffect(() => { + const timer = setInterval(() => { + setPhraseIndex((current) => (current + 1) % phrases.length) + }, 3000) + return () => clearInterval(timer) + }, []) + + return ( +
+
+ + + + + + + + + AI + {' '} + in Just{' '} + + 20 Days + + + + + Join John Lindquist, founder of{' '} + egghead.io, for an immersive, hands-on program that will revolutionize + your dev workflow. In just{' '} + 20 days, you'll master + building real-world AI applications that automate the tedious, amplify + your capabilities, and{' '} + + transform how your team ships software + + . + + + + + Join the Waitlist + + + +
+ ) +} diff --git a/src/pages/bootcamp/components/Instructor.tsx b/src/pages/bootcamp/components/Instructor.tsx new file mode 100644 index 0000000000..396852bc89 --- /dev/null +++ b/src/pages/bootcamp/components/Instructor.tsx @@ -0,0 +1,29 @@ +'use client' +import {motion} from 'framer-motion' +import {fadeInUp} from './animations' + +export default function Instructor() { + return ( +
+
+ +

+ Meet Your Instructor, John Lindquist +

+

+ John Lindquist is a recognized leader in developer education. He + founded egghead.io—an innovative platform that has guided thousands + of coders from novices to industry experts. With years of practical + teaching experience and a genuine passion for helping others + succeed, John will provide the clarity and support you need to + master AI development. +

+

+ Join John and a supportive community of developers as you build the + skills needed to create impactful AI solutions. +

+
+
+
+ ) +} diff --git a/src/pages/bootcamp/components/SignUpForm.tsx b/src/pages/bootcamp/components/SignUpForm.tsx new file mode 100644 index 0000000000..757071dfdf --- /dev/null +++ b/src/pages/bootcamp/components/SignUpForm.tsx @@ -0,0 +1,86 @@ +'use client' + +import {useState} from 'react' +import {Button} from './ui/button' +import {Input} from './ui/input' +import {motion} from 'framer-motion' +import {nanoid} from 'nanoid' +import useCio from '@/hooks/use-cio' + +export default function SignUpForm() { + const [email, setEmail] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + const {cioIdentify} = useCio() + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (isSubmitting) return + + setIsSubmitting(true) + const subscriberId = nanoid() + + try { + console.log('Submitting email:', email) + await cioIdentify(subscriberId, { + email, + created_at: Math.floor(Date.now() / 1000), + source: 'landing_page_waitlist', + }) + + setEmail('') + console.log('Successfully submitted email') + } catch (error) { + console.error('Failed to submit:', error) + } finally { + setIsSubmitting(false) + } + } + + return ( +
+
+ +

+ Ready to Build a Team of AI Devs? +

+

+ Secure your spot in this unique, 20-day cohort-based workshop. + You'll learn alongside a supportive community of developers, + all on the same journey to master AI. Enter your email below and be + the first to know when registration opens. +

+
+
+ setEmail(e.target.value)} + required + disabled={isSubmitting} + className="flex-grow bg-[#0A0A0A] border-gray-800 text-white placeholder:text-gray-500" + /> + +
+
+

+ We'll send you all the details—no spam, just practical info on + how to join. +

+
+
+
+ ) +} diff --git a/src/pages/bootcamp/components/WorkshopStructure.tsx b/src/pages/bootcamp/components/WorkshopStructure.tsx new file mode 100644 index 0000000000..d1ca299857 --- /dev/null +++ b/src/pages/bootcamp/components/WorkshopStructure.tsx @@ -0,0 +1,72 @@ +'use client' +import {motion} from 'framer-motion' +import {fadeInUp, staggerContainer, staggerItem} from './animations' + +const weeks = [ + { + id: 'week-1', + week: 'Week 1 (Days 1–5):', + title: 'Foundations & Simple Chatbot', + description: + 'Set up your project environment and connect to a Large Language Model (LLM). Establish a basic chatbot with short-term memory, demonstrating AI fundamentals.', + }, + { + id: 'week-2', + week: 'Week 2 (Days 6–10):', + title: 'Local Data & Vector Search', + description: + 'Integrate local or enterprise data sources and implement semantic querying. Build a smarter agent that learns from internal documents or databases.', + }, + { + id: 'week-3', + week: 'Week 3 (Days 11–15):', + title: 'Advanced Prompting, Testing & Polish', + description: + "Refine your AI's behavior with sophisticated prompt engineering. Enforce domain-specific rules and build automated tests to maintain output quality.", + }, + { + id: 'week-4', + week: 'Week 4 (Days 16–20):', + title: 'Multi-Agent Systems & Final Project', + description: + 'Develop specialized agents (e.g., HR, Support, Analytics) and deploy a cohesive, production-ready AI solution. Wrap up with a final demo and group presentations.', + }, +] + +export default function WorkshopStructure() { + return ( +
+
+ + Workshop Structure + + + {weeks.map((week) => ( + +
+ {week.week} +
+

+ {week.title} +

+

{week.description}

+
+ ))} +
+
+
+ ) +} diff --git a/src/pages/bootcamp/components/animations.ts b/src/pages/bootcamp/components/animations.ts new file mode 100644 index 0000000000..ca4035c82b --- /dev/null +++ b/src/pages/bootcamp/components/animations.ts @@ -0,0 +1,28 @@ +export const fadeInUp = { + initial: {opacity: 1, y: 20}, + whileInView: {opacity: 1, y: 0}, + viewport: {once: true}, + transition: {duration: 0.5}, +} + +export const staggerContainer = { + hidden: {opacity: 1}, + show: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, +} + +export const staggerItem = { + hidden: {opacity: 1, y: 20}, + show: {opacity: 1, y: 0}, +} + +export const scaleIn = { + initial: {opacity: 1, scale: 0.95}, + whileInView: {opacity: 1, scale: 1}, + viewport: {once: true}, + transition: {duration: 0.5}, +} diff --git a/src/pages/bootcamp/components/ui/button.tsx b/src/pages/bootcamp/components/ui/button.tsx new file mode 100644 index 0000000000..5d26d130b6 --- /dev/null +++ b/src/pages/bootcamp/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' +import {Slot} from '@radix-ui/react-slot' +import {cva, type VariantProps} from 'class-variance-authority' +import {cn} from './utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: + 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: + 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({className, variant, size, asChild = false, ...props}, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + }, +) +Button.displayName = 'Button' + +export {Button, buttonVariants} diff --git a/src/pages/bootcamp/components/ui/input.tsx b/src/pages/bootcamp/components/ui/input.tsx new file mode 100644 index 0000000000..44fe8b3bdf --- /dev/null +++ b/src/pages/bootcamp/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import {cn} from './utils' + +const Input = React.forwardRef>( + ({className, type, ...props}, ref) => { + return ( + + ) + }, +) +Input.displayName = 'Input' + +export {Input} diff --git a/src/pages/bootcamp/components/ui/utils.ts b/src/pages/bootcamp/components/ui/utils.ts new file mode 100644 index 0000000000..8fb87a345b --- /dev/null +++ b/src/pages/bootcamp/components/ui/utils.ts @@ -0,0 +1,6 @@ +import {clsx, type ClassValue} from 'clsx' +import {twMerge} from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/src/pages/bootcamp/index.tsx b/src/pages/bootcamp/index.tsx new file mode 100644 index 0000000000..c5ff3404b4 --- /dev/null +++ b/src/pages/bootcamp/index.tsx @@ -0,0 +1,54 @@ +import Hero from './components/Hero' +import Features from './components/Features' +import WorkshopStructure from './components/WorkshopStructure' +import Instructor from './components/Instructor' +import SignUpForm from './components/SignUpForm' +import {motion} from 'framer-motion' +import type {NextPage} from 'next' + +const BootcampPage: NextPage = () => { + console.log( + '[BootcampPage] Rendering on', + typeof window === 'undefined' ? 'server' : 'client', + ) + + return ( + + {/* Content */} +
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + ) +} + +export default BootcampPage diff --git a/src/pages/bootcamp/styles.css b/src/pages/bootcamp/styles.css new file mode 100644 index 0000000000..c76a6d2e73 --- /dev/null +++ b/src/pages/bootcamp/styles.css @@ -0,0 +1,59 @@ +.bootcamp { + /* Base colors from Tailwind dark mode */ + --accent-1: rgb(17, 24, 39); /* gray-900 */ + --accent-2: rgb(31, 41, 55); /* gray-800 */ + --accent-9: #61DAFB; + --accent-10: #82E6FF; + + /* Gradient backgrounds */ + --gradient-subtle: linear-gradient( + 180deg, + rgb(17, 24, 39, 0.05), + rgb(31, 41, 55, 0.01) + ); +} + +.pattern-dots { + background-image: + linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent), + radial-gradient(rgba(255, 255, 255, 0.15) 2px, transparent 2px); + background-size: 100% 100%, 30px 30px; + background-position: 0 0, 15px 15px; +} + +.pattern-dots-dense { + background-image: radial-gradient(rgba(255, 255, 255, 0.2) 1px, transparent 1px); + background-size: 16px 16px; + background-position: 8px 8px; +} + +.gradient-text { + /* Temporarily remove gradient for testing */ + color: #61DAFB; + font-weight: bold; + /* Add spacing for visibility */ + padding: 0 4px; + /* Debug */ + outline: 1px solid red; + background: none; + -webkit-text-fill-color: initial; +} + +.emphasis-box { + background: var(--gradient-subtle); + border: 1px solid rgb(31, 41, 55, 0.1); /* gray-800 with opacity */ + border-radius: 8px; + backdrop-filter: blur(8px); +} + +.emphasis-text { + @apply text-gray-900 dark:text-white; + font-weight: bold; +} + +/* Add a debug class to test basic visibility */ +.debug-text { + color: red !important; + background: none !important; + -webkit-text-fill-color: red !important; +} \ No newline at end of file diff --git a/src/pages/q/[[...all]].tsx b/src/pages/q/[[...all]].tsx index c05ac603c6..b72c40c44e 100644 --- a/src/pages/q/[[...all]].tsx +++ b/src/pages/q/[[...all]].tsx @@ -170,11 +170,11 @@ SearchIndex.getLayout = (Page: any, pageProps: any) => { export default SearchIndex -export const getServerSideProps: GetServerSideProps = async function ({ +export const getServerSideProps: GetServerSideProps = async ({ req, query, res, -}) { +}) => { setupHttpTracing({name: getServerSideProps.name, tracer, req, res}) res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate') const {all = [], ...rest} = query @@ -185,8 +185,7 @@ export const getServerSideProps: GetServerSideProps = async function ({ const pageTitle = titleFromPath(all as string[]) const path = req.url - console.log('mypath', path) - + // Get server state and sanitize it for serialization const serverState = await getServerState( , { @@ -194,11 +193,17 @@ export const getServerSideProps: GetServerSideProps = async function ({ }, ) - // Maps the InitialResults record to an array and gets the first (and only) result. - // From there you have access to the state and result which matches what we expected before the upgrade to react-instantsearch v7 - const resultsState = Object.keys(serverState.initialResults).map((key) => { - return serverState.initialResults[key] - })[0] + // Sanitize the serverState to remove undefined values + const sanitizedServerState = JSON.parse( + JSON.stringify(serverState, (_, value) => + value === undefined ? null : value, + ), + ) + + // Rest of the code remains the same... + const resultsState = Object.keys(sanitizedServerState.initialResults).map( + (key) => sanitizedServerState.initialResults[key], + )[0] const {results, state} = resultsState @@ -245,7 +250,7 @@ export const getServerSideProps: GetServerSideProps = async function ({ props: { initialSearchState, path, - serverState, + serverState: sanitizedServerState, // Use sanitized version pageTitle, noIndexInitial, initialInstructor,