Openstatus www.openstatus.dev

chore: improve home and pricing (#614)

authored by

Maximilian Kaske and committed by
GitHub
76168b30 7f1e2779

+195 -48
+2 -1
apps/web/contentlayer.config.ts
··· 3 3 import remarkGfm from "remark-gfm"; 4 4 5 5 import { Changelog } from "./src/contentlayer/documents/changelog"; 6 + import { FAQ } from "./src/contentlayer/documents/faq"; 6 7 import { LegalPost } from "./src/contentlayer/documents/legal"; 7 8 import { Post } from "./src/contentlayer/documents/post"; 8 9 import autolinkHeadings from "./src/contentlayer/plugins/autolink-headings"; ··· 10 11 11 12 export default makeSource({ 12 13 contentDirPath: "src/content/", 13 - documentTypes: [Post, LegalPost, Changelog], 14 + documentTypes: [Post, LegalPost, Changelog, FAQ], 14 15 mdx: { 15 16 remarkPlugins: [remarkGfm], 16 17 rehypePlugins: [rehypeSlug, prettyCode, autolinkHeadings],
+5
apps/web/src/app/(redirect)/cal/page.tsx
··· 1 + import { redirect } from "next/navigation"; 2 + 3 + export default function CalRedirect() { 4 + return redirect("http://cal.com/team/openstatus/30min"); 5 + }
+4 -2
apps/web/src/app/page.tsx
··· 1 1 import { MarketingLayout } from "@/components/layout/marketing-layout"; 2 2 import { AlertCard } from "@/components/marketing/alert/card"; 3 - import { FAQs } from "@/components/marketing/faqs"; 3 + import { BottomCTA } from "@/components/marketing/bottom-cta"; 4 4 import { Hero } from "@/components/marketing/hero"; 5 + import { LatestChangelogs } from "@/components/marketing/lastest-changelogs"; 5 6 import { MonitoringCard } from "@/components/marketing/monitor/card"; 6 7 import { Partners } from "@/components/marketing/partners"; 7 8 import { Stats } from "@/components/marketing/stats"; ··· 19 20 <Stats /> 20 21 <StatusPageCard /> 21 22 <AlertCard /> 22 - <FAQs /> 23 + <BottomCTA /> 24 + <LatestChangelogs /> 23 25 </div> 24 26 </MarketingLayout> 25 27 );
+2
apps/web/src/app/pricing/page.tsx
··· 2 2 3 3 import { Shell } from "@/components/dashboard/shell"; 4 4 import { MarketingLayout } from "@/components/layout/marketing-layout"; 5 + import { FAQs } from "@/components/marketing/faqs"; 5 6 import { EnterpricePlan } from "@/components/marketing/pricing/enterprice-plan"; 6 7 import { PricingWrapper } from "@/components/marketing/pricing/pricing-wrapper"; 7 8 ··· 31 32 <Shell> 32 33 <EnterpricePlan /> 33 34 </Shell> 35 + <FAQs /> 34 36 </div> 35 37 </MarketingLayout> 36 38 );
+1 -1
apps/web/src/components/content/mdx.tsx
··· 11 11 12 12 return ( 13 13 // FIXME: weird behaviour when `prose-headings:font-cal` and on mouse movement font gets bigger 14 - <div className="prose prose-neutral dark:prose-invert prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-img:rounded-lg prose-img:border prose-img:border-border"> 14 + <div className="prose prose-slate dark:prose-invert prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-img:rounded-lg prose-img:border prose-img:border-border"> 15 15 <MDXComponent components={{ ...components }} /> 16 16 </div> 17 17 );
+24
apps/web/src/components/marketing/bottom-cta.tsx
··· 1 + import Link from "next/link"; 2 + 3 + import { Button } from "@openstatus/ui"; 4 + 5 + export function BottomCTA() { 6 + return ( 7 + <div className="my-8 flex flex-col items-center justify-between gap-6"> 8 + <p className="text-muted-foreground max-w-lg text-center text-lg"> 9 + Learn over time how your services are performing, and inform your users 10 + when there are issues. 11 + </p> 12 + <div className="flex gap-2"> 13 + <Button className="rounded-full" asChild> 14 + <Link href="/app/sign-up">Start for Free</Link> 15 + </Button> 16 + <Button className="rounded-full" variant="outline" asChild> 17 + <Link href="/cal" target="_blank"> 18 + Schedule a Demo 19 + </Link> 20 + </Button> 21 + </div> 22 + </div> 23 + ); 24 + }
+2 -2
apps/web/src/components/marketing/card.tsx
··· 8 8 9 9 export function CardContainer({ children }: { children: React.ReactNode }) { 10 10 return ( 11 - <Shell className="grid gap-6 bg-gradient-to-br from-[hsl(var(--muted))] from-0% to-transparent to-20%"> 11 + <Shell className="flex flex-col gap-6 bg-gradient-to-br from-[hsl(var(--muted))] from-0% to-transparent to-20%"> 12 12 {children} 13 13 </Shell> 14 14 ); ··· 40 40 } 41 41 42 42 export function CardDescription({ children }: { children: React.ReactNode }) { 43 - return <p className="text-muted-foreground mt-2">{children}</p>; 43 + return <p className="text-muted-foreground text-center">{children}</p>; 44 44 } 45 45 46 46 export function CardContent({
+34 -41
apps/web/src/components/marketing/faqs.tsx
··· 1 + "use client"; 2 + 3 + import { allFAQs } from "contentlayer/generated"; 4 + 1 5 import { 2 6 Accordion, 3 7 AccordionContent, ··· 5 9 AccordionTrigger, 6 10 } from "@openstatus/ui"; 7 11 8 - import { Shell } from "@/components/dashboard/shell"; 9 - 10 - // REMINDER: we can create a contentlayer document and the faq into it 11 - const faqsConfig: Record<"q" | "a", string>[] = [ 12 - { 13 - q: "What are the limits?", 14 - a: "You will start with a free plan by default which includes a total of <strong>3 monitors</strong> and <strong>1 status</strong> page as well as cron jobs of either <code>10m</code>, <code>30m</code> or <code>1h</code>.<br />Learn more about our <a href='/pricing'>pricing</a>.", 15 - }, 16 - { 17 - q: "Who are we?", 18 - a: "We are <a href='https://twitter.com/thibaultleouay' target='_blank'>Thibault</a> and <a href='https://twitter.com/mxkaske' target='_blank'>Max</a>.", 19 - }, 20 - { 21 - q: "How does it work?", 22 - a: "We ping your endpoints from multiple regions to calculate uptime. We display the status on your status page.", 23 - }, 24 - { 25 - q: "What regions do we support?", 26 - a: "We support one region for each continent to allow multi-regions monitoring.", 27 - }, 28 - { 29 - q: "How can I help?", 30 - a: "You can star our project on <a href='https://github.com/openstatusHQ/openstatus'>GitHub</a>, or contribute to it. Or you can also become a <strong>Pro</strong> user.", 31 - }, 32 - ]; 12 + import { Mdx } from "../content/mdx"; 13 + import { 14 + CardContainer, 15 + CardDescription, 16 + CardHeader, 17 + CardIcon, 18 + CardTitle, 19 + } from "./card"; 33 20 34 21 export function FAQs() { 22 + const faqs = allFAQs.sort((a, b) => a.order - b.order); 35 23 return ( 36 - <Shell className="grid gap-1"> 37 - <h2 className="text-foreground font-cal text-center text-2xl">FAQ</h2> 38 - <Accordion type="single" collapsible className="w-full"> 39 - {faqsConfig.map(({ q, a }, i) => ( 40 - <AccordionItem key={i} value={`item-${i}`}> 41 - <AccordionTrigger>{q}</AccordionTrigger> 42 - <AccordionContent> 43 - <div 44 - className="prose dark:prose-invert prose-sm" 45 - dangerouslySetInnerHTML={{ __html: a }} 46 - /> 47 - </AccordionContent> 48 - </AccordionItem> 49 - ))} 50 - </Accordion> 51 - </Shell> 24 + <CardContainer> 25 + <CardHeader> 26 + <CardIcon icon="message-circle" /> 27 + <CardTitle>FAQs</CardTitle> 28 + <CardDescription> 29 + What you want to know about OpenStatus. 30 + </CardDescription> 31 + </CardHeader> 32 + <div> 33 + <Accordion type="single" collapsible className="w-full"> 34 + {faqs.map((faq, i) => ( 35 + <AccordionItem key={i} value={`item-${i}`}> 36 + <AccordionTrigger>{faq.title}</AccordionTrigger> 37 + <AccordionContent> 38 + <Mdx code={faq.body.code} /> 39 + </AccordionContent> 40 + </AccordionItem> 41 + ))} 42 + </Accordion> 43 + </div> 44 + </CardContainer> 52 45 ); 53 46 }
+54
apps/web/src/components/marketing/lastest-changelogs.tsx
··· 1 + import Link from "next/link"; 2 + import { allChangelogs } from "contentlayer/generated"; 3 + 4 + import { Button } from "@openstatus/ui"; 5 + 6 + import { formatDate } from "@/lib/utils"; 7 + import { 8 + CardContainer, 9 + CardDescription, 10 + CardHeader, 11 + CardIcon, 12 + CardTitle, 13 + } from "./card"; 14 + 15 + export function LatestChangelogs() { 16 + const latestChangelogs = allChangelogs 17 + .sort((a, b) => (a.publishedAt > b.publishedAt ? -1 : 1)) 18 + .slice(0, 4); 19 + 20 + return ( 21 + <CardContainer> 22 + <CardHeader> 23 + <CardIcon icon="zap" /> 24 + <CardTitle>We ship</CardTitle> 25 + <CardDescription> 26 + Check out the changelog to see our latest features. 27 + </CardDescription> 28 + </CardHeader> 29 + <ul className="mx-auto w-full max-w-xs"> 30 + {latestChangelogs.map((changelog) => ( 31 + <li 32 + key={changelog.slug} 33 + className="border-accent group relative grid gap-2 border-l-2 px-4 py-2" 34 + > 35 + <Link href={`/changelog/${changelog.slug}`}> 36 + <div className="bg-border group-hover:bg-muted-foreground absolute -left-1.5 top-4 h-2.5 w-2.5 rounded-full" /> 37 + <p className="text-muted-foreground"> 38 + {formatDate(new Date(changelog.publishedAt))} 39 + </p> 40 + <p className="line-clamp-1 text-lg font-medium"> 41 + {changelog.title} 42 + </p> 43 + </Link> 44 + </li> 45 + ))} 46 + </ul> 47 + <div className="flex justify-center"> 48 + <Button className="rounded-full" asChild> 49 + <Link href="/changelog">Full changelog</Link> 50 + </Button> 51 + </div> 52 + </CardContainer> 53 + ); 54 + }
+1 -1
apps/web/src/config/features.ts
··· 91 91 }, 92 92 { 93 93 icon: "zap", 94 - catchline: "Escalatation.", 94 + catchline: "Escalation.", 95 95 description: "Notify and escalate an alert to the right team member.", 96 96 badge: "Coming soon", 97 97 },
+9
apps/web/src/content/faq/about-us.mdx
··· 1 + --- 2 + title: Who are we? 3 + order: 2 4 + --- 5 + 6 + {/* TODO: write markdown */} 7 + 8 + We are <a href='https://twitter.com/thibaultleouay' target='_blank'>Thibault</a> 9 + and <a href='https://twitter.com/mxkaske' target='_blank'>Max</a>.
+7
apps/web/src/content/faq/functioning.mdx
··· 1 + --- 2 + title: How does it work? 3 + order: 3 4 + --- 5 + 6 + We ping your endpoints from multiple regions to calculate uptime. We display the 7 + status on your status page.
+10
apps/web/src/content/faq/help.mdx
··· 1 + --- 2 + title: How can I help? 3 + order: 5 4 + --- 5 + 6 + {/* TODO: write markdown */} 7 + 8 + You can star our project on 9 + <a href="https://github.com/openstatusHQ/openstatus">GitHub</a>, or contribute 10 + to it. Or you can also become a <strong>Pro</strong> user.
+11
apps/web/src/content/faq/limits.mdx
··· 1 + --- 2 + title: What are the limits? 3 + order: 1 4 + --- 5 + 6 + {/* TODO: write markdown */} 7 + 8 + You will start with a free plan by default which includes a total of <strong>3 9 + monitors</strong> and <strong>1 status</strong> page as well as cron jobs of 10 + either <code>10m</code>, <code>30m</code> or <code>1h</code>.<br />Learn more 11 + about our <a href='/pricing'>pricing</a>.
+6
apps/web/src/content/faq/supported-regions.mdx
··· 1 + --- 2 + title: What regions do we support? 3 + order: 4 4 + --- 5 + 6 + We support one region for each continent to allow multi-regions monitoring.
+23
apps/web/src/contentlayer/documents/faq.ts
··· 1 + import { defineDocumentType } from "contentlayer/source-files"; 2 + 3 + export const FAQ = defineDocumentType(() => ({ 4 + name: "FAQ", 5 + filePathPattern: `faq/*.mdx`, 6 + contentType: "mdx", 7 + fields: { 8 + title: { 9 + type: "string", 10 + required: true, 11 + }, 12 + order: { 13 + type: "number", 14 + required: true, 15 + }, 16 + }, 17 + computedFields: { 18 + slug: { 19 + type: "string", 20 + resolve: (post) => post._raw.sourceFileName.replace(/\.mdx$/, ""), 21 + }, 22 + }, 23 + }));