Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit f87f96f

Browse files
committed
feedback form and seo title fixes
1 parent 969d3e7 commit f87f96f

File tree

8 files changed

+229
-14
lines changed

8 files changed

+229
-14
lines changed

contentlayer.config.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,6 @@ const Doc = defineDocumentType(() => ({
4242
type: 'string',
4343
resolve: (doc) => doc._raw.flattenedPath,
4444
},
45-
seoTitle: {
46-
type: 'string',
47-
resolve: (doc) =>
48-
doc._raw.flattenedPath
49-
? doc._raw.flattenedPath
50-
.split('/')
51-
.slice(-2)
52-
.map((p) => title(p))
53-
.join(': ')
54-
: 'Welcome to Nitric Docs',
55-
},
5645
toc: { type: 'json', resolve: (doc) => extractTocHeadings(doc.body.raw) },
5746
editUrl: {
5847
type: 'string',

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@radix-ui/react-icons": "^1.3.0",
3333
"@radix-ui/react-label": "^2.1.0",
3434
"@radix-ui/react-navigation-menu": "^1.2.0",
35+
"@radix-ui/react-radio-group": "^1.2.0",
3536
"@radix-ui/react-scroll-area": "^1.1.0",
3637
"@radix-ui/react-select": "^2.1.1",
3738
"@radix-ui/react-separator": "^1.1.0",

src/actions/sendFeedback.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use server'
2+
3+
import { PrismaClient } from '@prisma/client'
4+
5+
const prisma = new PrismaClient()
6+
7+
export async function sendFeedback(prevState: any, formData: FormData) {
8+
const answer = formData.get('choice') || ''
9+
const comment = formData.get('comment') || ''
10+
const ua = formData.get('ua') || ''
11+
const url = formData.get('url') || ''
12+
13+
console.log('sendFeedback', { answer, comment, ua, url })
14+
15+
// disable on non prod
16+
if (process.env.NEXT_PUBLIC_VERCEL_ENV !== 'production') {
17+
return { message: 'Not available on production' }
18+
}
19+
20+
// validate answer
21+
if (!['yes', 'no', 'feedback'].includes(answer?.toString())) {
22+
return { message: 'invalid' }
23+
}
24+
25+
try {
26+
await prisma.feedback.create({
27+
data: {
28+
url: url?.toString(),
29+
answer: answer?.toString(),
30+
comment: comment?.toString(),
31+
label: 'is docs page helpful',
32+
ua: ua?.toString(),
33+
},
34+
})
35+
36+
return { message: 'Feedback sent!' }
37+
} catch (error) {
38+
console.error(error)
39+
return { message: 'failed to store feedback' }
40+
}
41+
}

src/app/[[...slug]]/layout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Feedback } from '@/components/Feedback'
77
import { Button } from '@/components/ui/button'
88
import { GitHubIcon } from '@/components/icons/GitHubIcon'
99
import Breadcrumbs from '@/components/Breadcrumbs'
10+
import FeedbackForm from '@/components/FeedbackForm'
1011

1112
export default function DocLayout({
1213
children,
@@ -25,7 +26,7 @@ export default function DocLayout({
2526
return (
2627
<article className="mx-auto flex h-full max-w-7xl flex-col gap-y-10 px-4 pb-10 pt-16">
2728
<div className="relative grid grid-cols-12 gap-10">
28-
<div className="col-span-12 w-full flex-auto space-y-10 md:col-span-9">
29+
<div className="col-span-12 w-full flex-auto space-y-20 md:col-span-9">
2930
<div>
3031
<Breadcrumbs doc={doc} className="mb-4" />
3132
<Prose>{children}</Prose>
@@ -44,7 +45,7 @@ export default function DocLayout({
4445
</Button>
4546
</div>
4647
<div className="w-full">
47-
<Feedback />
48+
<FeedbackForm />
4849
</div>
4950
<div className="text-2xs text-muted-foreground">
5051
Last updated on{' '}

src/app/[[...slug]]/page.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import MDXContent from '@/components/MDXContent'
33
import { Metadata } from 'next/types'
44
import { BASE_URL } from '@/lib/constants'
55
import { allDocs } from '@/content'
6+
import { getNavInfo } from '@/lib/getNavInfo'
7+
import { title } from 'radash'
68

79
export async function generateMetadata({
810
params,
@@ -32,8 +34,18 @@ export async function generateMetadata({
3234
]
3335
}
3436

37+
const { navItem } = getNavInfo(doc)
38+
39+
let seoTitle = doc.slug
40+
? `${title(doc.slug.split('/')[0])}: ${navItem?.title}`
41+
: 'Welcome to Nitric Docs'
42+
43+
if (navItem && navItem.breadcrumbParentItem?.title) {
44+
seoTitle = `${navItem.breadcrumbParentItem.title}: ${navItem.title}`
45+
}
46+
3547
return {
36-
title: doc.seoTitle,
48+
title: seoTitle,
3749
description: doc.description,
3850
openGraph: {
3951
siteName: 'Nitric Docs',

src/components/FeedbackForm.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { Button } from './ui/button'
5+
import { sendFeedback } from '@/actions/sendFeedback'
6+
import { RadioGroup } from './ui/radio-group'
7+
import { Label } from './ui/label'
8+
import { RadioGroupItem } from '@radix-ui/react-radio-group'
9+
import { useFormState } from 'react-dom'
10+
import { CheckIcon } from './icons/CheckIcon'
11+
import { usePathname } from 'next/navigation'
12+
13+
const choices = [
14+
{
15+
value: 'yes',
16+
label: 'It was helpful',
17+
emoji: '🤩',
18+
},
19+
{
20+
value: 'no',
21+
label: 'It was not helpful',
22+
emoji: '😓',
23+
},
24+
{
25+
value: 'feedback',
26+
label: 'I have feedback',
27+
emoji: '📣',
28+
},
29+
]
30+
31+
const initialState = {
32+
message: '',
33+
}
34+
35+
const FeedbackForm = () => {
36+
const [state, formAction] = useFormState(sendFeedback, initialState)
37+
const [selected, setSelected] = useState(false)
38+
const pathname = usePathname()
39+
40+
return state.message ? (
41+
<div className="flex items-center gap-x-2 text-sm">
42+
<CheckIcon className="h-5 w-5 flex-none fill-green-500 stroke-white dark:fill-green-200/20 dark:stroke-green-200" />
43+
Thank you for your feedback! 🙌
44+
</div>
45+
) : (
46+
<form
47+
action={formAction}
48+
className="flex flex-col items-start justify-start gap-6"
49+
>
50+
<input
51+
name="ua"
52+
value={navigator.userAgent}
53+
className="hidden"
54+
readOnly
55+
/>
56+
<input
57+
name="url"
58+
value={`/docs${pathname}`}
59+
className="hidden"
60+
readOnly
61+
/>
62+
<Label htmlFor="choice" className="text-sm text-zinc-900 dark:text-white">
63+
What did you think of this content?
64+
</Label>
65+
<RadioGroup
66+
className="flex flex-col gap-2 md:flex-row"
67+
onChange={() => {
68+
if (!selected) setSelected(true)
69+
}}
70+
name="choice"
71+
id="choice"
72+
>
73+
{choices.map(({ label, value, emoji }) => (
74+
<div key={value} className="group flex items-center">
75+
<RadioGroupItem value={value} id={value} className="group">
76+
<span className="zincscale group-checked:zincscale-0 group-data-[state=checked]:zincscale-0 mr-2">
77+
{emoji}
78+
</span>
79+
</RadioGroupItem>
80+
<Label htmlFor={value} className="cursor-pointer text-xs">
81+
{label}
82+
</Label>
83+
</div>
84+
))}
85+
</RadioGroup>
86+
{selected && (
87+
<div className="flex w-full max-w-[400px] flex-col gap-4 rounded-lg p-2 text-zinc-700 shadow-md ring-1 ring-zinc-300 dark:bg-white/2.5 dark:text-zinc-100 dark:ring-white/10 dark:hover:shadow-black/5">
88+
<label htmlFor="comment" className="sr-only">
89+
Comment
90+
</label>
91+
<div>
92+
<textarea
93+
rows={5}
94+
name="comment"
95+
id="comment"
96+
className="block w-full rounded-md border-0 bg-white p-2 py-1.5 text-zinc-900 shadow-sm ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-primary-600 dark:bg-zinc-700/70 dark:text-zinc-50 dark:ring-zinc-600 sm:text-sm sm:leading-6"
97+
placeholder="We'd love to hear your feedback!"
98+
defaultValue={''}
99+
autoFocus
100+
/>
101+
</div>
102+
<div className="mt-2 flex justify-end">
103+
<Button type="submit">Send</Button>
104+
</div>
105+
</div>
106+
)}
107+
</form>
108+
)
109+
}
110+
111+
export default FeedbackForm

src/components/ui/radio-group.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
import { CheckIcon } from '@radix-ui/react-icons'
5+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
6+
7+
import { cn } from '@/lib/utils'
8+
9+
const RadioGroup = React.forwardRef<
10+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
11+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
12+
>(({ className, ...props }, ref) => {
13+
return (
14+
<RadioGroupPrimitive.Root
15+
className={cn('grid gap-2', className)}
16+
{...props}
17+
ref={ref}
18+
/>
19+
)
20+
})
21+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22+
23+
const RadioGroupItem = React.forwardRef<
24+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
25+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
26+
>(({ className, ...props }, ref) => {
27+
return (
28+
<RadioGroupPrimitive.Item
29+
ref={ref}
30+
className={cn(
31+
'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
32+
className,
33+
)}
34+
{...props}
35+
>
36+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
37+
<CheckIcon className="h-3.5 w-3.5 fill-primary" />
38+
</RadioGroupPrimitive.Indicator>
39+
</RadioGroupPrimitive.Item>
40+
)
41+
})
42+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43+
44+
export { RadioGroup, RadioGroupItem }

yarn.lock

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,22 @@
14331433
dependencies:
14341434
"@radix-ui/react-slot" "1.1.0"
14351435

1436+
"@radix-ui/react-radio-group@^1.2.0":
1437+
version "1.2.0"
1438+
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.2.0.tgz#f937dd6b9436ded80c4bebdf3901c20cb8bcbb5a"
1439+
integrity sha512-yv+oiLaicYMBpqgfpSPw6q+RyXlLdIpQWDHZbUKURxe+nEh53hFXPPlfhfQQtYkS5MMK/5IWIa76SksleQZSzw==
1440+
dependencies:
1441+
"@radix-ui/primitive" "1.1.0"
1442+
"@radix-ui/react-compose-refs" "1.1.0"
1443+
"@radix-ui/react-context" "1.1.0"
1444+
"@radix-ui/react-direction" "1.1.0"
1445+
"@radix-ui/react-presence" "1.1.0"
1446+
"@radix-ui/react-primitive" "2.0.0"
1447+
"@radix-ui/react-roving-focus" "1.1.0"
1448+
"@radix-ui/react-use-controllable-state" "1.1.0"
1449+
"@radix-ui/react-use-previous" "1.1.0"
1450+
"@radix-ui/react-use-size" "1.1.0"
1451+
14361452
"@radix-ui/[email protected]":
14371453
version "1.1.0"
14381454
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"

0 commit comments

Comments
 (0)