From 0eddce6c83ff4b12ec6b0160337b389f8099646f Mon Sep 17 00:00:00 2001 From: Pritesh Date: Thu, 11 Jun 2026 10:37:43 +0530 Subject: [PATCH] ui rezig, onboarding, billing, hire us & on prem cues --- .gitignore | 1 + api/routes/user.py | 3 + api/schemas/user_configuration.py | 4 + api/services/configuration/masking.py | 4 + api/services/configuration/merge.py | 7 + api/services/configuration/registry.py | 12 +- api/services/workflow/dto.py | 10 +- api/services/workflow/workflow_graph.py | 4 +- ui/.env.example | 3 + ui/package-lock.json | 4 +- ui/public/dograh-logo-inverse.png | Bin 0 -> 18310 bytes ui/public/dograh-logo.png | Bin 0 -> 16681 bytes ui/public/dograh-mark.png | Bin 0 -> 14786 bytes ui/src/app/api-keys/page.tsx | 4 +- ui/src/app/auth/login/page.tsx | 89 ++--- ui/src/app/auth/signup/page.tsx | 113 +++--- ui/src/app/campaigns/new/page.tsx | 4 +- ui/src/app/globals.css | 23 ++ ui/src/app/handler/[...stack]/AuthShell.tsx | 82 ----- ui/src/app/handler/[...stack]/BackButton.tsx | 13 +- ui/src/app/handler/[...stack]/page.tsx | 56 ++- ui/src/app/model-configurations/page.tsx | 2 +- .../app/recordings/RecordingsUploadDialog.tsx | 2 +- ui/src/app/telephony-configurations/page.tsx | 2 +- ui/src/app/tools/[toolUuid]/page.tsx | 8 +- ui/src/app/tools/page.tsx | 4 +- .../[workflowId]/components/EmbedDialog.tsx | 8 +- .../components/PhoneCallDialog.tsx | 6 +- .../components/RecordingsDialog.tsx | 2 +- .../components/WorkflowEditorHeader.tsx | 2 +- .../workflow/[workflowId]/settings/page.tsx | 2 +- ui/src/app/workflow/create/page.tsx | 2 +- ui/src/components/BrandLogo.tsx | 38 +++ .../auth}/AuthEnterpriseCTA.tsx | 5 +- ui/src/components/auth/AuthShell.tsx | 86 +++++ .../components/billing/BuyCreditsControl.tsx | 134 +++++--- .../components/billing/DograhCreditsCard.tsx | 41 ++- ui/src/components/flow/ToolSelector.tsx | 2 +- ui/src/components/layout/AppLayout.tsx | 8 +- ui/src/components/layout/AppSidebar.tsx | 22 +- .../lead-forms/CaptchaChallenge.tsx | 82 +++++ .../lead-forms/EnterpriseLeadFields.tsx | 150 ++++++++ .../components/lead-forms/EnterpriseModal.tsx | 203 ++++------- .../components/lead-forms/HireExpertModal.tsx | 35 +- .../components/lead-forms/HireExpertNudge.tsx | 6 +- .../components/lead-forms/LeadModalShell.tsx | 49 +-- .../components/lead-forms/OnboardingModal.tsx | 321 ++++++++++++------ .../lead-forms/onboardingServiceClient.ts | 34 +- ui/src/components/ui/card.tsx | 2 +- ui/src/components/ui/input.tsx | 2 +- .../workflow/folders/FolderSection.tsx | 2 +- ui/src/context/LeadFormsContext.tsx | 29 +- ui/src/context/OnboardingContext.tsx | 2 +- ui/src/lib/billing/topup.ts | 4 + ui/src/middleware.ts | 4 +- 55 files changed, 1108 insertions(+), 629 deletions(-) create mode 100755 ui/public/dograh-logo-inverse.png create mode 100755 ui/public/dograh-logo.png create mode 100755 ui/public/dograh-mark.png delete mode 100644 ui/src/app/handler/[...stack]/AuthShell.tsx create mode 100644 ui/src/components/BrandLogo.tsx rename ui/src/{app/handler/[...stack] => components/auth}/AuthEnterpriseCTA.tsx (87%) create mode 100644 ui/src/components/auth/AuthShell.tsx create mode 100644 ui/src/components/lead-forms/CaptchaChallenge.tsx create mode 100644 ui/src/components/lead-forms/EnterpriseLeadFields.tsx diff --git a/.gitignore b/.gitignore index 8fa4c055..891e0344 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ node_modules/ # Superpowers brainstorm mockups (local only) .superpowers/ +.gstack/ diff --git a/api/routes/user.py b/api/routes/user.py index 20d0a41e..a3c25da2 100644 --- a/api/routes/user.py +++ b/api/routes/user.py @@ -85,6 +85,9 @@ class UserConfigurationRequestResponseSchema(BaseModel): test_phone_number: str | None = None timezone: str | None = None organization_pricing: dict[str, Union[float, str, bool]] | None = None + # Post-signup onboarding gate (see UserConfiguration). Set once on submit/skip. + onboarding_completed_at: datetime | None = None + onboarding_skipped: bool | None = None @router.get("/configurations/user") diff --git a/api/schemas/user_configuration.py b/api/schemas/user_configuration.py index 2e62396a..15608760 100644 --- a/api/schemas/user_configuration.py +++ b/api/schemas/user_configuration.py @@ -21,6 +21,10 @@ class UserConfiguration(BaseModel): test_phone_number: str | None = None timezone: str | None = None last_validated_at: datetime | None = None + # Post-signup onboarding gate: set once the user submits or skips the + # onboarding form, so it shows only once per user (server-side, cross-device). + onboarding_completed_at: datetime | None = None + onboarding_skipped: bool = False @model_validator(mode="before") @classmethod diff --git a/api/services/configuration/masking.py b/api/services/configuration/masking.py index 877cad97..a16950c1 100644 --- a/api/services/configuration/masking.py +++ b/api/services/configuration/masking.py @@ -141,6 +141,10 @@ def mask_user_config(config: UserConfiguration) -> Dict[str, Any]: "is_realtime": config.is_realtime, "test_phone_number": config.test_phone_number, "timezone": config.timezone, + # Onboarding gate flags (not secrets) — surfaced so the UI can decide + # whether to show the post-signup onboarding form on boot. + "onboarding_completed_at": config.onboarding_completed_at, + "onboarding_skipped": config.onboarding_skipped, } diff --git a/api/services/configuration/merge.py b/api/services/configuration/merge.py index f421648f..094800d3 100644 --- a/api/services/configuration/merge.py +++ b/api/services/configuration/merge.py @@ -113,6 +113,13 @@ def merge_user_configurations( if "timezone" in incoming_partial: merged["timezone"] = incoming_partial["timezone"] + # Onboarding gate flags — overwrite only when supplied (set once on submit/skip). + if "onboarding_completed_at" in incoming_partial: + merged["onboarding_completed_at"] = incoming_partial["onboarding_completed_at"] + + if "onboarding_skipped" in incoming_partial: + merged["onboarding_skipped"] = incoming_partial["onboarding_skipped"] + return UserConfiguration.model_validate(merged) diff --git a/api/services/configuration/registry.py b/api/services/configuration/registry.py index f05c5f71..7f831cf9 100644 --- a/api/services/configuration/registry.py +++ b/api/services/configuration/registry.py @@ -257,12 +257,12 @@ SPEACHES_PROVIDER_MODEL_CONFIG = provider_model_config( ) AZURE_SPEECH_PROVIDER_MODEL_CONFIG = provider_model_config( "Azure Speech Services", - description="Azure Cognitive Services Speech — TTS and STT via the Azure Speech SDK.", + description="Azure Cognitive Services Speech - TTS and STT via the Azure Speech SDK.", provider_docs_url="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/", ) AZURE_REALTIME_PROVIDER_MODEL_CONFIG = provider_model_config( "Azure OpenAI Realtime", - description="Azure OpenAI Realtime API — low-latency speech-to-speech conversations.", + description="Azure OpenAI Realtime API - low-latency speech-to-speech conversations.", provider_docs_url="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/realtime-audio-quickstart", ) @@ -360,7 +360,7 @@ class GoogleVertexLLMConfiguration(BaseLLMConfiguration): api_key: str | list[str] | None = Field( default=None, description=( - "Not used for Vertex AI — authentication is via the service account " + "Not used for Vertex AI - authentication is via the service account " "in `credentials` (or ADC). Leave blank." ), ) @@ -425,7 +425,7 @@ class AWSBedrockLLMConfiguration(BaseLLMConfiguration): provider: Literal[ServiceProviders.AWS_BEDROCK] = ServiceProviders.AWS_BEDROCK model: str = Field( default="us.amazon.nova-pro-v1:0", - description="Bedrock model ID — include the region inference-profile prefix (e.g. 'us.').", + description="Bedrock model ID - include the region inference-profile prefix (e.g. 'us.').", json_schema_extra={"examples": AWS_BEDROCK_MODELS, "allow_custom_input": True}, ) aws_access_key: str = Field( @@ -442,7 +442,7 @@ class AWSBedrockLLMConfiguration(BaseLLMConfiguration): ) api_key: str | list[str] | None = Field( default=None, - description="Not used for Bedrock — authentication is via the AWS credentials above. Leave blank.", + description="Not used for Bedrock - authentication is via the AWS credentials above. Leave blank.", ) @@ -682,7 +682,7 @@ class GoogleVertexRealtimeLLMConfiguration(BaseLLMConfiguration): api_key: str | list[str] | None = Field( default=None, description=( - "Not used for Vertex AI — authentication is via the service account " + "Not used for Vertex AI - authentication is via the service account " "in `credentials` (or ADC). Leave blank." ), ) diff --git a/api/services/workflow/dto.py b/api/services/workflow/dto.py index 60aad758..e63340a2 100644 --- a/api/services/workflow/dto.py +++ b/api/services/workflow/dto.py @@ -176,7 +176,7 @@ class _ToolDocumentRefsMixin(BaseModel): @node_spec( name="startCall", display_name="Start Call", - description="Entry point of the workflow — plays a greeting and opens the conversation.", + description="Entry point of the workflow - plays a greeting and opens the conversation.", llm_hint=( "The entry point of every workflow (exactly one required). Plays an " "optional greeting, can fetch context from an external API before the " @@ -344,7 +344,7 @@ class StartCallNodeData( @node_spec( name="agentNode", display_name="Agent Node", - description="Conversational step — the LLM runs one focused exchange.", + description="Conversational step - the LLM runs one focused exchange.", llm_hint=( "Mid-call step executed by the LLM. Most workflows are a chain of agent " "nodes connected by edges that describe transition conditions. Each agent " @@ -613,9 +613,9 @@ class GlobalNodeData(BaseNodeData, _PromptedNodeDataMixin): "description": ( "Path segment that uniquely identifies " "this trigger. Used in both URLs:\n" - " • Production: `/api/v1/public/agent/` — executes " + " • Production: `/api/v1/public/agent/` - executes " "the published agent.\n" - " • Test: `/api/v1/public/agent/test/` — executes " + " • Test: `/api/v1/public/agent/test/` - executes " "the latest draft.\n" "Can be customized to a descriptive value up to 36 characters " "using letters, numbers, hyphens, or underscores." @@ -708,7 +708,7 @@ class TriggerNodeData(BaseNodeData): "display_name": "Payload Template", "description": ( "JSON body of the request. Values are Jinja-rendered against the " - "run context — `{{workflow_run_id}}`, `{{gathered_context.foo}}`, " + "run context - `{{workflow_run_id}}`, `{{gathered_context.foo}}`, " "`{{annotations.qa_xxx}}`, etc." ), "ui_type": PropertyType.json, diff --git a/api/services/workflow/workflow_graph.py b/api/services/workflow/workflow_graph.py index a6268159..7e03d499 100644 --- a/api/services/workflow/workflow_graph.py +++ b/api/services/workflow/workflow_graph.py @@ -229,7 +229,7 @@ class WorkflowGraph: kind=ItemKind.workflow, id=None, field=None, - message="Workflow has no start node — exactly one is required", + message="Workflow has no start node - exactly one is required", ) ) elif len(start_nodes) > 1: @@ -239,7 +239,7 @@ class WorkflowGraph: id=None, field=None, message=( - f"Workflow has {len(start_nodes)} start nodes — " + f"Workflow has {len(start_nodes)} start nodes - " f"exactly one is required" ), ) diff --git a/ui/.env.example b/ui/.env.example index 741790e5..08da3a1c 100644 --- a/ui/.env.example +++ b/ui/.env.example @@ -1,3 +1,6 @@ BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_NODE_ENV=development +# Base URL of the separate user_onboarding service (lead-gen + onboarding form +# submissions). Leave unset to disable those POSTs (PostHog capture still fires). +NEXT_PUBLIC_ONBOARDING_API_URL=http://localhost:8001 diff --git a/ui/package-lock.json b/ui/package-lock.json index 9923bb0f..73681a43 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "ui", - "version": "1.30.1", + "version": "1.33.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ui", - "version": "1.30.1", + "version": "1.33.0", "dependencies": { "@dagrejs/dagre": "^1.1.4", "@radix-ui/react-alert-dialog": "^1.1.15", diff --git a/ui/public/dograh-logo-inverse.png b/ui/public/dograh-logo-inverse.png new file mode 100755 index 0000000000000000000000000000000000000000..8415e795deaf097a9c161ee0a3a26197895fbeb9 GIT binary patch literal 18310 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 eC3csn=)*dUOYq_?w~g`Jf< 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 YtGPCm;Cd4H2O^N7teQ;KGt-d&2ht)v{r~^~ literal 0 HcmV?d00001 diff --git a/ui/public/dograh-mark.png b/ui/public/dograh-mark.png new file mode 100755 index 0000000000000000000000000000000000000000..328f51f10eb66d6058802761bd3becd4f01f23e5 GIT binary patch literal 14786 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 Zcr<2$)jVb@%D7)yYO3lWN|mj`{tuzRE?@uv literal 0 HcmV?d00001 diff --git a/ui/src/app/api-keys/page.tsx b/ui/src/app/api-keys/page.tsx index 2558889b..05aa7c33 100644 --- a/ui/src/app/api-keys/page.tsx +++ b/ui/src/app/api-keys/page.tsx @@ -304,7 +304,7 @@ export default function APIKeysPage() { // Don't render content until auth is loaded if (loading || !user) { return ( -
+
@@ -319,7 +319,7 @@ export default function APIKeysPage() { const showServiceKeyArchiveControls = !isOSS; return ( -
+
diff --git a/ui/src/app/auth/login/page.tsx b/ui/src/app/auth/login/page.tsx index 39c6ceb1..a1fef886 100644 --- a/ui/src/app/auth/login/page.tsx +++ b/ui/src/app/auth/login/page.tsx @@ -5,8 +5,9 @@ import { useState } from "react"; import { toast } from "sonner"; import { loginApiV1AuthLoginPost } from "@/client/sdk.gen"; +import { AuthEnterpriseCTA } from "@/components/auth/AuthEnterpriseCTA"; +import { AuthShell } from "@/components/auth/AuthShell"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -46,48 +47,48 @@ export default function LoginPage() { }; return ( -
- - - Sign in - Enter your email and password to continue - - -
-
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - /> -
- -
-

- Don't have an account?{" "} - - Sign up - -

-
-
-
+ }> +
+

Sign in

+

+ Enter your email and password to continue +

+
+ +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ +
+ +

+ Don't have an account?{" "} + + Sign up + +

+
); } diff --git a/ui/src/app/auth/signup/page.tsx b/ui/src/app/auth/signup/page.tsx index d9d98c18..ad43ec83 100644 --- a/ui/src/app/auth/signup/page.tsx +++ b/ui/src/app/auth/signup/page.tsx @@ -5,8 +5,9 @@ import { useState } from "react"; import { toast } from "sonner"; import { signupApiV1AuthSignupPost } from "@/client/sdk.gen"; +import { AuthEnterpriseCTA } from "@/components/auth/AuthEnterpriseCTA"; +import { AuthShell } from "@/components/auth/AuthShell"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -58,61 +59,59 @@ export default function SignupPage() { }; return ( -
- - - Create an account - Enter your details to get started - - -
-
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - minLength={8} - /> -
-
- - setConfirmPassword(e.target.value)} - required - minLength={8} - /> -
- -
-

