Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cde6b75c1e | ||
|
|
9aeb5b8db6 | ||
|
|
2a66cd5eb7 | ||
|
|
80bd0e39b4 | ||
|
|
a1334c947d | ||
|
|
5ce8c1c3d7 | ||
|
|
b617f3e4b2 | ||
|
|
29cb6c18be | ||
|
|
49cfe240e3 | ||
|
|
218acfd50a | ||
|
|
2267d85b29 | ||
|
|
f1484f1625 | ||
|
|
a0ef39fb12 | ||
|
|
621ebf13bd | ||
|
|
693cc29339 | ||
|
|
cea740ceb7 | ||
|
|
8fe521d66d | ||
|
|
f728c8089d | ||
|
|
585ad7d9a4 | ||
|
|
b36a789518 | ||
|
|
68d36e314f | ||
|
|
1b17068948 | ||
|
|
2f5ba952f4 | ||
|
|
489f2b5bcc | ||
|
|
5468e8a625 | ||
|
|
476eff7f5d | ||
|
|
ffcb148626 | ||
|
|
f32aa4b517 | ||
|
|
a017c51c6b | ||
|
|
bdc9b63800 | ||
|
|
9275328a3a | ||
|
|
ff30c3ea61 | ||
|
|
086b131375 | ||
|
|
12b56e6a2f | ||
|
|
b0e6fc155d | ||
|
|
9c75c3b829 | ||
|
|
2d02261bbb | ||
|
|
f4f243bd3f | ||
|
|
944c6c4842 | ||
|
|
115690b88a | ||
|
|
4f7ed036b0 | ||
|
|
51b845e937 | ||
|
|
ae0eac9474 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class AccountBuilder
|
class AccountBuilder
|
||||||
include CustomExceptions::Account
|
include CustomExceptions::Account
|
||||||
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin, :locale]
|
pattr_initialize [:email!, :confirmed, :user, :user_password, :super_admin, :locale]
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
if @user.nil?
|
if @user.nil?
|
||||||
@@ -39,7 +39,7 @@ class AccountBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_account
|
def create_account
|
||||||
@account = Account.create!(name: @account_name, locale: I18n.locale)
|
@account = Account.create!(name: '', locale: I18n.locale)
|
||||||
Current.account = @account
|
Current.account = @account
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -63,8 +63,8 @@ class AccountBuilder
|
|||||||
def create_user
|
def create_user
|
||||||
@user = User.new(email: @email,
|
@user = User.new(email: @email,
|
||||||
password: user_password,
|
password: user_password,
|
||||||
password_confirmation: user_password,
|
name: '',
|
||||||
name: @user_full_name)
|
password_confirmation: user_password)
|
||||||
@user.type = 'SuperAdmin' if @super_admin
|
@user.type = 'SuperAdmin' if @super_admin
|
||||||
@user.confirm if @confirmed
|
@user.confirm if @confirmed
|
||||||
@user.save!
|
@user.save!
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@user, @account = AccountBuilder.new(
|
@user, @account = AccountBuilder.new(
|
||||||
account_name: account_params[:account_name],
|
|
||||||
user_full_name: account_params[:user_full_name],
|
|
||||||
email: account_params[:email],
|
email: account_params[:email],
|
||||||
user_password: account_params[:password],
|
user_password: account_params[:password],
|
||||||
locale: account_params[:locale],
|
locale: account_params[:locale],
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<with-label
|
||||||
|
:label="label"
|
||||||
|
:name="name"
|
||||||
|
:has-error="hasError"
|
||||||
|
:error-message="errorMessage"
|
||||||
|
>
|
||||||
|
<template #label> {{ label }} <slot /> </template>
|
||||||
|
|
||||||
|
<input
|
||||||
|
:id="name"
|
||||||
|
:name="name"
|
||||||
|
:type="type"
|
||||||
|
autocomplete="off"
|
||||||
|
:required="required"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:data-testid="dataTestid"
|
||||||
|
:value="value"
|
||||||
|
:class="{
|
||||||
|
'focus:ring-red-600 ring-red-600': hasError,
|
||||||
|
'dark:ring-slate-600 dark:focus:ring-woot-500 ring-slate-200':
|
||||||
|
!hasError,
|
||||||
|
'px-3 py-3': spacing === 'base',
|
||||||
|
'px-3 py-2 mb-0': spacing === 'compact',
|
||||||
|
}"
|
||||||
|
class="block w-full border-none rounded-md shadow-sm appearance-none outline outline-1 outline-slate-200 dark:outline-slate-800 focus:outline-none focus:outline-0 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 focus:ring-2 focus:ring-woot-500 sm:text-sm sm:leading-6 dark:bg-slate-800"
|
||||||
|
@input="onInput"
|
||||||
|
@blur="$emit('blur')"
|
||||||
|
/>
|
||||||
|
</with-label>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import WithLabel from './WithLabel.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WithLabel,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text',
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
dataTestid: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
type: String,
|
||||||
|
default: 'base',
|
||||||
|
validator: value => ['base', 'compact'].includes(value),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(e) {
|
||||||
|
this.$emit('input', e.target.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<with-label
|
||||||
|
:label="label"
|
||||||
|
:name="name"
|
||||||
|
:has-error="hasError"
|
||||||
|
:error-message="errorMessage"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
:id="id"
|
||||||
|
:selected="value"
|
||||||
|
:name="name"
|
||||||
|
:class="{
|
||||||
|
'text-slate-400': !value,
|
||||||
|
'text-slate-900 dark:text-slate-100': value,
|
||||||
|
}"
|
||||||
|
class="block w-full px-3 py-2 pr-6 border-0 rounded-md shadow-sm outline-none appearance-none select-caret ring-1 ring-inset placeholder:text-slate-400 focus:ring-2 focus:ring-inset focus:ring-woot-500 sm:text-sm sm:leading-6 dark:bg-slate-700 dark:ring-slate-600 dark:focus:ring-woot-500 ring-slate-200"
|
||||||
|
@input="onInput"
|
||||||
|
>
|
||||||
|
<option value="" disabled selected class="hidden">
|
||||||
|
{{ placeholder }}
|
||||||
|
</option>
|
||||||
|
<slot>
|
||||||
|
<option v-for="opt in options" :key="opt.value" :value="opt.value">
|
||||||
|
{{ opt.label }}
|
||||||
|
</option>
|
||||||
|
</slot>
|
||||||
|
</select>
|
||||||
|
</with-label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WithLabel from './WithLabel.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WithLabel,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(e) {
|
||||||
|
this.$emit('input', e.target.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.select-caret {
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='32' height='24' viewBox='0 0 32 24'><polygon points='0,0 32,0 16,24' style='fill: rgb%28110, 111, 115%29'></polygon></svg>");
|
||||||
|
background-origin: content-box;
|
||||||
|
background-position: right -1rem center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 9px 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<with-label
|
||||||
|
:label="label"
|
||||||
|
:name="name"
|
||||||
|
:has-error="hasError"
|
||||||
|
:error-message="errorMessage"
|
||||||
|
>
|
||||||
|
<template #label> {{ label }} <slot /> </template>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
:id="name"
|
||||||
|
:name="name"
|
||||||
|
autocomplete="off"
|
||||||
|
:required="required"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:data-testid="dataTestid"
|
||||||
|
:value="value"
|
||||||
|
:rows="rows"
|
||||||
|
:class="{
|
||||||
|
'focus:ring-red-600 ring-red-600': hasError,
|
||||||
|
'dark:ring-slate-600 dark:focus:ring-woot-500 ring-slate-200':
|
||||||
|
!hasError,
|
||||||
|
'px-3 py-3': spacing === 'base',
|
||||||
|
'px-3 py-2': spacing === 'compact',
|
||||||
|
'resize-none': !allowResize,
|
||||||
|
}"
|
||||||
|
class="block w-full border-none rounded-md shadow-sm appearance-none outline outline-1 outline-slate-200 dark:outline-slate-800 focus:outline-none focus:outline-0 text-slate-900 dark:text-slate-100 placeholder:text-slate-400 focus:ring-2 focus:ring-woot-500 sm:text-sm sm:leading-6 dark:bg-slate-800"
|
||||||
|
@input="onInput"
|
||||||
|
@blur="$emit('blur')"
|
||||||
|
/>
|
||||||
|
</with-label>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import WithLabel from './WithLabel.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WithLabel,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Number,
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
allowResize: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
dataTestid: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
type: String,
|
||||||
|
default: 'base',
|
||||||
|
validator: value => ['base', 'compact'].includes(value),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInput(e) {
|
||||||
|
this.$emit('input', e.target.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<label
|
||||||
|
v-if="label"
|
||||||
|
:for="name"
|
||||||
|
class="flex justify-between text-sm font-medium leading-6 text-slate-900 dark:text-white"
|
||||||
|
:class="{ 'text-red-500': hasError }"
|
||||||
|
>
|
||||||
|
<slot name="label">
|
||||||
|
{{ label }}
|
||||||
|
</slot>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
<span
|
||||||
|
v-if="errorMessage && hasError"
|
||||||
|
class="text-xs text-red-400 leading-2"
|
||||||
|
>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hasError: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -44,3 +44,4 @@ export default {
|
|||||||
EXAMPLE_WEBHOOK_URL: 'https://example/api/webhook',
|
EXAMPLE_WEBHOOK_URL: 'https://example/api/webhook',
|
||||||
};
|
};
|
||||||
export const DEFAULT_REDIRECT_URL = '/app/';
|
export const DEFAULT_REDIRECT_URL = '/app/';
|
||||||
|
export const ONBOARDING_START_URL = '/app/start/setup-profile';
|
||||||
|
|||||||
@@ -40,5 +40,65 @@
|
|||||||
},
|
},
|
||||||
"SUBMIT": "Create account",
|
"SUBMIT": "Create account",
|
||||||
"HAVE_AN_ACCOUNT": "Already have an account?"
|
"HAVE_AN_ACCOUNT": "Already have an account?"
|
||||||
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"PROFILE": {
|
||||||
|
"TITLE": "Tell us about yourself",
|
||||||
|
"BODY": "Here, we'll set up your details like profile picture, display name, contact info, etc., visible to end users, to make a positive first impression.",
|
||||||
|
"SUBMIT": "Continue to company setup",
|
||||||
|
"FULL_NAME": {
|
||||||
|
"LABEL": "Full name",
|
||||||
|
"PLACEHOLDER": "Enter your full name"
|
||||||
|
},
|
||||||
|
"DISPLAY_NAME": {
|
||||||
|
"LABEL": "Display name",
|
||||||
|
"PLACEHOLDER": "Enter the name you want to display your customers"
|
||||||
|
},
|
||||||
|
"PHONE_NUMBER": {
|
||||||
|
"LABEL": "Phone number (Optional)",
|
||||||
|
"PLACEHOLDER": "Enter your phone number"
|
||||||
|
},
|
||||||
|
"SIGNATURE": {
|
||||||
|
"LABEL": "Signature",
|
||||||
|
"PLACEHOLDER": "Add a signature for your messages. You can set this later in your settings."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"COMPANY": {
|
||||||
|
"TITLE": "Tell us about your company",
|
||||||
|
"BODY": "In this section, we'll set up the language, timezone, and company size. This information allows us to provide contextually accurate information and suggest relevant features for you.",
|
||||||
|
"SUBMIT": "Continue",
|
||||||
|
"COMPANY_NAME": {
|
||||||
|
"LABEL": "Company name",
|
||||||
|
"PLACEHOLDER": "Enter your company name",
|
||||||
|
"ERROR": "Company name is too short."
|
||||||
|
},
|
||||||
|
"INDUSTRY": {
|
||||||
|
"LABEL": "Industry",
|
||||||
|
"PLACEHOLDER": "Select your industry"
|
||||||
|
},
|
||||||
|
"TIMEZONE": {
|
||||||
|
"LABEL": "Timezone",
|
||||||
|
"PLACEHOLDER": "What timezone do you work from?"
|
||||||
|
},
|
||||||
|
"LOCALE": {
|
||||||
|
"LABEL": "Language",
|
||||||
|
"PLACEHOLDER": "Select the language you want to use"
|
||||||
|
},
|
||||||
|
"SIZE": {
|
||||||
|
"LABEL": "Company size",
|
||||||
|
"PLACEHOLDER": "How many people work at your company?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"INVITE_TEAM": {
|
||||||
|
"TITLE": "Invite your team",
|
||||||
|
"BODY": "Invite your team members to Chatwoot. You can add them later as well.",
|
||||||
|
"SUBMIT": "Send invitations",
|
||||||
|
"PLACEHOLDER": "robin@chatwoot.com",
|
||||||
|
"SKIP": "Skip for now"
|
||||||
|
},
|
||||||
|
"FOUNDERS_NOTE": {
|
||||||
|
"TITLE": "A note from the founder",
|
||||||
|
"SUBMIT": "Go to dashboard"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,10 @@ import helpcenterRoutes from './helpcenter/helpcenter.routes';
|
|||||||
|
|
||||||
const AppContainer = () => import('./Dashboard.vue');
|
const AppContainer = () => import('./Dashboard.vue');
|
||||||
const Suspended = () => import('./suspended/Index.vue');
|
const Suspended = () => import('./suspended/Index.vue');
|
||||||
|
const SetupProfile = () => import('./start/SetupProfile.vue');
|
||||||
|
const SetupCompany = () => import('./start/SetupCompany.vue');
|
||||||
|
const FoundersNote = () => import('./start/FoundersNote.vue');
|
||||||
|
const InviteTeam = () => import('./start/InviteTeam.vue');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
routes: [
|
routes: [
|
||||||
@@ -29,5 +33,25 @@ export default {
|
|||||||
roles: ['administrator', 'agent'],
|
roles: ['administrator', 'agent'],
|
||||||
component: Suspended,
|
component: Suspended,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('start/setup-profile'),
|
||||||
|
name: 'onboarding_setup_profile',
|
||||||
|
component: SetupProfile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('start/setup-company'),
|
||||||
|
name: 'onboarding_setup_company',
|
||||||
|
component: SetupCompany,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('start/founders-note'),
|
||||||
|
name: 'onboarding_founders_note',
|
||||||
|
component: FoundersNote,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('start/invite-team'),
|
||||||
|
name: 'onboarding_invite-team',
|
||||||
|
component: InviteTeam,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<onboarding-layout
|
||||||
|
:title="$t('ONBOARDING.FOUNDERS_NOTE.TITLE')"
|
||||||
|
current-step="founders-note"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<section class="mt-4 space-y-3 text-sm leading-relaxed">
|
||||||
|
<p>👋 Hey {{ user.name }},</p>
|
||||||
|
<p>
|
||||||
|
I'm Pranav, one of the co-founders at Chatwoot. Thanks for checking
|
||||||
|
out Chatwoot. We're eager to understand your needs and expectations.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you are facing trouble using the app or achieving your use case, we
|
||||||
|
want to help you as quickly as possible.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Feel free to schedule a call with our team. We are open to your
|
||||||
|
feedback. Talk soon!
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<figure class="mt-5 text-sm leading-relaxed">
|
||||||
|
<thumbnail
|
||||||
|
src="https://www.chatwoot.com/images/team/pranav.jpg"
|
||||||
|
username="Pranav"
|
||||||
|
/>
|
||||||
|
<p class="mt-2">Pranav, <br />Co-founder & CEO, Chatwoot</p>
|
||||||
|
</figure>
|
||||||
|
</template>
|
||||||
|
<submit-button
|
||||||
|
button-class="flex justify-center w-full text-sm text-center"
|
||||||
|
:button-text="$t('ONBOARDING.FOUNDERS_NOTE.SUBMIT')"
|
||||||
|
/>
|
||||||
|
</onboarding-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import OnboardingLayout from './components/OnboardingLayout.vue';
|
||||||
|
import Thumbnail from '../../../../dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
import SubmitButton from '../../../components/buttons/FormSubmitButton.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
OnboardingLayout,
|
||||||
|
Thumbnail,
|
||||||
|
SubmitButton,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
name: 'Shivam',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<onboarding-layout
|
||||||
|
:title="$t('ONBOARDING.INVITE_TEAM.TITLE')"
|
||||||
|
:body="$t('ONBOARDING.INVITE_TEAM.BODY')"
|
||||||
|
current-step="invite"
|
||||||
|
>
|
||||||
|
<section class="space-y-5">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<form-input
|
||||||
|
v-model="emailToAdd"
|
||||||
|
name="email"
|
||||||
|
spacing="compact"
|
||||||
|
class="flex-grow"
|
||||||
|
:placeholder="$t('ONBOARDING.INVITE_TEAM.PLACEHOLDER')"
|
||||||
|
@keyup.enter="pushEmail"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
@click="pushEmail"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="rounded-md min-h-[10rem] divide-y divide-slate-100"
|
||||||
|
:class="{
|
||||||
|
'border border-slate-200 dark:border-slate-700 border-dashed grid place-content-center':
|
||||||
|
emailsToInvite.length === 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div v-if="emailsToInvite.length === 0" class="px-5 py-5">
|
||||||
|
<p class="text-sm text-slate-400">No members to invite</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="email in emailsToInvite"
|
||||||
|
:key="email"
|
||||||
|
class="flex items-center justify-between gap-2 py-2 first:pt-0 last:pb-0"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<thumbnail :username="email" size="30px" />
|
||||||
|
{{ email }}
|
||||||
|
</div>
|
||||||
|
<woot-button
|
||||||
|
variant="clear"
|
||||||
|
size="small"
|
||||||
|
color-scheme="alert"
|
||||||
|
icon="delete"
|
||||||
|
@click="emailsToInvite.splice(emailsToInvite.indexOf(email), 1)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<submit-button
|
||||||
|
button-class="flex justify-center w-full text-sm text-center"
|
||||||
|
:button-text="$t('ONBOARDING.INVITE_TEAM.SUBMIT')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<woot-button
|
||||||
|
variant="clear"
|
||||||
|
color-scheme="secondary"
|
||||||
|
size="expanded"
|
||||||
|
is-expanded
|
||||||
|
>
|
||||||
|
{{ $t('ONBOARDING.INVITE_TEAM.SKIP') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</onboarding-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormInput from '../../../components/form/Input.vue';
|
||||||
|
import WootButton from '../../../components/ui/WootButton.vue';
|
||||||
|
import Thumbnail from '../../../../dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
import OnboardingLayout from './components/OnboardingLayout.vue';
|
||||||
|
import SubmitButton from '../../../components/buttons/FormSubmitButton.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
OnboardingLayout,
|
||||||
|
WootButton,
|
||||||
|
Thumbnail,
|
||||||
|
SubmitButton,
|
||||||
|
FormInput,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
emailsToInvite: [],
|
||||||
|
emailToAdd: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
pushEmail() {
|
||||||
|
if (!this.emailToAdd) return;
|
||||||
|
this.emailsToInvite.push(this.emailToAdd);
|
||||||
|
this.emailsToInvite = [...new Set(this.emailsToInvite)];
|
||||||
|
this.emailToAdd = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<onboarding-layout
|
||||||
|
:title="$t('ONBOARDING.COMPANY.TITLE')"
|
||||||
|
:body="$t('ONBOARDING.COMPANY.BODY')"
|
||||||
|
current-step="company"
|
||||||
|
>
|
||||||
|
<form class="space-y-8">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<form-input
|
||||||
|
v-model="companyName"
|
||||||
|
name="companyName"
|
||||||
|
spacing="compact"
|
||||||
|
:label="$t('ONBOARDING.COMPANY.COMPANY_NAME.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.COMPANY.COMPANY_NAME.PLACEHOLDER')"
|
||||||
|
:error-message="$t('ONBOARDING.COMPANY.COMPANY_NAME.ERROR')"
|
||||||
|
/>
|
||||||
|
<form-input
|
||||||
|
v-model="industry"
|
||||||
|
name="industry"
|
||||||
|
spacing="compact"
|
||||||
|
:label="$t('ONBOARDING.COMPANY.INDUSTRY.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.COMPANY.INDUSTRY.PLACEHOLDER')"
|
||||||
|
/>
|
||||||
|
<form-input
|
||||||
|
v-model="timezone"
|
||||||
|
name="timezone"
|
||||||
|
spacing="compact"
|
||||||
|
:label="$t('ONBOARDING.COMPANY.TIMEZONE.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.COMPANY.TIMEZONE.PLACEHOLDER')"
|
||||||
|
/>
|
||||||
|
<form-select
|
||||||
|
v-model="locale"
|
||||||
|
name="locale"
|
||||||
|
:label="$t('ONBOARDING.COMPANY.LOCALE.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.COMPANY.LOCALE.PLACEHOLDER')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="lang in languagesSortedByCode"
|
||||||
|
:key="lang.iso_639_1_code"
|
||||||
|
:value="lang.iso_639_1_code"
|
||||||
|
>
|
||||||
|
{{ lang.name }}
|
||||||
|
</option>
|
||||||
|
</form-select>
|
||||||
|
<form-select
|
||||||
|
v-model="companySize"
|
||||||
|
name="companySize"
|
||||||
|
:label="$t('ONBOARDING.COMPANY.SIZE.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.COMPANY.SIZE.PLACEHOLDER')"
|
||||||
|
:options="companySizeOptions"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<submit-button
|
||||||
|
button-class="flex justify-center w-full text-sm text-center"
|
||||||
|
:button-text="$t('ONBOARDING.COMPANY.SUBMIT')"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</onboarding-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormInput from '../../../components/form/Input.vue';
|
||||||
|
import FormSelect from '../../../components/form/Select.vue';
|
||||||
|
import SubmitButton from '../../../components/buttons/FormSubmitButton.vue';
|
||||||
|
import OnboardingLayout from './components/OnboardingLayout.vue';
|
||||||
|
import configMixin from 'shared/mixins/configMixin';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FormInput,
|
||||||
|
SubmitButton,
|
||||||
|
OnboardingLayout,
|
||||||
|
FormSelect,
|
||||||
|
},
|
||||||
|
mixins: [configMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
companyName: '',
|
||||||
|
timezone: '',
|
||||||
|
locale: '',
|
||||||
|
companySize: '',
|
||||||
|
industry: '',
|
||||||
|
companySizeOptions: [
|
||||||
|
{ value: '1-10', label: '1-10' },
|
||||||
|
{ value: '11-50', label: '11-50' },
|
||||||
|
{ value: '51-200', label: '51-200' },
|
||||||
|
{ value: '201-500', label: '201-500' },
|
||||||
|
{ value: '501-1000', label: '501-1000' },
|
||||||
|
{ value: '1001-5000', label: '1001-5000' },
|
||||||
|
{ value: '5001+', label: 'Over 5000' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
languagesSortedByCode() {
|
||||||
|
const enabledLanguages = [...this.enabledLanguages];
|
||||||
|
return enabledLanguages.sort((l1, l2) =>
|
||||||
|
l1.iso_639_1_code.localeCompare(l2.iso_639_1_code)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<onboarding-layout
|
||||||
|
:title="$t('ONBOARDING.PROFILE.TITLE')"
|
||||||
|
:body="$t('ONBOARDING.PROFILE.BODY')"
|
||||||
|
current-step="profile"
|
||||||
|
>
|
||||||
|
<form class="mt-8 space-y-8">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<form-input
|
||||||
|
v-model="fullName"
|
||||||
|
name="full_name"
|
||||||
|
spacing="compact"
|
||||||
|
:label="$t('ONBOARDING.PROFILE.FULL_NAME.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.PROFILE.FULL_NAME.PLACEHOLDER')"
|
||||||
|
:error-message="$t('ONBOARDING.PROFILE.FULL_NAME.ERROR')"
|
||||||
|
/>
|
||||||
|
<form-input
|
||||||
|
v-model="displayName"
|
||||||
|
name="display_name"
|
||||||
|
spacing="compact"
|
||||||
|
:label="$t('ONBOARDING.PROFILE.DISPLAY_NAME.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.PROFILE.DISPLAY_NAME.PLACEHOLDER')"
|
||||||
|
/>
|
||||||
|
<form-input
|
||||||
|
v-model="phoneNumber"
|
||||||
|
name="phone_number"
|
||||||
|
spacing="compact"
|
||||||
|
:label="$t('ONBOARDING.PROFILE.PHONE_NUMBER.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.PROFILE.PHONE_NUMBER.PLACEHOLDER')"
|
||||||
|
/>
|
||||||
|
<form-text-area
|
||||||
|
v-model="signature"
|
||||||
|
name="signature"
|
||||||
|
spacing="compact"
|
||||||
|
:allow-resize="false"
|
||||||
|
:label="$t('ONBOARDING.PROFILE.SIGNATURE.LABEL')"
|
||||||
|
:placeholder="$t('ONBOARDING.PROFILE.SIGNATURE.PLACEHOLDER')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<submit-button
|
||||||
|
button-class="flex justify-center w-full text-sm text-center"
|
||||||
|
:button-text="$t('ONBOARDING.PROFILE.SUBMIT')"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</onboarding-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormInput from '../../../components/form/Input.vue';
|
||||||
|
import FormTextArea from '../../../components/form/TextArea.vue';
|
||||||
|
import SubmitButton from '../../../components/buttons/FormSubmitButton.vue';
|
||||||
|
import OnboardingLayout from './components/OnboardingLayout.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FormInput,
|
||||||
|
SubmitButton,
|
||||||
|
OnboardingLayout,
|
||||||
|
FormTextArea,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fullName: '',
|
||||||
|
displayName: '',
|
||||||
|
phoneNumber: '',
|
||||||
|
signature: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<section
|
||||||
|
class="relative h-screen px-8 py-12 dark:text-white bg-woot-25 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<div class="absolute inset-0 w-full h-screen overflow-hidden">
|
||||||
|
<img
|
||||||
|
id="bg-grad"
|
||||||
|
src="/onboarding/bg-gradient.png"
|
||||||
|
class="w-full opacity-50 dark:mix-blend-color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="relative max-w-lg mx-auto">
|
||||||
|
<div
|
||||||
|
id="steps"
|
||||||
|
class="rounded-md w-48 -left-52 dark:shadow-[#000] p-1 border shadow-sm border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 absolute grid gap-1"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
class="px-2 pt-1 font-bold tracking-widest uppercase text-xxs text-slate-500"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</h6>
|
||||||
|
<onboarding-step
|
||||||
|
v-for="(step, index) in steps"
|
||||||
|
:key="step.name"
|
||||||
|
v-bind="step"
|
||||||
|
:step-number="index + 1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="modal-body"
|
||||||
|
class="dark:shadow-[#000] rounded-md mx-auto p-9 border shadow border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<h2 class="text-lg font-bold tracking-tight dark:text-white">
|
||||||
|
<slot name="title">
|
||||||
|
{{ title }}
|
||||||
|
</slot>
|
||||||
|
</h2>
|
||||||
|
<slot name="body">
|
||||||
|
<p class="mt-2 text-sm dark:text-slate-200">
|
||||||
|
{{ body }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
<div class="mt-8">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import OnboardingStep from './OnboardingStep.vue';
|
||||||
|
|
||||||
|
const UISteps = [
|
||||||
|
{
|
||||||
|
name: 'profile',
|
||||||
|
title: 'Setup Profile',
|
||||||
|
isActive: false,
|
||||||
|
isComplete: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'company',
|
||||||
|
title: 'Setup Company',
|
||||||
|
isActive: false,
|
||||||
|
isComplete: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invite',
|
||||||
|
title: 'Invite your team',
|
||||||
|
isActive: false,
|
||||||
|
isComplete: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'founders-note',
|
||||||
|
title: "Founder's Note",
|
||||||
|
hidden: true,
|
||||||
|
isActive: false,
|
||||||
|
isComplete: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
OnboardingStep,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
currentStep: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
steps() {
|
||||||
|
let foundCurrentStep = false;
|
||||||
|
|
||||||
|
return UISteps.map(step => {
|
||||||
|
if (step.name === this.currentStep) {
|
||||||
|
foundCurrentStep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...step,
|
||||||
|
isActive: step.name === this.currentStep,
|
||||||
|
isComplete: !foundCurrentStep,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="!hidden"
|
||||||
|
class="flex items-center gap-2 p-2 text-sm rounded-md"
|
||||||
|
:class="{
|
||||||
|
'text-slate-800 dark:text-slate-100 bg-slate-50 dark:bg-slate-800':
|
||||||
|
isActive,
|
||||||
|
'opacity-50': !isActive && !isComplete,
|
||||||
|
hidden: hidden,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="grid w-4 h-4 font-bold rounded-full text-xxs place-content-center"
|
||||||
|
:class="{
|
||||||
|
'bg-slate-200 dark:bg-slate-600': !isComplete,
|
||||||
|
'bg-green-500': isComplete,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
v-if="isComplete"
|
||||||
|
size="13"
|
||||||
|
class="text-white"
|
||||||
|
icon="checkmark"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
{{ stepNumber }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
:class="{
|
||||||
|
'line-through': isComplete,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ModalStep',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
stepNumber: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isComplete: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -45,11 +45,19 @@ export const validateAuthenticateRoutePermission = (to, next, { getters }) => {
|
|||||||
return next(frontendURL(`accounts/${user.account_id}/dashboard`));
|
return next(frontendURL(`accounts/${user.account_id}/dashboard`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we can let them access onboarding routes
|
||||||
|
// we will eventually need guards as onboarding is done, but for now
|
||||||
|
// we can let them access onboarding routes
|
||||||
|
if (to.name.startsWith('onboarding')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
const nextRoute = validateLoggedInRoutes(
|
const nextRoute = validateLoggedInRoutes(
|
||||||
to,
|
to,
|
||||||
getters.getCurrentUser,
|
getters.getCurrentUser,
|
||||||
window.roleWiseRoutes
|
window.roleWiseRoutes
|
||||||
);
|
);
|
||||||
|
|
||||||
return nextRoute ? next(frontendURL(nextRoute)) : next();
|
return nextRoute ? next(frontendURL(nextRoute)) : next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ export const login = async ({
|
|||||||
export const register = async creds => {
|
export const register = async creds => {
|
||||||
try {
|
try {
|
||||||
const response = await wootAPI.post('api/v1/accounts.json', {
|
const response = await wootAPI.post('api/v1/accounts.json', {
|
||||||
account_name: creds.accountName.trim(),
|
|
||||||
user_full_name: creds.fullName.trim(),
|
|
||||||
email: creds.email,
|
email: creds.email,
|
||||||
password: creds.password,
|
password: creds.password,
|
||||||
h_captcha_client_response: creds.hCaptchaClientResponse,
|
h_captcha_client_response: creds.hCaptchaClientResponse,
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
v-if="label"
|
|
||||||
:for="name"
|
|
||||||
class="flex justify-between text-sm font-medium leading-6 text-slate-900 dark:text-white"
|
|
||||||
:class="{ 'text-red-500': hasError }"
|
|
||||||
>
|
|
||||||
{{ label }}
|
|
||||||
<slot />
|
|
||||||
</label>
|
|
||||||
<div class="mt-1">
|
|
||||||
<input
|
|
||||||
:id="name"
|
|
||||||
:name="name"
|
|
||||||
:type="type"
|
|
||||||
autocomplete="off"
|
|
||||||
:required="required"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:data-testid="dataTestid"
|
|
||||||
:value="value"
|
|
||||||
:class="{
|
|
||||||
'focus:ring-red-600 ring-red-600': hasError,
|
|
||||||
'dark:ring-slate-600 dark:focus:ring-woot-500 ring-slate-200':
|
|
||||||
!hasError,
|
|
||||||
}"
|
|
||||||
class="block w-full rounded-md border-0 px-3 py-3 appearance-none shadow-sm ring-1 ring-inset text-slate-900 dark:text-slate-100 placeholder:text-slate-400 focus:ring-2 focus:ring-inset focus:ring-woot-500 sm:text-sm sm:leading-6 outline-none dark:bg-slate-700"
|
|
||||||
@input="onInput"
|
|
||||||
@blur="$emit('blur')"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
v-if="errorMessage && hasError"
|
|
||||||
class="text-xs leading-2 text-red-400"
|
|
||||||
>
|
|
||||||
{{ errorMessage }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'text',
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hasError: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
dataTestid: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onInput(e) {
|
|
||||||
this.$emit('input', e.target.value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { required, minLength } from 'vuelidate/lib/validators';
|
import { required, minLength } from 'vuelidate/lib/validators';
|
||||||
import FormInput from '../../../components/Form/Input.vue';
|
import FormInput from 'dashboard/components/form/Input.vue';
|
||||||
import SubmitButton from '../../../components/Button/SubmitButton.vue';
|
import SubmitButton from '../../../components/Button/SubmitButton.vue';
|
||||||
import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals';
|
import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals';
|
||||||
import { setNewPassword } from '../../../api/auth';
|
import { setNewPassword } from '../../../api/auth';
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
import { required, minLength, email } from 'vuelidate/lib/validators';
|
import { required, minLength, email } from 'vuelidate/lib/validators';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import FormInput from '../../../../components/Form/Input.vue';
|
import FormInput from 'dashboard/components/form/Input.vue';
|
||||||
import { resetPassword } from '../../../../api/auth';
|
import { resetPassword } from '../../../../api/auth';
|
||||||
import SubmitButton from '../../../../components/Button/SubmitButton.vue';
|
import SubmitButton from '../../../../components/Button/SubmitButton.vue';
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex-1 overflow-auto px-1">
|
<div class="flex-1 px-1 overflow-auto">
|
||||||
<form class="space-y-3" @submit.prevent="submit">
|
<form class="space-y-8" @submit.prevent="submit">
|
||||||
<div class="flex">
|
<section class="space-y-3">
|
||||||
<form-input
|
<form-input
|
||||||
v-model.trim="credentials.fullName"
|
v-model.trim="credentials.email"
|
||||||
name="full_name"
|
type="email"
|
||||||
class="flex-1"
|
name="email_address"
|
||||||
:class="{ error: $v.credentials.fullName.$error }"
|
:class="{ error: $v.credentials.email.$error }"
|
||||||
:label="$t('REGISTER.FULL_NAME.LABEL')"
|
:label="$t('REGISTER.EMAIL.LABEL')"
|
||||||
:placeholder="$t('REGISTER.FULL_NAME.PLACEHOLDER')"
|
:placeholder="$t('REGISTER.EMAIL.PLACEHOLDER')"
|
||||||
:has-error="$v.credentials.fullName.$error"
|
:has-error="$v.credentials.email.$error"
|
||||||
:error-message="$t('REGISTER.FULL_NAME.ERROR')"
|
:error-message="$t('REGISTER.EMAIL.ERROR')"
|
||||||
@blur="$v.credentials.fullName.$touch"
|
@blur="$v.credentials.email.$touch"
|
||||||
/>
|
/>
|
||||||
<form-input
|
<form-input
|
||||||
v-model.trim="credentials.accountName"
|
v-model.trim="credentials.password"
|
||||||
name="account_name"
|
type="password"
|
||||||
class="flex-1 ml-2"
|
name="password"
|
||||||
:class="{ error: $v.credentials.accountName.$error }"
|
:class="{ error: $v.credentials.password.$error }"
|
||||||
:label="$t('REGISTER.COMPANY_NAME.LABEL')"
|
:label="$t('LOGIN.PASSWORD.LABEL')"
|
||||||
:placeholder="$t('REGISTER.COMPANY_NAME.PLACEHOLDER')"
|
:placeholder="$t('SET_NEW_PASSWORD.PASSWORD.PLACEHOLDER')"
|
||||||
:has-error="$v.credentials.accountName.$error"
|
:has-error="$v.credentials.password.$error"
|
||||||
:error-message="$t('REGISTER.COMPANY_NAME.ERROR')"
|
:error-message="passwordErrorText"
|
||||||
@blur="$v.credentials.accountName.$touch"
|
@blur="$v.credentials.password.$touch"
|
||||||
/>
|
/>
|
||||||
</div>
|
</section>
|
||||||
<form-input
|
|
||||||
v-model.trim="credentials.email"
|
|
||||||
type="email"
|
|
||||||
name="email_address"
|
|
||||||
:class="{ error: $v.credentials.email.$error }"
|
|
||||||
:label="$t('REGISTER.EMAIL.LABEL')"
|
|
||||||
:placeholder="$t('REGISTER.EMAIL.PLACEHOLDER')"
|
|
||||||
:has-error="$v.credentials.email.$error"
|
|
||||||
:error-message="$t('REGISTER.EMAIL.ERROR')"
|
|
||||||
@blur="$v.credentials.email.$touch"
|
|
||||||
/>
|
|
||||||
<form-input
|
|
||||||
v-model.trim="credentials.password"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
:class="{ error: $v.credentials.password.$error }"
|
|
||||||
:label="$t('LOGIN.PASSWORD.LABEL')"
|
|
||||||
:placeholder="$t('SET_NEW_PASSWORD.PASSWORD.PLACEHOLDER')"
|
|
||||||
:has-error="$v.credentials.password.$error"
|
|
||||||
:error-message="passwordErrorText"
|
|
||||||
@blur="$v.credentials.password.$touch"
|
|
||||||
/>
|
|
||||||
<div v-if="globalConfig.hCaptchaSiteKey" class="mb-3">
|
<div v-if="globalConfig.hCaptchaSiteKey" class="mb-3">
|
||||||
<vue-hcaptcha
|
<vue-hcaptcha
|
||||||
ref="hCaptcha"
|
ref="hCaptcha"
|
||||||
@@ -83,9 +61,9 @@ import { required, minLength, email } from 'vuelidate/lib/validators';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import { DEFAULT_REDIRECT_URL } from 'dashboard/constants/globals';
|
import { ONBOARDING_START_URL } from 'dashboard/constants/globals';
|
||||||
import VueHcaptcha from '@hcaptcha/vue-hcaptcha';
|
import VueHcaptcha from '@hcaptcha/vue-hcaptcha';
|
||||||
import FormInput from '../../../../../components/Form/Input.vue';
|
import FormInput from 'dashboard/components/form/Input.vue';
|
||||||
import SubmitButton from '../../../../../components/Button/SubmitButton.vue';
|
import SubmitButton from '../../../../../components/Button/SubmitButton.vue';
|
||||||
import { isValidPassword } from 'shared/helpers/Validators';
|
import { isValidPassword } from 'shared/helpers/Validators';
|
||||||
import GoogleOAuthButton from '../../../../../components/GoogleOauth/Button.vue';
|
import GoogleOAuthButton from '../../../../../components/GoogleOauth/Button.vue';
|
||||||
@@ -103,10 +81,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
credentials: {
|
credentials: {
|
||||||
accountName: '',
|
email: 'Testing123@shivam.dev',
|
||||||
fullName: '',
|
password: 'Testing123@shivam.dev',
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
hCaptchaClientResponse: '',
|
hCaptchaClientResponse: '',
|
||||||
},
|
},
|
||||||
didCaptchaReset: false,
|
didCaptchaReset: false,
|
||||||
@@ -116,14 +92,6 @@ export default {
|
|||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
credentials: {
|
credentials: {
|
||||||
accountName: {
|
|
||||||
required,
|
|
||||||
minLength: minLength(2),
|
|
||||||
},
|
|
||||||
fullName: {
|
|
||||||
required,
|
|
||||||
minLength: minLength(2),
|
|
||||||
},
|
|
||||||
email: {
|
email: {
|
||||||
required,
|
required,
|
||||||
email,
|
email,
|
||||||
@@ -181,7 +149,7 @@ export default {
|
|||||||
this.isSignupInProgress = true;
|
this.isSignupInProgress = true;
|
||||||
try {
|
try {
|
||||||
await register(this.credentials);
|
await register(this.credentials);
|
||||||
window.location = DEFAULT_REDIRECT_URL;
|
window.location = ONBOARDING_START_URL;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let errorMessage =
|
let errorMessage =
|
||||||
error?.message || this.$t('REGISTER.API.ERROR_MESSAGE');
|
error?.message || this.$t('REGISTER.API.ERROR_MESSAGE');
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ import SubmitButton from '../../components/Button/SubmitButton.vue';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { parseBoolean } from '@chatwoot/utils';
|
import { parseBoolean } from '@chatwoot/utils';
|
||||||
import GoogleOAuthButton from '../../components/GoogleOauth/Button.vue';
|
import GoogleOAuthButton from '../../components/GoogleOauth/Button.vue';
|
||||||
import FormInput from '../../components/Form/Input.vue';
|
import FormInput from 'dashboard/components/form/Input.vue';
|
||||||
import { login } from '../../api/auth';
|
import { login } from '../../api/auth';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
const ERROR_MESSAGES = {
|
const ERROR_MESSAGES = {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class Account < ApplicationRecord
|
|||||||
check_for_column: false
|
check_for_column: false
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
validates :name, presence: true
|
|
||||||
validates :auto_resolve_duration, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 999, allow_nil: true }
|
validates :auto_resolve_duration, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 999, allow_nil: true }
|
||||||
validates :domain, length: { maximum: 100 }
|
validates :domain, length: { maximum: 100 }
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -68,8 +68,7 @@ class User < ApplicationRecord
|
|||||||
# work because :validatable in devise overrides this.
|
# work because :validatable in devise overrides this.
|
||||||
# validates_uniqueness_of :email, scope: :account_id
|
# validates_uniqueness_of :email, scope: :account_id
|
||||||
|
|
||||||
validates :email, :name, presence: true
|
validates :email, presence: true
|
||||||
validates_length_of :name, minimum: 1, maximum: 255
|
|
||||||
|
|
||||||
has_many :account_users, dependent: :destroy_async
|
has_many :account_users, dependent: :destroy_async
|
||||||
has_many :accounts, through: :account_users
|
has_many :accounts, through: :account_users
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 472 KiB |
Reference in New Issue
Block a user