From d56f473c2aeae3eb1162751848e943d03057e6b7 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Tue, 26 Mar 2024 08:25:55 -0500 Subject: [PATCH 01/17] feat: leave as string to avoid `as number` --- .../src/forms/subscribe-to-convertkit/index.tsx | 2 +- packages/convertkit-react-ui/src/hooks/use-convertkit-form.ts | 4 ++-- .../skill-lesson/convertkit/convertkit-subscribe-form.tsx | 2 +- packages/skill-lesson/hooks/use-convertkit-form.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/convertkit-react-ui/src/forms/subscribe-to-convertkit/index.tsx b/packages/convertkit-react-ui/src/forms/subscribe-to-convertkit/index.tsx index 3b6576efb..7b102cd72 100644 --- a/packages/convertkit-react-ui/src/forms/subscribe-to-convertkit/index.tsx +++ b/packages/convertkit-react-ui/src/forms/subscribe-to-convertkit/index.tsx @@ -10,7 +10,7 @@ export type SubscribeFormProps = { submitButtonElem?: React.ReactElement onError?: (error?: any) => void onSuccess?: (subscriber?: ConvertkitSubscriber, email?: string) => void - formId?: number + formId?: string subscribeApiURL?: string id?: string fields?: Record diff --git a/packages/convertkit-react-ui/src/hooks/use-convertkit-form.ts b/packages/convertkit-react-ui/src/hooks/use-convertkit-form.ts index c085cc03c..34164068d 100644 --- a/packages/convertkit-react-ui/src/hooks/use-convertkit-form.ts +++ b/packages/convertkit-react-ui/src/hooks/use-convertkit-form.ts @@ -12,13 +12,13 @@ import { **/ export function useConvertkitForm({ submitUrl = CONVERTKIT_SUBSCRIBE_API_URL, - formId = (CONVERTKIT_SIGNUP_FORM || 0) as number, + formId = CONVERTKIT_SIGNUP_FORM || '0', fields, onSuccess, onError, }: { submitUrl?: string - formId?: number + formId?: string onSuccess: (subscriber: ConvertkitSubscriber, email?: string) => void onError: (error?: any) => void fields?: any diff --git a/packages/skill-lesson/convertkit/convertkit-subscribe-form.tsx b/packages/skill-lesson/convertkit/convertkit-subscribe-form.tsx index 32c5ed04d..242abcf80 100644 --- a/packages/skill-lesson/convertkit/convertkit-subscribe-form.tsx +++ b/packages/skill-lesson/convertkit/convertkit-subscribe-form.tsx @@ -14,7 +14,7 @@ export type SubscribeFormProps = { submitButtonElem?: React.ReactElement onError?: (error?: any) => void onSuccess?: (subscriber?: Subscriber, email?: string) => void - formId?: number + formId?: string subscribeApiURL?: string id?: string fields?: Record diff --git a/packages/skill-lesson/hooks/use-convertkit-form.ts b/packages/skill-lesson/hooks/use-convertkit-form.ts index f9fa070e5..94d1651dd 100644 --- a/packages/skill-lesson/hooks/use-convertkit-form.ts +++ b/packages/skill-lesson/hooks/use-convertkit-form.ts @@ -9,7 +9,7 @@ import { export function useConvertkitForm({ submitUrl = CONVERTKIT_SUBSCRIBE_API_URL, - formId = (CONVERTKIT_SIGNUP_FORM || 0) as number, + formId = CONVERTKIT_SIGNUP_FORM || '0', fields, onSuccess, onError, @@ -17,7 +17,7 @@ export function useConvertkitForm({ validateOnChange = false, }: { submitUrl?: string - formId?: number + formId?: string onSuccess: (subscriber: Subscriber, email?: string) => void onError: (error?: any) => void fields?: any From 100a3c47baad631e33eeed8575077eed593e7c30 Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Wed, 27 Mar 2024 10:05:42 +0100 Subject: [PATCH 02/17] feat(tt): add FeedbackFormButton MDX component for articles --- .../src/components/mdx/index.tsx | 31 +++++++++++++++++++ .../src/styles/newsletter.css | 2 +- .../src/templates/article-template.tsx | 9 +++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/total-typescript/src/components/mdx/index.tsx b/apps/total-typescript/src/components/mdx/index.tsx index a4e10bafd..4651ad2a9 100644 --- a/apps/total-typescript/src/components/mdx/index.tsx +++ b/apps/total-typescript/src/components/mdx/index.tsx @@ -11,6 +11,9 @@ import {useCopyToClipboard} from 'react-use' import Balancer from 'react-wrap-balancer' import {twMerge} from 'tailwind-merge' import {MDXEditor, MDXTranspilePreview} from '../code-editor/mdx-editor' +import {useFeedback} from '@/feedback-widget/feedback-context' +import {cn} from '@skillrecordings/ui/utils/cn' +import {ChatAltIcon} from '@heroicons/react/outline' export const MDXComponents = { TypeError: (props) => , @@ -326,3 +329,31 @@ export const linkedHeadingComponents: MDXComponentsType = { h3: (props) => , h4: (props) => , } + +export const FeedbackFormButton: React.FC< + React.ButtonHTMLAttributes +> = (props) => { + const {setIsFeedbackDialogOpen} = useFeedback() + return ( + + ) +} diff --git a/apps/total-typescript/src/styles/newsletter.css b/apps/total-typescript/src/styles/newsletter.css index 94641ce2d..cf881cd6b 100644 --- a/apps/total-typescript/src/styles/newsletter.css +++ b/apps/total-typescript/src/styles/newsletter.css @@ -107,7 +107,7 @@ @apply inline-block text-gray-400 decoration-transparent; } [data-sr-button] { - @apply relative mt-5 flex flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-t from-cyan-500 to-cyan-300 px-5 py-2 text-lg font-semibold text-black shadow-2xl shadow-black/80 transition hover:brightness-125 focus-visible:ring-white md:mt-0; + @apply relative mt-5 flex flex-shrink-0 items-center justify-center rounded bg-primary px-5 py-2 text-lg font-semibold text-black text-primary-foreground shadow-2xl shadow-black/80 transition hover:brightness-125 focus-visible:ring-white md:mt-0; svg { @apply h-7 w-7; } diff --git a/apps/total-typescript/src/templates/article-template.tsx b/apps/total-typescript/src/templates/article-template.tsx index a91cb9f34..065661d52 100644 --- a/apps/total-typescript/src/templates/article-template.tsx +++ b/apps/total-typescript/src/templates/article-template.tsx @@ -12,7 +12,11 @@ import {type MDXRemoteSerializeResult} from 'next-mdx-remote' import MDX from '@skillrecordings/skill-lesson/markdown/mdx' import removeMarkdown from 'remove-markdown' import '@/styles/shiki-twoslash.css' -import {linkedHeadingComponents, ShareImageMDX} from '@/components/mdx' +import { + FeedbackFormButton, + linkedHeadingComponents, + ShareImageMDX, +} from '@/components/mdx' import {cn} from '@skillrecordings/ui/utils/cn' import {trpc} from '@/trpc/trpc.client' import Link from 'next/link' @@ -190,6 +194,9 @@ const ArticleTemplate: React.FC = ({ ), ...linkedHeadingComponents, hr: () =>
, + FeedbackFormButton: (props) => ( + + ), }} /> )} From a01a5cd77d73242932b0c040592d45c5ee6c644f Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Wed, 27 Mar 2024 10:49:36 +0000 Subject: [PATCH 03/17] Fixed bug where exercises in folders did not trigger a start command --- .../src/exercise/stackblitz-iframe.test.ts | 10 ++++++++++ .../src/exercise/stackblitz-iframe.tsx | 19 +++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/apps/total-typescript/src/exercise/stackblitz-iframe.test.ts b/apps/total-typescript/src/exercise/stackblitz-iframe.test.ts index e1b8bb36f..b4927dfb2 100644 --- a/apps/total-typescript/src/exercise/stackblitz-iframe.test.ts +++ b/apps/total-typescript/src/exercise/stackblitz-iframe.test.ts @@ -24,6 +24,16 @@ describe('getStartCommand', () => { 'src/02-unions-and-indexing/07.1-terminology.solution.2.ts', 's-07.1', ], + [ + 'exercise', + 'src/009-cannot-redeclare-block-scoped-variable.problem/index.ts', + 'e-009', + ], + [ + 'exercise', + 'src/04-example/004-another-example.problem/index.ts', + 'e-004', + ], ])( `Should calculate the correct start command`, (_type, stackblitz, result) => { diff --git a/apps/total-typescript/src/exercise/stackblitz-iframe.tsx b/apps/total-typescript/src/exercise/stackblitz-iframe.tsx index 4ac42108b..098bf0563 100644 --- a/apps/total-typescript/src/exercise/stackblitz-iframe.tsx +++ b/apps/total-typescript/src/exercise/stackblitz-iframe.tsx @@ -53,6 +53,17 @@ export const StackBlitzIframe: React.FC<{ ) } +const findLastPathPartStartingWithANumber = (path: string) => { + const split = path.split('/') + + return split.findLast((part) => { + const firstChar = part[0] + return !isNaN(Number(firstChar)) + }) +} + +const DEFAULT = 'e-01' + // Figures out start command: e.g. s-01, e-02, etc export const getStartCommand = ( exercise: {_type: string}, @@ -60,13 +71,13 @@ export const getStartCommand = ( ) => { // Reasonably sensbile fallback, not sure // what we should do when Stacblitz is not defined - if (!stackblitz) return 'e-01' + if (!stackblitz) return DEFAULT - const stackblitzSplit = stackblitz.split('/') + const pathPart = findLastPathPartStartingWithANumber(stackblitz) - const lastPathPart = stackblitzSplit[stackblitzSplit.length - 1] + if (!pathPart) return DEFAULT - const codeFileNumber = lastPathPart.split('-')[0] + const codeFileNumber = pathPart.split('-')[0] const startCommand = `${exercise._type.substring(0, 1)}-${codeFileNumber}` From 4443ef64c59b53ef3bf78d80eb43d85e85ec64b8 Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Wed, 27 Mar 2024 15:04:56 +0100 Subject: [PATCH 04/17] refactor(ewd): authors are contributors with a role (#1475) * refactor(ewd): article authors are contributors with author role * refactor(ewd): event hosts * refactor(ewd): talk presenters * refactor(ewd): workshop instructors * refactor(ewd): tip instructors --- apps/epic-web/schemas/documents/article.tsx | 5 ++ apps/epic-web/schemas/documents/author.ts | 2 +- .../schemas/documents/contributor.tsx | 78 ++++++++++++++++++ apps/epic-web/schemas/documents/event.tsx | 5 ++ apps/epic-web/schemas/documents/module.tsx | 5 ++ apps/epic-web/schemas/documents/talk.ts | 5 ++ apps/epic-web/schemas/documents/tip.ts | 5 ++ apps/epic-web/schemas/index.ts | 4 + .../epic-web/schemas/objects/contributors.tsx | 54 +++++++++++++ apps/epic-web/src/lib/articles.ts | 57 +++++++------ apps/epic-web/src/lib/contributors.ts | 11 +++ apps/epic-web/src/lib/events.ts | 42 ++-------- apps/epic-web/src/lib/talks.ts | 80 +++++++++++++------ apps/epic-web/src/lib/tips.ts | 43 ++++++---- apps/epic-web/src/lib/tutorials.ts | 37 ++++++--- apps/epic-web/src/lib/workshops.ts | 32 ++++++++ apps/epic-web/src/pages/articles.tsx | 1 + apps/epic-web/src/pages/talks/index.tsx | 2 +- apps/epic-web/src/pages/tips/index.tsx | 2 +- apps/epic-web/src/pages/tutorials/index.tsx | 2 +- apps/epic-web/src/templates/talk-template.tsx | 8 +- apps/epic-web/src/templates/tip-template.tsx | 4 +- .../src/templates/tutorial-template.tsx | 2 +- 23 files changed, 363 insertions(+), 123 deletions(-) create mode 100644 apps/epic-web/schemas/documents/contributor.tsx create mode 100644 apps/epic-web/schemas/objects/contributors.tsx diff --git a/apps/epic-web/schemas/documents/article.tsx b/apps/epic-web/schemas/documents/article.tsx index 84016f0c8..e7acb0969 100644 --- a/apps/epic-web/schemas/documents/article.tsx +++ b/apps/epic-web/schemas/documents/article.tsx @@ -19,6 +19,11 @@ export default defineType({ type: 'reference', to: {type: 'author'}, }), + defineField({ + name: 'contributors', + type: 'contributors', + title: 'Contributors', + }), defineField({ name: 'slug', title: 'Slug', diff --git a/apps/epic-web/schemas/documents/author.ts b/apps/epic-web/schemas/documents/author.ts index 57ac84455..37d395b31 100644 --- a/apps/epic-web/schemas/documents/author.ts +++ b/apps/epic-web/schemas/documents/author.ts @@ -3,7 +3,7 @@ import {defineField, defineType} from 'sanity' export default defineType({ name: 'author', - title: 'Contributor', + title: 'Author', icon: UserIcon, type: 'document', fields: [ diff --git a/apps/epic-web/schemas/documents/contributor.tsx b/apps/epic-web/schemas/documents/contributor.tsx new file mode 100644 index 000000000..ccb347e6e --- /dev/null +++ b/apps/epic-web/schemas/documents/contributor.tsx @@ -0,0 +1,78 @@ +import {UserIcon} from '@sanity/icons' +import {defineField, defineType} from 'sanity' + +export default defineType({ + name: 'contributor', + title: 'Contributor', + icon: UserIcon, + type: 'document', + fields: [ + defineField({ + name: 'name', + title: 'Name', + type: 'string', + validation: (rule) => rule.required(), + }), + defineField({ + name: 'slug', + title: 'Slug', + type: 'slug', + validation: (Rule) => Rule.required(), + options: { + source: 'name', + maxLength: 96, + }, + }), + defineField({ + name: 'twitterHandle', + title: 'Twitter handle', + type: 'string', + description: 'without @ symbol', + }), + defineField({ + name: 'bio', + title: 'Bio', + description: 'A short bio about the author.', + type: 'markdown', + }), + defineField({ + name: 'picture', + title: 'Picture', + type: 'image', + fields: [ + { + name: 'alt', + type: 'string', + title: 'Alternative text', + description: 'Important for SEO and accessiblity.', + }, + ], + options: {hotspot: true}, + validation: (rule) => rule.required(), + }), + defineField({ + name: 'links', + title: 'Links', + type: 'array', + of: [ + { + type: 'object', + name: 'link', + title: 'Link', + fields: [ + { + name: 'label', + type: 'string', + title: 'Label', + }, + { + name: 'url', + type: 'url', + title: 'URL', + }, + ], + }, + ], + }), + ], +}) diff --git a/apps/epic-web/schemas/documents/event.tsx b/apps/epic-web/schemas/documents/event.tsx index 0ba512093..b57ca95f2 100644 --- a/apps/epic-web/schemas/documents/event.tsx +++ b/apps/epic-web/schemas/documents/event.tsx @@ -29,6 +29,11 @@ export default defineType({ type: 'reference', to: {type: 'author'}, }), + defineField({ + name: 'contributors', + type: 'contributors', + title: 'Contributors', + }), defineField({ name: 'startsAt', title: 'Starts at (Pacific time)', diff --git a/apps/epic-web/schemas/documents/module.tsx b/apps/epic-web/schemas/documents/module.tsx index 1124d6c80..e71af9a0a 100644 --- a/apps/epic-web/schemas/documents/module.tsx +++ b/apps/epic-web/schemas/documents/module.tsx @@ -21,6 +21,11 @@ export default defineType({ type: 'reference', to: {type: 'author'}, }), + defineField({ + name: 'contributors', + type: 'contributors', + title: 'Contributors', + }), defineField({ name: 'moduleType', title: 'Module Type', diff --git a/apps/epic-web/schemas/documents/talk.ts b/apps/epic-web/schemas/documents/talk.ts index 1d612066c..6eed29f01 100644 --- a/apps/epic-web/schemas/documents/talk.ts +++ b/apps/epic-web/schemas/documents/talk.ts @@ -30,6 +30,11 @@ export default defineType({ type: 'reference', to: {type: 'author'}, }), + defineField({ + name: 'contributors', + type: 'contributors', + title: 'Contributors', + }), defineField({ name: 'slug', title: 'Slug', diff --git a/apps/epic-web/schemas/documents/tip.ts b/apps/epic-web/schemas/documents/tip.ts index 22ee815a3..e48ca7657 100644 --- a/apps/epic-web/schemas/documents/tip.ts +++ b/apps/epic-web/schemas/documents/tip.ts @@ -30,6 +30,11 @@ export default defineType({ type: 'reference', to: {type: 'author'}, }), + defineField({ + name: 'contributors', + type: 'contributors', + title: 'Contributors', + }), defineField({ name: 'slug', title: 'Slug', diff --git a/apps/epic-web/schemas/index.ts b/apps/epic-web/schemas/index.ts index f10f68a6d..7dccde821 100644 --- a/apps/epic-web/schemas/index.ts +++ b/apps/epic-web/schemas/index.ts @@ -18,6 +18,7 @@ import talk from './documents/talk' import interview from './documents/interview' import bonus from './documents/bonus' import author from './documents/author' +import contributor from './documents/contributor' // —— objects import body from './objects/body' import bodyVideo from './objects/bodyVideo' @@ -41,6 +42,7 @@ import feature from './objects/feature' import github from './objects/resources/github' import transcript from './objects/transcript' import workshopApp from './objects/workshop-app' +import contributors from './objects/contributors' export const schemaTypes = [ // —— documents @@ -63,6 +65,7 @@ export const schemaTypes = [ interview, bonus, author, + contributor, // —— objects body, bodyVideo, @@ -86,4 +89,5 @@ export const schemaTypes = [ bodyTestimonial, feature, workshopApp, + contributors, ] diff --git a/apps/epic-web/schemas/objects/contributors.tsx b/apps/epic-web/schemas/objects/contributors.tsx new file mode 100644 index 000000000..fb13faf42 --- /dev/null +++ b/apps/epic-web/schemas/objects/contributors.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import {defineField, defineType} from 'sanity' + +export default defineType({ + name: 'contributors', + type: 'array', + title: 'Contributors', + of: [ + { + type: 'object', + name: 'contributor', + fields: [ + defineField({ + name: 'contributor', + title: 'Contributor', + type: 'reference', + to: {type: 'contributor'}, + validation: (Rule) => Rule.required(), + }), + defineField({ + title: 'Role', + name: 'role', + type: 'string', + options: { + list: [ + {title: 'Author', value: 'author'}, + {title: 'Instructor', value: 'instructor'}, + {title: 'Host', value: 'host'}, + {title: 'Presenter', value: 'presenter'}, + {title: 'Editor', value: 'editor'}, + {title: 'Reviewer', value: 'reviewer'}, + {title: 'Illustrator', value: 'illustrator'}, + ], + }, + validation: (Rule) => Rule.required(), + }), + ], + preview: { + select: { + title: 'contributor.name', + role: 'role', + imageUrl: 'contributor.picture.asset.url', + }, + prepare(selection) { + const {title, role, imageUrl} = selection + return { + title: `${title}${typeof role === 'string' ? ` (${role})` : ''}`, + imageUrl, + } + }, + }, + }, + ], +}) diff --git a/apps/epic-web/src/lib/articles.ts b/apps/epic-web/src/lib/articles.ts index 22fb37ff5..60f0bd9aa 100644 --- a/apps/epic-web/src/lib/articles.ts +++ b/apps/epic-web/src/lib/articles.ts @@ -52,21 +52,21 @@ export const getAllArticles = async (): Promise => { body, image, ogImage, - author-> { + "author": contributors[@.role == 'author'][0].contributor->{ _id, - _type, - _updatedAt, - _createdAt, - name, - bio, - links[] { - url, label - }, - picture { - "url": asset->url, - alt - }, - "slug": slug.current, + _type, + _updatedAt, + _createdAt, + name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, + "slug": slug.current, }, resources[]->{ ... @@ -90,22 +90,21 @@ export const getArticle = async (slug: string): Promise
=> { body, image, ogImage, - author-> { + "author": contributors[@.role == 'author'][0].contributor->{ _id, - _type, - _updatedAt, - _createdAt, - name, - bio, - links[]{ - url, label - }, - twitterHandle, - picture { - "url": asset->url, - alt - }, - "slug": slug.current, + _type, + _updatedAt, + _createdAt, + name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, + "slug": slug.current, }, resources[]->{ ... diff --git a/apps/epic-web/src/lib/contributors.ts b/apps/epic-web/src/lib/contributors.ts index 2467b211a..459a200fb 100644 --- a/apps/epic-web/src/lib/contributors.ts +++ b/apps/epic-web/src/lib/contributors.ts @@ -2,6 +2,16 @@ import {sanityClient} from '../utils/sanity-client' import groq from 'groq' import z from 'zod' +export const ContributorRoleSchema = z.enum([ + 'author', + 'instructor', + 'host', + 'presenter', + 'editor', + 'reviewer', + 'illustrator', +]) + export const ContributorSchema = z.object({ _id: z.string(), _type: z.string(), @@ -32,6 +42,7 @@ export const ContributorSchema = z.object({ export const ContributorsSchema = z.array(ContributorSchema) export type Contributor = z.infer +export type ContributorRole = z.infer export const getAllContributors = async (): Promise => { const contributors = diff --git a/apps/epic-web/src/lib/events.ts b/apps/epic-web/src/lib/events.ts index cbc09b4db..a0517adaf 100644 --- a/apps/epic-web/src/lib/events.ts +++ b/apps/epic-web/src/lib/events.ts @@ -1,31 +1,7 @@ import {sanityClient} from '../utils/sanity-client' import groq from 'groq' import z from 'zod' -import {TalkSchema} from './talks' - -export const AuthorSchema = z.object({ - _id: z.string(), - _type: z.string(), - _updatedAt: z.string(), - _createdAt: z.string(), - name: z.string(), - bio: z.string(), - links: z - .object({ - url: z.string(), - label: z.string(), - }) - .array() - .nullable(), - twitterHandle: z.string(), - picture: z - .object({ - url: z.string(), - alt: z.string(), - }) - .nullable(), - slug: z.string(), -}) +import {ContributorSchema} from './contributors' export const EventSchema = z.object({ _id: z.string(), @@ -36,7 +12,7 @@ export const EventSchema = z.object({ slug: z.string(), startsAt: z.string().nullable(), endsAt: z.string().nullable(), - author: AuthorSchema.nullable(), + author: ContributorSchema.nullable(), description: z.nullable(z.string()).optional(), body: z.nullable(z.string()).optional(), state: z.enum(['published', 'draft']), @@ -89,22 +65,21 @@ export const getAllEvents = async (onlyPublished = true): Promise => { _type, _updatedAt, _createdAt, - author-> { + "author": contributors[@.role == 'host'][0].contributor->{ _id, _type, _updatedAt, _createdAt, name, bio, - links[]{ + links[] { url, label }, - twitterHandle, picture { "url": asset->url, alt }, - "slug": slug.current + "slug": slug.current, }, title, state, @@ -136,22 +111,21 @@ export const getEvent = async (slug: string): Promise => { _updatedAt, _createdAt, events[]{...}, - author-> { + "author": contributors[@.role == 'host'][0].contributor->{ _id, _type, _updatedAt, _createdAt, name, bio, - links[]{ + links[] { url, label }, - twitterHandle, picture { "url": asset->url, alt }, - "slug": slug.current + "slug": slug.current, }, title, state, diff --git a/apps/epic-web/src/lib/talks.ts b/apps/epic-web/src/lib/talks.ts index a9c147c05..21913d787 100644 --- a/apps/epic-web/src/lib/talks.ts +++ b/apps/epic-web/src/lib/talks.ts @@ -2,6 +2,7 @@ import {sanityClient} from '@skillrecordings/skill-lesson/utils/sanity-client' import groq from 'groq' import z from 'zod' import {pickBy} from 'lodash' +import {ContributorSchema} from './contributors' export const TalkSchema = z.object({ _id: z.string(), @@ -16,7 +17,6 @@ export const TalkSchema = z.object({ muxPlaybackId: z.nullable(z.string()).optional(), videoPosterUrl: z.nullable(z.string()).optional(), state: z.enum(['new', 'processing', 'reviewing', 'published', 'retired']), - // event: z.any(), event: z .object({ title: z.string(), @@ -38,16 +38,7 @@ export const TalkSchema = z.object({ videoResourceId: z.nullable(z.string()).optional(), transcript: z.nullable(z.string()).optional(), tweetId: z.nullable(z.string()).optional(), - author: z - .object({ - name: z.string(), - slug: z.string(), - image: z.string(), - imageAlt: z.string(), - twitterHandle: z.string().optional(), - }) - .nullable() - .optional(), + author: ContributorSchema.optional().nullable(), }) export const TalksSchema = z.array(TalkSchema) @@ -81,12 +72,21 @@ export const getRelatedTalks = async ( 0 ]->castingwords.transcript, "tweetId": resources[@._type == 'tweet'][0].tweetId, - author-> { + "author": contributors[@.role == 'presenter'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt, - twitterHandle, }, }`, talk, @@ -120,12 +120,21 @@ export const getAllTalks = async ( "slug": slug.current, "transcript": resources[@->._type == 'videoResource'][0]-> castingwords.transcript, "tweetId": resources[@._type == 'tweet'][0].tweetId, - author-> { + "author": contributors[@.role == 'presenter'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt, - twitterHandle, }, }`) @@ -156,12 +165,21 @@ export const getTalk = async (slug: string): Promise => { "legacyTranscript": resources[@->._type == 'videoResource'][0]-> castingwords.transcript, "transcript": resources[@->._type == 'videoResource'][0]-> transcript.text, "tweetId": resources[@._type == 'tweet'][0].tweetId, - author-> { + "author": contributors[@.role == 'presenter'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt, - twitterHandle, }, }`, {slug}, @@ -188,11 +206,21 @@ export const getAllConf24Talks = async (count?: number): Promise => { "slug": slug.current, "transcript": resources[@->._type == 'videoResource'][0]->castingwords.transcript, "tweetId": resources[@._type == 'tweet'][0].tweetId, - author-> { + "author": contributors[@.role == 'presenter'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt }, } }`) diff --git a/apps/epic-web/src/lib/tips.ts b/apps/epic-web/src/lib/tips.ts index 2ad9f2f8a..eebc9b108 100644 --- a/apps/epic-web/src/lib/tips.ts +++ b/apps/epic-web/src/lib/tips.ts @@ -2,6 +2,7 @@ import {sanityClient} from '@skillrecordings/skill-lesson/utils/sanity-client' import groq from 'groq' import z from 'zod' import {pickBy} from 'lodash' +import {ContributorSchema} from './contributors' export const TipSchema = z.object({ _id: z.string(), _type: z.string(), @@ -28,15 +29,7 @@ export const TipSchema = z.object({ videoResourceId: z.nullable(z.string()).optional(), transcript: z.nullable(z.string()).optional(), tweetId: z.nullable(z.string()).optional(), - author: z - .object({ - name: z.string(), - slug: z.string(), - image: z.string(), - imageAlt: z.string(), - }) - .nullable() - .optional(), + author: ContributorSchema.optional().nullable(), }) export const TipsSchema = z.array(TipSchema) @@ -56,11 +49,21 @@ export const getAllTips = async (onlyPublished = true): Promise => { description, summary, body, - author-> { + "author": contributors[@.role == 'instructor'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt }, "videoResourceId": resources[@->._type == 'videoResource'][0]->_id, "muxPlaybackId": resources[@->._type == 'videoResource'][0]-> muxAsset.muxPlaybackId, @@ -84,11 +87,21 @@ export const getTip = async (slug: string): Promise => { description, summary, body, - author-> { + "author": contributors[@.role == 'instructor'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt }, "videoResourceId": resources[@->._type == 'videoResource'][0]->_id, "videoPosterUrl": resources[@->._type == 'videoResource'][0]->poster, diff --git a/apps/epic-web/src/lib/tutorials.ts b/apps/epic-web/src/lib/tutorials.ts index 13ea774dc..6b2604ec2 100644 --- a/apps/epic-web/src/lib/tutorials.ts +++ b/apps/epic-web/src/lib/tutorials.ts @@ -16,11 +16,21 @@ export const getAllTutorials = async (onlyPublished = true) => { description, moduleType, state, - author-> { + "author": contributors[@.role == 'instructor'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt }, "sections": resources[@->._type == 'section']->{ _id, @@ -67,13 +77,22 @@ export const getTutorial = async (slug: string) => _updatedAt, "image": image.asset->url, body, - author-> { - name, - "slug": slug.current, - "image": picture.asset->url, - "imageAlt": picture.alt, - twitterHandle, + "author": contributors[@.role == 'instructor'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, + name, + bio, + links[] { + url, label }, + picture { + "url": asset->url, + alt + }, + "slug": slug.current, + }, "testimonials": resources[@->._type == 'testimonial']->{ _id, _type, diff --git a/apps/epic-web/src/lib/workshops.ts b/apps/epic-web/src/lib/workshops.ts index eaff2673d..79e962be6 100644 --- a/apps/epic-web/src/lib/workshops.ts +++ b/apps/epic-web/src/lib/workshops.ts @@ -12,6 +12,22 @@ const workshopsQuery = groq`*[_type == "module" && moduleType == 'workshop'] | o _createdAt, description, state, + "author": contributors[@.role == 'instructor'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, + name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, + "slug": slug.current, + }, 'product': *[_type=='product' && references(^._id)][]{ "slug": slug.current, state, @@ -79,6 +95,22 @@ export const getWorkshop = async (slug: string) => ogImage, description, _updatedAt, + "author": contributors[@.role == 'instructor'][0].contributor->{ + _id, + _type, + _updatedAt, + _createdAt, + name, + bio, + links[] { + url, label + }, + picture { + "url": asset->url, + alt + }, + "slug": slug.current, + }, "testimonials": resources[@->._type == 'testimonial']->{ _id, _type, diff --git a/apps/epic-web/src/pages/articles.tsx b/apps/epic-web/src/pages/articles.tsx index 200de3cfc..05ca01072 100644 --- a/apps/epic-web/src/pages/articles.tsx +++ b/apps/epic-web/src/pages/articles.tsx @@ -28,6 +28,7 @@ const Articles: React.FC<{articles: Article[]}> = ({articles}) => { ? articles : articles.filter(({state}) => state === 'published') const {subscriber} = useConvertkit() + return ( diff --git a/apps/epic-web/src/pages/tips/index.tsx b/apps/epic-web/src/pages/tips/index.tsx index 008cdaed2..9a77d7ead 100644 --- a/apps/epic-web/src/pages/tips/index.tsx +++ b/apps/epic-web/src/pages/tips/index.tsx @@ -127,7 +127,7 @@ const TipCard: React.FC<{tip: Tip; i: number}> = ({tip, i}) => { diff --git a/apps/epic-web/src/pages/tutorials/index.tsx b/apps/epic-web/src/pages/tutorials/index.tsx index ac302b827..d94adde1c 100644 --- a/apps/epic-web/src/pages/tutorials/index.tsx +++ b/apps/epic-web/src/pages/tutorials/index.tsx @@ -107,7 +107,7 @@ const TutorialsPage: React.FC<{tutorials: SanityDocument[]}> = ({ diff --git a/apps/epic-web/src/templates/talk-template.tsx b/apps/epic-web/src/templates/talk-template.tsx index 29f3b4929..a74c9230e 100644 --- a/apps/epic-web/src/templates/talk-template.tsx +++ b/apps/epic-web/src/templates/talk-template.tsx @@ -70,7 +70,7 @@ const TalkTemplate: React.FC<{ muxPlaybackId: talk.muxPlaybackId ?? undefined, bgImage: talk.videoPosterUrl ?? thumbnail, authorName: talk.author?.name ?? undefined, - authorImage: talk.author?.image ?? undefined, + authorImage: talk.author?.picture?.url ?? undefined, }) const handleOnSuccess = (subscriber: any, email?: string) => { @@ -185,7 +185,7 @@ const TalkTemplate: React.FC<{ className="mt-3 inline-flex text-base [&_img]:w-10 [&_span]:font-normal" name={talk.author?.name} slug={talk.author?.slug} - image={talk.author?.image} + image={talk.author?.picture?.url} /> {talk.body && ( @@ -351,7 +351,9 @@ const TipOverlay: React.FC<{talks: Talk[]}> = ({talks}) => { ) } -const VideoOverlayTipCard: React.FC<{suggestedTip: Tip}> = ({suggestedTip}) => { +const VideoOverlayTipCard: React.FC<{suggestedTip: Talk}> = ({ + suggestedTip, +}) => { const router = useRouter() const {handlePlay} = useMuxPlayer() const {resourceCompleted} = useResourceComplete(suggestedTip.slug) diff --git a/apps/epic-web/src/templates/tip-template.tsx b/apps/epic-web/src/templates/tip-template.tsx index c2d7a8200..72a4c2108 100644 --- a/apps/epic-web/src/templates/tip-template.tsx +++ b/apps/epic-web/src/templates/tip-template.tsx @@ -163,7 +163,7 @@ const TipTemplate: React.FC<{ {tip.body && ( @@ -183,7 +183,7 @@ const TipTemplate: React.FC<{ diff --git a/apps/epic-web/src/templates/tutorial-template.tsx b/apps/epic-web/src/templates/tutorial-template.tsx index e3d4ad916..6fa80d161 100644 --- a/apps/epic-web/src/templates/tutorial-template.tsx +++ b/apps/epic-web/src/templates/tutorial-template.tsx @@ -133,7 +133,7 @@ const Header: React.FC<{tutorial: any}> = ({tutorial}) => {
From 98bbc49dcb568ca98e20869156b0efc1f3e45dd4 Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Wed, 27 Mar 2024 15:45:40 +0100 Subject: [PATCH 05/17] refactor(ewd): entirely replace notion of authors with contributors --- apps/epic-web/schemas/deskStructure.ts | 5 ++ apps/epic-web/schemas/documents/article.tsx | 6 -- apps/epic-web/schemas/documents/author.ts | 79 ------------------- apps/epic-web/schemas/documents/event.tsx | 6 -- apps/epic-web/schemas/documents/exercise.tsx | 7 +- apps/epic-web/schemas/documents/explainer.tsx | 7 +- apps/epic-web/schemas/documents/lesson.tsx | 7 +- apps/epic-web/schemas/documents/module.tsx | 6 -- apps/epic-web/schemas/documents/talk.ts | 6 -- apps/epic-web/schemas/documents/tip.ts | 6 -- apps/epic-web/schemas/index.ts | 2 - .../schemas/structure/contributors.js | 10 +++ apps/epic-web/src/lib/contributors.ts | 35 +------- .../src/pages/contributors/[slug].tsx | 6 +- 14 files changed, 29 insertions(+), 159 deletions(-) delete mode 100644 apps/epic-web/schemas/documents/author.ts create mode 100644 apps/epic-web/schemas/structure/contributors.js diff --git a/apps/epic-web/schemas/deskStructure.ts b/apps/epic-web/schemas/deskStructure.ts index bb8ee1a7b..6a7bcd005 100644 --- a/apps/epic-web/schemas/deskStructure.ts +++ b/apps/epic-web/schemas/deskStructure.ts @@ -16,6 +16,7 @@ import talks from './structure/talks' import bonuses from './structure/bonuses' import interviews from './structure/interviews' import liveProducts from './structure/live-products' +import contributors from './structure/contributors' const hiddenDocTypes = (listItem: any) => ![ @@ -34,6 +35,7 @@ const hiddenDocTypes = (listItem: any) => 'event', 'talk', 'interview', + 'contributor', ].includes(listItem.getId()) export default (S: any) => @@ -44,6 +46,8 @@ export default (S: any) => products(S), liveProducts(S), S.divider(), + contributors(S), + S.divider(), workshops(S), tutorials(S), articles(S), @@ -60,6 +64,7 @@ export default (S: any) => interviews(S), sections(S), links(S), + S.divider(), ...S.documentTypeListItems().filter(hiddenDocTypes), ]) diff --git a/apps/epic-web/schemas/documents/article.tsx b/apps/epic-web/schemas/documents/article.tsx index e7acb0969..4b4f53033 100644 --- a/apps/epic-web/schemas/documents/article.tsx +++ b/apps/epic-web/schemas/documents/article.tsx @@ -13,12 +13,6 @@ export default defineType({ type: 'string', validation: (Rule) => Rule.required(), }), - defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, - }), defineField({ name: 'contributors', type: 'contributors', diff --git a/apps/epic-web/schemas/documents/author.ts b/apps/epic-web/schemas/documents/author.ts deleted file mode 100644 index 37d395b31..000000000 --- a/apps/epic-web/schemas/documents/author.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {TwitterIcon, UserIcon} from '@sanity/icons' -import {defineField, defineType} from 'sanity' - -export default defineType({ - name: 'author', - title: 'Author', - icon: UserIcon, - type: 'document', - fields: [ - defineField({ - name: 'name', - title: 'Name', - type: 'string', - validation: (rule) => rule.required(), - }), - defineField({ - name: 'slug', - title: 'Slug', - type: 'slug', - validation: (Rule) => Rule.required(), - options: { - source: 'name', - maxLength: 96, - }, - }), - defineField({ - name: 'twitterHandle', - title: 'Twitter handle', - type: 'string', - description: 'without @ symbol', - }), - defineField({ - name: 'bio', - title: 'Bio', - description: 'A short bio about the author.', - type: 'markdown', - }), - defineField({ - name: 'picture', - title: 'Picture', - type: 'image', - fields: [ - { - name: 'alt', - type: 'string', - title: 'Alternative text', - description: 'Important for SEO and accessiblity.', - }, - ], - options: {hotspot: true}, - validation: (rule) => rule.required(), - }), - - defineField({ - name: 'links', - title: 'Links', - type: 'array', - of: [ - { - type: 'object', - name: 'link', - title: 'Link', - fields: [ - { - name: 'label', - type: 'string', - title: 'Label', - }, - { - name: 'url', - type: 'url', - title: 'URL', - }, - ], - }, - ], - }), - ], -}) diff --git a/apps/epic-web/schemas/documents/event.tsx b/apps/epic-web/schemas/documents/event.tsx index b57ca95f2..10486b73f 100644 --- a/apps/epic-web/schemas/documents/event.tsx +++ b/apps/epic-web/schemas/documents/event.tsx @@ -23,12 +23,6 @@ export default defineType({ maxLength: 96, }, }), - defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, - }), defineField({ name: 'contributors', type: 'contributors', diff --git a/apps/epic-web/schemas/documents/exercise.tsx b/apps/epic-web/schemas/documents/exercise.tsx index 5d1c4dad5..0c0cfe6a3 100644 --- a/apps/epic-web/schemas/documents/exercise.tsx +++ b/apps/epic-web/schemas/documents/exercise.tsx @@ -33,10 +33,9 @@ export default defineType({ validation: (Rule) => Rule.max(90), }), defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, + name: 'contributors', + type: 'contributors', + title: 'Contributors', }), defineField({ name: 'slug', diff --git a/apps/epic-web/schemas/documents/explainer.tsx b/apps/epic-web/schemas/documents/explainer.tsx index 77eacade6..560c97510 100644 --- a/apps/epic-web/schemas/documents/explainer.tsx +++ b/apps/epic-web/schemas/documents/explainer.tsx @@ -33,10 +33,9 @@ export default defineType({ validation: (Rule) => Rule.max(90), }), defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, + name: 'contributors', + type: 'contributors', + title: 'Contributors', }), defineField({ name: 'explainerType', diff --git a/apps/epic-web/schemas/documents/lesson.tsx b/apps/epic-web/schemas/documents/lesson.tsx index 1edb04046..a9e0c5311 100644 --- a/apps/epic-web/schemas/documents/lesson.tsx +++ b/apps/epic-web/schemas/documents/lesson.tsx @@ -27,10 +27,9 @@ export default defineType({ validation: (Rule) => Rule.max(90), }), defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, + name: 'contributors', + type: 'contributors', + title: 'Contributors', }), defineField({ name: 'slug', diff --git a/apps/epic-web/schemas/documents/module.tsx b/apps/epic-web/schemas/documents/module.tsx index e71af9a0a..b455e92bf 100644 --- a/apps/epic-web/schemas/documents/module.tsx +++ b/apps/epic-web/schemas/documents/module.tsx @@ -15,12 +15,6 @@ export default defineType({ type: 'string', validation: (Rule) => Rule.required(), }), - defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, - }), defineField({ name: 'contributors', type: 'contributors', diff --git a/apps/epic-web/schemas/documents/talk.ts b/apps/epic-web/schemas/documents/talk.ts index 6eed29f01..6b8f8b3fc 100644 --- a/apps/epic-web/schemas/documents/talk.ts +++ b/apps/epic-web/schemas/documents/talk.ts @@ -24,12 +24,6 @@ export default defineType({ type: 'string', validation: (Rule) => Rule.required(), }), - defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, - }), defineField({ name: 'contributors', type: 'contributors', diff --git a/apps/epic-web/schemas/documents/tip.ts b/apps/epic-web/schemas/documents/tip.ts index e48ca7657..3bb65d4c4 100644 --- a/apps/epic-web/schemas/documents/tip.ts +++ b/apps/epic-web/schemas/documents/tip.ts @@ -24,12 +24,6 @@ export default defineType({ type: 'string', validation: (Rule) => Rule.required(), }), - defineField({ - name: 'author', - title: 'Author', - type: 'reference', - to: {type: 'author'}, - }), defineField({ name: 'contributors', type: 'contributors', diff --git a/apps/epic-web/schemas/index.ts b/apps/epic-web/schemas/index.ts index 7dccde821..d1edf6b91 100644 --- a/apps/epic-web/schemas/index.ts +++ b/apps/epic-web/schemas/index.ts @@ -17,7 +17,6 @@ import event from './documents/event' import talk from './documents/talk' import interview from './documents/interview' import bonus from './documents/bonus' -import author from './documents/author' import contributor from './documents/contributor' // —— objects import body from './objects/body' @@ -64,7 +63,6 @@ export const schemaTypes = [ talk, interview, bonus, - author, contributor, // —— objects body, diff --git a/apps/epic-web/schemas/structure/contributors.js b/apps/epic-web/schemas/structure/contributors.js new file mode 100644 index 000000000..3c05532b7 --- /dev/null +++ b/apps/epic-web/schemas/structure/contributors.js @@ -0,0 +1,10 @@ +import {MdPerson} from 'react-icons/md' +import {defineType} from 'sanity' + +const contributors = (S) => + S.listItem() + .title('Contributors') + .icon(MdPerson) + .child(S.documentTypeList('contributor').title('All Contributors')) + +export default contributors diff --git a/apps/epic-web/src/lib/contributors.ts b/apps/epic-web/src/lib/contributors.ts index 459a200fb..02e21ffac 100644 --- a/apps/epic-web/src/lib/contributors.ts +++ b/apps/epic-web/src/lib/contributors.ts @@ -46,7 +46,7 @@ export type ContributorRole = z.infer export const getAllContributors = async (): Promise => { const contributors = - await sanityClient.fetch(groq`*[_type == "author"] | order(_createdAt desc) { + await sanityClient.fetch(groq`*[_type == "contributor"] | order(_createdAt desc) { _id, _type, _updatedAt, @@ -71,7 +71,7 @@ export const getContributor = async ( slug: string, ): Promise => { const contributor = await sanityClient.fetch( - groq`*[_type == "author" && slug.current == $slug][0] { + groq`*[_type == "contributor" && slug.current == $slug][0] { _id, _type, _updatedAt, @@ -102,7 +102,7 @@ export const getContributor = async ( } // ———————————————————————————————————————————————————————————————————————————————— -// AUTHOR RESOURCES +// CONTRIBUTOR RESOURCES // ———————————————————————————————————————————————————————————————————————————————— export const ContributorResourceSchema = z.object({ @@ -126,7 +126,7 @@ export const getContributorResources = async ( id: string, ): Promise => { const resources = await sanityClient.fetch( - groq`*[author->_id == $id && _type in ["article", "tip", "module", "talk"] && state == 'published'] | order(_createdAt desc) { + groq`*[$id in contributors[].contributor._ref && _type in ["article", "tip", "module", "talk"] && state == 'published'] | order(_createdAt desc) { _id, _type, _updatedAt, @@ -150,30 +150,3 @@ export const getContributorResources = async ( return null } } - -export const getKentsResources = async (): Promise< - ContributorResource[] | null -> => { - const resources = await sanityClient.fetch( - groq`*[author == null && _type in ["article", "tip", "module"] && state == 'published'] | order(_createdAt desc) { - _id, - _type, - _updatedAt, - _createdAt, - title, - description, - summary, - "slug": slug.current, - "moduleType": moduleType, - "image": coalesce(image.asset->url, image.secure_url), - "muxPlaybackId": resources[@->._type == 'videoResource'][0]-> muxAsset.muxPlaybackId, - }`, - ) - const result = ContributorResourcesSchema.safeParse(resources) - - if (result.success) { - return result.data - } else { - return null - } -} diff --git a/apps/epic-web/src/pages/contributors/[slug].tsx b/apps/epic-web/src/pages/contributors/[slug].tsx index 15e17a152..768367c4c 100644 --- a/apps/epic-web/src/pages/contributors/[slug].tsx +++ b/apps/epic-web/src/pages/contributors/[slug].tsx @@ -6,7 +6,6 @@ import { getAllContributors, getContributor, getContributorResources, - getKentsResources, } from 'lib/contributors' import ContributorTemplate from 'templates/contributor-template' @@ -20,11 +19,8 @@ export const getStaticPaths = async () => { } export const getStaticProps: GetStaticProps = async ({params}) => { - const isKent = params?.slug === 'kent-c-dodds' const contributor = await getContributor(params?.slug as string) - const resources = isKent - ? await getKentsResources() - : await getContributorResources(contributor?._id as string) + const resources = await getContributorResources(contributor?._id as string) return {props: {contributor, resources}, revalidate: 10} } From 144f568e63c942ad0560ec1fa4c39dee1b48d412 Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Wed, 27 Mar 2024 16:03:16 +0100 Subject: [PATCH 06/17] refactor(ewd): entirely replace notion of authors with contributors --- apps/epic-web/src/lib/events.ts | 6 ++-- apps/epic-web/src/lib/talks.ts | 10 +++--- apps/epic-web/src/lib/tips.ts | 6 ++-- apps/epic-web/src/lib/tutorials.ts | 4 +-- apps/epic-web/src/lib/workshops.ts | 4 +-- apps/epic-web/src/pages/events/index.tsx | 10 +++--- apps/epic-web/src/pages/talks/index.tsx | 6 ++-- apps/epic-web/src/pages/tips/index.tsx | 6 ++-- apps/epic-web/src/pages/tutorials/index.tsx | 8 ++--- .../epic-web/src/templates/event-template.tsx | 34 +++++++++---------- apps/epic-web/src/templates/talk-template.tsx | 12 +++---- apps/epic-web/src/templates/tip-template.tsx | 12 +++---- .../src/templates/tutorial-template.tsx | 8 ++--- 13 files changed, 63 insertions(+), 63 deletions(-) diff --git a/apps/epic-web/src/lib/events.ts b/apps/epic-web/src/lib/events.ts index a0517adaf..67c19eecc 100644 --- a/apps/epic-web/src/lib/events.ts +++ b/apps/epic-web/src/lib/events.ts @@ -12,7 +12,7 @@ export const EventSchema = z.object({ slug: z.string(), startsAt: z.string().nullable(), endsAt: z.string().nullable(), - author: ContributorSchema.nullable(), + host: ContributorSchema.nullable(), description: z.nullable(z.string()).optional(), body: z.nullable(z.string()).optional(), state: z.enum(['published', 'draft']), @@ -65,7 +65,7 @@ export const getAllEvents = async (onlyPublished = true): Promise => { _type, _updatedAt, _createdAt, - "author": contributors[@.role == 'host'][0].contributor->{ + "host": contributors[@.role == 'host'][0].contributor->{ _id, _type, _updatedAt, @@ -111,7 +111,7 @@ export const getEvent = async (slug: string): Promise => { _updatedAt, _createdAt, events[]{...}, - "author": contributors[@.role == 'host'][0].contributor->{ + "host": contributors[@.role == 'host'][0].contributor->{ _id, _type, _updatedAt, diff --git a/apps/epic-web/src/lib/talks.ts b/apps/epic-web/src/lib/talks.ts index 21913d787..60d40576a 100644 --- a/apps/epic-web/src/lib/talks.ts +++ b/apps/epic-web/src/lib/talks.ts @@ -38,7 +38,7 @@ export const TalkSchema = z.object({ videoResourceId: z.nullable(z.string()).optional(), transcript: z.nullable(z.string()).optional(), tweetId: z.nullable(z.string()).optional(), - author: ContributorSchema.optional().nullable(), + presenter: ContributorSchema.optional().nullable(), }) export const TalksSchema = z.array(TalkSchema) @@ -72,7 +72,7 @@ export const getRelatedTalks = async ( 0 ]->castingwords.transcript, "tweetId": resources[@._type == 'tweet'][0].tweetId, - "author": contributors[@.role == 'presenter'][0].contributor->{ + "presenter": contributors[@.role == 'presenter'][0].contributor->{ _id, _type, _updatedAt, @@ -120,7 +120,7 @@ export const getAllTalks = async ( "slug": slug.current, "transcript": resources[@->._type == 'videoResource'][0]-> castingwords.transcript, "tweetId": resources[@._type == 'tweet'][0].tweetId, - "author": contributors[@.role == 'presenter'][0].contributor->{ + "presenter": contributors[@.role == 'presenter'][0].contributor->{ _id, _type, _updatedAt, @@ -165,7 +165,7 @@ export const getTalk = async (slug: string): Promise => { "legacyTranscript": resources[@->._type == 'videoResource'][0]-> castingwords.transcript, "transcript": resources[@->._type == 'videoResource'][0]-> transcript.text, "tweetId": resources[@._type == 'tweet'][0].tweetId, - "author": contributors[@.role == 'presenter'][0].contributor->{ + "presenter": contributors[@.role == 'presenter'][0].contributor->{ _id, _type, _updatedAt, @@ -206,7 +206,7 @@ export const getAllConf24Talks = async (count?: number): Promise => { "slug": slug.current, "transcript": resources[@->._type == 'videoResource'][0]->castingwords.transcript, "tweetId": resources[@._type == 'tweet'][0].tweetId, - "author": contributors[@.role == 'presenter'][0].contributor->{ + "presenter": contributors[@.role == 'presenter'][0].contributor->{ _id, _type, _updatedAt, diff --git a/apps/epic-web/src/lib/tips.ts b/apps/epic-web/src/lib/tips.ts index eebc9b108..e763746d4 100644 --- a/apps/epic-web/src/lib/tips.ts +++ b/apps/epic-web/src/lib/tips.ts @@ -29,7 +29,7 @@ export const TipSchema = z.object({ videoResourceId: z.nullable(z.string()).optional(), transcript: z.nullable(z.string()).optional(), tweetId: z.nullable(z.string()).optional(), - author: ContributorSchema.optional().nullable(), + instructor: ContributorSchema.optional().nullable(), }) export const TipsSchema = z.array(TipSchema) @@ -49,7 +49,7 @@ export const getAllTips = async (onlyPublished = true): Promise => { description, summary, body, - "author": contributors[@.role == 'instructor'][0].contributor->{ + "instructor": contributors[@.role == 'instructor'][0].contributor->{ _id, _type, _updatedAt, @@ -87,7 +87,7 @@ export const getTip = async (slug: string): Promise => { description, summary, body, - "author": contributors[@.role == 'instructor'][0].contributor->{ + "instructor": contributors[@.role == 'instructor'][0].contributor->{ _id, _type, _updatedAt, diff --git a/apps/epic-web/src/lib/tutorials.ts b/apps/epic-web/src/lib/tutorials.ts index 6b2604ec2..9a3765ad6 100644 --- a/apps/epic-web/src/lib/tutorials.ts +++ b/apps/epic-web/src/lib/tutorials.ts @@ -16,7 +16,7 @@ export const getAllTutorials = async (onlyPublished = true) => { description, moduleType, state, - "author": contributors[@.role == 'instructor'][0].contributor->{ + "instructor": contributors[@.role == 'instructor'][0].contributor->{ _id, _type, _updatedAt, @@ -77,7 +77,7 @@ export const getTutorial = async (slug: string) => _updatedAt, "image": image.asset->url, body, - "author": contributors[@.role == 'instructor'][0].contributor->{ + "instructor": contributors[@.role == 'instructor'][0].contributor->{ _id, _type, _updatedAt, diff --git a/apps/epic-web/src/lib/workshops.ts b/apps/epic-web/src/lib/workshops.ts index 79e962be6..27783469f 100644 --- a/apps/epic-web/src/lib/workshops.ts +++ b/apps/epic-web/src/lib/workshops.ts @@ -12,7 +12,7 @@ const workshopsQuery = groq`*[_type == "module" && moduleType == 'workshop'] | o _createdAt, description, state, - "author": contributors[@.role == 'instructor'][0].contributor->{ + "instructor": contributors[@.role == 'instructor'][0].contributor->{ _id, _type, _updatedAt, @@ -95,7 +95,7 @@ export const getWorkshop = async (slug: string) => ogImage, description, _updatedAt, - "author": contributors[@.role == 'instructor'][0].contributor->{ + "instructor": contributors[@.role == 'instructor'][0].contributor->{ _id, _type, _updatedAt, diff --git a/apps/epic-web/src/pages/events/index.tsx b/apps/epic-web/src/pages/events/index.tsx index 5d8ebbe2d..29c64e227 100644 --- a/apps/epic-web/src/pages/events/index.tsx +++ b/apps/epic-web/src/pages/events/index.tsx @@ -142,21 +142,21 @@ const Events: React.FC<{events: Event[]}> = ({events}) => {
- {event.author ? ( + {event.host ? (
- {event.author.picture && ( + {event.host.picture && (
{event.author.name}
)}
Hosted by
-
{event.author.name}
+
{event.host.name}
) : null} diff --git a/apps/epic-web/src/pages/talks/index.tsx b/apps/epic-web/src/pages/talks/index.tsx index d18eb8fc2..9e16f0c8a 100644 --- a/apps/epic-web/src/pages/talks/index.tsx +++ b/apps/epic-web/src/pages/talks/index.tsx @@ -137,9 +137,9 @@ export const TalkItem: React.FC<{ {talk.title} diff --git a/apps/epic-web/src/pages/tips/index.tsx b/apps/epic-web/src/pages/tips/index.tsx index 9a77d7ead..4e7b407f5 100644 --- a/apps/epic-web/src/pages/tips/index.tsx +++ b/apps/epic-web/src/pages/tips/index.tsx @@ -125,9 +125,9 @@ const TipCard: React.FC<{tip: Tip; i: number}> = ({tip, i}) => { {resourceCompleted && (watched)} diff --git a/apps/epic-web/src/pages/tutorials/index.tsx b/apps/epic-web/src/pages/tutorials/index.tsx index d94adde1c..cbda0a14e 100644 --- a/apps/epic-web/src/pages/tutorials/index.tsx +++ b/apps/epic-web/src/pages/tutorials/index.tsx @@ -68,7 +68,7 @@ const TutorialsPage: React.FC<{tutorials: SanityDocument[]}> = ({ {tutorials && (
    {tutorials.map( - ({title, slug, image, description, sections, author}, i) => { + ({title, slug, image, description, sections, instructor}, i) => { return (
  • = ({
    diff --git a/apps/epic-web/src/templates/event-template.tsx b/apps/epic-web/src/templates/event-template.tsx index 7bea5301a..c63ab80cd 100644 --- a/apps/epic-web/src/templates/event-template.tsx +++ b/apps/epic-web/src/templates/event-template.tsx @@ -63,7 +63,7 @@ const EventTemplate: React.FC< slug, timezone, ogImage: _ogImage, - author, + host, } = event const product = products && products[0] const image = event?.image?.secure_url @@ -71,15 +71,15 @@ const EventTemplate: React.FC< ? {url: _ogImage.secure_url, alt: title} : getOgImage({ title: title, - authorImage: event.author?.picture?.url, - authorName: event.author?.name, + authorImage: event.host?.picture?.url, + authorName: event.host?.name, }) const pageDescription = description || (mdx ? `${mdx.compiledSource.substring(0, 157)}...` : undefined) - const authorName = - author?.name ?? + const hostName = + host?.name ?? `${process.env.NEXT_PUBLIC_PARTNER_FIRST_NAME} ${process.env.NEXT_PUBLIC_PARTNER_LAST_NAME}` const url = `${process.env.NEXT_PUBLIC_URL}${router.asPath}` @@ -117,7 +117,7 @@ const EventTemplate: React.FC< location={{name: 'Zoom'} as any} title={title} images={image ? [image] : []} - authorName={author} + authorName={hostName} datePublished={_createdAt} dateModified={_updatedAt} description={pageDescription} @@ -149,20 +149,20 @@ const EventTemplate: React.FC<
    with{' '} - {author?.picture?.url && ( + {host?.picture?.url && ( {authorName} )} - {authorName} + {hostName}
    @@ -200,16 +200,16 @@ const EventTemplate: React.FC< `Hosted by ${name}`} - bio={event.author?.bio} + bio={event.host?.bio} /> ) diff --git a/apps/epic-web/src/templates/talk-template.tsx b/apps/epic-web/src/templates/talk-template.tsx index a74c9230e..411b93318 100644 --- a/apps/epic-web/src/templates/talk-template.tsx +++ b/apps/epic-web/src/templates/talk-template.tsx @@ -69,8 +69,8 @@ const TalkTemplate: React.FC<{ type: 'background-thumbnail', muxPlaybackId: talk.muxPlaybackId ?? undefined, bgImage: talk.videoPosterUrl ?? thumbnail, - authorName: talk.author?.name ?? undefined, - authorImage: talk.author?.picture?.url ?? undefined, + authorName: talk.presenter?.name ?? undefined, + authorImage: talk.presenter?.picture?.url ?? undefined, }) const handleOnSuccess = (subscriber: any, email?: string) => { @@ -183,9 +183,9 @@ const TalkTemplate: React.FC<{ {talk.body && ( @@ -199,7 +199,7 @@ const TalkTemplate: React.FC<{ className="mt-5" title={talk.title} contentType="talk" - contributor={talk.author} + contributor={talk.presenter} /> diff --git a/apps/epic-web/src/templates/tip-template.tsx b/apps/epic-web/src/templates/tip-template.tsx index 72a4c2108..195a244b9 100644 --- a/apps/epic-web/src/templates/tip-template.tsx +++ b/apps/epic-web/src/templates/tip-template.tsx @@ -161,9 +161,9 @@ const TipTemplate: React.FC<{ )} {tip.body && ( @@ -181,9 +181,9 @@ const TipTemplate: React.FC<{
    diff --git a/apps/epic-web/src/templates/tutorial-template.tsx b/apps/epic-web/src/templates/tutorial-template.tsx index 6fa80d161..62249c219 100644 --- a/apps/epic-web/src/templates/tutorial-template.tsx +++ b/apps/epic-web/src/templates/tutorial-template.tsx @@ -102,7 +102,7 @@ const TutorialTemplate: React.FC<{ export default TutorialTemplate const Header: React.FC<{tutorial: any}> = ({tutorial}) => { - const {title, slug, sections, image, github, author} = tutorial + const {title, slug, sections, image, github, instructor} = tutorial const {data: moduleProgress, status: moduleProgressStatus} = trpc.moduleProgress.bySlug.useQuery({ slug: tutorial.slug.current, @@ -131,9 +131,9 @@ const Header: React.FC<{tutorial: any}> = ({tutorial}) => {
    From edd033bcfb6a71d3393d6dbe8bc69a64d90f8169 Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Wed, 27 Mar 2024 16:06:21 +0100 Subject: [PATCH 07/17] refactor(ewd): entirely replace notion of authors with contributors --- .../src/templates/purchased-event-template.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/epic-web/src/templates/purchased-event-template.tsx b/apps/epic-web/src/templates/purchased-event-template.tsx index a4804573a..f343ce3ec 100644 --- a/apps/epic-web/src/templates/purchased-event-template.tsx +++ b/apps/epic-web/src/templates/purchased-event-template.tsx @@ -235,16 +235,16 @@ const PurchasedEventTemplate = ({ {product.title} `Hosted by ${name}`} - bio={event.author?.bio} + bio={event.host?.bio} className="m-0 py-5 sm:py-12" /> From fb758cd413ce9314db9198e199e1eaa76755a0dd Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Mon, 25 Mar 2024 14:17:42 -0500 Subject: [PATCH 08/17] feat(ew): add PaymentOptions, remove stripeData --- .../pages/api/skill/[...skillRecordings].ts | 12 ++++++++++ apps/epic-web/src/pages/thanks/purchase.tsx | 24 +++++++++---------- apps/epic-web/src/pages/welcome/index.tsx | 24 +++++++++---------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/apps/epic-web/src/pages/api/skill/[...skillRecordings].ts b/apps/epic-web/src/pages/api/skill/[...skillRecordings].ts index 147f05bd1..871d842a6 100644 --- a/apps/epic-web/src/pages/api/skill/[...skillRecordings].ts +++ b/apps/epic-web/src/pages/api/skill/[...skillRecordings].ts @@ -6,6 +6,17 @@ import {nextAuthOptions} from '../auth/[...nextauth]' import {getToken} from 'next-auth/jwt' import {NextApiRequest} from 'next' import {getCurrentAbility, UserSchema} from '@skillrecordings/skill-lesson' +import { + defaultPaymentOptions, + StripeProvider, +} from '@skillrecordings/commerce-server' + +export const paymentOptions = defaultPaymentOptions({ + stripeProvider: StripeProvider({ + stripeSecretKey: process.env.STRIPE_SECRET_TOKEN, + apiVersion: '2020-08-27', + }), +}) export const skillOptions: SkillRecordingsOptions = { site: { @@ -13,6 +24,7 @@ export const skillOptions: SkillRecordingsOptions = { supportEmail: process.env.NEXT_PUBLIC_SUPPORT_EMAIL, }, nextAuthOptions, + paymentOptions, getAbility: async (req: IncomingRequest) => { const token = await getToken({req: req as unknown as NextApiRequest}) return getCurrentAbility({user: UserSchema.parse(token)}) diff --git a/apps/epic-web/src/pages/thanks/purchase.tsx b/apps/epic-web/src/pages/thanks/purchase.tsx index b0f323634..41cccb7ed 100644 --- a/apps/epic-web/src/pages/thanks/purchase.tsx +++ b/apps/epic-web/src/pages/thanks/purchase.tsx @@ -5,7 +5,6 @@ import { convertToSerializeForNextResponse, determinePurchaseType, PurchaseType, - stripeData, } from '@skillrecordings/commerce-server' import {getSdk, Purchase} from '@skillrecordings/database' import CopyInviteLink from '@skillrecordings/skill-lesson/team/copy-invite-link' @@ -17,32 +16,34 @@ import {isEmpty} from 'lodash' import {Transfer} from 'purchase-transfer/purchase-transfer' import {trpc} from 'trpc/trpc.client' import {getPostPurchaseThanksText} from 'utils/get-post-purchase-thanks-text' +import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' export const getServerSideProps: GetServerSideProps = async (context) => { const {query} = context - const {session_id} = query + const session_id = + query.session_id instanceof Array ? query.session_id[0] : query.session_id - if (!session_id) { + const paymentProvider = paymentOptions.providers.stripe + + if (!session_id || !paymentProvider) { return { notFound: true, } } - const purchaseInfo = await stripeData({ - checkoutSessionId: session_id as string, - }) + const purchaseInfo = await paymentProvider.getPurchaseInfo(session_id) const { email, - stripeChargeId, + chargeIdentifier, quantity: seatsPurchased, - stripeProduct, + product: merchantProduct, } = purchaseInfo - const stripeProductName = stripeProduct.name + const stripeProductName = merchantProduct.name - const purchase = await getSdk().getPurchaseForStripeCharge(stripeChargeId) + const purchase = await getSdk().getPurchaseForStripeCharge(chargeIdentifier) if (!purchase || !email) { return { @@ -51,7 +52,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { } const purchaseType = await determinePurchaseType({ - checkoutSessionId: session_id as string, + checkoutSessionId: session_id, }) const product = await getProduct(purchase.productId) @@ -81,7 +82,6 @@ const InlineTeamInvite = ({ }) => { if (!bulkCouponId) return null - return (

    Invite your team

    diff --git a/apps/epic-web/src/pages/welcome/index.tsx b/apps/epic-web/src/pages/welcome/index.tsx index 9fec40066..a3b8e1bcb 100644 --- a/apps/epic-web/src/pages/welcome/index.tsx +++ b/apps/epic-web/src/pages/welcome/index.tsx @@ -1,40 +1,38 @@ import * as React from 'react' -import {DocumentTextIcon, UserGroupIcon} from '@heroicons/react/outline' -import { - convertToSerializeForNextResponse, - stripeData, -} from '@skillrecordings/commerce-server' import {useSession} from 'next-auth/react' import {GetServerSideProps} from 'next' import {getToken} from 'next-auth/jwt' import Layout from 'components/app/layout' import {getSdk, prisma} from '@skillrecordings/database' import Link from 'next/link' -import {first, isString} from 'lodash' +import {isString} from 'lodash' import InviteTeam from '@skillrecordings/skill-lesson/team' import {InvoiceCard} from 'pages/invoices' -import MuxPlayer from '@mux/mux-player-react' import {SanityDocument} from '@sanity/client' import Image from 'next/legacy/image' import {trpc} from '../../trpc/trpc.client' import {Transfer} from 'purchase-transfer/purchase-transfer' import {getProduct} from 'lib/products' +import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' export const getServerSideProps: GetServerSideProps = async ({req, query}) => { - const {purchaseId: purchaseQueryParam, session_id, upgrade} = query + const {purchaseId: purchaseQueryParam, upgrade} = query + const session_id = + query.session_id instanceof Array ? query.session_id[0] : query.session_id const token = await getToken({req}) const {getPurchaseDetails} = getSdk() + const paymentProvider = paymentOptions.providers.stripe + let purchaseId = purchaseQueryParam - if (session_id) { - const {stripeChargeId} = await stripeData({ - checkoutSessionId: session_id as string, - }) + if (session_id && paymentProvider) { + const {chargeIdentifier} = await paymentProvider.getPurchaseInfo(session_id) + const purchase = await prisma.purchase.findFirst({ where: { merchantCharge: { - identifier: stripeChargeId, + identifier: chargeIdentifier, }, }, }) From 8d45f9c8acee568d3a4c62c11eec1a84cb07cf64 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Mon, 25 Mar 2024 14:20:09 -0500 Subject: [PATCH 09/17] feat(ew): remove last Stripe import --- apps/epic-web/src/pages/invoices/[merchantChargeId].tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/epic-web/src/pages/invoices/[merchantChargeId].tsx b/apps/epic-web/src/pages/invoices/[merchantChargeId].tsx index 9daf733fa..c44e1995b 100644 --- a/apps/epic-web/src/pages/invoices/[merchantChargeId].tsx +++ b/apps/epic-web/src/pages/invoices/[merchantChargeId].tsx @@ -1,10 +1,7 @@ import * as React from 'react' import {DownloadIcon} from '@heroicons/react/outline' -import {convertToSerializeForNextResponse} from '@skillrecordings/commerce-server' import {useLocalStorage} from 'react-use' import {GetServerSideProps} from 'next' -import {Coupon, MerchantProduct} from '@skillrecordings/database' -import {Stripe} from 'stripe' import fromUnixTime from 'date-fns/fromUnixTime' import Layout from 'components/app/layout' import format from 'date-fns/format' @@ -66,7 +63,9 @@ const Invoice: React.FC< const {charge, product, bulkCoupon, quantity} = chargeDetails.result - const customer = charge.customer as Stripe.Customer + const customer = z + .object({name: z.string().nullish(), email: z.string().nullish()}) + .parse(charge.customer) const formatUsd = (amount: number) => { return Intl.NumberFormat('en-US', { style: 'currency', From f1a2d5ed5a639f2367bb4729f11342302c9bdfcb Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Tue, 26 Mar 2024 13:52:12 -0500 Subject: [PATCH 10/17] feat(ew): use payment options in inngest funcs --- .../sanity/product/sanity-product-created.ts | 8 +++++-- .../sanity/product/sanity-product-deleted.ts | 8 +++++-- .../sanity/product/sanity-product-updated.ts | 8 +++++-- .../functions/stripe/webhook-received.ts | 23 +++++++++---------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts index 9d36b5ed0..d2709ab83 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts @@ -1,12 +1,12 @@ import {inngest} from 'inngest/inngest.server' import {v4} from 'uuid' import {prisma} from '@skillrecordings/database' -import {defaultContext as defaultStripeContext} from '@skillrecordings/stripe-sdk' import {loadSanityProduct} from './index' import {sanityWriteClient} from 'utils/sanity-server' import {SANITY_WEBHOOK_EVENT} from '../sanity-inngest-events' +import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' -const {stripe} = defaultStripeContext +const stripe = paymentOptions.providers.stripe?.paymentClient export const sanityProductCreated = inngest.createFunction( {id: `product-create`, name: 'Create Product in Database'}, @@ -15,6 +15,10 @@ export const sanityProductCreated = inngest.createFunction( if: 'event.data.event == "product.create"', }, async ({event, step}) => { + if (!stripe) { + throw new Error('Payment provider (Stripe) is missing') + } + const sanityProduct = await step.run('get sanity product', async () => { return loadSanityProduct(event.data._id) }) diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts index b1d165756..690bff6eb 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts @@ -1,9 +1,9 @@ import {inngest} from 'inngest/inngest.server' import {prisma} from '@skillrecordings/database' import {SANITY_WEBHOOK_EVENT} from '../sanity-inngest-events' -import {defaultContext as defaultStripeContext} from '@skillrecordings/stripe-sdk' +import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' -const {stripe} = defaultStripeContext +const stripe = paymentOptions.providers.stripe?.paymentClient export const sanityProductDeleted = inngest.createFunction( {id: `product-delete`, name: 'Deactivate Product in Database'}, @@ -12,6 +12,10 @@ export const sanityProductDeleted = inngest.createFunction( if: 'event.data.event == "product.delete"', }, async ({event, step}) => { + if (!stripe) { + throw new Error('Payment provider (Stripe) is missing') + } + const {productId} = event.data if (!productId) { diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts index 1240be456..bdbd8eff1 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts @@ -3,9 +3,9 @@ import {prisma} from '@skillrecordings/database' import {v4} from 'uuid' import {loadSanityProduct} from './index' import {SANITY_WEBHOOK_EVENT} from '../sanity-inngest-events' -import {defaultContext as defaultStripeContext} from '@skillrecordings/stripe-sdk' +import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' -const {stripe} = defaultStripeContext +const stripe = paymentOptions.providers.stripe?.paymentClient export const sanityProductUpdated = inngest.createFunction( { @@ -22,6 +22,10 @@ export const sanityProductUpdated = inngest.createFunction( if: 'event.data.event == "product.update"', }, async ({event, step}) => { + if (!stripe) { + throw new Error('Payment provider (Stripe) is missing') + } + const sanityProduct = await step.run('get sanity product', async () => { return loadSanityProduct(event.data._id) }) diff --git a/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts b/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts index 9f3a1a8aa..de13ca447 100644 --- a/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts +++ b/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts @@ -5,22 +5,21 @@ import {prisma} from '@skillrecordings/database' import {NonRetriableError} from 'inngest' import {postToSlack} from '@skillrecordings/skill-api' import {WebClient} from '@slack/web-api' -import { - defaultContext as defaultStripeContext, - Stripe, -} from '@skillrecordings/stripe-sdk' +import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' +import {z} from 'zod' -const {stripe} = defaultStripeContext +const stripe = paymentOptions.providers.stripe?.paymentClient -const redis = new Redis({ - url: process.env.UPSTASH_REDIS_REST_URL!, - token: process.env.UPSTASH_REDIS_REST_TOKEN!, -}) +const CustomerSchema = z.object({id: z.string(), email: z.string()}) export const stripeWebhookReceived = inngest.createFunction( {id: `stripe-webhook-received`, name: 'Stripe Webhook Received'}, {event: STRIPE_WEBHOOK_RECEIVED_EVENT}, async ({event, step}) => { + if (!stripe) { + throw new Error('Payment provider (Stripe) is missing') + } + const stripeAccountId = await step.run( 'get stripe account id', async () => { @@ -54,9 +53,9 @@ export const stripeWebhookReceived = inngest.createFunction( } const customer = await step.run('get customer', async () => { - return (await stripe.customers.retrieve( - invoice.customer as string, - )) as Stripe.Customer + return CustomerSchema.parse( + await stripe.customers.retrieve(invoice.customer as string), + ) }) if (!customer) { From 8081b7f89a61c39fe543b7a549c31ccb1778c410 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Tue, 26 Mar 2024 17:52:38 -0500 Subject: [PATCH 11/17] feat(ew): use NonRetriableError if stripe missing --- .../inngest/functions/sanity/product/sanity-product-created.ts | 3 ++- .../inngest/functions/sanity/product/sanity-product-deleted.ts | 3 ++- .../inngest/functions/sanity/product/sanity-product-updated.ts | 3 ++- apps/epic-web/src/inngest/functions/stripe/webhook-received.ts | 3 +-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts index d2709ab83..e93cfe3f1 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-created.ts @@ -5,6 +5,7 @@ import {loadSanityProduct} from './index' import {sanityWriteClient} from 'utils/sanity-server' import {SANITY_WEBHOOK_EVENT} from '../sanity-inngest-events' import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' +import {NonRetriableError} from 'inngest' const stripe = paymentOptions.providers.stripe?.paymentClient @@ -16,7 +17,7 @@ export const sanityProductCreated = inngest.createFunction( }, async ({event, step}) => { if (!stripe) { - throw new Error('Payment provider (Stripe) is missing') + throw new NonRetriableError('Payment provider (Stripe) is missing') } const sanityProduct = await step.run('get sanity product', async () => { diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts index 690bff6eb..7d24b8d3c 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-deleted.ts @@ -2,6 +2,7 @@ import {inngest} from 'inngest/inngest.server' import {prisma} from '@skillrecordings/database' import {SANITY_WEBHOOK_EVENT} from '../sanity-inngest-events' import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' +import {NonRetriableError} from 'inngest' const stripe = paymentOptions.providers.stripe?.paymentClient @@ -13,7 +14,7 @@ export const sanityProductDeleted = inngest.createFunction( }, async ({event, step}) => { if (!stripe) { - throw new Error('Payment provider (Stripe) is missing') + throw new NonRetriableError('Payment provider (Stripe) is missing') } const {productId} = event.data diff --git a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts index bdbd8eff1..d47ca1ea5 100644 --- a/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts +++ b/apps/epic-web/src/inngest/functions/sanity/product/sanity-product-updated.ts @@ -4,6 +4,7 @@ import {v4} from 'uuid' import {loadSanityProduct} from './index' import {SANITY_WEBHOOK_EVENT} from '../sanity-inngest-events' import {paymentOptions} from 'pages/api/skill/[...skillRecordings]' +import {NonRetriableError} from 'inngest' const stripe = paymentOptions.providers.stripe?.paymentClient @@ -23,7 +24,7 @@ export const sanityProductUpdated = inngest.createFunction( }, async ({event, step}) => { if (!stripe) { - throw new Error('Payment provider (Stripe) is missing') + throw new NonRetriableError('Payment provider (Stripe) is missing') } const sanityProduct = await step.run('get sanity product', async () => { diff --git a/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts b/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts index de13ca447..c3d87d2f3 100644 --- a/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts +++ b/apps/epic-web/src/inngest/functions/stripe/webhook-received.ts @@ -1,6 +1,5 @@ import {inngest} from 'inngest/inngest.server' import {STRIPE_WEBHOOK_RECEIVED_EVENT} from '@skillrecordings/inngest' -import {Redis} from '@upstash/redis' import {prisma} from '@skillrecordings/database' import {NonRetriableError} from 'inngest' import {postToSlack} from '@skillrecordings/skill-api' @@ -17,7 +16,7 @@ export const stripeWebhookReceived = inngest.createFunction( {event: STRIPE_WEBHOOK_RECEIVED_EVENT}, async ({event, step}) => { if (!stripe) { - throw new Error('Payment provider (Stripe) is missing') + throw new NonRetriableError('Payment provider (Stripe) is missing') } const stripeAccountId = await step.run( From fe59538d26715b9dca932af8dd32e407da1229be Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Thu, 28 Mar 2024 09:30:41 +0100 Subject: [PATCH 12/17] fix(ewd): display logic for live event banner --- packages/skill-lesson/lib/products.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/skill-lesson/lib/products.ts b/packages/skill-lesson/lib/products.ts index 0b2762ce0..c2c84a7ae 100644 --- a/packages/skill-lesson/lib/products.ts +++ b/packages/skill-lesson/lib/products.ts @@ -55,15 +55,15 @@ export const getProducts = async (productIds: string[]) => { export const getAllProducts = async () => { const products = await sanityClient.fetch( - groq`*[_type == 'product'][]{ + groq`*[_type == 'product'][] | order(_createdAt desc){ _id, + _createdAt, title, description, productId, state, type, "slug": slug.current, - _id, image { url, alt From 50c86d0e7595eba991f1b61a096787bda62b410a Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Thu, 28 Mar 2024 10:54:54 +0100 Subject: [PATCH 13/17] fix(ewd): event time zone madness --- .../epic-web/src/templates/event-template.tsx | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/apps/epic-web/src/templates/event-template.tsx b/apps/epic-web/src/templates/event-template.tsx index c63ab80cd..fd0f94b12 100644 --- a/apps/epic-web/src/templates/event-template.tsx +++ b/apps/epic-web/src/templates/event-template.tsx @@ -4,7 +4,6 @@ import MuxPlayer from '@mux/mux-player-react' import {EventJsonLd} from '@skillrecordings/next-seo' import {useRouter} from 'next/router' import Balancer from 'react-wrap-balancer' -import {format} from 'date-fns' import {formatInTimeZone} from 'date-fns-tz' import Image from 'next/image' import Share from 'components/share' @@ -18,19 +17,14 @@ import { LocationMarkerIcon, } from '@heroicons/react/outline' import {getOgImage} from 'utils/get-og-image' -import { - PriceCheckProvider, - usePriceCheck, -} from '@skillrecordings/skill-lesson/path-to-purchase/pricing-check-context' +import {PriceCheckProvider} from '@skillrecordings/skill-lesson/path-to-purchase/pricing-check-context' import {Pricing} from '@skillrecordings/skill-lesson/path-to-purchase/pricing' -import {trpc} from 'trpc/trpc.client' import type { CommerceProps, SanityProduct, } from '@skillrecordings/commerce-server/dist/@types' import {useCoupon} from '@skillrecordings/skill-lesson/path-to-purchase/use-coupon' import Link from 'next/link' -import {cn} from '@skillrecordings/ui/utils/cn' import pluralize from 'pluralize' const EventTemplate: React.FC< @@ -221,6 +215,7 @@ export const EventDetails: React.FC<{ event: Event }> = ({event}) => { const {startsAt, endsAt, timezone, events, image} = event + const eventDate = startsAt && `${formatInTimeZone( @@ -249,25 +244,31 @@ export const EventDetails: React.FC<{ events && events.reduce((acc: any, event) => { const {title, startsAt, endsAt} = event - const formattedDate = `${format( - new Date(startsAt), - 'MMMM d, yyyy', - // 'America/Los_Angeles', - )}` - const startTime = `${formatInTimeZone( + const formattedDate = `${formatInTimeZone( new Date(startsAt), 'America/Los_Angeles', - 'ha', + 'MMMM d, yyyy', )}` - const endTime = `${formatInTimeZone( - new Date(endsAt), - 'America/Los_Angeles', - 'ha', - )}` + const startsAtDate = new Date(startsAt) + const endsAtDate = new Date(endsAt) + + // Since the original time is in UTC and we want to display it in PT (which is 2 hours ahead ofUTC), + // we can simply add 2 hours to the UTC hours. + startsAtDate.setUTCHours(startsAtDate.getUTCHours() + 2) + endsAtDate.setUTCHours(endsAtDate.getUTCHours() + 2) - const timeRange = `${startTime}-${endTime}` + const startsAtHour = startsAtDate.getUTCHours() + const endsAtHour = endsAtDate.getUTCHours() + const formattedStartsAtTime = `${startsAtHour % 12 || 12}${ + startsAtHour < 12 ? 'AM' : 'PM' + }` + const formattedEndsAtTime = `${endsAtHour % 12 || 12}${ + endsAtHour < 12 ? 'AM' : 'PM' + }` + + const timeRange = `${formattedStartsAtTime}-${formattedEndsAtTime}` if (!acc[title]) { acc[title] = {dates: [formattedDate], time: timeRange} } else { @@ -303,9 +304,9 @@ export const EventDetails: React.FC<{ {title} -
    - {' '} -
    {formattedDates}
    +
    + {' '} +
    {formattedDates} (Pacific time)
    {' '} @@ -314,6 +315,18 @@ export const EventDetails: React.FC<{
  • ) })} + {timezone && ( + + )}
) : (
From fca6cba85f2ed96f0e1b4717d7c8e9d6473c9c1e Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Thu, 28 Mar 2024 11:57:18 +0100 Subject: [PATCH 14/17] fix(ewd): event time zone --- .../epic-web/src/templates/event-template.tsx | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/apps/epic-web/src/templates/event-template.tsx b/apps/epic-web/src/templates/event-template.tsx index fd0f94b12..24b33097c 100644 --- a/apps/epic-web/src/templates/event-template.tsx +++ b/apps/epic-web/src/templates/event-template.tsx @@ -215,7 +215,7 @@ export const EventDetails: React.FC<{ event: Event }> = ({event}) => { const {startsAt, endsAt, timezone, events, image} = event - + const PT = 'America/Los_Angeles' const eventDate = startsAt && `${formatInTimeZone( @@ -251,24 +251,11 @@ export const EventDetails: React.FC<{ 'MMMM d, yyyy', )}` - const startsAtDate = new Date(startsAt) - const endsAtDate = new Date(endsAt) - - // Since the original time is in UTC and we want to display it in PT (which is 2 hours ahead ofUTC), - // we can simply add 2 hours to the UTC hours. - startsAtDate.setUTCHours(startsAtDate.getUTCHours() + 2) - endsAtDate.setUTCHours(endsAtDate.getUTCHours() + 2) - - const startsAtHour = startsAtDate.getUTCHours() - const endsAtHour = endsAtDate.getUTCHours() - const formattedStartsAtTime = `${startsAtHour % 12 || 12}${ - startsAtHour < 12 ? 'AM' : 'PM' - }` - const formattedEndsAtTime = `${endsAtHour % 12 || 12}${ - endsAtHour < 12 ? 'AM' : 'PM' - }` - - const timeRange = `${formattedStartsAtTime}-${formattedEndsAtTime}` + const timeRange = `${formatInTimeZone( + new Date(startsAt), + PT, + 'ha', + )}-${formatInTimeZone(new Date(endsAt), PT, 'ha')}` if (!acc[title]) { acc[title] = {dates: [formattedDate], time: timeRange} } else { From 789a6a49c6f00ab41c80f5ebda50609997ed6e3e Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Thu, 28 Mar 2024 12:14:50 +0100 Subject: [PATCH 15/17] fix(ewd): event time zone --- apps/epic-web/schemas/documents/event.tsx | 8 ++++---- apps/epic-web/src/pages/events/index.tsx | 14 +++++++++----- apps/epic-web/src/templates/event-template.tsx | 17 ++++++----------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/apps/epic-web/schemas/documents/event.tsx b/apps/epic-web/schemas/documents/event.tsx index 10486b73f..f55e6f082 100644 --- a/apps/epic-web/schemas/documents/event.tsx +++ b/apps/epic-web/schemas/documents/event.tsx @@ -30,7 +30,7 @@ export default defineType({ }), defineField({ name: 'startsAt', - title: 'Starts at (Pacific time)', + title: 'Starts at (in UTC)', description: 'When does the event start?', type: 'datetime', options: { @@ -42,7 +42,7 @@ export default defineType({ }), defineField({ name: 'endsAt', - title: 'Ends at (Pacific time)', + title: 'Ends at (in UTC)', description: 'When does the event end?', type: 'datetime', options: { @@ -71,7 +71,7 @@ export default defineType({ }), defineField({ name: 'startsAt', - title: 'Starts at (Pacific time)', + title: 'Starts at (in UTC)', description: 'When does the event start?', type: 'datetime', options: { @@ -83,7 +83,7 @@ export default defineType({ }), defineField({ name: 'endsAt', - title: 'Ends at (Pacific time)', + title: 'Ends at (in UTC)', description: 'When does the event end?', type: 'datetime', options: { diff --git a/apps/epic-web/src/pages/events/index.tsx b/apps/epic-web/src/pages/events/index.tsx index 29c64e227..6e2dca8fc 100644 --- a/apps/epic-web/src/pages/events/index.tsx +++ b/apps/epic-web/src/pages/events/index.tsx @@ -17,6 +17,7 @@ import Spinner from 'components/spinner' import {cn} from '@skillrecordings/ui/utils/cn' import Icon from 'components/icons' import {IS_PAST_CONF_24} from 'pages/conf' +import {formatInTimeZone} from 'date-fns-tz' export const getStaticProps: GetStaticProps = async (context) => { const events = await getAllEvents() @@ -41,6 +42,7 @@ const Events: React.FC<{events: Event[]}> = ({events}) => { ? new Date(event.events[0].startsAt) > new Date() : false, ) + const PT = 'America/Los_Angeles' return ( = ({events}) => { const {title, image, slug, description, startsAt, endsAt, state} = event const eventDate = - startsAt && `${format(new Date(startsAt), 'MMMM d, yyyy')}` + startsAt && + `${formatInTimeZone(new Date(startsAt), PT, 'MMMM d, yyyy')}` const eventTime = startsAt && endsAt && - `${format(new Date(startsAt), 'h:mm a')} — ${format( - new Date(endsAt), + `${formatInTimeZone( + new Date(startsAt), + PT, 'h:mm a', - )}` + )} — ${formatInTimeZone(new Date(endsAt), PT, 'h:mm a')}` const {data: availability} = trpc.products.getQuantityAvailableById.useQuery( @@ -166,7 +170,7 @@ const Events: React.FC<{events: Event[]}> = ({events}) => {
{eventDate} -
{eventTime} (PST)
+
{eventTime} (PT)
diff --git a/apps/epic-web/src/templates/event-template.tsx b/apps/epic-web/src/templates/event-template.tsx index 24b33097c..748447a91 100644 --- a/apps/epic-web/src/templates/event-template.tsx +++ b/apps/epic-web/src/templates/event-template.tsx @@ -217,21 +217,16 @@ export const EventDetails: React.FC<{ const {startsAt, endsAt, timezone, events, image} = event const PT = 'America/Los_Angeles' const eventDate = - startsAt && - `${formatInTimeZone( - new Date(startsAt), - 'America/Los_Angeles', - 'MMMM d, yyyy', - )}` + startsAt && `${formatInTimeZone(new Date(startsAt), PT, 'MMMM d, yyyy')}` const eventTime = startsAt && endsAt && - `${formatInTimeZone( - new Date(startsAt), - 'America/Los_Angeles', + `${formatInTimeZone(new Date(startsAt), PT, 'h:mm a')} — ${formatInTimeZone( + new Date(endsAt), + PT, 'h:mm a', - )} — ${formatInTimeZone(new Date(endsAt), 'America/Los_Angeles', 'h:mm a')}` + )}` interface GroupedEvents { [title: string]: { @@ -247,7 +242,7 @@ export const EventDetails: React.FC<{ const formattedDate = `${formatInTimeZone( new Date(startsAt), - 'America/Los_Angeles', + PT, 'MMMM d, yyyy', )}` From 357b521c2c9e4f497f5dde869e00b7601a20eb9e Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Thu, 28 Mar 2024 12:28:35 +0100 Subject: [PATCH 16/17] fix(ewd): event time zone --- apps/epic-web/src/pages/events/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/epic-web/src/pages/events/index.tsx b/apps/epic-web/src/pages/events/index.tsx index 6e2dca8fc..dd9521484 100644 --- a/apps/epic-web/src/pages/events/index.tsx +++ b/apps/epic-web/src/pages/events/index.tsx @@ -181,17 +181,19 @@ const Events: React.FC<{events: Event[]}> = ({events}) => {
- {format( + {formatInTimeZone( new Date(event.events[0].startsAt), + PT, 'MMMM d, yyyy', )} {' – '} - {format( + {formatInTimeZone( new Date( event.events[ event.events.length - 1 ].endsAt, ), + PT, 'MMMM d, yyyy', )} From 2a7b5022f1ce319f625a51bd122e8a5e048992bf Mon Sep 17 00:00:00 2001 From: Vojta Holik Date: Thu, 28 Mar 2024 13:01:28 +0100 Subject: [PATCH 17/17] fix(proaws): font import --- apps/pro-aws/src/styles/globals.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pro-aws/src/styles/globals.css b/apps/pro-aws/src/styles/globals.css index c02deb4e0..42a796d5c 100644 --- a/apps/pro-aws/src/styles/globals.css +++ b/apps/pro-aws/src/styles/globals.css @@ -1,3 +1,4 @@ +@import url('https://use.typekit.net/tjw2gng.css'); @tailwind base; @tailwind components; @tailwind utilities; @@ -20,7 +21,6 @@ @import './ui/button.css'; @import './login.css'; @import './commerce.css'; -@import url('https://use.typekit.net/tjw2gng.css'); @layer base { :root {