diff --git a/apps/epic-react/package.json b/apps/epic-react/package.json index 38fa3bab6..16ad7abf5 100644 --- a/apps/epic-react/package.json +++ b/apps/epic-react/package.json @@ -78,6 +78,7 @@ "feed": "^4.2.2", "formik": "2.2.9", "framer-motion": "^10.12.4", + "get-user-locale": "^2.3.1", "gray-matter": "^4.0.3", "groq": "^2.15.0", "inngest": "3.1.1", diff --git a/apps/epic-react/public/assets/satellite.png b/apps/epic-react/public/assets/satellite.png new file mode 100644 index 000000000..c4a5fb291 Binary files /dev/null and b/apps/epic-react/public/assets/satellite.png differ diff --git a/apps/epic-react/public/og-images/credits@2x.png b/apps/epic-react/public/og-images/credits@2x.png new file mode 100644 index 000000000..73ab4c9ae Binary files /dev/null and b/apps/epic-react/public/og-images/credits@2x.png differ diff --git a/apps/epic-react/public/og-images/livestreams@2x.png b/apps/epic-react/public/og-images/livestreams@2x.png new file mode 100644 index 000000000..b34eb2389 Binary files /dev/null and b/apps/epic-react/public/og-images/livestreams@2x.png differ diff --git a/apps/epic-react/public/og-images/og-image@2x.jpg b/apps/epic-react/public/og-images/og-image@2x.jpg new file mode 100644 index 000000000..8a487c974 Binary files /dev/null and b/apps/epic-react/public/og-images/og-image@2x.jpg differ diff --git a/apps/epic-react/schemas/documents/interview.tsx b/apps/epic-react/schemas/documents/interview.tsx index 4d272153a..c8de983d5 100644 --- a/apps/epic-react/schemas/documents/interview.tsx +++ b/apps/epic-react/schemas/documents/interview.tsx @@ -1,25 +1,26 @@ import * as React from 'react' import {MdPeople} from 'react-icons/md' +import {defineArrayMember, defineField, defineType} from 'sanity' -export default { +export default defineType({ name: 'interview', title: 'Interview', type: 'document', icon: MdPeople, fields: [ - { + defineField({ name: 'title', title: 'Title', type: 'string', validation: (Rule) => Rule.required(), - }, - { + }), + defineField({ name: 'description', title: 'Description', type: 'markdown', - validation: (Rule) => Rule.required(), - }, - { + validation: (Rule) => Rule.max(160), + }), + defineField({ name: 'slug', title: 'Slug', type: 'slug', @@ -28,14 +29,14 @@ export default { source: 'title', maxLength: 96, }, - }, - { - name: 'isMultiple', - title: 'Is it multiple interview?', - type: 'boolean', - initialValue: false, - }, - { + }), + defineField({ + name: 'body', + description: 'Body in MDX', + title: 'Body', + type: 'markdown', + }), + defineField({ name: 'portraits', title: 'Portraits', type: 'object', @@ -46,28 +47,20 @@ export default { type: 'externalImage', validation: (Rule) => Rule.required(), }, - { - name: 'image2', - title: 'Image 2', - type: 'externalImage', - hidden: ({document}) => { - return !document.isMultiple - }, - }, ], - }, - { + }), + defineField({ name: 'resources', title: 'Resources', type: 'array', of: [ - { + defineArrayMember({ title: 'Video Resource', type: 'reference', to: [{type: 'videoResource'}], - }, + }), ], - }, + }), ], preview: { select: { @@ -82,4 +75,4 @@ export default { } }, }, -} +}) diff --git a/apps/epic-react/src/components/app/layout.tsx b/apps/epic-react/src/components/app/layout.tsx index 675a00698..71b9f4f71 100644 --- a/apps/epic-react/src/components/app/layout.tsx +++ b/apps/epic-react/src/components/app/layout.tsx @@ -61,7 +61,7 @@ const Layout: React.FC> = ({ description={description} titleTemplate={ titleAppendSiteName - ? `%s | ${process.env.NEXT_PUBLIC_SITE_TITLE}` + ? `%s | ${process.env.NEXT_PUBLIC_SITE_TITLE} by ${process.env.NEXT_PUBLIC_PARTNER_FIRST_NAME} ${process.env.NEXT_PUBLIC_PARTNER_LAST_NAME}` : undefined } openGraph={{ diff --git a/apps/epic-react/src/components/livestream.tsx b/apps/epic-react/src/components/livestream.tsx new file mode 100644 index 000000000..8b90f1ccf --- /dev/null +++ b/apps/epic-react/src/components/livestream.tsx @@ -0,0 +1,91 @@ +import React from 'react' +import getUserLocale from 'get-user-locale' +import {formatDistance, isFuture} from 'date-fns' + +const Livestream = ({ + title, + startDatetime, + description, + livestreamUrl, + children, + calendarUrl, +}: any) => { + const userLocale = getUserLocale + const milliseconds = startDatetime + const isFutureLivestream = isFuture(startDatetime) + const date = new Date(milliseconds) + const dateOptions = { + timeZoneName: 'short', + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + } + + const url = `${livestreamUrl}&utm_source=${process.env.SITE_NAME}` + + return ( +
+ {isFutureLivestream && ( + + ● Live + + )} + +

{title}

+
+ + {description && ( +

{description}

+ )} + {children} + {isFutureLivestream && calendarUrl ? ( + + {/* prettier-ignore */} + + Remind Me + + ) : ( + + {/* prettier-ignore */} + + Watch Recording + + )} + {isFutureLivestream && ( + + {/* prettier-ignore */} + + Open on Youtube + + )} +
+ ) +} + +export default Livestream diff --git a/apps/epic-react/src/components/mdx-components/podcast-transcript/index.tsx b/apps/epic-react/src/components/mdx-components/podcast-transcript/index.tsx index 5eac051f1..5fe05cecd 100644 --- a/apps/epic-react/src/components/mdx-components/podcast-transcript/index.tsx +++ b/apps/epic-react/src/components/mdx-components/podcast-transcript/index.tsx @@ -61,7 +61,7 @@ const PodcastTranscript = ({children}: {children: React.ReactNode}) => { diff --git a/apps/epic-react/src/components/pricing-section.tsx b/apps/epic-react/src/components/pricing-section.tsx index 55e2de192..e34728b1c 100644 --- a/apps/epic-react/src/components/pricing-section.tsx +++ b/apps/epic-react/src/components/pricing-section.tsx @@ -62,8 +62,7 @@ const PricingSection: React.FC<{ purchases={purchases} couponIdFromCoupon={couponIdFromCoupon} couponFromCode={couponFromCode} - allowPurchase={true} - // allowPurchase={allowPurchase} + allowPurchase={allowPurchase} /> diff --git a/apps/epic-react/src/content/livestreams.ts b/apps/epic-react/src/content/livestreams.ts new file mode 100644 index 000000000..83d7560af --- /dev/null +++ b/apps/epic-react/src/content/livestreams.ts @@ -0,0 +1,138 @@ +export default [ + { + title: 'React Patterns', + description: + 'Why React Patterns are so critical to your success as an abstraction author.', + startDatetime: 1600707600000, // timestamp in milliseconds (https://www.epochconverter.com/) + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MmRzYzBhOWxsNjA0Mm1kY2VwaGJoYzJnY3AgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=WV0UUcSPk-0', + }, + { + title: 'Never Use Shallow Rendering', + description: + 'Tests should help me be confident that my application is working and tests that use shallow rendering are tests that need to be baby-sat.', + startDatetime: 1600794000000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MGpwbjE4MzlrajI0NWIybGxrdWVpb2llYTQgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=zkpwdw_JWBg', + }, + { + title: 'AMA about React & Epic React', + startDatetime: 1600880400000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=NXIzZG40YzlkNGxsM2tuOW1hZ25zMDJiMmEgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=gl4X59r6FnE', + }, + { + title: 'Tour of EpicReact.Dev', + startDatetime: 1601053200000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=M3EzaGtqaXBwY29rbzNlY2w0M2lja3QyYm0gOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=U-DFEZXbnkE', + }, + { + title: 'EpicReact.Dev Q&A', + startDatetime: 1601312400000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=NHBrazYwdGZqMGtmbThucnBjbnYwZmcya2MgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=3pVB-tzac5o', + }, + { + title: 'EpicReact is LIVE! What EpicReact.Dev is all about', + startDatetime: 1601398800000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MmluaDZwZWs3NWRpdmFta2FpaDY3NGRndW8gOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=Lr1fE1oLWjM', + }, + { + title: 'EpicReact is LIVE! What EpicReact.Dev is all about', + startDatetime: 1601485200000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MGpvcDZhbGdpcGY3bm5oZWlpNmtmaGgxYnQgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=YYAb-t5Pvck', + }, + { + title: 'EpicReact.Dev Review of React Fundamentals', + description: `Learn everything you need to be effective with the fundamental building block of React applications. When you’re finished, you’ll be prepared to create React components to build excellent experiences for your app's users. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1601571600000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MTgzMTY0ZzJqNXZhOW1xaGtndHMyMjM0ZzYgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=qmSXWPsonfg', + }, + { + title: 'EpicReact.Dev Review of React Hooks', + description: `Learn the ins and outs of React Hooks. I will take you on a deep dive into React Hooks, and show you what you need to know to start using them in your applications right away. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1601658000000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=NXVuOHBldGhqYjNocmFiYnBicTU2YWgzNXAgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=qyuwWlhyBqc', + }, + { + title: 'EpicReact.Dev Review of Advanced React Hooks', + description: `We’ll look at some of the more advanced hooks and ways they can be used to optimize your components and custom hooks. We’ll also look at several patterns you can follow to make custom hooks that provide great APIs for developers to be productive building applications. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1601917200000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=N2E2c2UxMGk4ajgwZ2RycTJtOG12MTJnamQgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=y_6tdBzljBY', + }, + { + title: + 'Livestream with Kent: EpicReact.Dev Review of Advanced React Patterns', + description: `Not only learn great patterns you can use but also the strengths and weaknesses of each, so you know which to reach for to provide your custom hooks and components the flexibility and power you need. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1602003600000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=NDk4c2VpZzB0aGxiMmhrbThwN2lwaGluZDUgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=rEMtM5vsWE8', + }, + { + title: 'EpicReact.Dev Review of React Performance', + description: `Learn everything you need to diagnose, profile, and fix performance problems in your React application using the Browser Performance Profiler, React DevTools Profiler, and proven React optimization techniques. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1602090000000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=Mzc0dTltMTFtdXNwcnRuc2Y3azZmYWN1aDggOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=aXVpX7eRRd8', + }, + { + title: 'EpicReact.Dev Review of Testing React Apps', + description: `In this hands-on workshop you'll learn everything you need to test React components and applications with ease and get the knowledge you need to ship your applications with confidence. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1602176400000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MmtsMWdodjdxNjZsY2xuMWJob2I2ZzV0ZmIgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=rJr3EZXf2ms', + }, + { + title: 'EpicReact.Dev Review of React Suspense', + description: `Learn how Suspense works under the hood, preparing you for the future of asynchronous state management. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1602262800000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MnRib2xjbGxoM3NhM2U5aDllYmIyM3NsdTAgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=39K7X_6mR8M', + }, + { + title: + 'Livestream with Kent: EpicReact.Dev Review of Build an Epic React App', + description: `The React and JavaScript ecosystem is full of tools and libraries to help you build your applications. In this (huge) workshop we’ll build an application from scratch using widely supported and proven tools and techniques. We’ll cover everything about building frontend React applications, from the absolute basics to the tricky parts you'll run into building real world React apps and how to create great abstractions. We'll review the material for the workshop and answer any questions you have about it.`, + startDatetime: 1602522000000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=NGwxYTFjYXVyZDBmMnBhbXBqbDdqa2h2ODkgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=W4JWlf5q4Xc', + }, + { + title: + 'Livestream with Kent: EpicReact.Dev Review of Epic React Expert Interviews', + description: `Kent Interviews experts on various topics. We'll go over what topics are covered in the interviews as well as the interviewees themselves.`, + startDatetime: 1602608400000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MmJ2c2s1ZXBrYnA0YWxxZHZwa21pYW1hcmEgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=QYGLUrDBglE', + }, + { + title: 'EpicReact.Dev Final Q&A', + description: `The EpicReact.Dev launch sale is almost over! Join us and get your last minute questions answered.`, + startDatetime: 1602622800000, + calendarUrl: + 'https://calendar.google.com/event?action=TEMPLATE&tmeid=MXRybDk4dGtnNTJmOWhmOTU4Mm1va2ZqaXEgOHM5ZHM1YmpyZzNhdHBoZW1nb2ozcnI5dTBAZw&tmsrc=8s9ds5bjrg3atphemgoj3rr9u0%40group.calendar.google.com', + livestreamUrl: 'https://www.youtube.com/watch?v=nZTVrhdkJBE', + }, +] diff --git a/apps/epic-react/src/lib/bonuses.ts b/apps/epic-react/src/lib/bonuses.ts index 547c1e3d5..390b8ccd1 100644 --- a/apps/epic-react/src/lib/bonuses.ts +++ b/apps/epic-react/src/lib/bonuses.ts @@ -4,10 +4,10 @@ import {z} from 'zod' const ResourceSchema = z.object({ _id: z.string(), - _type: z.string(), + _type: z.literal('interview'), _updatedAt: z.string(), title: z.string(), - description: z.string(), + description: z.string().nullable(), slug: z.string(), }) @@ -15,7 +15,7 @@ export const BonusSchema = z.object({ _id: z.string(), _type: z.string(), title: z.string(), - state: z.string(), + state: z.union([z.literal('published'), z.literal('draft')]), slug: z.object({current: z.string()}), moduleType: z.string(), description: z.string(), @@ -23,6 +23,7 @@ export const BonusSchema = z.object({ _updatedAt: z.string(), resources: z.array(ResourceSchema), image: z.string(), + body: z.string().nullable(), }) const bonusesForProductQuery = groq`*[_type == "product" && productId == $productId][0]{ @@ -36,6 +37,7 @@ const bonusesForProductQuery = groq`*[_type == "product" && productId == $produc description, _createdAt, _updatedAt, + body, "resources": resources[@->._type in ["interview"]]->{ _id, _type, @@ -69,6 +71,7 @@ const bonusesQuery = groq`*[_type == "module" && moduleType == 'bonus'] | order( _createdAt, description, state, + body, "lessons": resources[@->._type in ['interview']]->{ _id, _type, @@ -96,6 +99,7 @@ export const getBonus = async (slug: string) => ogImage, description, _updatedAt, + body, "lessons": resources[@->._type in ['interview']]->{ _id, _type, @@ -128,3 +132,5 @@ export const getBonus = async (slug: string) => }`, {slug: `${slug}`}, ) + +export type Bonus = z.infer diff --git a/apps/epic-react/src/lib/workshops.ts b/apps/epic-react/src/lib/workshops.ts index 8d5793f1a..90710dfe9 100644 --- a/apps/epic-react/src/lib/workshops.ts +++ b/apps/epic-react/src/lib/workshops.ts @@ -62,6 +62,7 @@ const workshopsQuery = groq`*[_type == "module" && moduleType == 'workshop'] | o _createdAt, description, state, + body, 'product': *[_type=='product' && references(^._id)][]{ "slug": slug.current, state, @@ -118,7 +119,8 @@ export const WorkshopSchema = z.object({ _updatedAt: z.string(), _createdAt: z.string(), description: z.string().nullable(), - state: z.string(), // TODO: make this a union/literal of the 3 states + body: z.string().nullable(), + state: z.union([z.literal('published'), z.literal('draft')]), resources: z.array( z.object({ _id: z.string(), diff --git a/apps/epic-react/src/pages/learn.tsx b/apps/epic-react/src/pages/learn.tsx index c674f0750..f6ee11230 100644 --- a/apps/epic-react/src/pages/learn.tsx +++ b/apps/epic-react/src/pages/learn.tsx @@ -2,13 +2,15 @@ import * as React from 'react' import {GetServerSideProps} from 'next' import Image from 'next/image' import Link from 'next/link' +import cx from 'classnames' +import {twMerge} from 'tailwind-merge' import { ModuleProgressProvider, useModuleProgress, } from '@skillrecordings/skill-lesson/video/module-progress' -import {getWorkshopsForProduct, WorkshopSchema, Workshop} from '@/lib/workshops' -import {BonusSchema, getBonusesForProduct} from '@/lib/bonuses' +import {getWorkshopsForProduct, Workshop, WorkshopSchema} from '@/lib/workshops' +import {Bonus, BonusSchema, getBonusesForProduct} from '@/lib/bonuses' import {getOgImage} from '@/utils/get-og-image' import Layout from '@/components/app/layout' import Footer from '@/components/app/footer' @@ -30,36 +32,49 @@ const ResourceLink: React.FC<{ resourceSlug: string isCompleted: boolean }> = ({title, workshopSlug, resourceSlug, isCompleted}) => { + const [isHovered, setHovered] = React.useState(false) return ( - - {isCompleted && '✅'} - {title} - +
  • + setHovered(true)} + onMouseOut={() => setHovered(false)} + href={`/workshops/${workshopSlug}/${resourceSlug}`} + className="-mx-3 flex w-full items-center rounded-lg p-3 transition-colors duration-75 ease-in-out hover:bg-er-gray-100" + > + {/* {isCompleted && '✅'} + {title} */} +
    + {isCompleted ? ( + isHovered ? ( + /* prettier-ignore */ + + ) : ( + /* prettier-ignore */ + + ) + ) : ( + /* prettier-ignore */ + + )} +
    +

    + {workshopSlug === 'welcome-to-epic-react' && + resourceSlug === 'welcome-to-epic-react' && ( + + Start here + + )} + {title} +

    + +
  • ) } -// const isLessonCompleted = ( -// resourceId: string, -// lessons: { -// id: string -// lessonCompleted: boolean -// }[], -// ) => { -// const lesson = lessons.find((lesson) => lesson.id === resourceId) -// return lesson ? lesson.lessonCompleted : false -// } - -// const isSectionCompleted = ( -// resourceId: string, -// sections: { -// id: string -// sectionCompleted: boolean -// }[], -// ) => { -// const section = sections.find((section) => section.id === resourceId) -// return section ? section.sectionCompleted : false -// } - const isResourceCompleted = ( resourceId: string, resourceType: 'lesson' | 'section', @@ -77,27 +92,63 @@ const isResourceCompleted = ( : false } -const WorkshopItem = ({workshop}: {workshop: Workshop}) => { +type WorkshopResource = Workshop['resources'][0] +type BonusResource = Bonus['resources'][0] + +type Module = { + _type: string + title: string + body: string | null + slug: { + current: string + } + resources: Array +} + +const WorkshopItem = ({module}: {module: Module}) => { + const isBonusModule = module._type === 'bonus' + const moduleProgress = useModuleProgress() return ( -
    -

    {workshop.title}

    -
      - {workshop.resources.map((resource) => { - if (resource._type === 'explainer') { - const isCompleted = - (moduleProgress && +
      +
      +

      + + {module.title} + +

      +
      + {module.body} +
      +
      +
        + {module.resources.map((resource) => { + if ( + resource._type === 'explainer' || + resource._type === 'interview' + ) { + const isCompleted = Boolean( + moduleProgress && isResourceCompleted( resource._id, 'lesson', moduleProgress?.lessons, - )) || - false + ), + ) return ( @@ -105,19 +156,19 @@ const WorkshopItem = ({workshop}: {workshop: Workshop}) => { } if (resource._type === 'section' && resource?.resources) { - const isCompleted = - (moduleProgress && + const isCompleted = Boolean( + moduleProgress && isResourceCompleted( resource._id, 'section', moduleProgress?.sections, - )) || - false + ), + ) return ( @@ -197,53 +248,41 @@ const Learn: React.FC<{workshops: any[]; bonuses: any[]}> = ({ priority /> -
        -

        Learn Page

        -
          +
          +
            {workshops.map((workshop) => { return ( -
          • -
            +
          • +
            - +
          • ) })} {bonuses.map((bonus) => { return ( -
          • -
            - -
            -
            -

            {bonus.title}

            -
              - {bonus.resources.map((resource) => { - // TODO: is `/workshops/...` the right path prefix for interviews? - return ( - - {resource.title} - - ) - })} -
            -
            -
          • + +
          • +
            + +
            + +
          • +
            ) })}
          diff --git a/apps/epic-react/src/pages/livestreams.tsx b/apps/epic-react/src/pages/livestreams.tsx new file mode 100644 index 000000000..e9aeb9c06 --- /dev/null +++ b/apps/epic-react/src/pages/livestreams.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' +import Image from 'next/image' +import Link from 'next/link' + +import Layout from '@/components/app/layout' +import Divider from '@/components/divider' +import Livestream from '@/components/livestream' +import livestreams from '@/content/livestreams' + +const LivestreamsPage = () => { + const [hasMounted, setMounted] = React.useState(false) + const title = 'Livestreams Schedule' + React.useEffect(() => { + setMounted(true) + }, []) + return hasMounted ? ( + +
          +
          + livestreams schedule +
          +

          + Epic Livestreams +

          + +
          +

          + ↓ Past +

          + {livestreams.map((livestream) => { + return + })} +
          +
          +
          + ) : null +} + +export default LivestreamsPage diff --git a/apps/epic-react/src/pages/workshops/[module]/[lesson]/index.tsx b/apps/epic-react/src/pages/workshops/[module]/[lesson]/index.tsx index 8c650a726..8677a69a1 100644 --- a/apps/epic-react/src/pages/workshops/[module]/[lesson]/index.tsx +++ b/apps/epic-react/src/pages/workshops/[module]/[lesson]/index.tsx @@ -8,14 +8,19 @@ import {ModuleProgressProvider} from '@skillrecordings/skill-lesson/video/module import {getSection} from '@/lib/sections' import serializeMDX from '@skillrecordings/skill-lesson/markdown/serialize-mdx' import {getAllWorkshops, getWorkshop} from '@/lib/workshops' +import {getAllBonuses, getBonus} from '@/lib/bonuses' import {serialize} from 'next-mdx-remote/serialize' export const getStaticProps: GetStaticProps = async (context) => { const {params} = context const lessonSlug = params?.lesson as string const sectionSlug = params?.section as string + const moduleSlug = params?.module as string + const isBonusModule = moduleSlug === 'epic-react-expert-interviews' - const module = await getWorkshop(params?.module as string) + const module = isBonusModule + ? await getBonus(moduleSlug) + : await getWorkshop(moduleSlug) const moduleWithSectionsAndLessons = { ...module, @@ -48,8 +53,9 @@ export const getStaticProps: GetStaticProps = async (context) => { export const getStaticPaths: GetStaticPaths = async (context) => { const tutorials = await getAllWorkshops() + const bonuses = await getAllBonuses() - const paths = tutorials.flatMap((tutorial: any) => { + const paths = [...tutorials, ...bonuses].flatMap((tutorial: any) => { return ( tutorial.sections?.flatMap((section: any) => { return ( diff --git a/apps/epic-react/src/styles/commerce.css b/apps/epic-react/src/styles/commerce.css index 704ec5ae0..7a19248b5 100644 --- a/apps/epic-react/src/styles/commerce.css +++ b/apps/epic-react/src/styles/commerce.css @@ -30,7 +30,7 @@ [data-pricing-footer] { @apply rounded-b-lg border-t-2 border-er-gray-100 px-5 pb-8 pt-5 sm:px-8 sm:py-7; [data-product-description] { - @apply max-w-none px-0 pb-6 pt-2 text-base; + @apply max-w-none px-0 pb-6 pt-2 text-lg; p { @apply text-text; } @@ -58,11 +58,26 @@ } } [data-bonuses] { - @apply mb-8; + @apply mb-10; + strong { + @apply mb-2; + } + } + [data-features] { + strong { + @apply mb-3 block; + } + ul { + @apply space-y-4 text-lg; + li { + @apply flex before:mr-3 before:text-yellow-400 before:content-['✓']; + } + } } [data-workshops] { + @apply mb-10; strong { - @apply mb-3; + @apply mb-3 block; } } [data-workshops], @@ -152,8 +167,16 @@ [data-title] { @apply text-center text-2xl font-semibold leading-9 text-er-gray-900 sm:-mx-6 sm:text-4xl; } + [data-features] { + strong { + @apply text-xl; + } + } [data-workshops], [data-bonuses] { + strong { + @apply text-xl; + } li { @apply space-x-6; p { diff --git a/apps/epic-react/src/templates/podcast-template.tsx b/apps/epic-react/src/templates/podcast-template.tsx index f73e41e4d..e69a422af 100644 --- a/apps/epic-react/src/templates/podcast-template.tsx +++ b/apps/epic-react/src/templates/podcast-template.tsx @@ -57,7 +57,7 @@ const PodcastTemplate: React.FC = ({
          = ({ Tweet