Skip to main content
Glama
rate.tsx5.3 kB
'use client'; import { cn } from '@/lib/utils'; import { buttonVariants } from 'fumadocs-ui/components/ui/button'; import { ThumbsDown, ThumbsUp } from 'lucide-react'; import { type SyntheticEvent, useEffect, useState, useTransition } from 'react'; import { Collapsible, CollapsibleContent, } from 'fumadocs-ui/components/ui/collapsible'; import { cva } from 'class-variance-authority'; import { usePathname } from 'next/navigation'; const rateButtonVariants = cva( 'inline-flex items-center gap-2 px-3 py-2 rounded-full font-medium border text-sm [&_svg]:size-4 disabled:cursor-not-allowed', { variants: { active: { true: 'bg-fd-accent text-fd-accent-foreground [&_svg]:fill-current', false: 'text-fd-muted-foreground', }, }, }, ); export interface Feedback { opinion: 'good' | 'bad'; url?: string; message: string; } export interface ActionResponse { githubUrl: string; } interface Result extends Feedback { response?: ActionResponse; } export function Rate({ onRateAction, }: { onRateAction: (url: string, feedback: Feedback) => Promise<ActionResponse>; }) { const url = usePathname(); const [previous, setPrevious] = useState<Result | null>(null); const [opinion, setOpinion] = useState<'good' | 'bad' | null>(null); const [message, setMessage] = useState(''); const [isPending, startTransition] = useTransition(); useEffect(() => { const item = localStorage.getItem(`docs-feedback-${url}`); if (item === null) return; setPrevious(JSON.parse(item) as Result); }, [url]); useEffect(() => { const key = `docs-feedback-${url}`; if (previous) localStorage.setItem(key, JSON.stringify(previous)); else localStorage.removeItem(key); }, [previous, url]); function submit(e?: SyntheticEvent) { if (opinion == null) return; startTransition(async () => { const feedback: Feedback = { opinion, message, }; void onRateAction(url, feedback).then((response) => { setPrevious({ response, ...feedback, }); setMessage(''); setOpinion(null); }); }); e?.preventDefault(); } const activeOpinion = previous?.opinion ?? opinion; return ( <Collapsible open={opinion !== null || previous !== null} onOpenChange={(v) => { if (!v) setOpinion(null); }} className="border-y py-3" > <div className="flex flex-row items-center gap-2"> <p className="text-sm font-medium pe-2">How is this guide?</p> <button disabled={previous !== null} className={cn( rateButtonVariants({ active: activeOpinion === 'good', }), )} onClick={() => { setOpinion('good'); }} > <ThumbsUp /> Good </button> <button disabled={previous !== null} className={cn( rateButtonVariants({ active: activeOpinion === 'bad', }), )} onClick={() => { setOpinion('bad'); }} > <ThumbsDown /> Bad </button> </div> <CollapsibleContent className="mt-3"> {previous ? ( <div className="px-3 py-6 flex flex-col items-center gap-3 bg-fd-card text-fd-muted-foreground text-sm text-center rounded-xl"> <p>Thank you for your feedback!</p> <div className="flex flex-row items-center gap-2"> <a href={previous.response?.githubUrl} rel="noreferrer noopener" target="_blank" className={cn( buttonVariants({ color: 'primary', }), 'text-xs', )} > View on GitHub </a> <button className={cn( buttonVariants({ color: 'secondary', }), 'text-xs', )} onClick={() => { setOpinion(previous.opinion); setPrevious(null); }} > Submit Again </button> </div> </div> ) : ( <form className="flex flex-col gap-3" onSubmit={submit}> <textarea autoFocus required value={message} onChange={(e) => setMessage(e.target.value)} className="border rounded-lg bg-fd-secondary text-fd-secondary-foreground p-3 resize-none focus-visible:outline-none placeholder:text-fd-muted-foreground" placeholder="Leave your feedback..." onKeyDown={(e) => { if (!e.shiftKey && e.key === 'Enter') { submit(e); } }} /> <button type="submit" className={cn(buttonVariants({ color: 'outline' }), 'w-fit px-3')} disabled={isPending} > Submit </button> </form> )} </CollapsibleContent> </Collapsible> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcpauth/mcpauth'

If you have feedback or need assistance with the MCP directory API, please join our Discord server