- Already have an account?{" "} - - Sign in - -

-
-
-
+ }> +
+

Create an account

+

Enter your details to get started

+
+ +
+
+ + setEmail(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + minLength={8} + /> +
+
+ + setConfirmPassword(e.target.value)} + required + minLength={8} + /> +
+ +
+ +

+ Already have an account?{" "} + + Sign in + +

+
); } diff --git a/ui/src/app/campaigns/new/page.tsx b/ui/src/app/campaigns/new/page.tsx index aab2adcf..c6499b4c 100644 --- a/ui/src/app/campaigns/new/page.tsx +++ b/ui/src/app/campaigns/new/page.tsx @@ -253,7 +253,7 @@ export default function NewCampaignPage() { } if (maxConcurrencyValue > effectiveLimit) { if (availableFromNumbersCount > 0 && availableFromNumbersCount < orgConcurrentLimit) { - toast.error(`Max concurrent calls cannot exceed ${effectiveLimit}. The selected configuration has ${availableFromNumbersCount} phone number(s) — add more CLIs to increase concurrency.`); + toast.error(`Max concurrent calls cannot exceed ${effectiveLimit}. The selected configuration has ${availableFromNumbersCount} phone number(s) - add more CLIs to increase concurrency.`); } else { toast.error(`Max concurrent calls cannot exceed organization limit (${effectiveLimit})`); } @@ -455,7 +455,7 @@ export default function NewCampaignPage() { value={config.id.toString()} > {config.name} ({config.provider}) - {config.is_default_outbound ? ' — default' : ''} + {config.is_default_outbound ? ' - default' : ''} )) )} diff --git a/ui/src/app/globals.css b/ui/src/app/globals.css index 087479fc..e4d83dca 100644 --- a/ui/src/app/globals.css +++ b/ui/src/app/globals.css @@ -184,4 +184,27 @@ @media (prefers-reduced-motion: reduce) { .auth-waveform span { animation: none; } } + + /* Atmospheric app background — premium dark depth instead of flat black. + Decorative only; applied to content areas via the .app-surface class so it + cascades to every page without per-page edits. Light mode stays clean. */ + .app-surface { + background-color: var(--background); + } + .dark .app-surface { + background-image: + radial-gradient(55rem 32rem at 100% 100%, color-mix(in oklch, var(--cta) 13%, transparent), transparent 55%), + radial-gradient(48rem 30rem at 0% 100%, color-mix(in oklch, var(--primary) 10%, transparent), transparent 52%), + linear-gradient(0deg, color-mix(in oklch, var(--foreground) 4%, transparent), transparent 38%); + background-repeat: no-repeat; + } + + /* Faint warm wash at the bottom of the sidebar for subtle depth (dark only). */ + .dark .app-sidebar-surface { + background-image: linear-gradient( + 0deg, + color-mix(in oklch, var(--cta) 8%, transparent), + transparent 32% + ); + } } diff --git a/ui/src/app/handler/[...stack]/AuthShell.tsx b/ui/src/app/handler/[...stack]/AuthShell.tsx deleted file mode 100644 index b438ead1..00000000 --- a/ui/src/app/handler/[...stack]/AuthShell.tsx +++ /dev/null @@ -1,82 +0,0 @@ -// Dark two-column auth shell. LEFT (lg+ only): a brand/value panel with a -// CSS-only audio-waveform motif, proof points, and a Bland-style enterprise CTA -// block at the bottom (passed in as `enterpriseSlot`). RIGHT: a centered -// zinc-900 card that wraps the Stack Auth form (`children`). Mobile collapses to -// the single card column. Palette is the app's blacks/greys with one warm CTA -// accent on the waveform + focus. - -import type { ReactNode } from "react"; - -const PROOF_POINTS = [ - "Open source", - "7+ telephony providers", - "Open architecture", -]; - -export function AuthShell({ - children, - enterpriseSlot, -}: { - children: ReactNode; - enterpriseSlot?: ReactNode; -}) { - return ( -
- {/* Brand / value panel — hidden on mobile */} - - - {/* Form column */} -
-
- {/* Mobile-only wordmark (brand panel is hidden) */} -
-
- -
- Dograh -
- {children} -
-
-
- ); -} diff --git a/ui/src/app/handler/[...stack]/BackButton.tsx b/ui/src/app/handler/[...stack]/BackButton.tsx index d36a4a03..5827c8ef 100644 --- a/ui/src/app/handler/[...stack]/BackButton.tsx +++ b/ui/src/app/handler/[...stack]/BackButton.tsx @@ -8,11 +8,22 @@ import { Button } from "@/components/ui/button"; export function BackButton() { const router = useRouter(); + // On a direct load (e.g. an OAuth redirect or a deep link to /handler/sign-in) + // there's no in-app history, so router.back() would bounce the user off-app. + // Fall back to the home route in that case. + const handleBack = () => { + if (typeof window !== "undefined" && window.history.length > 1) { + router.back(); + } else { + router.push("/"); + } + }; + return ( @@ -436,7 +436,7 @@ export function EmbedDialog({

Integration Instructions

  • • Add the embed script tag to your page (see below).
  • -
  • • The widget renders no UI — render your own buttons.
  • +
  • • The widget renders no UI - render your own buttons.
  • • Call window.DograhWidget.start() to begin a call.
  • • Call window.DograhWidget.end() to end it.
  • • Subscribe to onCallStart, onCallEnd, onStatusChange, onError to drive your UI.
  • @@ -445,12 +445,12 @@ export function EmbedDialog({
-

Example — track status in your own state

+

Example - track status in your own state

Mirror the call status into a variable you control, then render whatever UI you like from it. The status values are idle, connecting, connected, failed.

-                                                    {`// Vanilla JS — keep your own state, render however you want
+                                                    {`// Vanilla JS - keep your own state, render however you want
 let callStatus = 'idle';
 
 window.DograhWidget?.onStatusChange((status) => {
diff --git a/ui/src/app/workflow/[workflowId]/components/PhoneCallDialog.tsx b/ui/src/app/workflow/[workflowId]/components/PhoneCallDialog.tsx
index 5e737ae2..1d03c6d6 100644
--- a/ui/src/app/workflow/[workflowId]/components/PhoneCallDialog.tsx
+++ b/ui/src/app/workflow/[workflowId]/components/PhoneCallDialog.tsx
@@ -268,7 +268,7 @@ export const PhoneCallDialog = ({
                             {telephonyConfigs.map((config) => (
                                 
                                     {config.name} ({config.provider})
-                                    {config.is_default_outbound ? " — default" : ""}
+                                    {config.is_default_outbound ? " - default" : ""}
                                 
                             ))}
                         
@@ -294,8 +294,8 @@ export const PhoneCallDialog = ({
                             
                                 {fromPhoneNumbers.map((phone) => (
                                     
-                                        {phone.label ? `${phone.label} — ${phone.address}` : phone.address}
-                                        {phone.is_default_caller_id ? " — default" : ""}
+                                        {phone.label ? `${phone.label} - ${phone.address}` : phone.address}
+                                        {phone.is_default_caller_id ? " - default" : ""}
                                     
                                 ))}
                             
diff --git a/ui/src/app/workflow/[workflowId]/components/RecordingsDialog.tsx b/ui/src/app/workflow/[workflowId]/components/RecordingsDialog.tsx
index 645f8fb7..582c6d8f 100644
--- a/ui/src/app/workflow/[workflowId]/components/RecordingsDialog.tsx
+++ b/ui/src/app/workflow/[workflowId]/components/RecordingsDialog.tsx
@@ -181,7 +181,7 @@ export const RecordingsDialog = ({
         const valid: PendingFile[] = [];
         for (const file of files) {
             if (file.size > MAX_FILE_SIZE) {
-                setError(`${file.name} (${(file.size / (1024 * 1024)).toFixed(1)}MB) exceeds 5MB limit — skipped.`);
+                setError(`${file.name} (${(file.size / (1024 * 1024)).toFixed(1)}MB) exceeds 5MB limit - skipped.`);
                 continue;
             }
             const id = `pending-${++pendingFileCounter}`;
diff --git a/ui/src/app/workflow/[workflowId]/components/WorkflowEditorHeader.tsx b/ui/src/app/workflow/[workflowId]/components/WorkflowEditorHeader.tsx
index 30eec85e..01fa7b41 100644
--- a/ui/src/app/workflow/[workflowId]/components/WorkflowEditorHeader.tsx
+++ b/ui/src/app/workflow/[workflowId]/components/WorkflowEditorHeader.tsx
@@ -305,7 +305,7 @@ export const WorkflowEditorHeader = ({
                     
- Viewing {activeVersionLabel} — Read only + Viewing {activeVersionLabel} - Read only
)} diff --git a/ui/src/app/workflow/[workflowId]/settings/page.tsx b/ui/src/app/workflow/[workflowId]/settings/page.tsx index b1bff348..d12ed8aa 100644 --- a/ui/src/app/workflow/[workflowId]/settings/page.tsx +++ b/ui/src/app/workflow/[workflowId]/settings/page.tsx @@ -583,7 +583,7 @@ function GeneralSection({

Context Compaction

- Automatically summarize conversation context when transitioning between nodes. Not applicable in Realtime mode — the speech-to-speech service manages its own conversation state and this setting is ignored. + Automatically summarize conversation context when transitioning between nodes. Not applicable in Realtime mode - the speech-to-speech service manages its own conversation state and this setting is ignored.

diff --git a/ui/src/app/workflow/create/page.tsx b/ui/src/app/workflow/create/page.tsx index 1a11efff..73700745 100644 --- a/ui/src/app/workflow/create/page.tsx +++ b/ui/src/app/workflow/create/page.tsx @@ -80,7 +80,7 @@ export default function CreateWorkflowPage() { }; return ( -
+

Create Voice Agent

diff --git a/ui/src/components/BrandLogo.tsx b/ui/src/components/BrandLogo.tsx new file mode 100644 index 00000000..745f8155 --- /dev/null +++ b/ui/src/components/BrandLogo.tsx @@ -0,0 +1,38 @@ +import { cn } from "@/lib/utils"; + +// Reusable Dograh wordmark. Theme-aware by default: the dark logo shows on light +// surfaces and the light/cream logo shows on dark. Pass `inverse` to force the +// light logo on an always-dark surface (e.g. the auth brand panel). Pass `mark` +// to render the square logo mark instead of the full wordmark (e.g. the app +// sidebar header). Height is controlled by the caller via className (e.g. +// "h-7"); width stays auto so each lockup keeps its aspect ratio. +export function BrandLogo({ + className, + inverse = false, + mark = false, +}: { + className?: string; + inverse?: boolean; + mark?: boolean; +}) { + if (mark) { + return ( + // eslint-disable-next-line @next/next/no-img-element + Dograh + ); + } + if (inverse) { + return ( + // eslint-disable-next-line @next/next/no-img-element + Dograh + ); + } + return ( + <> + {/* eslint-disable-next-line @next/next/no-img-element */} + Dograh + {/* eslint-disable-next-line @next/next/no-img-element */} + Dograh + + ); +} diff --git a/ui/src/app/handler/[...stack]/AuthEnterpriseCTA.tsx b/ui/src/components/auth/AuthEnterpriseCTA.tsx similarity index 87% rename from ui/src/app/handler/[...stack]/AuthEnterpriseCTA.tsx rename to ui/src/components/auth/AuthEnterpriseCTA.tsx index 3efeb0e9..3f507cf2 100644 --- a/ui/src/app/handler/[...stack]/AuthEnterpriseCTA.tsx +++ b/ui/src/components/auth/AuthEnterpriseCTA.tsx @@ -2,7 +2,8 @@ // Bland-style enterprise call-to-action rendered inside the auth brand panel. // Links out to the main marketing site's enterprise intake form rather than the -// in-app modal, since the visitor is not yet authenticated here. +// in-app modal, since the visitor is not yet authenticated here. Shared by the +// Stack Auth handler and the local/OSS auth pages. import { Button } from "@/components/ui/button"; @@ -18,7 +19,7 @@ export function AuthEnterpriseCTA() { variant="outline" className="w-full border-white/20 bg-white/5 text-zinc-100 hover:bg-white/10 hover:text-white" > - Talk to our team + Enterprise Enquiry ); diff --git a/ui/src/components/auth/AuthShell.tsx b/ui/src/components/auth/AuthShell.tsx new file mode 100644 index 00000000..5c472691 --- /dev/null +++ b/ui/src/components/auth/AuthShell.tsx @@ -0,0 +1,86 @@ +// Shared dark two-column auth shell, used by BOTH the Stack Auth handler +// (/handler/[...stack], cloud) and the local/OSS auth pages (/auth/login, +// /auth/signup). LEFT: a centered card that wraps the auth form (`children`). +// RIGHT (lg+ only): a brand/value panel with the Dograh logo, proof points, and +// a Bland-style enterprise CTA block at the bottom (passed in as `enterpriseSlot`). +// Mobile collapses to the single card column. The form column scrolls and stays +// centered so tall (sign-up) forms never clip on short viewports. Palette is the +// app's blacks/greys with one warm CTA accent. + +import type { ReactNode } from "react"; + +import { BrandLogo } from "@/components/BrandLogo"; + +const HIGHLIGHTS = [ + "Speech-to-speech", + "MCP-native", + "BYOK - any model", +]; + +export function AuthShell({ + children, + enterpriseSlot, +}: { + children: ReactNode; + enterpriseSlot?: ReactNode; +}) { + return ( +
+ {/* Form column (LEFT) — scrolls and stays centered so tall forms never clip. */} +
+
+
+ {/* Mobile-only wordmark (brand panel is hidden) */} +
+ +
+ {children} +
+
+
+ + {/* Brand / value panel (RIGHT) — hidden on mobile */} + +
+ ); +} diff --git a/ui/src/components/billing/BuyCreditsControl.tsx b/ui/src/components/billing/BuyCreditsControl.tsx index 608f58cf..2c6b15be 100644 --- a/ui/src/components/billing/BuyCreditsControl.tsx +++ b/ui/src/components/billing/BuyCreditsControl.tsx @@ -1,30 +1,40 @@ "use client"; -// Compact self-serve "Buy Credits" control for the billing card. Preset amount -// chips plus a custom amount (min $5) feed the Razorpay seam in -// @/lib/billing/topup. Analytics: chip selection and the buy click are captured -// for funnel analysis. The seam currently throws "not wired yet"; we surface -// that as a calm inline note rather than an error toast. +// Compact self-serve "Buy Credits" control. The amount chips + custom input live +// in a popover that only opens when the user clicks "Buy Credits" — so the +// billing card stays clean until they intend to top up. Presets + custom (min $5) +// feed the Razorpay seam in @/lib/billing/topup, which currently throws "not +// wired yet"; we surface that as a calm inline note rather than an error toast. import posthog from "posthog-js"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { PostHogEvent } from "@/constants/posthog-events"; -import { MIN_TOPUP_USD, startTopUp, TOPUP_PRESETS } from "@/lib/billing/topup"; +import { MAX_TOPUP_USD, MIN_TOPUP_USD, startTopUp, TOPUP_PRESETS } from "@/lib/billing/topup"; import { cn } from "@/lib/utils"; -export function BuyCreditsControl() { +// Round to whole cents and reject non-positive / non-finite input so a typo +// (e.g. "5.999", "-1", "abc") can't produce a NaN or fractional-cent order. +const parseAmount = (raw: string): number | null => { + const n = Number(raw); + if (!Number.isFinite(n) || n <= 0) return null; + return Math.round(n * 100) / 100; +}; + +export function BuyCreditsControl({ className }: { className?: string }) { + const [open, setOpen] = useState(false); const [selected, setSelected] = useState(null); const [custom, setCustom] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); // The effective amount: a parsed custom value takes precedence when present. - const customAmount = custom.trim() ? Number(custom) : null; + const customAmount = custom.trim() ? parseAmount(custom) : null; const amount = customAmount ?? selected; - const valid = amount != null && Number.isFinite(amount) && amount >= MIN_TOPUP_USD; + const valid = amount != null && amount >= MIN_TOPUP_USD && amount <= MAX_TOPUP_USD; const selectPreset = (value: number) => { setSelected(value); @@ -37,8 +47,8 @@ export function BuyCreditsControl() { setCustom(raw); setSelected(null); setError(null); - const parsed = Number(raw); - if (raw.trim() && Number.isFinite(parsed) && parsed >= MIN_TOPUP_USD) { + const parsed = parseAmount(raw); + if (parsed != null && parsed >= MIN_TOPUP_USD && parsed <= MAX_TOPUP_USD) { posthog.capture(PostHogEvent.BUY_CREDITS_AMOUNT_SELECTED, { amount: parsed }); } }; @@ -59,52 +69,66 @@ export function BuyCreditsControl() { }; return ( -
-
- {TOPUP_PRESETS.map((value) => ( - - ))} -
- - $ - - onCustomChange(e.target.value)} - placeholder="Custom" - aria-label={`Custom amount (min $${MIN_TOPUP_USD})`} - className="h-9 w-28 pl-5" - /> + + + + + +
+

Top up credits

+

Pick an amount (min ${MIN_TOPUP_USD}).

-
- {error ? ( -

{error}

- ) : ( -

Minimum ${MIN_TOPUP_USD}.

- )} +
+ {TOPUP_PRESETS.map((value) => ( + + ))} +
+ + $ + + onCustomChange(e.target.value)} + placeholder="Custom" + aria-label={`Custom amount (min $${MIN_TOPUP_USD})`} + className="h-9 w-24 pl-5" + /> +
+
- -
+ {error &&

{error}

} + + + + ); } diff --git a/ui/src/components/billing/DograhCreditsCard.tsx b/ui/src/components/billing/DograhCreditsCard.tsx index f94f74ef..cdaf0419 100644 --- a/ui/src/components/billing/DograhCreditsCard.tsx +++ b/ui/src/components/billing/DograhCreditsCard.tsx @@ -24,7 +24,11 @@ export function DograhCreditsCard() { if (!auth.isAuthenticated) return; try { const response = await getMpsCreditsApiV1OrganizationsUsageMpsCreditsGet(); - if (response.data) { + // The generated client resolves to { data, error } and does NOT throw on + // 4xx/5xx (see ui/AGENTS.md) — check error explicitly. + if (response.error) { + console.error("Failed to fetch MPS credits:", response.error); + } else if (response.data) { setMpsCredits(response.data); } } catch (error) { @@ -74,7 +78,7 @@ export function DograhCreditsCard() {
{mpsCredits.total_quota > 0 && ( - + )}
) : ( @@ -83,20 +87,23 @@ export function DograhCreditsCard() {

)} - {/* Footer CTAs — card ends with self-serve + done-for-you actions */} + {/* Footer CTAs — self-serve + done-for-you side by side, with the + custom-pricing link directly beneath. */}
-
-
-

Running low?

-

Top up instantly, or have us build it for you.

-
-
- - -
+
+

Running low?

+

Top up instantly, or have us build it for you.

+
+
+ +
diff --git a/ui/src/components/flow/ToolSelector.tsx b/ui/src/components/flow/ToolSelector.tsx index 86c300f2..00ffc458 100644 --- a/ui/src/components/flow/ToolSelector.tsx +++ b/ui/src/components/flow/ToolSelector.tsx @@ -280,7 +280,7 @@ export function ToolSelector({ )} {fns.length === 0 && !err && (

- No tools discovered — Refresh. + No tools discovered - Refresh.

)} {fns.map((fn) => { diff --git a/ui/src/components/layout/AppLayout.tsx b/ui/src/components/layout/AppLayout.tsx index 8b2b90c8..e2c67752 100644 --- a/ui/src/components/layout/AppLayout.tsx +++ b/ui/src/components/layout/AppLayout.tsx @@ -19,7 +19,7 @@ function AppHeader() { const { toggleSidebar } = useSidebar(); return ( -
+
+ +
+
+ ); +} diff --git a/ui/src/components/lead-forms/EnterpriseLeadFields.tsx b/ui/src/components/lead-forms/EnterpriseLeadFields.tsx new file mode 100644 index 00000000..067daa8b --- /dev/null +++ b/ui/src/components/lead-forms/EnterpriseLeadFields.tsx @@ -0,0 +1,150 @@ +"use client"; + +// Shared enterprise lead fields, rendered by BOTH the standalone EnterpriseModal +// and the inline on-prem expansion of the onboarding form. One source of truth so +// the two stay identical and submit through the same /api/v1/leads/enterprise +// path. Controlled: the parent owns the values + the submit/captcha flow. + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; + +import { + ENTERPRISE_DEPLOYMENT_OPTIONS, + ENTERPRISE_VOLUME_OPTIONS, +} from "./leadFieldOptions"; +import { PhoneField } from "./PhoneField"; + +export interface EnterpriseFieldsValue { + name: string; + company: string; + jobTitle: string; + workEmail: string; + phone: string; + volume: string; + deployment: string; + agentGoal: string; +} + +export const EMPTY_ENTERPRISE_FIELDS: EnterpriseFieldsValue = { + name: "", + company: "", + jobTitle: "", + workEmail: "", + phone: "", + volume: "", + deployment: "", + agentGoal: "", +}; + +interface EnterpriseLeadFieldsProps { + // Unique prefix for input ids/labels (e.g. "ent", "ob-op") so the two + // instances never collide when both exist in the DOM. + idPrefix: string; + value: EnterpriseFieldsValue; + onChange: (patch: Partial) => void; + // Work email is mandatory only when the visitor is logged out. + workEmailRequired: boolean; + // The deployment question is surfaced only for certain entry points; elsewhere + // it is hidden and the caller defaults the payload to "yes". + showDeployment: boolean; + emailError?: string | null; +} + +export function EnterpriseLeadFields({ + idPrefix: p, + value, + onChange, + workEmailRequired, + showDeployment, + emailError, +}: EnterpriseLeadFieldsProps) { + return ( +
+
+
+ + onChange({ name: e.target.value })} /> +
+
+ + onChange({ company: e.target.value })} /> +
+
+ +
+
+ + onChange({ jobTitle: e.target.value })} /> +
+
+ + onChange({ workEmail: e.target.value })} + /> + {emailError &&

{emailError}

} +
+
+ +
+
+ + onChange({ phone })} required /> +
+
+ + +
+
+ + {showDeployment && ( +
+ + +
+ )} + +
+ +