diff --git a/.env.example b/.env.example index 0bd9fcbf3..c875154b4 100644 --- a/.env.example +++ b/.env.example @@ -44,6 +44,10 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key ### ZeroBounce API key for email validation # ZEROBOUNCE_API_KEY= +### Plain API key for customer support integration +# (Required if NEXT_PUBLIC_INCLUDE_REPORT_ISSUE=1) +# PLAIN_API_KEY= + ### Loki Configuration # LOKI_SERVICE_NAME= # LOKI_HOST= @@ -72,6 +76,10 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key ### When enabled, both BILLING_API_URL and NEXT_PUBLIC_STRIPE_BILLING_URL must be provided # NEXT_PUBLIC_INCLUDE_BILLING=0 +### Enable report issue feature: set to 1 to enable +### When enabled, PLAIN_API_KEY must be provided +# NEXT_PUBLIC_INCLUDE_REPORT_ISSUE=0 + ### Set to 1 to use mock data # NEXT_PUBLIC_MOCK_DATA=0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a3d335de..e2be52531 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,7 @@ jobs: BILLING_API_URL: https://billing.e2b-test.dev NEXT_PUBLIC_E2B_DOMAIN: e2b-test.dev NEXT_PUBLIC_POSTHOG_KEY: test-posthog-key + NEXT_PUBLIC_PLAIN_API_KEY: test-plain-api-key NEXT_PUBLIC_SUPABASE_URL: https://test-supabase-url.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY: test-supabase-anon-key NEXT_PUBLIC_STRIPE_BILLING_URL: https://test-stripe-billing.example.com diff --git a/README.md b/README.md index 3be1b2241..03909eca2 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Our Dashboard is a modern, feature-rich web application built to manage and moni - Vercel account - Supabase account - PostHog account (optional for analytics) +- Plain account (optional for customer support) ### Local Development Setup diff --git a/bun.lock b/bun.lock index 5b3de5f68..bc14c090f 100644 --- a/bun.lock +++ b/bun.lock @@ -47,6 +47,7 @@ "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.13.12", + "@team-plain/typescript-sdk": "^5.11.0", "@theguild/remark-mermaid": "^0.2.0", "@trpc/client": "^11.7.1", "@trpc/react-query": "^11.7.1", @@ -361,6 +362,8 @@ "@google-cloud/storage": ["@google-cloud/storage@7.17.3", "", { "dependencies": { "@google-cloud/paginator": "^5.0.0", "@google-cloud/projectify": "^4.0.0", "@google-cloud/promisify": "<4.1.0", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "duplexify": "^4.1.3", "fast-xml-parser": "^4.4.1", "gaxios": "^6.0.2", "google-auth-library": "^9.6.3", "html-entities": "^2.5.2", "mime": "^3.0.0", "p-limit": "^3.0.1", "retry-request": "^7.0.0", "teeny-request": "^9.0.0", "uuid": "^8.0.0" } }, "sha512-gOnCAbFgAYKRozywLsxagdevTF7Gm+2Ncz5u5CQAuOv/2VCa0rdGJWvJFDOftPx1tc+q8TXiC2pEJfFKu+yeMQ=="], + "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.0", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], @@ -955,6 +958,8 @@ "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="], + "@team-plain/typescript-sdk": ["@team-plain/typescript-sdk@5.11.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "graphql": "^16.6.0", "lodash.get": "^4.4.2", "zod": "3.22.4" } }, "sha512-EEyfQjD1d1tFMLFtqV7StWtiwAesFD1B5zdTYvcjpoJLlftr4NMS0BVllSKnr7n7jjWivTsVuuG7yZcZD6ZonA=="], + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], @@ -1835,6 +1840,8 @@ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="], + "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="], "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], @@ -2099,6 +2106,8 @@ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + "lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -3183,6 +3192,10 @@ "@tanem/svg-injector/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@team-plain/typescript-sdk/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "@team-plain/typescript-sdk/zod": ["zod@3.22.4", "", {}, "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg=="], + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], @@ -3501,6 +3514,8 @@ "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "@team-plain/typescript-sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@types/jest/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], diff --git a/package.json b/package.json index e579091b5..2c888bb1d 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.13.12", + "@team-plain/typescript-sdk": "^5.11.0", "@theguild/remark-mermaid": "^0.2.0", "@trpc/client": "^11.7.1", "@trpc/react-query": "^11.7.1", diff --git a/src/configs/flags.ts b/src/configs/flags.ts index 863408044..991ebcd6b 100644 --- a/src/configs/flags.ts +++ b/src/configs/flags.ts @@ -9,3 +9,5 @@ export const USE_MOCK_DATA = export const INCLUDE_DASHBOARD_FEEDBACK_SURVEY = process.env.NEXT_PUBLIC_POSTHOG_DASHBOARD_FEEDBACK_SURVEY_ID && process.env.NEXT_PUBLIC_POSTHOG_KEY + +export const INCLUDE_REPORT_ISSUE = process.env.NEXT_PUBLIC_INCLUDE_REPORT_ISSUE === '1' diff --git a/src/features/dashboard/navbar/dashboard-survey-popover.tsx b/src/features/dashboard/navbar/dashboard-survey-popover.tsx index 9176a5cbd..34255b946 100644 --- a/src/features/dashboard/navbar/dashboard-survey-popover.tsx +++ b/src/features/dashboard/navbar/dashboard-survey-popover.tsx @@ -92,8 +92,8 @@ function DashboardSurveyPopover({ trigger }: DashboardSurveyPopoverProps) { {dashboardFeedbackSurvey && ( { + e.preventDefault() + + if (!sandboxId.trim() || !description.trim()) { + toast.error('Please fill in all fields') + return + } + + setIsSubmitting(true) + + try { + const result = await reportIssueAction({ + sandboxId: sandboxId.trim(), + description: description.trim(), + }) + + if (result?.data?.success) { + posthog.capture('issue_reported', { + sandbox_id: sandboxId.trim(), + thread_id: result.data.threadId, + }) + setWasSubmitted(true) + toast.success('Issue reported successfully. Our team will review it shortly.') + setIsOpen(false) + // reset state + setSandboxId('') + setDescription('') + setTimeout(() => { + setWasSubmitted(false) + }, 100) + } else { + toast.error('Failed to report issue. Please try again.') + } + } catch (error) { + console.error('Error reporting issue:', error) + toast.error('Failed to report issue. Please try again.') + } finally { + setIsSubmitting(false) + } + } + + return ( + { + if (open) { + posthog.capture('issue_report_shown') + } + if (!open && !wasSubmitted) { + posthog.capture('issue_report_dismissed') + } + setIsOpen(open) + }} + > + {trigger} + + + + Report Issue + + Our team will get back to you shortly + + + +
+ setSandboxId(e.target.value)} + disabled={isSubmitting} + /> +