web/satellite: add all projects onboarding

This change implements onboarding for the all projects dashboard.

Issue: https://github.com/storj/storj/issues/5514

Change-Id: If8a48c21e6df264c84d362d17bf01d770d9ca768
This commit is contained in:
Wilfred Asomani 2023-02-22 10:13:31 +00:00 committed by Storj Robot
parent d6b661a412
commit 52abe8ddb4
11 changed files with 519 additions and 10 deletions

View File

@ -6,8 +6,10 @@
<div v-if="!isOptional" class="label-container">
<div class="label-container__main">
<ErrorIcon v-if="error" class="label-container__error-icon" />
<h3 v-if="!error" class="label-container__main__label">{{ label }}</h3>
<h3 v-if="!error" class="label-container__main__label add-label">{{ additionalLabel }}</h3>
<h3 v-if="!error" class="label-container__main__label">
<span>{{ label }}</span>
<span class="add-label">{{ additionalLabel }}</span>
</h3>
<h3 v-if="error" class="label-container__main__error">{{ error }}</h3>
<div v-if="isLoading" class="loader" />
</div>
@ -234,14 +236,23 @@ export default defineComponent({
}
&__main {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
&__label {
width: 100%;
display: flex;
justify-content: space-between;
font-size: 16px;
line-height: 21px;
color: #354049;
& .add-label {
font-size: 14px;
color: var(--c-grey-5) !important;
}
}
&__error {
@ -253,6 +264,7 @@ export default defineComponent({
}
&__limit {
margin-left: 5px;
font-size: 16px;
line-height: 21px;
color: rgb(56 75 101 / 40%);

View File

@ -12,6 +12,7 @@ import { Component, Vue } from 'vue-property-decorator';
import CreateProjectPromptModal from '@/components/modals/CreateProjectPromptModal.vue';
import CreateProjectModal from '@/components/modals/CreateProjectModal.vue';
import NewCreateProjectModal from '@/components/modals/NewCreateProjectModal.vue';
import CreateBucketModal from '@/components/modals/CreateBucketModal.vue';
import AddPaymentMethodModal from '@/components/modals/AddPaymentMethodModal.vue';
import OpenBucketModal from '@/components/modals/OpenBucketModal.vue';
@ -43,6 +44,7 @@ import PricingPlanModal from '@/components/modals/PricingPlanModal.vue';
DeleteBucketModal,
CreateProjectPromptModal,
CreateProjectModal,
NewCreateProjectModal,
CreateBucketModal,
AddPaymentMethodModal,
OpenBucketModal,

View File

@ -0,0 +1,300 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<div class="modal__header">
<blue-box-icon />
<span class="modal__header__title">Create new project</span>
</div>
<div class="modal__divider" />
<p class="modal__info">
Projects are where you and your team can upload and manage data, view
usage statistics and billing.
</p>
<div class="modal__divider" />
<VInput
label="Project Name"
additional-label="Required. Up to 20 characters"
placeholder="Enter Project Name"
:max-symbols="20"
:error="nameError"
@setData="setProjectName"
/>
<div class="modal__divider" />
<VInput
v-if="hasDescription"
label="Project Description"
placeholder="Enter Project Description"
additional-label="Optional. Up to 50 characters"
:max-symbols="50"
is-multiline
height="100px"
@setData="setProjectDescription"
/>
<div v-else class="modal__project-description">
<p class="modal__project-description__label">Project Description</p>
<a
class="modal__project-description__action" href=""
@click.prevent="hasDescription = !hasDescription"
>Add Description (optional)</a>
</div>
<div class="modal__divider" />
<div class="modal__button-container">
<VButton
label="Cancel"
width="100%"
height="48px"
:on-press="closeModal"
:is-transparent="true"
/>
<VButton
label="Create Project -->"
width="100%"
height="48px"
:on-press="onCreateProjectClick"
:is-disabled="!projectName"
/>
</div>
<div v-if="isLoading" class="modal__blur">
<VLoader class="modal__blur__loader" width="50px" height="50px" />
</div>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { RouteConfig } from '@/router';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { LocalData } from '@/utils/localData';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { OBJECTS_MUTATIONS } from '@/store/modules/objects';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { useNotify, useRouter, useStore } from '@/utils/hooks';
import VLoader from '@/components/common/VLoader.vue';
import VInput from '@/components/common/VInput.vue';
import VModal from '@/components/common/VModal.vue';
import VButton from '@/components/common/VButton.vue';
import BlueBoxIcon from '@/../static/images/common/blueBox.svg';
const router = useRouter();
const notify = useNotify();
const store = useStore();
const description = ref('');
const createdProjectId = ref('');
const hasDescription = ref(false);
const isLoading = ref(false);
const projectName = ref('');
const nameError = ref('');
const analytics = new AnalyticsHttpApi();
/**
* Sets project name from input value.
*/
function setProjectName(value: string): void {
projectName.value = value;
nameError.value = '';
}
/**
* Sets project description from input value.
*/
function setProjectDescription(value: string): void {
description.value = value;
}
/**
* Creates project and refreshes store.
*/
async function onCreateProjectClick(): Promise<void> {
if (isLoading.value) {
return;
}
const isFirstProject = store.getters.projectsCount === 0;
isLoading.value = true;
projectName.value = projectName.value.trim();
const project = new ProjectFields(
projectName.value,
description.value,
store.getters.user.id,
);
try {
project.checkName();
} catch (error) {
isLoading.value = false;
nameError.value = error.message;
analytics.errorEventTriggered(AnalyticsErrorEventSource.CREATE_PROJECT_MODAL);
return;
}
try {
const createdProject = await store.dispatch(PROJECTS_ACTIONS.CREATE, project);
createdProjectId.value = createdProject.id;
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.CREATE_PROJECT_MODAL);
isLoading.value = false;
return;
}
await selectCreatedProject();
await notify.success('Project created successfully!');
isLoading.value = false;
closeModal();
store.commit(OBJECTS_MUTATIONS.CLEAR);
if (isFirstProject) {
analytics.pageVisit(RouteConfig.OnboardingTour.with(RouteConfig.FirstOnboardingStep).path);
await router.push(RouteConfig.OnboardingTour.with(RouteConfig.FirstOnboardingStep).path);
return;
}
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.enterPassphrase);
}
/**
* Selects just created project.
*/
async function selectCreatedProject() {
await store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
LocalData.setSelectedProjectId(createdProjectId.value);
}
/**
* Closes create project modal.
*/
function closeModal(): void {
store.commit(APP_STATE_MUTATIONS.REMOVE_ACTIVE_MODAL);
}
</script>
<style scoped lang="scss">
.modal {
width: 400px;
padding: 54px 48px 51px;
display: flex;
flex-direction: column;
font-family: 'font_regular', sans-serif;
&__header {
display: flex;
align-items: center;
gap: 16px;
&__title {
font-family: 'font-medium', sans-serif;
font-weight: bold;
font-size: 24px;
line-height: 31px;
}
}
&__divider {
margin: 20px 0;
border: 0.5px solid var(--c-grey-2);
}
&__project-description {
font-family: 'font_regular', sans-serif;
text-align: start;
&__label {
font-weight: bold;
font-size: 16px;
line-height: 21px;
color: #354049;
}
&__action {
font-size: 14px;
line-height: 22px;
text-decoration: underline;
color: #354049;
}
}
@media screen and (max-width: 550px) {
width: calc(100% - 48px);
padding: 54px 24px 32px;
}
&__info {
font-family: 'font_regular', sans-serif;
text-align: start;
font-size: 16px;
line-height: 21px;
color: #354049;
}
&__button-container {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 20px;
@media screen and (max-width: 550px) {
column-gap: unset;
row-gap: 8px;
flex-direction: column-reverse;
}
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgb(229 229 229 / 20%);
border-radius: 8px;
z-index: 100;
&__loader {
width: 25px;
height: 25px;
position: absolute;
right: 40px;
top: 40px;
}
}
}
.full-input {
margin-top: 20px;
}
@media screen and (max-width: 550px) {
:deep(.add-label) {
display: none;
}
}
</style>

View File

@ -106,6 +106,10 @@ router.beforeEach(async (to, from, next) => {
store.commit(APP_STATE_MUTATIONS.TOGGLE_HAS_JUST_LOGGED_IN);
}
if (to.name === RouteConfig.AllProjectsDashboard.name && from.name === RouteConfig.Login.name) {
store.commit(APP_STATE_MUTATIONS.TOGGLE_HAS_JUST_LOGGED_IN);
}
// On very first login we try to redirect user to project dashboard
// but since there is no project we then redirect user to onboarding flow.
// That's why we toggle this flag here back to false not show create project passphrase modal again

View File

@ -28,6 +28,7 @@ import UploadCancelPopup from '@/components/objects/UploadCancelPopup.vue';
import ObjectDetailsModal from '@/components/modals/ObjectDetailsModal.vue';
import EnterPassphraseModal from '@/components/modals/EnterPassphraseModal.vue';
import PricingPlanModal from '@/components/modals/PricingPlanModal.vue';
import NewCreateProjectModal from '@/components/modals/NewCreateProjectModal.vue';
export const APP_STATE_DROPDOWNS = {
ACCOUNT: 'isAccountDropdownShown',
@ -70,6 +71,7 @@ enum Modals {
OBJECT_DETAILS = 'objectDetails',
ENTER_PASSPHRASE = 'enterPassphrase',
PRICING_PLAN = 'pricingPlan',
NEW_CREATE_PROJECT = 'newCreateProject',
}
// modals could be of VueConstructor type or Object (for composition api components).
@ -98,4 +100,5 @@ export const MODALS: Record<Modals, unknown> = {
[Modals.OBJECT_DETAILS]: ObjectDetailsModal,
[Modals.ENTER_PASSPHRASE]: EnterPassphraseModal,
[Modals.PRICING_PLAN]: PricingPlanModal,
[Modals.NEW_CREATE_PROJECT]: NewCreateProjectModal,
};

View File

@ -253,7 +253,13 @@ const limitState = computed((): { eightyIsShown: boolean, hundredIsShown: boolea
* Indicates if navigation sidebar is hidden.
*/
const isNavigationHidden = computed((): boolean => {
return isOnboardingTour.value || isCreateProjectPage.value;
return (!isAllProjectsDashboard.value && isOnboardingTour.value)
|| isCreateProjectPage.value;
});
/* whether all projects dashboard should be used */
const isAllProjectsDashboard = computed((): boolean => {
return store.state.appStateModule.isAllProjectsDashboard;
});
/* whether the project limit banner should be shown. */

View File

@ -0,0 +1,131 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="empty-project-item">
<div class="empty-project-item__header">
<div class="empty-project-item__header__tag">
<box-icon />
<span> Project </span>
</div>
</div>
<p class="empty-project-item__title">
Welcome
</p>
<p class="empty-project-item__subtitle">
Create a new project to start.
</p>
<VButton
class="empty-project-item__button"
icon="addcircle"
:on-press="onCreateProjectClicked"
label="Create a Project"
/>
</div>
</template>
<script setup lang="ts">
import { useStore } from '@/utils/hooks';
import {
AnalyticsEvent,
} from '@/utils/constants/analyticsEventNames';
import { User } from '@/types/users';
import { AnalyticsHttpApi } from '@/api/analytics';
import { RouteConfig } from '@/router';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { MODALS } from '@/utils/constants/appStatePopUps';
import VButton from '@/components/common/VButton.vue';
import BoxIcon from '@/../static/images/allDashboard/box.svg';
const store = useStore();
const analytics = new AnalyticsHttpApi();
/**
* Route to create project page.
*/
function onCreateProjectClicked(): void {
analytics.eventTriggered(AnalyticsEvent.CREATE_NEW_CLICKED);
const user: User = store.getters.user;
const ownProjectsCount: number = store.getters.projectsCount;
if (!user.paidTier && user.projectLimit === ownProjectsCount) {
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.createProjectPrompt);
} else {
analytics.pageVisit(RouteConfig.CreateProject.path);
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.newCreateProject);
}
}
</script>
<style scoped lang="scss">
.empty-project-item {
padding: 24px;
background: var(--c-white);
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
border-radius: 8px;
&__header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&__tag {
display: flex;
justify-content: space-between;
align-items: center;
gap: 5px;
padding: 4px 8px;
border: 1px solid var(--c-light-blue-4);
border-radius: 24px;
color: var(--c-blue-4);
font-size: 12px;
font-family: 'font_regular', sans-serif;
& :deep(svg path) {
fill: var(--c-blue-4);
}
}
}
&__title {
margin-top: 16px;
font-weight: bold;
font-family: 'font_regular', sans-serif;
font-size: 24px;
line-height: 31px;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-align: start;
}
&__subtitle {
font-weight: 400;
font-size: 14px;
margin-top: 5px;
font-family: 'font_regular', sans-serif;
color: var(--c-grey-6);
line-height: 20px;
}
&__button {
margin-top: 20px;
padding: 10px 16px;
border-radius: 8px;
& :deep(.label svg path) {
fill: var(--c-white);
}
}
}
</style>

View File

@ -15,9 +15,13 @@
/>
</div>
<div class="my-projects__list">
<div v-if="projects.length" class="my-projects__list">
<project-item v-for="project in projects" :key="project.id" :project="project" />
</div>
<div v-else class="my-projects__empty-area">
<empty-project-item class="my-projects__empty-area__item" />
<rocket-icon class="my-projects__empty-area__icon" />
</div>
</div>
</template>
@ -34,10 +38,13 @@ import { User } from '@/types/users';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { AnalyticsHttpApi } from '@/api/analytics';
import EmptyProjectItem from '@/views/all-dashboard/components/EmptyProjectItem.vue';
import ProjectItem from '@/views/all-dashboard/components/ProjectItem.vue';
import VButton from '@/components/common/VButton.vue';
import RocketIcon from '@/../static/images/common/rocket.svg';
const router = useRouter();
const route = useRoute();
const store = useStore();
@ -63,7 +70,7 @@ function onCreateProjectClicked(): void {
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.createProjectPrompt);
} else {
analytics.pageVisit(RouteConfig.CreateProject.path);
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.createProject);
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.newCreateProject);
}
}
</script>
@ -120,5 +127,31 @@ function onCreateProjectClicked(): void {
grid-template-columns: auto;
}
}
&__empty-area {
display: flex;
justify-content: center;
align-items: center;
padding-top: 60px;
position: relative;
&__item {
position: absolute;
top: 30px;
left: 0;
}
@media screen and (max-width: 425px) {
& :deep(.empty-project-item) {
width: 100%;
box-sizing: border-box;
}
&__icon {
display: none;
}
}
}
}
</style>

