Skip to content

Commit

Permalink
finish podcast app page
Browse files Browse the repository at this point in the history
  • Loading branch information
bmorrisondev committed May 19, 2024
1 parent 07bacc2 commit 6850439
Show file tree
Hide file tree
Showing 9 changed files with 685 additions and 6 deletions.
331 changes: 328 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
"@brianmmdev/clerk-webhooks-handler": "^1.0.3",
"@clerk/nextjs": "^5.0.3",
"@hookform/resolvers": "^3.4.0",
"@notionhq/client": "^2.2.15",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toggle": "^1.0.3",
"class-variance-authority": "^0.7.0",
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function RootLayout({
}>) {
return (
<ClerkProvider>
<html lang="en">
<html lang="en" className="dark">
<PHProvider>
<body
className={`${inter.className} px-4 py-8 max-w-screen-lg xl:px-0 mx-auto`}
Expand Down
2 changes: 0 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"use client";

import JoinButton from "./lib/components/JoinButton";
import UiCard from "./lib/components/UiCard";

Expand Down
110 changes: 110 additions & 0 deletions src/app/podcast/apply/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use server'

import { Client } from '@notionhq/client'
import { CreatePageParameters } from '@notionhq/client/build/src/api-endpoints'

export type NotionRecord = {
yourName: string
email?: string
twitter?: string
productName?: string
productType?: string[]
productUrl?: string
productDescription?: string
}

export async function addRecordToNotion(record: NotionRecord) {
const notion = new Client({
auth: process.env.NOTION_KEY
})

const databaseId = process.env.NOTION_PODAPPS_DBID

const types = []
if(record.productType?.includes("webapp")) {
types.push("Web app")
}
if(record.productType?.includes("mobileapp")) {
types.push("Mobile app")
}
if(record.productType?.includes("desktopapp")) {
types.push("Desktop app")
}
if(record.productType?.includes("devtools")) {
types.push("Dev tools")
}

const props: CreatePageParameters = {
parent: {
database_id: databaseId as string
},
properties: {
"Name": {
title: [
{
text: {
content: record.yourName as string
}
}
]
},
}
}

if(record.email) {
props.properties["Email"] = {
email: record.email
}
}

if (record.twitter) {
props.properties["Twitter"] = {
rich_text: [
{
text: {
content: record.twitter
}
}
]
}
}

if (record.productName) {
props.properties["Product name"] = {
rich_text: [
{
text: {
content: record.productName
}
}
]
}
}

if (record.productDescription) {
props.properties["Product description"] = {
rich_text: [
{
text: {
content: record.productDescription
}
}
]
}
}

if (record.productUrl) {
props.properties["Product URL"] = {
url: record.productUrl
}
}

if (types.length > 0) {
props.properties["Product type"] = {
multi_select: types.map(type => ({ name: type }))
}
}


let res = await notion.pages.create(props)
}
150 changes: 150 additions & 0 deletions src/app/podcast/apply/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
'use client'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import React, { FormEvent, useState } from 'react'
import { NotionRecord, addRecordToNotion } from './actions'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { RadioGroup, RadioGroupItem } from '@radix-ui/react-radio-group'
import { Textarea } from '@/components/ui/textarea'
import toast from 'react-hot-toast'
import { useRouter } from 'next/navigation'

function Page() {
const router = useRouter()

const [isLoading, setIsLoading] = useState(false)

async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
try {
setIsLoading(true)
const formData = new FormData(event.currentTarget)
let data: NotionRecord = {
yourName: formData.get('name') as string,
email: formData.get('email') as string,
twitter: formData.get('twitter') as string,
productName: formData.get('productName') as string,
productUrl: formData.get('productUrl') as string,
productType: ['webapp', 'mobileapp', 'desktopapp', 'devtools'].filter((type) => formData.get(type) === 'on'),
productDescription: formData.get('productDescription') as string,
}
await addRecordToNotion(data)
router.push('/podcast/apply/success')
} catch(err) {
console.error(err)
toast.error("Something went wrong! Reach out to @brianmmdev on Twitter for help.")
} finally {
setIsLoading(false)
}
}

return (
<section className="space-y-8">
<header className="flex flex-col gap-2">
<h1>Apply to be on the podcast</h1>
<p className="lg:text-lg text-balance md:max-w-prose">
The goal of the fullstack.chat podcast is to share the stories of developers building amazing web, desktop, or mobile applications. We want to hear about your journey, the challenges you've faced, and the lessons you've learned along the way.
</p>
</header>

<form onSubmit={onSubmit} className='flex flex-col gap-2' >
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="name">Your full name</Label>
<Input type="text" id="name" name="name" placeholder="Your full name" disabled={isLoading} />
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="email">Email</Label>
<Input type="email" id="email" name="email" placeholder="Email" disabled={isLoading} />
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="twitter">Twitter/X handle</Label>
<Input type="text" id="twitter" name="twitter" placeholder="@fullstackchat" disabled={isLoading} />
</div>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label className="mb-1">Type of product</Label>
<div className="items-top flex space-x-2">
<Checkbox disabled={isLoading} id="webapp" name='webapp' />
<div className="grid gap-1.5 leading-none">
<Label htmlFor="webapp" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" >
Web
</Label>
</div>
</div>
<div className="items-top flex space-x-2">
<Checkbox disabled={isLoading} id="mobileapp" name='mobileapp' />
<div className="grid gap-1.5 leading-none">
<Label htmlFor="mobileapp" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" >
Mobile
</Label>
</div>
</div>
<div className="items-top flex space-x-2">
<Checkbox disabled={isLoading} id="desktopapp" name='desktopapp' />
<div className="grid gap-1.5 leading-none">
<Label htmlFor="desktopapp" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" >
Desktop
</Label>
</div>
</div>
<div className="items-top flex space-x-2">
<Checkbox disabled={isLoading} id="devtools" name='devtools' />
<div className="grid gap-1.5 leading-none">
<Label htmlFor="devtools" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" >
Dev tools
</Label>
</div>
</div>
</div>

<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="productName">Product name</Label>
<Input type="text" id="productName" name="productName" placeholder="Product name" disabled={isLoading} />
</div>

<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="productUrl">Product URL</Label>
<Input type="text" id="productUrl" name="productUrl" placeholder="Product URL" disabled={isLoading} />
</div>

{/* <div className="grid w-full max-w-sm items-center gap-1.5">
<RadioGroup defaultValue="planning" name='phase'>
<div className="flex items-center space-x-2">
<RadioGroupItem value="planning" id="planning" />
<Label htmlFor="planning">Planing</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="indev" id="indev" />
<Label htmlFor="indev">In development</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="launched" id="launched" />
<Label htmlFor="launched">Launched</Label>
</div>
</RadioGroup>
</div> */}

{/* <div className="grid w-full max-w-sm items-center gap-1.5">
tech used (make this a multiselect and save to tags)
</div> */}

<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="productDescription">Product description</Label>
<Textarea placeholder="Product description" id="productDescription" name="productDescription" disabled={isLoading} />
<p className="text-sm text-muted-foreground">
What does your product do? What problem does it solve?
</p>

</div>

<div className='flex'>
<Button type='submit' disabled={isLoading}>
{ isLoading ? 'Submitting...' : 'Submit'}
</Button>
</div>
</form>
</section>
)
}

export default Page
19 changes: 19 additions & 0 deletions src/app/podcast/apply/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import JoinButton from '@/app/lib/components/JoinButton'
import React from 'react'

function Page() {
return (

<header className="flex flex-col gap-2">
<h1>Thanks for applying!</h1>
<p className="lg:text-lg text-balance md:max-w-prose">
If we feel you're a good fit for the show, we'll reach out to schedule! In the meantime, join the Discord if you aren't already a member and introduce yourself!
</p>
<div className='flex'>
<JoinButton />
</div>
</header>
)
}

export default Page
30 changes: 30 additions & 0 deletions src/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client"

import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"

import { cn } from "@/lib/utils"

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

export { Checkbox }
44 changes: 44 additions & 0 deletions src/components/ui/radio-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client"

import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"

import { cn } from "@/lib/utils"

const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName

const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName

export { RadioGroup, RadioGroupItem }

0 comments on commit 6850439

Please sign in to comment.