diff --git a/apps/web/content-collections.ts b/apps/web/content-collections.ts index 72ee6430a4..1671923c75 100644 --- a/apps/web/content-collections.ts +++ b/apps/web/content-collections.ts @@ -419,6 +419,7 @@ const shortcuts = defineCollection({ description: z.string(), category: z.string(), prompt: z.string(), + targets: z.array(z.string()).optional(), }), transform: async (document, context) => { const mdx = await compileMDX(context, document, { diff --git a/apps/web/content/shortcuts/action-items.mdx b/apps/web/content/shortcuts/action-items.mdx index 747b1c3b48..0939d36065 100644 --- a/apps/web/content/shortcuts/action-items.mdx +++ b/apps/web/content/shortcuts/action-items.mdx @@ -3,6 +3,11 @@ title: "Extract Action Items" description: "Pull out all action items, tasks, and commitments from the meeting" category: "Productivity" prompt: "You are a meticulous executive assistant. Review the meeting transcript and extract all action items, tasks, and commitments. For each item, identify: 1) The specific task or action 2) Who is responsible (if mentioned) 3) The deadline or timeframe (if mentioned) 4) Any dependencies or blockers. Format the output as a clear, prioritized list that can be easily copied into a task management system. Flag any items that seem urgent or time-sensitive." +targets: + - "Team Meetings" + - "Project Kickoffs" + - "Sprint Planning" + - "1:1s" --- ## When to use diff --git a/apps/web/content/shortcuts/customer-concerns.mdx b/apps/web/content/shortcuts/customer-concerns.mdx index c7dfa03574..baa3d54bb9 100644 --- a/apps/web/content/shortcuts/customer-concerns.mdx +++ b/apps/web/content/shortcuts/customer-concerns.mdx @@ -3,6 +3,11 @@ title: "Customer Concerns Analysis" description: "Identify and analyze customer concerns, objections, and pain points from the conversation" category: "Customer Success" prompt: "You are an expert customer success manager. Analyze this conversation and identify all customer concerns, objections, and pain points. For each concern: 1) Clearly state the concern 2) Assess its severity (high/medium/low) 3) Identify the root cause if apparent 4) Note how it was addressed in the conversation (if at all) 5) Suggest follow-up actions to resolve or mitigate the concern. Prioritize concerns by their potential impact on the customer relationship or deal." +targets: + - "Customer Calls" + - "Support Conversations" + - "Sales Meetings" + - "QBRs" --- ## When to use diff --git a/apps/web/content/shortcuts/decision-summary.mdx b/apps/web/content/shortcuts/decision-summary.mdx index cb868a85ac..fea97b3924 100644 --- a/apps/web/content/shortcuts/decision-summary.mdx +++ b/apps/web/content/shortcuts/decision-summary.mdx @@ -3,6 +3,11 @@ title: "Decision Summary" description: "Extract and document all decisions made during the meeting" category: "Productivity" prompt: "You are a skilled meeting facilitator and documenter. Review the meeting transcript and identify all decisions that were made. For each decision: 1) State the decision clearly and concisely 2) Note who made or approved the decision 3) Capture the reasoning or context behind the decision 4) Identify any conditions, caveats, or dependencies 5) Note any dissenting opinions or concerns raised. Format the output as a clear decision log that can be shared with stakeholders or referenced in the future." +targets: + - "Strategy Meetings" + - "Planning Sessions" + - "Board Meetings" + - "Leadership Syncs" --- ## When to use diff --git a/apps/web/content/shortcuts/follow-up-email.mdx b/apps/web/content/shortcuts/follow-up-email.mdx index da86799a90..8db1d2ddd2 100644 --- a/apps/web/content/shortcuts/follow-up-email.mdx +++ b/apps/web/content/shortcuts/follow-up-email.mdx @@ -3,6 +3,11 @@ title: "Draft Follow-up Email" description: "Generate a professional follow-up email based on the meeting discussion" category: "Communication" prompt: "You are an expert business communicator. Based on the meeting transcript, draft a professional follow-up email. The email should: 1) Thank participants for their time 2) Summarize the key points discussed 3) Confirm any decisions made 4) List action items with owners and deadlines 5) Propose next steps or a follow-up meeting if appropriate. Keep the tone professional but warm, and make the email concise and scannable. Include a clear subject line suggestion." +targets: + - "Client Meetings" + - "Sales Calls" + - "Team Syncs" + - "Interviews" --- ## When to use diff --git a/apps/web/content/shortcuts/meeting-insights.mdx b/apps/web/content/shortcuts/meeting-insights.mdx index 81d2703673..a2bf0323ac 100644 --- a/apps/web/content/shortcuts/meeting-insights.mdx +++ b/apps/web/content/shortcuts/meeting-insights.mdx @@ -3,6 +3,11 @@ title: "Meeting Insights" description: "Get deep insights and analysis from your meeting conversation" category: "Analysis" prompt: "You are a strategic business analyst. Analyze this meeting transcript and provide deep insights. Focus on: 1) Key themes and topics discussed 2) Sentiment and tone of the conversation 3) Areas of agreement and disagreement 4) Underlying concerns or motivations that may not have been explicitly stated 5) Opportunities or risks identified 6) Recommendations for follow-up. Provide your analysis in a structured format that helps the reader quickly understand the dynamics and outcomes of the meeting." +targets: + - "Strategy Meetings" + - "Negotiations" + - "Stakeholder Calls" + - "Executive Reviews" --- ## When to use diff --git a/apps/web/content/shortcuts/sales-feedback.mdx b/apps/web/content/shortcuts/sales-feedback.mdx index a5e48dba79..2a6efc1959 100644 --- a/apps/web/content/shortcuts/sales-feedback.mdx +++ b/apps/web/content/shortcuts/sales-feedback.mdx @@ -3,6 +3,11 @@ title: "Sales Call Feedback" description: "Analyze your sales call and get actionable feedback on what you could have done better" category: "Sales" prompt: "You are an expert sales coach. Analyze the transcript of this sales call and provide constructive feedback. Focus on: 1) Discovery questions - were the right questions asked to understand the prospect's needs? 2) Objection handling - how well were concerns addressed? 3) Value proposition - was the value clearly communicated? 4) Next steps - were clear next steps established? 5) Rapport building - how was the relationship developed? Provide specific examples from the call and actionable suggestions for improvement." +targets: + - "Discovery Calls" + - "Demo Calls" + - "Negotiation Calls" + - "Closing Calls" --- ## When to use diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 481f05b66b..93cc5d2d11 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -156,10 +156,10 @@ export function Footer() {
  • - Templates + Prompt Gallery
  • diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index 48b0443f60..bfcde3d2a0 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -35,8 +35,8 @@ const featuresList = [ { to: "/product/ai-notetaking", label: "AI Notetaking" }, { to: "/product/ai-assistant", label: "AI Assistant" }, { to: "/product/mini-apps", label: "Mini Apps" }, + { to: "/gallery", label: "Templates & Shortcuts" }, { to: "/product/workflows", label: "Workflows", badge: "Coming Soon" }, - { to: "/templates", label: "Templates" }, ]; export function Header() { diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index dc77f2b4d4..c666662293 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -36,8 +36,10 @@ import { Route as ViewDocsRouteRouteImport } from './routes/_view/docs/route' import { Route as ViewCompanyHandbookRouteRouteImport } from './routes/_view/company-handbook/route' import { Route as ViewAppRouteRouteImport } from './routes/_view/app/route' import { Route as ViewTemplatesIndexRouteImport } from './routes/_view/templates/index' +import { Route as ViewShortcutsIndexRouteImport } from './routes/_view/shortcuts/index' import { Route as ViewRoadmapIndexRouteImport } from './routes/_view/roadmap/index' import { Route as ViewLegalIndexRouteImport } from './routes/_view/legal/index' +import { Route as ViewGalleryIndexRouteImport } from './routes/_view/gallery/index' import { Route as ViewDownloadIndexRouteImport } from './routes/_view/download/index' import { Route as ViewDocsIndexRouteImport } from './routes/_view/docs/index' import { Route as ViewCompanyHandbookIndexRouteImport } from './routes/_view/company-handbook/index' @@ -57,6 +59,7 @@ import { Route as ViewSolutionHealthcareRouteImport } from './routes/_view/solut import { Route as ViewSolutionGovernmentRouteImport } from './routes/_view/solution/government' import { Route as ViewSolutionFieldEngineeringRouteImport } from './routes/_view/solution/field-engineering' import { Route as ViewSolutionCustomerSuccessRouteImport } from './routes/_view/solution/customer-success' +import { Route as ViewShortcutsSlugRouteImport } from './routes/_view/shortcuts/$slug' import { Route as ViewRoadmapSlugRouteImport } from './routes/_view/roadmap/$slug' import { Route as ViewProductWorkflowsRouteImport } from './routes/_view/product/workflows' import { Route as ViewProductSelfHostingRouteImport } from './routes/_view/product/self-hosting' @@ -83,6 +86,7 @@ import { Route as ViewAppIntegrationRouteImport } from './routes/_view/app/integ import { Route as ViewAppFileTranscriptionRouteImport } from './routes/_view/app/file-transcription' import { Route as ViewAppCheckoutRouteImport } from './routes/_view/app/checkout' import { Route as ViewAppAccountRouteImport } from './routes/_view/app/account' +import { Route as ViewGalleryTypeSlugRouteImport } from './routes/_view/gallery/$type.$slug' const YoutubeRoute = YoutubeRouteImport.update({ id: '/youtube', @@ -219,6 +223,11 @@ const ViewTemplatesIndexRoute = ViewTemplatesIndexRouteImport.update({ path: '/templates/', getParentRoute: () => ViewRouteRoute, } as any) +const ViewShortcutsIndexRoute = ViewShortcutsIndexRouteImport.update({ + id: '/shortcuts/', + path: '/shortcuts/', + getParentRoute: () => ViewRouteRoute, +} as any) const ViewRoadmapIndexRoute = ViewRoadmapIndexRouteImport.update({ id: '/roadmap/', path: '/roadmap/', @@ -229,6 +238,11 @@ const ViewLegalIndexRoute = ViewLegalIndexRouteImport.update({ path: '/legal/', getParentRoute: () => ViewRouteRoute, } as any) +const ViewGalleryIndexRoute = ViewGalleryIndexRouteImport.update({ + id: '/gallery/', + path: '/gallery/', + getParentRoute: () => ViewRouteRoute, +} as any) const ViewDownloadIndexRoute = ViewDownloadIndexRouteImport.update({ id: '/download/', path: '/download/', @@ -328,6 +342,11 @@ const ViewSolutionCustomerSuccessRoute = path: '/solution/customer-success', getParentRoute: () => ViewRouteRoute, } as any) +const ViewShortcutsSlugRoute = ViewShortcutsSlugRouteImport.update({ + id: '/shortcuts/$slug', + path: '/shortcuts/$slug', + getParentRoute: () => ViewRouteRoute, +} as any) const ViewRoadmapSlugRoute = ViewRoadmapSlugRouteImport.update({ id: '/roadmap/$slug', path: '/roadmap/$slug', @@ -461,6 +480,11 @@ const ViewAppAccountRoute = ViewAppAccountRouteImport.update({ path: '/account', getParentRoute: () => ViewAppRouteRoute, } as any) +const ViewGalleryTypeSlugRoute = ViewGalleryTypeSlugRouteImport.update({ + id: '/gallery/$type/$slug', + path: '/gallery/$type/$slug', + getParentRoute: () => ViewRouteRoute, +} as any) export interface FileRoutesByFullPath { '/auth': typeof AuthRoute @@ -514,6 +538,7 @@ export interface FileRoutesByFullPath { '/product/self-hosting': typeof ViewProductSelfHostingRoute '/product/workflows': typeof ViewProductWorkflowsRoute '/roadmap/$slug': typeof ViewRoadmapSlugRoute + '/shortcuts/$slug': typeof ViewShortcutsSlugRoute '/solution/customer-success': typeof ViewSolutionCustomerSuccessRoute '/solution/field-engineering': typeof ViewSolutionFieldEngineeringRoute '/solution/government': typeof ViewSolutionGovernmentRoute @@ -533,9 +558,12 @@ export interface FileRoutesByFullPath { '/company-handbook/': typeof ViewCompanyHandbookIndexRoute '/docs/': typeof ViewDocsIndexRoute '/download': typeof ViewDownloadIndexRoute + '/gallery': typeof ViewGalleryIndexRoute '/legal': typeof ViewLegalIndexRoute '/roadmap': typeof ViewRoadmapIndexRoute + '/shortcuts': typeof ViewShortcutsIndexRoute '/templates': typeof ViewTemplatesIndexRoute + '/gallery/$type/$slug': typeof ViewGalleryTypeSlugRoute } export interface FileRoutesByTo { '/auth': typeof AuthRoute @@ -586,6 +614,7 @@ export interface FileRoutesByTo { '/product/self-hosting': typeof ViewProductSelfHostingRoute '/product/workflows': typeof ViewProductWorkflowsRoute '/roadmap/$slug': typeof ViewRoadmapSlugRoute + '/shortcuts/$slug': typeof ViewShortcutsSlugRoute '/solution/customer-success': typeof ViewSolutionCustomerSuccessRoute '/solution/field-engineering': typeof ViewSolutionFieldEngineeringRoute '/solution/government': typeof ViewSolutionGovernmentRoute @@ -605,9 +634,12 @@ export interface FileRoutesByTo { '/company-handbook': typeof ViewCompanyHandbookIndexRoute '/docs': typeof ViewDocsIndexRoute '/download': typeof ViewDownloadIndexRoute + '/gallery': typeof ViewGalleryIndexRoute '/legal': typeof ViewLegalIndexRoute '/roadmap': typeof ViewRoadmapIndexRoute + '/shortcuts': typeof ViewShortcutsIndexRoute '/templates': typeof ViewTemplatesIndexRoute + '/gallery/$type/$slug': typeof ViewGalleryTypeSlugRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -663,6 +695,7 @@ export interface FileRoutesById { '/_view/product/self-hosting': typeof ViewProductSelfHostingRoute '/_view/product/workflows': typeof ViewProductWorkflowsRoute '/_view/roadmap/$slug': typeof ViewRoadmapSlugRoute + '/_view/shortcuts/$slug': typeof ViewShortcutsSlugRoute '/_view/solution/customer-success': typeof ViewSolutionCustomerSuccessRoute '/_view/solution/field-engineering': typeof ViewSolutionFieldEngineeringRoute '/_view/solution/government': typeof ViewSolutionGovernmentRoute @@ -682,9 +715,12 @@ export interface FileRoutesById { '/_view/company-handbook/': typeof ViewCompanyHandbookIndexRoute '/_view/docs/': typeof ViewDocsIndexRoute '/_view/download/': typeof ViewDownloadIndexRoute + '/_view/gallery/': typeof ViewGalleryIndexRoute '/_view/legal/': typeof ViewLegalIndexRoute '/_view/roadmap/': typeof ViewRoadmapIndexRoute + '/_view/shortcuts/': typeof ViewShortcutsIndexRoute '/_view/templates/': typeof ViewTemplatesIndexRoute + '/_view/gallery/$type/$slug': typeof ViewGalleryTypeSlugRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -740,6 +776,7 @@ export interface FileRouteTypes { | '/product/self-hosting' | '/product/workflows' | '/roadmap/$slug' + | '/shortcuts/$slug' | '/solution/customer-success' | '/solution/field-engineering' | '/solution/government' @@ -759,9 +796,12 @@ export interface FileRouteTypes { | '/company-handbook/' | '/docs/' | '/download' + | '/gallery' | '/legal' | '/roadmap' + | '/shortcuts' | '/templates' + | '/gallery/$type/$slug' fileRoutesByTo: FileRoutesByTo to: | '/auth' @@ -812,6 +852,7 @@ export interface FileRouteTypes { | '/product/self-hosting' | '/product/workflows' | '/roadmap/$slug' + | '/shortcuts/$slug' | '/solution/customer-success' | '/solution/field-engineering' | '/solution/government' @@ -831,9 +872,12 @@ export interface FileRouteTypes { | '/company-handbook' | '/docs' | '/download' + | '/gallery' | '/legal' | '/roadmap' + | '/shortcuts' | '/templates' + | '/gallery/$type/$slug' id: | '__root__' | '/_view' @@ -888,6 +932,7 @@ export interface FileRouteTypes { | '/_view/product/self-hosting' | '/_view/product/workflows' | '/_view/roadmap/$slug' + | '/_view/shortcuts/$slug' | '/_view/solution/customer-success' | '/_view/solution/field-engineering' | '/_view/solution/government' @@ -907,9 +952,12 @@ export interface FileRouteTypes { | '/_view/company-handbook/' | '/_view/docs/' | '/_view/download/' + | '/_view/gallery/' | '/_view/legal/' | '/_view/roadmap/' + | '/_view/shortcuts/' | '/_view/templates/' + | '/_view/gallery/$type/$slug' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -1121,6 +1169,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ViewTemplatesIndexRouteImport parentRoute: typeof ViewRouteRoute } + '/_view/shortcuts/': { + id: '/_view/shortcuts/' + path: '/shortcuts' + fullPath: '/shortcuts' + preLoaderRoute: typeof ViewShortcutsIndexRouteImport + parentRoute: typeof ViewRouteRoute + } '/_view/roadmap/': { id: '/_view/roadmap/' path: '/roadmap' @@ -1135,6 +1190,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ViewLegalIndexRouteImport parentRoute: typeof ViewRouteRoute } + '/_view/gallery/': { + id: '/_view/gallery/' + path: '/gallery' + fullPath: '/gallery' + preLoaderRoute: typeof ViewGalleryIndexRouteImport + parentRoute: typeof ViewRouteRoute + } '/_view/download/': { id: '/_view/download/' path: '/download' @@ -1268,6 +1330,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ViewSolutionCustomerSuccessRouteImport parentRoute: typeof ViewRouteRoute } + '/_view/shortcuts/$slug': { + id: '/_view/shortcuts/$slug' + path: '/shortcuts/$slug' + fullPath: '/shortcuts/$slug' + preLoaderRoute: typeof ViewShortcutsSlugRouteImport + parentRoute: typeof ViewRouteRoute + } '/_view/roadmap/$slug': { id: '/_view/roadmap/$slug' path: '/roadmap/$slug' @@ -1450,6 +1519,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ViewAppAccountRouteImport parentRoute: typeof ViewAppRouteRoute } + '/_view/gallery/$type/$slug': { + id: '/_view/gallery/$type/$slug' + path: '/gallery/$type/$slug' + fullPath: '/gallery/$type/$slug' + preLoaderRoute: typeof ViewGalleryTypeSlugRouteImport + parentRoute: typeof ViewRouteRoute + } } } @@ -1548,6 +1624,7 @@ interface ViewRouteRouteChildren { ViewProductSelfHostingRoute: typeof ViewProductSelfHostingRoute ViewProductWorkflowsRoute: typeof ViewProductWorkflowsRoute ViewRoadmapSlugRoute: typeof ViewRoadmapSlugRoute + ViewShortcutsSlugRoute: typeof ViewShortcutsSlugRoute ViewSolutionCustomerSuccessRoute: typeof ViewSolutionCustomerSuccessRoute ViewSolutionFieldEngineeringRoute: typeof ViewSolutionFieldEngineeringRoute ViewSolutionGovernmentRoute: typeof ViewSolutionGovernmentRoute @@ -1562,9 +1639,12 @@ interface ViewRouteRouteChildren { ViewBlogIndexRoute: typeof ViewBlogIndexRoute ViewChangelogIndexRoute: typeof ViewChangelogIndexRoute ViewDownloadIndexRoute: typeof ViewDownloadIndexRoute + ViewGalleryIndexRoute: typeof ViewGalleryIndexRoute ViewLegalIndexRoute: typeof ViewLegalIndexRoute ViewRoadmapIndexRoute: typeof ViewRoadmapIndexRoute + ViewShortcutsIndexRoute: typeof ViewShortcutsIndexRoute ViewTemplatesIndexRoute: typeof ViewTemplatesIndexRoute + ViewGalleryTypeSlugRoute: typeof ViewGalleryTypeSlugRoute } const ViewRouteRouteChildren: ViewRouteRouteChildren = { @@ -1600,6 +1680,7 @@ const ViewRouteRouteChildren: ViewRouteRouteChildren = { ViewProductSelfHostingRoute: ViewProductSelfHostingRoute, ViewProductWorkflowsRoute: ViewProductWorkflowsRoute, ViewRoadmapSlugRoute: ViewRoadmapSlugRoute, + ViewShortcutsSlugRoute: ViewShortcutsSlugRoute, ViewSolutionCustomerSuccessRoute: ViewSolutionCustomerSuccessRoute, ViewSolutionFieldEngineeringRoute: ViewSolutionFieldEngineeringRoute, ViewSolutionGovernmentRoute: ViewSolutionGovernmentRoute, @@ -1614,9 +1695,12 @@ const ViewRouteRouteChildren: ViewRouteRouteChildren = { ViewBlogIndexRoute: ViewBlogIndexRoute, ViewChangelogIndexRoute: ViewChangelogIndexRoute, ViewDownloadIndexRoute: ViewDownloadIndexRoute, + ViewGalleryIndexRoute: ViewGalleryIndexRoute, ViewLegalIndexRoute: ViewLegalIndexRoute, ViewRoadmapIndexRoute: ViewRoadmapIndexRoute, + ViewShortcutsIndexRoute: ViewShortcutsIndexRoute, ViewTemplatesIndexRoute: ViewTemplatesIndexRoute, + ViewGalleryTypeSlugRoute: ViewGalleryTypeSlugRoute, } const ViewRouteRouteWithChildren = ViewRouteRoute._addFileChildren( diff --git a/apps/web/src/routes/_view/gallery/$type.$slug.tsx b/apps/web/src/routes/_view/gallery/$type.$slug.tsx new file mode 100644 index 0000000000..084d4222df --- /dev/null +++ b/apps/web/src/routes/_view/gallery/$type.$slug.tsx @@ -0,0 +1,399 @@ +import { MDXContent } from "@content-collections/mdx/react"; +import { Icon } from "@iconify-icon/react"; +import { createFileRoute, Link, notFound } from "@tanstack/react-router"; +import { allShortcuts, allTemplates } from "content-collections"; + +import { cn } from "@hypr/utils"; + +import { DownloadButton } from "@/components/download-button"; + +type GalleryType = "template" | "shortcut"; + +export const Route = createFileRoute("/_view/gallery/$type/$slug")({ + component: Component, + loader: async ({ params }) => { + const { type, slug } = params; + + if (type !== "template" && type !== "shortcut") { + throw notFound(); + } + + if (type === "template") { + const template = allTemplates.find((t) => t.slug === slug); + if (!template) { + throw notFound(); + } + return { type: "template" as const, item: template }; + } else { + const shortcut = allShortcuts.find((s) => s.slug === slug); + if (!shortcut) { + throw notFound(); + } + return { type: "shortcut" as const, item: shortcut }; + } + }, + head: ({ loaderData }) => { + if (!loaderData) return { meta: [] }; + + const { type, item } = loaderData; + const typeLabel = type === "template" ? "Template" : "Shortcut"; + const url = `https://hyprnote.com/gallery/${type}/${item.slug}`; + + const ogImageUrl = `https://hyprnote.com/og?type=gallery&title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(item.category)}${item.description ? `&description=${encodeURIComponent(item.description)}` : ""}`; + + return { + meta: [ + { title: `${item.title} - ${typeLabel} - Hyprnote` }, + { name: "description", content: item.description }, + { + property: "og:title", + content: `${item.title} - ${typeLabel}`, + }, + { property: "og:description", content: item.description }, + { property: "og:type", content: "article" }, + { property: "og:url", content: url }, + { property: "og:image", content: ogImageUrl }, + { name: "twitter:card", content: "summary_large_image" }, + { + name: "twitter:title", + content: `${item.title} - ${typeLabel}`, + }, + { name: "twitter:description", content: item.description }, + { name: "twitter:image", content: ogImageUrl }, + ], + }; + }, +}); + +function Component() { + const data = Route.useLoaderData(); + const { type, item } = data; + + return ( +
    +
    +
    + + + +
    +
    +
    + ); +} + +function LeftSidebar({ + type, + item, +}: { + type: GalleryType; + item: (typeof allTemplates)[0] | (typeof allShortcuts)[0]; +}) { + return ( + + ); +} + +function MainContent({ + type, + item, +}: { + type: GalleryType; + item: (typeof allTemplates)[0] | (typeof allShortcuts)[0]; +}) { + return ( +
    +
    + + + Back to gallery + +
    + + + + + +
    + ); +} + +function ItemHeader({ + type, + item, +}: { + type: GalleryType; + item: (typeof allTemplates)[0] | (typeof allShortcuts)[0]; +}) { + const isTemplate = type === "template"; + + return ( +
    +
    + + {isTemplate ? "Template" : "Shortcut"} + + {item.category} +
    +

    + {item.title} +

    +

    + {item.description} +

    + + {isTemplate && "targets" in item && item.targets && ( +
    + {item.targets.map((target) => ( + + {target} + + ))} +
    + )} +
    + ); +} + +function ItemContent({ + type, + item, +}: { + type: GalleryType; + item: (typeof allTemplates)[0] | (typeof allShortcuts)[0]; +}) { + const isTemplate = type === "template"; + + return ( +
    +

    + {isTemplate ? "Structure" : "Details"} +

    +
    + {isTemplate && "sections" in item && ( +
    +

    + Template Sections +

    +
    + {item.sections.map((section, index) => ( +
    +
    + + {index + 1} + +

    + {section.title} +

    +
    +

    + {section.description} +

    +
    + ))} +
    +
    + )} + +
    + +
    +
    +
    + ); +} + +function SuggestedItems({ + type, + item, +}: { + type: GalleryType; + item: (typeof allTemplates)[0] | (typeof allShortcuts)[0]; +}) { + const suggestedItems = + type === "template" + ? allTemplates.filter( + (t) => t.category === item.category && t.slug !== item.slug, + ) + : allShortcuts.filter( + (s) => s.category === item.category && s.slug !== item.slug, + ); + + if (suggestedItems.length === 0) return null; + + return ( +
    +

    + Other {item.category} {type === "template" ? "templates" : "shortcuts"} +

    +
    + {suggestedItems.map((t) => ( + +

    + {t.title} +

    +

    + {t.description} +

    + + ))} +
    +
    + ); +} + +function ItemFooter({ type }: { type: GalleryType }) { + return ( + + ); +} + +function RightSidebar({ + type, + item, +}: { + type: GalleryType; + item: (typeof allTemplates)[0] | (typeof allShortcuts)[0]; +}) { + const isTemplate = type === "template"; + const contentDir = isTemplate ? "templates" : "shortcuts"; + const rawMdxUrl = `https://github.com/fastrepl/hyprnote/blob/main/apps/web/content/${contentDir}/${item.slug}.mdx?plain=1`; + + return ( + + ); +} diff --git a/apps/web/src/routes/_view/gallery/index.tsx b/apps/web/src/routes/_view/gallery/index.tsx new file mode 100644 index 0000000000..34cacf1466 --- /dev/null +++ b/apps/web/src/routes/_view/gallery/index.tsx @@ -0,0 +1,583 @@ +import { Icon } from "@iconify-icon/react"; +import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; +import { allShortcuts, allTemplates } from "content-collections"; +import { CircleHelp } from "lucide-react"; +import { useMemo, useState } from "react"; + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@hypr/ui/components/ui/tooltip"; +import { cn } from "@hypr/utils"; + +import { DownloadButton } from "@/components/download-button"; +import { SlashSeparator } from "@/components/slash-separator"; + +type GalleryType = "template" | "shortcut"; + +type GallerySearch = { + type?: GalleryType; + category?: string; +}; + +export const Route = createFileRoute("/_view/gallery/")({ + component: Component, + validateSearch: (search: Record): GallerySearch => { + return { + type: + search.type === "template" || search.type === "shortcut" + ? search.type + : undefined, + category: + typeof search.category === "string" ? search.category : undefined, + }; + }, + head: () => ({ + meta: [ + { title: "Templates & Shortcuts Gallery - Hyprnote" }, + { + name: "description", + content: + "Discover our library of AI meeting templates and shortcuts. Get structured summaries, extract action items, and more with Hyprnote's AI-powered tools.", + }, + { + property: "og:title", + content: "Templates & Shortcuts Gallery - Hyprnote", + }, + { + property: "og:description", + content: + "Browse our collection of AI meeting templates and shortcuts. From engineering standups to sales discovery calls, find the perfect tool for your workflow.", + }, + { property: "og:type", content: "website" }, + { property: "og:url", content: "https://hyprnote.com/gallery" }, + ], + }), +}); + +type GalleryItem = + | { type: "template"; item: (typeof allTemplates)[0] } + | { type: "shortcut"; item: (typeof allShortcuts)[0] }; + +function Component() { + const navigate = useNavigate({ from: Route.fullPath }); + const search = Route.useSearch(); + const [searchQuery, setSearchQuery] = useState(""); + + const selectedType = search.type || null; + const selectedCategory = search.category || null; + + const setSelectedType = (type: GalleryType | null) => { + navigate({ + search: { + type: type || undefined, + category: selectedCategory || undefined, + }, + resetScroll: false, + }); + }; + + const setSelectedCategory = (category: string | null) => { + navigate({ + search: { + type: selectedType || undefined, + category: category || undefined, + }, + resetScroll: false, + }); + }; + + const allItems: GalleryItem[] = useMemo(() => { + const templates: GalleryItem[] = allTemplates.map((t) => ({ + type: "template" as const, + item: t, + })); + const shortcuts: GalleryItem[] = allShortcuts.map((s) => ({ + type: "shortcut" as const, + item: s, + })); + return [...templates, ...shortcuts]; + }, []); + + const itemsByCategory = useMemo(() => { + return allItems.reduce( + (acc, item) => { + const category = item.item.category; + if (!acc[category]) { + acc[category] = []; + } + acc[category].push(item); + return acc; + }, + {} as Record, + ); + }, [allItems]); + + const categories = Object.keys(itemsByCategory).sort(); + + const filteredItems = useMemo(() => { + let items = allItems; + + if (selectedType) { + items = items.filter((i) => i.type === selectedType); + } + + if (selectedCategory) { + items = items.filter((i) => i.item.category === selectedCategory); + } + + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase(); + items = items.filter( + (i) => + i.item.title.toLowerCase().includes(query) || + i.item.description.toLowerCase().includes(query) || + i.item.category.toLowerCase().includes(query), + ); + } + + return items; + }, [allItems, searchQuery, selectedType, selectedCategory]); + + const filteredCategories = useMemo(() => { + if (!selectedType) return categories; + const items = allItems.filter((i) => i.type === selectedType); + const cats = new Set(items.map((i) => i.item.category)); + return Array.from(cats).sort(); + }, [allItems, selectedType, categories]); + + const filteredItemsByCategory = useMemo(() => { + return filteredItems.reduce( + (acc, item) => { + const category = item.item.category; + if (!acc[category]) { + acc[category] = []; + } + acc[category].push(item); + return acc; + }, + {} as Record, + ); + }, [filteredItems]); + + return ( +
    +
    + + + + + + + +
    +
    + ); +} + +function ContributeBanner() { + return ( + + + + Community-driven: Have an idea?{" "} + + Contribute on GitHub + + + + ); +} + +function HeroSection({ + searchQuery, + setSearchQuery, + selectedType, + setSelectedType, +}: { + searchQuery: string; + setSearchQuery: (query: string) => void; + selectedType: GalleryType | null; + setSelectedType: (type: GalleryType | null) => void; +}) { + return ( +
    +
    +
    +

    + Gallery +

    +

    + Browse and discover{" "} + + + + + templates + + + + + AI instructions for summarizing meetings + + + {" "} + and{" "} + + + + + shortcuts + + + + + Quick commands for the AI chat assistant + + + {" "} + for your workflow +

    +
    + +
    + + + +
    + +
    +
    + setSearchQuery(e.target.value)} + className="flex-1 px-4 py-2.5 text-sm outline-none bg-white text-center placeholder:text-center" + /> +
    +
    +
    +
    + ); +} + +function QuoteSection() { + return ( +
    +

    + "Curated by Hyprnote and the community" +

    +
    + ); +} + +function MobileCategoriesSection({ + categories, + selectedCategory, + setSelectedCategory, +}: { + categories: string[]; + selectedCategory: string | null; + setSelectedCategory: (category: string | null) => void; +}) { + return ( +
    +
    + + {categories.map((category) => ( + + ))} +
    +
    + ); +} + +function GallerySection({ + categories, + selectedCategory, + setSelectedCategory, + itemsByCategory, + filteredItems, +}: { + categories: string[]; + selectedCategory: string | null; + setSelectedCategory: (category: string | null) => void; + itemsByCategory: Record; + filteredItems: GalleryItem[]; +}) { + return ( +
    +
    + + +
    +
    + ); +} + +function DesktopSidebar({ + categories, + selectedCategory, + setSelectedCategory, + itemsByCategory, + totalCount, +}: { + categories: string[]; + selectedCategory: string | null; + setSelectedCategory: (category: string | null) => void; + itemsByCategory: Record; + totalCount: number; +}) { + return ( + + ); +} + +function GalleryGrid({ filteredItems }: { filteredItems: GalleryItem[] }) { + if (filteredItems.length === 0) { + return ( +
    +
    + +

    + No items found matching your search. +

    +
    +
    + ); + } + + return ( +
    +
    + {filteredItems.map((item) => ( + + ))} + +
    +
    + ); +} + +function ItemCard({ item }: { item: GalleryItem }) { + const isTemplate = item.type === "template"; + + return ( + +
    +

    + + {isTemplate ? "Template" : "Shortcut"} + + / + {item.item.category} +

    +

    + {item.item.title} +

    +

    + {item.item.description} +

    +
    + {"targets" in item.item && + item.item.targets && + item.item.targets.length > 0 && ( +
    +
    + For +
    +
    + {item.item.targets.join(", ")} +
    +
    + )} +
    + ); +} + +function ContributeCard() { + return ( +
    +

    Contribute

    +

    + Have an idea? Submit a PR and help the community. +

    + + + Open on GitHub + +
    + ); +} + +function CTASection() { + return ( +
    +
    +

    + Ready to transform your meetings? +

    +

    + Download Hyprnote and start using these templates and shortcuts to + capture perfect meeting notes with AI. +

    +
    + +

    + Free to use. No credit card required. +

    +
    +
    +
    + ); +} diff --git a/apps/web/src/routes/_view/shortcuts/$slug.tsx b/apps/web/src/routes/_view/shortcuts/$slug.tsx new file mode 100644 index 0000000000..b5a67660a8 --- /dev/null +++ b/apps/web/src/routes/_view/shortcuts/$slug.tsx @@ -0,0 +1,271 @@ +import { MDXContent } from "@content-collections/mdx/react"; +import { Icon } from "@iconify-icon/react"; +import { createFileRoute, Link, notFound } from "@tanstack/react-router"; +import { allShortcuts } from "content-collections"; + +import { cn } from "@hypr/utils"; + +import { DownloadButton } from "@/components/download-button"; + +export const Route = createFileRoute("/_view/shortcuts/$slug")({ + component: Component, + loader: async ({ params }) => { + const shortcut = allShortcuts.find( + (shortcut) => shortcut.slug === params.slug, + ); + if (!shortcut) { + throw notFound(); + } + return { shortcut }; + }, + head: ({ loaderData }) => { + const { shortcut } = loaderData!; + const url = `https://hyprnote.com/shortcuts/${shortcut.slug}`; + + const ogImageUrl = `https://hyprnote.com/og?type=shortcuts&title=${encodeURIComponent(shortcut.title)}&category=${encodeURIComponent(shortcut.category)}${shortcut.description ? `&description=${encodeURIComponent(shortcut.description)}` : ""}`; + + return { + meta: [ + { title: `${shortcut.title} - AI Shortcut - Hyprnote` }, + { name: "description", content: shortcut.description }, + { + property: "og:title", + content: `${shortcut.title} - AI Shortcut`, + }, + { property: "og:description", content: shortcut.description }, + { property: "og:type", content: "article" }, + { property: "og:url", content: url }, + { property: "og:image", content: ogImageUrl }, + { name: "twitter:card", content: "summary_large_image" }, + { + name: "twitter:title", + content: `${shortcut.title} - AI Shortcut`, + }, + { name: "twitter:description", content: shortcut.description }, + { name: "twitter:image", content: ogImageUrl }, + ], + }; + }, +}); + +function Component() { + const { shortcut } = Route.useLoaderData(); + + return ( +
    +
    +
    + + + +
    +
    +
    + ); +} + +function LeftSidebar({ shortcut }: { shortcut: (typeof allShortcuts)[0] }) { + return ( + + ); +} + +function MainContent({ shortcut }: { shortcut: (typeof allShortcuts)[0] }) { + return ( +
    +
    + + + Back to shortcuts + +
    + + + + + +
    + ); +} + +function ShortcutHeader({ shortcut }: { shortcut: (typeof allShortcuts)[0] }) { + return ( +
    +
    + + Shortcut + + {shortcut.category} +
    +

    + {shortcut.title} +

    +

    + {shortcut.description} +

    +
    + ); +} + +function ShortcutContent({ shortcut }: { shortcut: (typeof allShortcuts)[0] }) { + return ( +
    +

    Details

    +
    +
    + +
    +
    +
    + ); +} + +function SuggestedShortcuts({ + shortcut, +}: { + shortcut: (typeof allShortcuts)[0]; +}) { + const suggestedShortcuts = allShortcuts.filter( + (s) => s.category === shortcut.category && s.slug !== shortcut.slug, + ); + + if (suggestedShortcuts.length === 0) return null; + + return ( +
    +

    + Other {shortcut.category} shortcuts +

    +
    + {suggestedShortcuts.map((s) => ( + +

    + {s.title} +

    +

    + {s.description} +

    + + ))} +
    +
    + ); +} + +function ShortcutFooter() { + return ( +
    + + + View all shortcuts + +
    + ); +} + +function RightSidebar({ shortcut }: { shortcut: (typeof allShortcuts)[0] }) { + const rawMdxUrl = `https://github.com/fastrepl/hyprnote/blob/main/apps/web/content/shortcuts/${shortcut.slug}.mdx?plain=1`; + + return ( + + ); +} diff --git a/apps/web/src/routes/_view/shortcuts/index.tsx b/apps/web/src/routes/_view/shortcuts/index.tsx new file mode 100644 index 0000000000..163c82893c --- /dev/null +++ b/apps/web/src/routes/_view/shortcuts/index.tsx @@ -0,0 +1,554 @@ +import { MDXContent } from "@content-collections/mdx/react"; +import { Icon } from "@iconify-icon/react"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { allShortcuts } from "content-collections"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { cn } from "@hypr/utils"; + +import { DownloadButton } from "@/components/download-button"; +import { SlashSeparator } from "@/components/slash-separator"; + +type ShortcutsSearch = { + category?: string; +}; + +export const Route = createFileRoute("/_view/shortcuts/")({ + component: Component, + validateSearch: (search: Record): ShortcutsSearch => { + return { + category: + typeof search.category === "string" ? search.category : undefined, + }; + }, + head: () => ({ + meta: [ + { title: "AI Shortcuts - Hyprnote" }, + { + name: "description", + content: + "Discover our library of AI shortcuts for meeting conversations. Extract action items, draft follow-up emails, get meeting insights, and more with quick chat commands.", + }, + { property: "og:title", content: "AI Shortcuts - Hyprnote" }, + { + property: "og:description", + content: + "Browse our collection of AI shortcuts. Quick commands for extracting insights, drafting emails, and analyzing your meeting conversations.", + }, + { property: "og:type", content: "website" }, + { property: "og:url", content: "https://hyprnote.com/shortcuts" }, + ], + }), +}); + +function Component() { + const navigate = useNavigate({ from: Route.fullPath }); + const search = Route.useSearch(); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedShortcut, setSelectedShortcut] = useState< + (typeof allShortcuts)[0] | null + >(null); + + const selectedCategory = search.category || null; + + const setSelectedCategory = (category: string | null) => { + navigate({ search: category ? { category } : {}, resetScroll: false }); + }; + + const handleShortcutClick = (shortcut: (typeof allShortcuts)[0]) => { + setSelectedShortcut(shortcut); + window.history.pushState({}, "", `/shortcuts/${shortcut.slug}`); + }; + + const handleModalClose = useCallback(() => { + setSelectedShortcut(null); + const url = selectedCategory + ? `/shortcuts?category=${encodeURIComponent(selectedCategory)}` + : "/shortcuts"; + window.history.pushState({}, "", url); + }, [selectedCategory]); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && selectedShortcut) { + handleModalClose(); + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedShortcut, handleModalClose]); + + useEffect(() => { + if (selectedShortcut) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + return () => { + document.body.style.overflow = ""; + }; + }, [selectedShortcut]); + + const shortcutsByCategory = getShortcutsByCategory(); + const categories = Object.keys(shortcutsByCategory); + + const filteredShortcuts = useMemo(() => { + let shortcuts = allShortcuts; + + if (selectedCategory) { + shortcuts = shortcuts.filter((s) => s.category === selectedCategory); + } + + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase(); + shortcuts = shortcuts.filter( + (s) => + s.title.toLowerCase().includes(query) || + s.description.toLowerCase().includes(query) || + s.category.toLowerCase().includes(query), + ); + } + + return shortcuts; + }, [searchQuery, selectedCategory]); + + return ( +
    +
    + + + + + + + +
    + + {selectedShortcut && ( + + )} +
    + ); +} + +function ContributeBanner() { + return ( + + + + Community-driven: Have a shortcut idea?{" "} + + Contribute on GitHub + + + + ); +} + +function HeroSection({ + searchQuery, + setSearchQuery, +}: { + searchQuery: string; + setSearchQuery: (query: string) => void; +}) { + return ( +
    +
    +
    +

    + Shortcuts +

    +

    + Quick AI commands for your meeting conversations. Use shortcuts in + the chat assistant to extract insights, draft emails, and analyze + discussions instantly. +

    +
    + +
    +
    + setSearchQuery(e.target.value)} + className="flex-1 px-4 py-2.5 text-sm outline-none bg-white text-center placeholder:text-center" + /> +
    +
    +
    +
    + ); +} + +function QuoteSection() { + return ( +
    +

    + "Curated by Hyprnote and the community" +

    +
    + ); +} + +function MobileCategoriesSection({ + categories, + selectedCategory, + setSelectedCategory, +}: { + categories: string[]; + selectedCategory: string | null; + setSelectedCategory: (category: string | null) => void; +}) { + return ( +
    +
    + + {categories.map((category) => ( + + ))} +
    +
    + ); +} + +function ShortcutsSection({ + categories, + selectedCategory, + setSelectedCategory, + shortcutsByCategory, + filteredShortcuts, + onShortcutClick, +}: { + categories: string[]; + selectedCategory: string | null; + setSelectedCategory: (category: string | null) => void; + shortcutsByCategory: Record; + filteredShortcuts: typeof allShortcuts; + onShortcutClick: (shortcut: (typeof allShortcuts)[0]) => void; +}) { + return ( +
    +
    + + +
    +
    + ); +} + +function DesktopSidebar({ + categories, + selectedCategory, + setSelectedCategory, + shortcutsByCategory, +}: { + categories: string[]; + selectedCategory: string | null; + setSelectedCategory: (category: string | null) => void; + shortcutsByCategory: Record; +}) { + return ( + + ); +} + +function ShortcutsGrid({ + filteredShortcuts, + onShortcutClick, +}: { + filteredShortcuts: typeof allShortcuts; + onShortcutClick: (shortcut: (typeof allShortcuts)[0]) => void; +}) { + if (filteredShortcuts.length === 0) { + return ( +
    +
    + +

    + No shortcuts found matching your search. +

    +
    +
    + ); + } + + return ( +
    +
    + {filteredShortcuts.map((shortcut) => ( + onShortcutClick(shortcut)} + /> + ))} + +
    +
    + ); +} + +function ShortcutCard({ + shortcut, + onClick, +}: { + shortcut: (typeof allShortcuts)[0]; + onClick: () => void; +}) { + return ( + + ); +} + +function ContributeCard() { + return ( +
    +

    + Contribute a shortcut +

    +

    + Have a shortcut idea? Submit a PR and help the community. +

    + + + Open on GitHub + +
    + ); +} + +function CTASection() { + return ( +
    +
    +

    + Ready to transform your meetings? +

    +

    + Download Hyprnote and start using these shortcuts to get instant + insights from your meeting conversations. +

    +
    + +

    + Free to use. No credit card required. +

    +
    +
    +
    + ); +} + +function ShortcutModal({ + shortcut, + onClose, +}: { + shortcut: (typeof allShortcuts)[0]; + onClose: () => void; +}) { + return ( +
    +
    +
    +
    e.stopPropagation()} + > +
    +
    + + +
    +
    + + Shortcut + + + {shortcut.category} + +
    +

    + {shortcut.title} +

    +

    {shortcut.description}

    + +
    +
    + +
    +
    + +
    +
    + +

    + Download Hyprnote to use this shortcut +

    +
    +
    +
    +
    +
    +
    +
    + ); +} + +function getShortcutsByCategory() { + return allShortcuts.reduce( + (acc, shortcut) => { + const category = shortcut.category; + if (!acc[category]) { + acc[category] = []; + } + acc[category].push(shortcut); + return acc; + }, + {} as Record, + ); +} diff --git a/apps/web/src/routes/_view/templates/index.tsx b/apps/web/src/routes/_view/templates/index.tsx index 2d1caf3542..f3ab63623b 100644 --- a/apps/web/src/routes/_view/templates/index.tsx +++ b/apps/web/src/routes/_view/templates/index.tsx @@ -401,7 +401,12 @@ function TemplateCard({ onClick={onClick} className="group p-4 border border-neutral-200 rounded-sm bg-white hover:shadow-md hover:border-neutral-300 transition-all text-left cursor-pointer flex flex-col items-start" > -
    +
    +

    + Template + / + {template.category} +

    {template.title}

    @@ -413,20 +418,8 @@ function TemplateCard({
    For
    -
    - {template.targets.slice(0, 3).map((target) => ( - - {target} - - ))} - {template.targets.length > 3 && ( - - +{template.targets.length - 3} more - - )} +
    + {template.targets.join(", ")}
    @@ -529,15 +522,13 @@ function TemplateModal({

    {template.description}

    -
    - {template.targets.map((target) => ( - - {target} - - ))} +
    + + For:{" "} + + + {template.targets.join(", ")} +
    diff --git a/apps/web/src/utils/sitemap.ts b/apps/web/src/utils/sitemap.ts index be80fa2b65..808b78e482 100644 --- a/apps/web/src/utils/sitemap.ts +++ b/apps/web/src/utils/sitemap.ts @@ -146,6 +146,10 @@ export function getSitemap(): Sitemap { priority: 0.7, changeFrequency: "weekly", }, + "/shortcuts": { + priority: 0.7, + changeFrequency: "weekly", + }, "/download": { priority: 0.7,