View File

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4423 0H23.3463C28.8835 0 31.0805 0.613723 33.2353 1.76617C35.3902 2.91861 37.0814 4.60977 38.2338 6.76466L38.3214 6.93055C39.4029 9.00672 39.9846 11.2 40 16.4423V23.3463C40 28.8835 39.3863 31.0805 38.2338 33.2353C37.0814 35.3902 35.3902 37.0814 33.2353 38.2338L33.0694 38.3214C30.9933 39.4029 28.8 39.9846 23.5577 40H16.6537C11.1165 40 8.91954 39.3863 6.76466 38.2338C4.60977 37.0814 2.91861 35.3902 1.76617 33.2353L1.67858 33.0694C0.597074 30.9933 0.0154219 28.8 0 23.5577V16.6537C0 11.1165 0.613723 8.91954 1.76617 6.76466C2.91861 4.60977 4.60977 2.91861 6.76466 1.76617L6.93055 1.67858C9.00672 0.597074 11.2 0.0154219 16.4423 0Z" fill="#D8DEE3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.2508 7L31.3096 13.3848L20.2508 19.7695L9.19189 13.3848L20.2508 7Z" fill="#87A9FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.2506 19.6758L20.2506 32.4454L9.1919 26.0606L9.19189 13.291L20.2506 19.6758Z" fill="#0218A7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1558 19.6758L20.1558 32.4454L31.2145 26.0606L31.2145 13.291L20.1558 19.6758Z" fill="#0149FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,15 @@
<svg width="445" height="325" viewBox="0 0 445 325" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_20566_237156" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="103" width="445" height="222">
<path d="M0 103.186H444.729V304.544C444.729 315.59 435.774 324.544 424.729 324.544H0V103.186Z" fill="#C4C4C4"/>
</mask>
<g mask="url(#mask0_20566_237156)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M230.525 121.877H216.208L194.452 259.44C193.392 259.295 192.314 259.22 191.223 259.22C184.489 259.22 178.347 262.056 173.845 266.682H168.105V275.697C167.47 277.357 166.99 279.104 166.685 280.916C160.041 273.171 150.484 268.456 140.092 268.456C121.362 268.456 105.818 283.696 104.327 303.311L104.011 307.462C97.0562 300.662 87.7594 296.523 77.5972 296.523C61.3774 296.523 47.0343 307.119 41.2916 322.855L39.1779 328.647L30.4876 328.647C15.0197 328.647 2.48047 341.908 2.48047 358.266C2.48047 374.624 15.0197 387.885 30.4876 387.885H72.3192C72.5094 387.885 72.6951 387.883 72.8768 387.879L73.0608 387.875L73.4884 387.884L73.7325 387.885H128.79C140.605 387.885 150.184 377.755 150.184 365.259C150.184 360.627 148.868 356.32 146.609 352.733H183.548C185.185 352.733 186.786 352.572 188.338 352.264H200.826C202.743 352.947 204.796 353.317 206.931 353.317H209.128C208.873 354.926 208.739 356.58 208.739 358.266C208.739 374.624 221.278 387.885 236.746 387.885H278.578C278.662 387.885 278.745 387.885 278.827 387.884C278.931 387.883 279.034 387.882 279.136 387.879L279.32 387.875L279.747 387.884L279.991 387.885H300.78H335.048H342.611C342.801 387.885 342.987 387.883 343.169 387.879L343.353 387.875L343.78 387.884L344.024 387.885H399.082C410.897 387.885 420.476 377.755 420.476 365.259C420.476 352.764 410.897 342.634 399.081 342.634L387.294 342.633L386.661 334.309C385.044 313.045 368.194 296.523 347.889 296.523C335.157 296.523 323.581 303.052 316.414 313.471C313.401 308.034 308.605 303.836 302.876 301.772C297.731 298.718 291.872 296.866 285.647 296.566L285.144 295.188C280.128 281.445 267.774 272.088 253.687 271.666V266.682H208.607C208.476 266.548 208.344 266.415 208.211 266.284H254.727L230.525 121.877Z" fill="#EBEEF1"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M244.833 130.103C247.966 141.797 241.026 153.817 229.332 156.95C222.643 158.743 215.847 157.239 210.669 153.435C213.484 155.469 217.158 156.266 220.775 155.297C227.153 153.588 230.939 147.032 229.23 140.653C227.521 134.274 220.964 130.489 214.586 132.198C208.207 133.907 204.422 140.464 206.131 146.842C206.828 149.445 208.333 151.616 210.303 153.158C206.607 150.325 203.783 146.295 202.485 141.45C199.351 129.756 206.291 117.736 217.985 114.603C229.679 111.469 241.699 118.409 244.833 130.103Z" fill="#C8D3DE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M224.655 101.899C238.412 101.899 249.565 113.051 249.565 126.809C249.565 132.013 247.969 136.845 245.239 140.841C245.462 139.853 245.579 138.825 245.579 137.769C245.579 130.065 239.334 123.819 231.63 123.819C223.926 123.819 217.68 130.065 217.68 137.769C217.68 144.451 222.378 150.035 228.651 151.4C227.35 151.61 226.015 151.719 224.655 151.719C210.897 151.719 199.745 140.566 199.745 126.809C199.745 113.051 210.897 101.899 224.655 101.899Z" fill="#FFC600"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M224.655 52.0784L263.514 115.848L185.795 115.848L224.655 52.0784Z" fill="#0218A7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M223.857 20.912L224.654 20.1936C251.164 43.7572 259.801 81.497 246.277 114.201L245.579 115.848L203.73 115.848L203.448 115.19C189.616 82.9162 197.547 45.5161 223.078 21.6316L223.857 20.912Z" fill="#276CFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M224.655 20.1937C230.037 24.9776 234.682 30.3457 238.555 36.1374L210.755 36.1374C214.435 30.6339 218.813 25.5128 223.857 20.9121L224.655 20.1937Z" fill="#FF458B"/>
<path d="M224.655 83.9634C232.91 83.9634 239.601 77.2718 239.601 69.0174C239.601 60.7629 232.909 54.0713 224.655 54.0713C216.401 54.0713 209.709 60.7629 209.709 69.0173C209.709 77.2718 216.401 83.9634 224.655 83.9634Z" fill="white"/>
<path d="M224.655 79.978C230.708 79.978 235.615 75.0708 235.615 69.0175C235.615 62.9643 230.708 58.0571 224.655 58.0571C218.601 58.0571 213.694 62.9643 213.694 69.0175C213.694 75.0708 218.601 79.978 224.655 79.978Z" fill="#0218A7"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -5,8 +5,7 @@ exports[`VInput.vue renders correctly with default props 1`] = `
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label"></h3>
<h3 class="label-container__main__label add-label"></h3>
<h3 class="label-container__main__label"><span></span> <span class="add-label"></span></h3>
<!---->
<!---->
</div>
@ -25,7 +24,6 @@ exports[`VInput.vue renders correctly with input error 1`] = `
<div class="label-container__main">
<erroricon-stub class="label-container__error-icon"></erroricon-stub>
<!---->
<!---->
<h3 class="label-container__main__error">testError</h3>
<!---->
</div>
@ -43,8 +41,7 @@ exports[`VInput.vue renders correctly with isMultiline props 1`] = `
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label"></h3>
<h3 class="label-container__main__label add-label"></h3>
<h3 class="label-container__main__label"><span></span> <span class="add-label"></span></h3>
<!---->
<!---->
</div>