1<template>
2 <div
3 class="relative flex min-h-screen flex-col overflow-y-auto bg-surface-base transition-opacity duration-300 ease-out"
4 :class="leaving ? 'opacity-0' : 'opacity-100'"
5 >
6 <div class="flex flex-1 flex-col justify-start pb-8 pt-24">
7 <Questionnaire
8 class="h-full"
9 :questions="questions"
10 @submit="submitPersona"
11 />
12 </div>
13 </div>
14</template>
15
16<script setup lang="ts">
17import Questionnaire from "@/components/Questionnaire.vue";
18import type { Question } from "@/components/Questionnaire.types";
19import {
20 setActiveSettingsTab,
21 showSettingsModal,
22} from "@/components/Settings/settingsModal";
23import { markPersonaCaptured } from "@/persona";
24import { capture } from "@/telemetry";
25import { __ } from "@/translation";
26import { call, usePageMeta } from "frappe-ui";
27import { ref } from "vue";
28import { useRouter } from "vue-router";
29
30const router = useRouter();
31const leaving = ref(false);
32const FADE_MS = 300;
33
34// Team size is the one optional answer; an untouched dropdown reports this
35// rather than dropping the key, so the answer set is always complete.
36const TEAM_SIZE_UNSPECIFIED = "prefer_not_to_say";
37
38// The last question's "first goal" answer maps to a settings tab; other goals
39// route directly (create ticket, explore).
40const settingsTabForGoal: Record<
41 string,
42 "Email Accounts" | "Invite Agents" | "General"
43> = {
44 connect_email: "Email Accounts",
45 invite_team: "Invite Agents",
46 setup_portal: "General",
47};
48
49async function finishOnboarding(answers: Record<string, string | string[]>) {
50 leaving.value = true;
51 // Persist and fade in parallel; localStorage is the durable guard.
52 const brandName =
53 typeof answers.company_name === "string" ? answers.company_name : undefined;
54 const fade = new Promise((resolve) => setTimeout(resolve, FADE_MS));
55 await Promise.allSettled([markPersonaCaptured(brandName), fade]);
56 try {
57 await routeToGoal(answers.first_goal);
58 } catch {
59 leaving.value = false; // navigation failed — un-fade so we're not stuck
60 }
61}
62
63// Send the admin to their first goal: new ticket, a settings tab over
64// Tickets, or the seeded welcome ticket. Skipping leaves no goal, so it lands
65// on the welcome ticket like "explore".
66async function routeToGoal(goal?: string | string[]) {
67 if (goal === "create_ticket") {
68 await router.push({ name: "TicketAgentNew" });
69 return;
70 }
71 if (!goal || goal === "explore") {
72 const ticket = await call(
73 "opendesk.api.onboarding.get_welcome_ticket"
74 ).catch(() => null);
75 await router.push(
76 ticket
77 ? { name: "TicketAgent", params: { ticketId: ticket } }
78 : { name: "TicketsAgent" }
79 );
80 return;
81 }
82 await router.push({ name: "TicketsAgent" });
83 const tab = typeof goal === "string" ? settingsTabForGoal[goal] : undefined;
84 if (tab) {
85 setActiveSettingsTab(tab);
86 showSettingsModal.value = true;
87 }
88}
89
90async function submitPersona(answers: Record<string, string | string[]>) {
91 const data = {
92 ...answers,
93 company_size: answers.company_size || TEAM_SIZE_UNSPECIFIED,
94 };
95 capture("onboarding_persona_od", { data });
96 await finishOnboarding(data);
97}
98
99const questions = [
100 {
101 key: "company_name",
102 title: __("Tell us about your organization"),
103 type: "text",
104 label: __("What is your organization's name?"),
105 placeholder: __("e.g. Acme Inc."),
106 required: true,
107 dropdown: {
108 key: "company_size",
109 label: __("How big is your support team?"),
110 placeholder: __("Select team size"),
111 options: [
112 { label: __("1–5"), value: "1-5" },
113 { label: __("6–10"), value: "6-10" },
114 { label: __("11–25"), value: "11-25" },
115 { label: __("26–50"), value: "26-50" },
116 { label: __("51–100"), value: "51-100" },
117 { label: __("100+"), value: "100+" },
118 ],
119 },
120 },
121 {
122 key: "current_solution",
123 title: __("What do you currently use to manage customer support?"),
124 type: "choice",
125 options: [
126 { label: __("Spreadsheets"), value: "spreadsheets" },
127 { label: __("Notion"), value: "notion" },
128 { label: __("Zendesk"), value: "zendesk" },
129 { label: __("Freshdesk"), value: "freshdesk" },
130 { label: __("Intercom"), value: "intercom" },
131 { label: __("Jira Service Management"), value: "jira" },
132 { label: __("Zoho Desk"), value: "zoho_desk" },
133 { label: __("Salesforce Service Cloud"), value: "salesforce" },
134 { label: __("We don't use any opendesk yet"), value: "none" },
135 {
136 label: __("Shared email inbox (Gmail, Outlook, etc.)"),
137 value: "shared_inbox",
138 },
139 { label: __("Other"), value: "other" },
140 ],
141 otherPlaceholder: __("Which tool does your team use?"),
142 },
143 {
144 key: "contact_channels",
145 title: __("How do your customers currently contact your support team?"),
146 type: "choice",
147 multiple: true,
148 options: [
149 { label: __("Email"), value: "email" },
150 { label: __("Support portal"), value: "support_portal" },
151 { label: __("WhatsApp"), value: "whatsapp" },
152 { label: __("Phone calls"), value: "phone_calls" },
153 { label: __("Live chat"), value: "live_chat" },
154 { label: __("Web form"), value: "web_form" },
155 { label: __("Social media"), value: "social_media" },
156 { label: __("In person"), value: "in_person" },
157 { label: __("Other"), value: "other" },
158 ],
159 otherPlaceholder: __("Which channel do they use?"),
160 },
161 {
162 key: "preferred_channels",
163 title: __(
164 "How would you like your customers to create support tickets in Opendesk?"
165 ),
166 type: "choice",
167 multiple: true,
168 options: [
169 { label: __("Via email"), value: "email" },
170 { label: __("Through a support portal"), value: "support_portal" },
171 { label: __("Via WhatsApp"), value: "whatsapp" },
172 { label: __("Live chat"), value: "live_chat" },
173 { label: __("I'm not sure yet"), value: "not_sure" },
174 ],
175 },
176 {
177 key: "biggest_challenges",
178 title: __("What's your biggest support challenge today?"),
179 type: "choice",
180 multiple: true,
181 options: [
182 {
183 label: __("Managing customer conversations across channels"),
184 value: "omnichannel",
185 },
186 {
187 label: __("Finding past conversations"),
188 value: "finding_conversations",
189 },
190 {
191 label: __("Measuring team performance"),
192 value: "measuring_performance",
193 },
194 { label: __("Scaling support as we grow"), value: "scaling" },
195 { label: __("Missing or delayed responses"), value: "delayed_responses" },
196 {
197 label: __("Keeping track of customer requests"),
198 value: "tracking_requests",
199 },
200 {
201 label: __("Assigning tickets"),
202 value: "assignment",
203 },
204 { label: __("Other"), value: "other" },
205 ],
206 otherPlaceholder: __("What makes support hard right now?"),
207 },
208 {
209 key: "referral_source",
210 title: __("How did you hear about Opendesk?"),
211 type: "choice",
212 options: [
213 { label: __("GitHub"), value: "github" },
214 { label: __("Google Ads"), value: "google_ads" },
215 { label: __("YouTube"), value: "youtube" },
216 { label: __("Frappe Discuss"), value: "frappe_discuss" },
217 {
218 label: __("Another Frappe product (ERPNext, Frappe Cloud, etc.)"),
219 value: "frappe_product",
220 },
221 { label: __("LinkedIn"), value: "linkedin" },
222 { label: __("X (Twitter)"), value: "x" },
223 { label: __("Reddit"), value: "reddit" },
224 { label: __("A friend or colleague"), value: "word_of_mouth" },
225 { label: __("A Frappe partner or consultant"), value: "partner" },
226 { label: __("An event or conference"), value: "event" },
227 { label: __("Other"), value: "other" },
228 ],
229 otherPlaceholder: __("Where did you hear about us?"),
230 },
231 {
232 key: "first_goal",
233 title: __("What would you like to do first in Opendesk?"),
234 type: "choice",
235 options: [
236 { label: __("Connect my support email"), value: "connect_email" },
237 { label: __("Set up a customer portal"), value: "setup_portal" },
238 { label: __("Create my first ticket"), value: "create_ticket" },
239 { label: __("Invite my support team"), value: "invite_team" },
240 { label: __("Explore the product first"), value: "explore" },
241 ],
242 },
243] satisfies Question[];
244
245usePageMeta(() => ({ title: __("Welcome to Opendesk") }));
246</script>
247