Skip to content

Commit 19bc331

Browse files
committed
feat(ec-onboarding): setup onboarding card with timeline and video (#5)
fixes #5
1 parent 2c8e394 commit 19bc331

File tree

4 files changed

+304
-27
lines changed

4 files changed

+304
-27
lines changed

src/components/EcHome.vue

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,9 @@
254254

255255
<div class="row justify-content-end">
256256
<div
257-
v-for="({ label, value, diffValue, diffPercent }) in amountMetrics"
257+
v-for="({ label, value, diffValue, diffPercent }, i) in amountMetrics"
258+
:key="`amount-${i}`"
259+
v-if="value"
258260
class="col-lg-auto mt-3"
259261
>
260262
<template v-if="value">
@@ -316,35 +318,54 @@
316318
>
317319
</div>
318320

319-
<template v-if="!isMobile && ordersMetrics.countCreated">
321+
<template v-if="!hasNoOrders">
322+
<template v-if="!isMobile && ordersMetrics.countCreated">
323+
<slide-y-up-transition>
324+
<ec-orders-graphs
325+
v-if="hasLoadedAllMetrics"
326+
:date-range="fixedDateRange"
327+
@load="hasLoadedOrdersGraphs = hasLoadedOnce = true"
328+
/>
329+
</slide-y-up-transition>
330+
331+
<template v-if="!isLoading && !hasLoadedOrdersGraphs">
332+
<div v-once>
333+
<b-skeleton
334+
v-for="i in 5"
335+
:key="`skeleton-${i}`"
336+
animation="wave"
337+
:width="`${(Math.floor(Math.random() * (95 - 35)) + 35)}%`"
338+
height="35px"
339+
></b-skeleton>
340+
</div>
341+
</template>
342+
</template>
343+
320344
<slide-y-up-transition>
321-
<ec-orders-graphs
322-
v-if="hasLoadedAllMetrics"
323-
:date-range="fixedDateRange"
324-
@load="hasLoadedOrdersGraphs = hasLoadedOnce = true"
345+
<ec-home-cards
346+
v-if="hasLoadedOnce"
347+
:start-date="dateRangeIso.start"
348+
:end-date="dateRangeIso.end"
325349
/>
326350
</slide-y-up-transition>
327-
328-
<template v-if="!isLoading && !hasLoadedOrdersGraphs">
329-
<div v-once>
330-
<b-skeleton
331-
v-for="i in 5"
332-
:key="`skeleton-${i}`"
333-
animation="wave"
334-
:width="`${(Math.floor(Math.random() * (95 - 35)) + 35)}%`"
335-
height="35px"
336-
></b-skeleton>
337-
</div>
338-
</template>
339351
</template>
340352

341-
<slide-y-up-transition>
342-
<ec-home-cards
343-
v-if="hasLoadedOnce"
344-
:start-date="dateRangeIso.start"
345-
:end-date="dateRangeIso.end"
346-
/>
347-
</slide-y-up-transition>
353+
<template v-if="hasLoadedOnce">
354+
<slide-y-up-transition>
355+
<ec-onboarding
356+
v-if="canShowOnboarding"
357+
:ecom-auth="ecomAuth"
358+
/>
359+
</slide-y-up-transition>
360+
<button
361+
v-if="!canShowOnboarding"
362+
class="btn btn-light"
363+
@click="canShowOnboarding = true"
364+
>
365+
<i class="fa fa-question-circle mr-1"></i>
366+
{{ i19firstSteps }}
367+
</button>
368+
</template>
348369
</div>
349370
</template>
350371

src/components/EcOnboarding.vue

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<template>
2+
<article class="ec-onboarding">
3+
<div class="card">
4+
<h4 class="card-title fw-400">
5+
{{ i19firstSteps }}
6+
</h4>
7+
8+
<ol class="timeline timeline-activity timeline-sm timeline-content-right w-100 py-20 pr-20">
9+
<li
10+
v-for="({ isDone, icon, text, ctas }, i) in steps"
11+
:key="`step-${i}`"
12+
class="timeline-block"
13+
>
14+
<div class="timeline-point">
15+
<span :class="`avatar${(isDone ? ' bg-success' : '')}`">
16+
<i :class="`fa fa-${icon}`"></i>
17+
</span>
18+
</div>
19+
20+
<div class="timeline-content">
21+
<p class="lead">
22+
{{ text }}
23+
</p>
24+
<template v-if="ctas">
25+
<div class="gap-items">
26+
<a
27+
v-for="([label, link, isSecondary]) in ctas"
28+
:key="link"
29+
:href="link"
30+
class="btn btn-sm"
31+
:class="isSecondary ? 'd-none d-lg-inline-block btn-outline-light' :
32+
`btn-${(isDone ? 'light' : 'success')}`"
33+
>
34+
<i
35+
v-if="ctas.length === 1"
36+
:class="`mr-1 fa fa-${(isDone ? 'check text-success' : 'chevron-circle-right')}`"
37+
></i>
38+
{{ label }}
39+
</a>
40+
</div>
41+
<hr>
42+
</template>
43+
</div>
44+
</li>
45+
</ol>
46+
47+
<div>
48+
<div
49+
class="mx-auto"
50+
style="max-width: 800px"
51+
>
52+
<div class="embed-responsive embed-responsive-16by9">
53+
<iframe
54+
class="embed-responsive-item"
55+
:src="`https://www.youtube.com/embed/${youtubeVideo}?rel=0&list=${youtubePlaylist}&index=1`"
56+
allowfullscreen
57+
></iframe>
58+
</div>
59+
</div>
60+
</div>
61+
</div>
62+
</article>
63+
</template>
64+
65+
<script src="./js/EcOnboarding.js"></script>

src/components/js/EcHome.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
i19comparedPreviousPeriodMsg,
55
i19domain,
66
i19editStorefront,
7+
// i19firstSteps,
78
i19goToStore,
89
i19invalidDomainName,
910
i19newOrders,
@@ -28,6 +29,7 @@ import { ShareNetwork } from 'vue-social-sharing'
2829
import EcDatesPicker from '../EcDatesPicker.vue'
2930
import EcOrdersGraphs from '../EcOrdersGraphs.vue'
3031
import EcHomeCards from '../EcHomeCards.vue'
32+
import EcOnboarding from '../EcOnboarding.vue'
3133

3234
const i19totalAmount = 'Montante total'
3335

@@ -46,7 +48,8 @@ export default {
4648
ShareNetwork,
4749
EcDatesPicker,
4850
EcOrdersGraphs,
49-
EcHomeCards
51+
EcHomeCards,
52+
EcOnboarding
5053
},
5154

5255
props: {
@@ -81,7 +84,9 @@ export default {
8184
isLoadingMetrics: false,
8285
hasLoadedAllMetrics: false,
8386
hasLoadedOrdersGraphs: false,
84-
hasLoadedOnce: false
87+
hasLoadedOnce: false,
88+
hasNoOrders: false,
89+
canShowOnboarding: false
8590
}
8691
},
8792

@@ -91,6 +96,7 @@ export default {
9196
i19comparedPreviousPeriodMsg: () => i18n(i19comparedPreviousPeriodMsg),
9297
i19domain: () => i18n(i19domain),
9398
i19editStorefront: () => i18n(i19editStorefront),
99+
i19firstSteps: () => 'Primeiros passos',
94100
i19goToStore: () => i18n(i19goToStore),
95101
i19invalidDomainName: () => i18n(i19invalidDomainName),
96102
i19newOrders: () => i18n(i19newOrders),
@@ -313,6 +319,18 @@ export default {
313319
.finally(() => {
314320
this.isLoadingMetrics = false
315321
})
322+
},
323+
324+
hasLoadedOnce () {
325+
if (!this.ordersMetrics.countCreated) {
326+
this.ecomAuth.requestApi('$count', 'post', {
327+
resource: 'orders'
328+
})
329+
.then(({ data }) => {
330+
this.canShowOnboarding = this.hasNoOrders = data.count === 0
331+
})
332+
.catch(handleApiError)
333+
}
316334
}
317335
},
318336

src/components/js/EcOnboarding.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import {
2+
i19apps,
3+
i19brands,
4+
i19categories
5+
// i19customPayment,
6+
// i19customShipping,
7+
// i19firstSteps,
8+
// i19registerProduct,
9+
// i19setUp
10+
} from '@ecomplus/i18n'
11+
12+
import { i18n } from '@ecomplus/utils'
13+
import ecomAuth from '@ecomplus/auth'
14+
import { handleApiError } from '@ecomplus/admin-helpers'
15+
16+
export default {
17+
name: 'EcOnboarding',
18+
19+
props: {
20+
youtubeVideo: {
21+
type: String,
22+
default: '-PayqoKBs4o'
23+
},
24+
youtubePlaylist: {
25+
type: String,
26+
default: 'PLye2cV82WMW3advpehZ1ERj0nx3Amkf1h'
27+
},
28+
ecomAuth: {
29+
type: Object,
30+
default () {
31+
return ecomAuth
32+
}
33+
}
34+
},
35+
36+
data () {
37+
return {
38+
isStoreConfigured: false,
39+
hasCategoryOrBrand: false,
40+
hasProduct: false,
41+
hasShippingMethod: false,
42+
hasPaymentMethod: false
43+
}
44+
},
45+
46+
computed: {
47+
i19apps: () => i18n(i19apps),
48+
i19brands: () => i18n(i19brands),
49+
i19categories: () => i18n(i19categories),
50+
i19customPayment: () => 'Pagamento personalizado',
51+
i19customShipping: () => 'Frete personalizado',
52+
i19firstSteps: () => 'Primeiros passos',
53+
i19registerProduct: () => 'Cadastrar produto',
54+
i19setUp: () => 'Configurar',
55+
56+
steps () {
57+
return [{
58+
isDone: this.isStoreConfigured,
59+
icon: 'wrench',
60+
text: i18n({
61+
en_us: 'Configure the store info such as name, address, contact, logo, domain and brand colors.',
62+
pt_br: 'Configure os dados da loja como nome, endereço, contato, logo, domínio e cores da sua marca.'
63+
}),
64+
ctas: [
65+
[this.i19setUp, '/#/settings']
66+
]
67+
}, {
68+
isDone: this.hasCategoryOrBrand,
69+
icon: 'bookmark',
70+
text: i18n({
71+
en_us: 'Start preparing your catalog with categories and brands.',
72+
pt_br: 'Comece a preparar seu catálogo com categorias e marcas.'
73+
}),
74+
ctas: [
75+
[this.i19categories, '/#/resources/categories'],
76+
[this.i19brands, '/#/resources/brands']
77+
]
78+
}, {
79+
isDone: this.hasProduct,
80+
icon: 'tags',
81+
text: i18n({
82+
en_us: 'Register the store\'s first product.',
83+
pt_br: 'Cadastre o primeiro produto da loja.'
84+
}),
85+
ctas: [
86+
[this.i19registerProduct, '/#/resources/products/new']
87+
]
88+
}, {
89+
isDone: this.hasShippingMethod,
90+
icon: 'truck',
91+
text: i18n({
92+
en_us: 'Visit our app store to see carriers and other shipping methods, ' +
93+
'install and configure at least one shipping app.',
94+
pt_br: 'Visita nossa loja de aplicativos para conhecer as transportadoras e outras formas de envio, ' +
95+
'instale e configure pelo menos um app de envio.'
96+
}),
97+
ctas: [
98+
[this.i19apps, '/#/apps'],
99+
[this.i19customShipping, '/#/apps/edit/1253', true],
100+
['Correios', '/#/apps/edit/1248', true],
101+
['Melhor Envio', '/#/apps/edit/1236', true]
102+
]
103+
}, {
104+
isDone: this.hasPaymentMethod,
105+
icon: 'credit-card',
106+
text: i18n({
107+
en_us: 'Check our app store for all integrated payment options, ' +
108+
'choose and configure at least one for your store.',
109+
pt_br: 'Verifique em nossa loja de aplicativos todas as opções de pagamento integradas, ' +
110+
'escolha e configure pelo menos uma para sua loja.'
111+
}),
112+
ctas: [
113+
[this.i19apps, '/#/apps'],
114+
[this.i19customPayment, '/#/apps/edit/108091', true],
115+
['PagHiper', '/#/apps/edit/1251', true],
116+
['Pagar.me', '/#/apps/edit/117391', true]
117+
]
118+
}, {
119+
isDone: false,
120+
icon: 'inbox',
121+
text: i18n({
122+
en_us: 'Close a purchase in your store to test the first order!',
123+
pt_br: 'Feche uma compra na sua loja para testar o primeiro pedido!'
124+
})
125+
}]
126+
}
127+
},
128+
129+
created () {
130+
ecomAuth.fetchStore()
131+
.then(store => {
132+
const fields = ['name', 'domain', 'logo', 'contact_email', 'address']
133+
for (let i = 0; i < fields.length; i++) {
134+
if (!store[fields[i]]) {
135+
return
136+
}
137+
}
138+
this.isStoreConfigured = true
139+
})
140+
.then(() => {
141+
const checkListResult = ({ data }, instanceField = 'hasCategoryOrBrand') => {
142+
if (data.result.length) {
143+
this[instanceField] = true
144+
return true
145+
}
146+
return false
147+
}
148+
return ecomAuth.requestApi('/categories?limit=1')
149+
.then(response => {
150+
if (!checkListResult(response)) {
151+
return ecomAuth.requestApi('/brands?limit=1').then(checkListResult)
152+
}
153+
})
154+
.then(() => {
155+
return ecomAuth.requestApi('/$count', 'post', {
156+
resource: 'products'
157+
}).then(({ data }) => {
158+
this.hasProduct = Boolean(data.count)
159+
})
160+
})
161+
.then(() => {
162+
return ecomAuth.requestApi('/applications?limit=1&modules.calculate_shipping.enabled=true')
163+
.then(response => checkListResult(response, 'hasShippingMethod'))
164+
})
165+
.then(() => {
166+
return ecomAuth.requestApi('/applications?limit=1&modules.create_transaction.enabled=true')
167+
.then(response => checkListResult(response, 'hasPaymentMethod'))
168+
})
169+
})
170+
.then(() => this.$emit('load'))
171+
.catch(handleApiError)
172+
}
173+
}

0 commit comments

Comments
 (0)