diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..392d93e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sendvelo.iml b/.idea/sendvelo.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/sendvelo.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1ac928b..32eeb5d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,4 +1,112 @@ - {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - ); -} diff --git a/app/mcp/route.ts b/app/mcp/route.ts deleted file mode 100644 index aae6eba..0000000 --- a/app/mcp/route.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { baseURL } from "@/baseUrl"; -import { createMcpHandler } from "mcp-handler"; -import { z } from "zod"; - -const getAppsSdkCompatibleHtml = async (baseUrl: string, path: string) => { - const result = await fetch(`${baseUrl}${path}`); - return await result.text(); -}; - -type ContentWidget = { - id: string; - title: string; - templateUri: string; - invoking: string; - invoked: string; - html: string; - description: string; - widgetDomain: string; -}; - -function widgetMeta(widget: ContentWidget) { - return { - "openai/outputTemplate": widget.templateUri, - "openai/toolInvocation/invoking": widget.invoking, - "openai/toolInvocation/invoked": widget.invoked, - "openai/widgetAccessible": false, - "openai/resultCanProduceWidget": true, - } as const; -} - -const handler = createMcpHandler(async (server) => { - const html = await getAppsSdkCompatibleHtml(baseURL, "/"); - - const contentWidget: ContentWidget = { - id: "show_content", - title: "Show Content", - templateUri: "ui://widget/content-template.html", - invoking: "Loading content...", - invoked: "Content loaded", - html: html, - description: "Displays the homepage content", - widgetDomain: "https://nextjs.org/docs", - }; - server.registerResource( - "content-widget", - contentWidget.templateUri, - { - title: contentWidget.title, - description: contentWidget.description, - mimeType: "text/html+skybridge", - _meta: { - "openai/widgetDescription": contentWidget.description, - "openai/widgetPrefersBorder": true, - }, - }, - async (uri) => ({ - contents: [ - { - uri: uri.href, - mimeType: "text/html+skybridge", - text: `${contentWidget.html}`, - _meta: { - "openai/widgetDescription": contentWidget.description, - "openai/widgetPrefersBorder": true, - "openai/widgetDomain": contentWidget.widgetDomain, - }, - }, - ], - }) - ); - - server.registerTool( - contentWidget.id, - { - title: contentWidget.title, - description: - "Fetch and display the homepage content with the name of the user", - inputSchema: { - name: z.string().describe("The name of the user to display on the homepage"), - }, - _meta: widgetMeta(contentWidget), - }, - async ({ name }) => { - return { - content: [ - { - type: "text", - text: name, - }, - ], - structuredContent: { - name: name, - timestamp: new Date().toISOString(), - }, - _meta: widgetMeta(contentWidget), - }; - } - ); -}); - -export const GET = handler; -export const POST = handler; diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index c0d11bb..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,129 +0,0 @@ -"use client"; - -import Image from "next/image"; -import Link from "next/link"; -import { - useWidgetProps, - useMaxHeight, - useDisplayMode, - useRequestDisplayMode, - useIsChatGptApp, -} from "./hooks"; - -export default function Home() { - const toolOutput = useWidgetProps<{ - name?: string; - result?: { structuredContent?: { name?: string } }; - }>(); - const maxHeight = useMaxHeight() ?? undefined; - const displayMode = useDisplayMode(); - const requestDisplayMode = useRequestDisplayMode(); - const isChatGptApp = useIsChatGptApp(); - - const name = toolOutput?.result?.structuredContent?.name || toolOutput?.name; - - return ( -
- {displayMode !== "fullscreen" && ( - - )} -
- {!isChatGptApp && ( -
-
- -
-

- This app relies on data from a ChatGPT session. -

-

- No{" "} - - window.openai - {" "} - property detected -

-
-
-
- )} - Next.js logo -
    -
  1. - Welcome to the ChatGPT Apps SDK Next.js Starter -
  2. -
  3. - Name returned from tool call: {name ?? "..."} -
  4. -
  5. MCP server path: /mcp
  6. -
- -
- - Visit another page - - - Deploy on Vercel - -
-
-
- ); -} diff --git a/approval-tool/app/(app)/dashboard/page.tsx b/approval-tool/app/(app)/dashboard/page.tsx deleted file mode 100644 index 53da823..0000000 --- a/approval-tool/app/(app)/dashboard/page.tsx +++ /dev/null @@ -1,348 +0,0 @@ -"use client"; - -import { api, type RouterOutputs } from "@/trpc/react"; -import { STRIPE_PRICES } from "@/lib/stripe"; -import { useState } from "react"; -import { useTranslations } from "next-intl"; - -type Review = RouterOutputs["review"]["getMyReviews"]["reviews"][number]; - -/** - * Dashboard Page - * - * Shows user's reviews, subscription status, and allows creating new reviews. - */ - -export default function DashboardPage() { - const t = useTranslations("dashboard"); - const tCommon = useTranslations("common"); - const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); - - const { data: profile, isLoading: profileLoading } = - api.user.getProfile.useQuery(); - - const { data: reviewsData, isLoading: reviewsLoading, refetch } = - api.review.getMyReviews.useQuery(); - - const reviews = reviewsData?.reviews || []; - - const { data: stats } = api.review.getStats.useQuery(); - - const createReviewMutation = api.review.create.useMutation({ - onSuccess: () => { - refetch(); - setIsCreateModalOpen(false); - // TODO: Add toast notification - toast.success(t("reviews.createSuccess")) - }, - onError: (error) => { - console.error("Failed to create review:", error); - alert(error.message || "Failed to create review"); - }, - }); - - const deleteReviewMutation = api.review.delete.useMutation({ - onSuccess: () => { - refetch(); - // TODO: Add toast notification - toast.success(t("reviews.deleteSuccess")) - }, - onError: (error) => { - console.error("Failed to delete review:", error); - alert(error.message || "Failed to delete review"); - }, - }); - - const createCheckoutMutation = api.user.createCheckout.useMutation({ - onSuccess: (data) => { - if (data.url) { - window.location.href = data.url; - } - }, - onError: (error) => { - console.error("Failed to create checkout session:", error); - alert(error.message || "Failed to start checkout"); - }, - }); - - const createPortalMutation = api.user.createPortalSession.useMutation({ - onSuccess: (data) => { - if (data.url) { - window.location.href = data.url; - } - }, - onError: (error) => { - console.error("Failed to create portal session:", error); - alert(error.message || "Failed to open billing portal"); - }, - }); - - const [newReview, setNewReview] = useState({ - title: "", - content: "", - reviewerEmail: "", - }); - - const handleCreateReview = async (e: React.FormEvent) => { - e.preventDefault(); - try { - await createReviewMutation.mutateAsync(newReview); - setNewReview({ title: "", content: "", reviewerEmail: "" }); - } catch (error) { - // Error is already handled by onError callback - // Keep form data so user can retry - } - }; - - if (profileLoading || reviewsLoading) { - return ( -
-
-

{tCommon("loading")}

-
-
- ); - } - - const tier = profile?.subscriptionTier ?? "free"; - const remaining = - tier === "free" ? 5 - (profile?.reviewsThisMonth ?? 0) : "unlimited"; - - return ( -
- {/* Stats Cards */} -
-
-

- {t("stats.totalReviews")} -

-

- {stats?.total ?? 0} -

-
-
-

- {t("stats.pending")} -

-

- {stats?.pending ?? 0} -

-
-
-

- {t("stats.subscription")} -

-
-

- {tier} -

- {tier === "free" ? ( - - ) : ( - - )} -
-

- {remaining === "unlimited" - ? t("subscription.unlimited") - : t("subscription.reviewsRemaining", { count: remaining })} -

-
-
- - {/* Header */} -
-

- {t("reviews.title")} -

- -
- - {/* Reviews List */} - {reviews.length > 0 ? ( -
- - - - - - - - - - - - {reviews.map((review: Review) => ( - - - - - - - - ))} - -
- {t("reviews.table.title")} - - {t("reviews.table.reviewer")} - - {t("reviews.table.status")} - - {t("reviews.table.created")} - - {t("reviews.table.actions")} -
- - {review.title} - - - {review.reviewerEmail} - - - {review.status} - - - {new Date(review.createdAt).toLocaleDateString()} - - -
-
- ) : ( -
-

{t("reviews.noReviews")}

-
- )} - - {/* Create Review Modal */} - {isCreateModalOpen && ( -
-
-

- {t("createModal.title")} -

- -
-
- - - setNewReview({ ...newReview, title: e.target.value }) - } - required - className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - /> -
- -
- - - setNewReview({ ...newReview, reviewerEmail: e.target.value }) - } - required - className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" - /> -
- -
- -