Ngiler SH3LL 360
Home
Information
Create File
Create Folder
:
/
home
/
tbf
/
tbfguest.tbf.ro
/
src
/
components
/
public
/
sales
/
Information Server
MySQL :
OFF
Perl :
OFF
CURL :
ON
WGET :
OFF
PKEXEC :
OFF
Directive
Local Value
IP Address
89.40.16.97
System
Linux server.atelieruldeit.ro 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
User
tbf
PHP Version
7.3.33
Software
Apache
Doc root
Writable
close
Edit File :
ProdusUpsell.vue
| Size :
18.24
KB
Copy
<!-- @format --> <template> <!-- Informatii generale --> <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3" ref="formUpsell"> <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900">Produs Upsell</h2> <p class="mt-1 text-sm leading-6 text-gray-600"> Descrie produsul pe care îl vom vinde ca upsell celor care cumpără produsul principal. Acest produs este important să aibă o corelație cu cel ales la începutul paginii. Să fie un produs de care publicul țintă are nevoie dacă alege să îl cumpere pe cel principal. </p> </div> <form class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2" @submit.prevent="submitForm"> <div class="px-4 py-6 sm:p-8"> <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> <!-- Nume produs upsell --> <div class="sm:col-span-4"> <label for="upsell_name" class="block text-sm font-medium leading-6 text-gray-900"> Numele produsului <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Exemple: Serviciu de garanție extinsă, cauciucuri de iarnă, set de pensule pentru machiaj, etc.</p> </div> </template> </Popper> </label> <div class="mt-2 relative"> <template v-if="!onlyPreviewMode"> <input type="text" id="upsell_name" v-model="name" :class="[ v$.name.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" placeholder="" class="text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500 block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> </template> <p v-else class="text-sm text-gray-700 leading-6">{{ name }}</p> </div> </div> <!-- Descriere produs upsell --> <div class="col-span-full"> <div class="flex justify-between"> <label for="upsell_characteristics" class="block text-sm font-medium leading-6 text-gray-900"> Caracteristici produs upsell <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Scrie toate caracteristicile relevante ale produsul de upsell</p> </div> </template> </Popper> </label> <div class="text-sm text-gray-500" :class="[{ 'text-green-500': descriptionLength() > 150 }, { 'text-red-500': v$.characteristics.$errors.length }]" v-if="!onlyPreviewMode"> {{ descriptionLength() }}/150 </div> </div> <div class="mt-2"> <template v-if="!onlyPreviewMode"> <div class="relative"> <resize-textarea :key="`textareaDescription${keyInputs}`" id="upsell_characteristics" v-model="characteristics" :class="[ v$.characteristics.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" :rows="5" placeholder="Ex: Cremă hidratantă cu aloe vera pură, concepută pentru a oferi o hidratare profundă și a revigora pielea. Fără parabeni, sulfati sau alte chimicale. Testată dermatologic. Tub de 200ml. Rezistă închisă pentru 5 ani. Grăbește vindecarea arsurilor solare... (listează toate caracteristicile)" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6" /> <div class="absolute top-0 left-0 w-full rounded-lg h-full flex items-center justify-center bg-white/50 z-50 backdrop-blur-sm" v-if="loadedGenerateAi"> <LoaderTbf color="text-blue-500" /> </div> </div> <button class="mt-2 text-sm cursor-pointer text-blue-500 hover:text-blue-600 font-medium inline-flex items-center disabled:cursor-not-allowed disabled:text-blue-300 disabled:hover:text-blue-300" v-if="descriptionLength() > 150 && name && false" :disabled="loadedGenerateAi" @click="generateDescWithAi"> <LoaderTbf color="text-blue-500" class="mr-1 flex items-center justify-center" v-if="loadedGenerateAi" /> <div class="w-[20px] h-[19px] flex items-center justify-center mr-1" v-else> <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" class="inline-block"> <path fill="currentColor" d="M327.5 85.2c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L384 128l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L448 128l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L448 64 426.8 7.5C425.1 3 420.8 0 416 0s-9.1 3-10.8 7.5L384 64 327.5 85.2zM205.1 73.3c-2.6-5.7-8.3-9.3-14.5-9.3s-11.9 3.6-14.5 9.3L123.3 187.3 9.3 240C3.6 242.6 0 248.3 0 254.6s3.6 11.9 9.3 14.5l114.1 52.7L176 435.8c2.6 5.7 8.3 9.3 14.5 9.3s11.9-3.6 14.5-9.3l52.7-114.1 114.1-52.7c5.7-2.6 9.3-8.3 9.3-14.5s-3.6-11.9-9.3-14.5L257.8 187.4 205.1 73.3zM384 384l-56.5 21.2c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L384 448l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L448 448l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L448 384l-21.2-56.5c-1.7-4.5-6-7.5-10.8-7.5s-9.1 3-10.8 7.5L384 384z" /> </svg> </div> Generează cu AI </button> </template> <p v-else-if="characteristics" class="text-sm text-gray-700 leading-6 whitespace-pre-wrap" v-html="characteristics.replace(/(?:\r\n|\r|\n)/g, `<div class='h-[6px]'></div>`)"></p> </div> </div> <!-- Ofertă listă --> <div class="sm:col-span-6"> <label for="upsell_list_price" class="block text-sm font-medium leading-6 text-gray-900"> Oferta de listă a produsului de upsell <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Scrie oferta cu care se vinde acest produs în mod normal</p> </div> </template> </Popper> </label> <div class="mt-2 relative"> <template v-if="!onlyPreviewMode"> <resize-textarea type="text" id="upsell_list_price" :rows="3" v-model="list_price" :class="[ v$.list_price.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" placeholder="Ex: Produsul se vinde în mod normal cu 100 RON plus 16 RON transport" class="text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500 block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> </template> <p v-else-if="list_price" class="text-sm text-gray-700 leading-6 whitespace-pre-wrap" v-html="list_price.replace(/(?:\r\n|\r|\n)/g, `<div class='h-[6px]'></div>`)"></p> </div> </div> <!-- Ofertă ca upsell --> <div class="sm:col-span-6"> <label for="upsell_offer" class="block text-sm font-medium leading-6 text-gray-900"> Ofertă ca upsell la produsul principal <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Care este oferta pe care o oferim persoanei care a cumpărat produsul principal. Este necesar să fie mai avantajoasă decât cea de listă.</p> </div> </template> </Popper> </label> <div class="mt-2 relative"> <template v-if="!onlyPreviewMode"> <resize-textarea type="text" id="upsell_offer" :rows="3" v-model="upsell_offer" :class="[ v$.upsell_offer.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" placeholder="Ex: Dacă clientul cumpără acest produs împreună cu cel principal îl va putea achiziționa la 80 RON iar transportul este gratuit." class="text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500 block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> </template> <p v-else-if="upsell_offer" class="text-sm text-gray-700 leading-6 whitespace-pre-wrap" v-html="upsell_offer.replace(/(?:\r\n|\r|\n)/g, `<div class='h-[6px]'></div>`)"></p> </div> </div> <!-- Ofertă după refuz --> <div class="sm:col-span-6"> <label for="upsell_after_rejection_offer" class="block text-sm font-medium leading-6 text-gray-900"> Ofertă după refuzul ofertei de upsell <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Cea mai bună ofertă pe care o poți oferi la acest produs. O vom folosi doar pentru clienții care refuză oferta de mai sus.</p> </div> </template> </Popper> </label> <div class="mt-2 relative"> <template v-if="!onlyPreviewMode"> <resize-textarea type="text" id="upsell_after_rejection_offer" :rows="3" v-model="upsell_after_rejection_offer" :class="[ v$.upsell_after_rejection_offer.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" placeholder="Ex: Dacă clientul refuză oferta de upsell, îi putem oferi produsul la 80 RON cu transport gratuit și îi putem oferi bonus o cremă extraordinară pentru protecție solară care nu îți lasă pielea unsuroasă." class="text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500 block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> </template> <p v-else-if="upsell_after_rejection_offer" class="text-sm text-gray-700 leading-6 whitespace-pre-wrap" v-html="upsell_after_rejection_offer.replace(/(?:\r\n|\r|\n)/g, `<div class='h-[6px]'></div>`)"></p> </div> </div> </div> </div> <!-- Buttons --> <div class="flex items-center justify-end gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> <p class="text-sm text-red-600" v-if="v$.$errors.length">Completează formularul pentru a salva.</p> <div class="flex items-center gap-x-6 ml-auto"> <template v-if="!onlyPreviewMode"> <button v-if="canOnlyPreview" @click="changePreviewMode" type="button" class="text-sm font-semibold leading-6 text-gray-900">Anulează</button> <button v-else @click="resetForm" type="button" class="text-sm font-semibold leading-6 text-gray-900">Resetează</button> </template> <template v-if="onlyPreviewMode"> <button @click="changePreviewMode" type="button" class="rounded-md flex items-center px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 bg-white border-gray-300 text-gray-900 hover:bg-gray-50 border"> Modifică </button> </template> <button v-else type="submit" class="rounded-md flex items-center px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 bg-blue-500 text-white hover:bg-blue-400" :class="{ 'disabled:cursor-not-allowed': loadingSubmit }" :disabled="loadingSubmit"> <LoaderTbf class="inline-block mr-2" color="text-white" v-if="loadingSubmit" /> Salvează </button> </div> </div> </form> </div> </template> <script> import { userNotificationsStore } from "@/stores/notifications.js"; import { useVuelidate } from "@vuelidate/core"; import { required, helpers } from "@vuelidate/validators"; import LoaderTbf from "@/components/public/LoadingTbf.vue"; import { RadioGroup, RadioGroupLabel, RadioGroupOption } from "@headlessui/vue"; const requiredAndMinim150 = (value) => helpers.req(value) && value.replace(/ /g, "").length > 150; export default { setup: () => ({ v$: useVuelidate() }), props: { productData: Object, isActive: Boolean, requiredStep: Object, }, components: { LoaderTbf, RadioGroup, RadioGroupLabel, RadioGroupOption, }, data() { return { loadingSubmit: false, name: "", characteristics: "", list_price: "", upsell_offer: "", upsell_after_rejection_offer: "", realTimeNotifications: userNotificationsStore(), onlyPreviewMode: false, canOnlyPreview: false, loadedGenerateAi: false, keyInputs: 1, }; }, validations() { return { name: { required }, characteristics: { requiredAndMinim150 }, list_price: { required }, upsell_offer: { required }, upsell_after_rejection_offer: { required }, }; }, watch: { "productData.upsell"(newValue) { this.populateData(); }, }, async mounted() { this.populateData(); if ( this.name !== "" && this.name != null && this.characteristics !== "" && this.characteristics != null && this.list_price !== "" && this.list_price != null && this.upsell_offer !== "" && this.upsell_offer != null && this.upsell_after_rejection_offer !== "" && this.upsell_after_rejection_offer != null ) { this.onlyPreviewMode = true; this.canOnlyPreview = true; } }, methods: { descriptionLength() { return this.characteristics.replace(/ /g, "").length; }, async submitForm() { if (this.isActive) { const isFormCorrect = await this.v$.$validate(); if (!isFormCorrect) { this.$refs.formUpsell.scrollIntoView({ behavior: "smooth", block: "start" }); this.loadingSubmit = false; return; } this.loadingSubmit = true; var formData = { name: this.name, characteristics: this.characteristics, list_price: this.list_price, upsell_offer: this.upsell_offer, upsell_after_rejection_offer: this.upsell_after_rejection_offer, }; axios .post(`instances/${this.$auth.user().instance.id}/products/${this.productData.id}/add-upsell`, formData) .then(({ data }) => { this.$emit("updateProduct", data.data); this.v$.$reset(); this.canOnlyPreview = true; this.onlyPreviewMode = true; }) .catch((error) => { var responseError = error.response.data; this.realTimeNotifications.addNotification({ type: "error", title: "TBF ERROR", description: `"${responseError.message}"<br>Vă rugăm să ne contactați și să ne transmiteți eroare. În interesul remedierii acestei probleme cât mai curând posibil.`, }); }) .finally(() => { this.loadingSubmit = false; }); } else { this.realTimeNotifications.addNotification({ type: "error", title: "Formularul trebuie completat în ordine.", description: `Pentru a introduce date în secțiunea <span class="font-medium">Produs Upsell</span> e nevoie prima data să completezi începând cu secțiunea <span class="font-medium">${this.requiredStep.step}</span>. ${this.requiredStep.message}`, }); } }, populateData() { if (this.productData.upsell) { const upsellData = this.productData.upsell; this.name = upsellData.name; this.characteristics = upsellData.characteristics; this.list_price = upsellData.list_price; this.upsell_offer = upsellData.upsell_offer; this.upsell_after_rejection_offer = upsellData.upsell_after_rejection_offer; } }, resetForm() { this.name = ""; this.characteristics = ""; this.list_price = ""; this.upsell_offer = ""; this.upsell_after_rejection_offer = ""; }, changePreviewMode() { this.v$.$reset(); this.onlyPreviewMode = !this.onlyPreviewMode; this.populateData(); }, generateDescWithAi() { if (this.name && this.characteristics) { this.loadedGenerateAi = true; axios .post(`/generate/product-description`, { name: this.name, description: this.characteristics }) .then(({ data }) => { this.characteristics = data.message; }) .finally(() => { this.keyInputs++; this.loadedGenerateAi = false; }); } }, }, }; </script>
Back