init git
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="product-comment-item">
|
||||
<p>{{ content }}</p>
|
||||
<div>
|
||||
<span>
|
||||
{{
|
||||
new Date(createdAt).toLocaleDateString('fa', {
|
||||
month: 'long', year: 'numeric', day: 'numeric'
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span>{{ userID?.firstName }} {{ userID?.lastName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['data'])
|
||||
const { createdAt, userID, content } = props.data
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-comment-card {
|
||||
@apply w-[16.9rem] h-[13.1rem] rounded-xl border p-[1.1rem] flex flex-col justify-between;
|
||||
|
||||
p {
|
||||
@apply w-full text-[#7F7F7F] font-vazir text-xs leading-5;
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply flex gap-1.5 items-center;
|
||||
|
||||
span:nth-child(1) {
|
||||
@apply text-[#B2B2B2] font-vazir text-[0.7em];
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
@apply relative flex items-center gap-1.5;
|
||||
|
||||
&::before {
|
||||
@apply content-['_'] w-1.5 h-1.5 bg-neutral-200 rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="product-comment-item">
|
||||
<div>
|
||||
<span>{{ point }}</span>
|
||||
<span>{{ role || $t('buyer') }}</span>
|
||||
<span>
|
||||
{{
|
||||
new Date(createdAt).toLocaleDateString('fa', {
|
||||
month: 'long', year: 'numeric', day: 'numeric'
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span>{{ userID?.firstName }} {{ userID?.lastName }}</span>
|
||||
</div>
|
||||
<p>{{ content }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['data'])
|
||||
const { point, role, createdAt, userID, content } = props.data
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-comment-item {
|
||||
@apply w-full max-w-[47.9rem] xl:h-[8.3rem] flex flex-col border-b border-[#E5E5E5];
|
||||
@apply pb-4 lg:pb-8;
|
||||
@apply gap-4 lg:gap-6;
|
||||
|
||||
div {
|
||||
@apply flex gap-4 items-center;
|
||||
|
||||
span:nth-child(-n + 2) {
|
||||
@apply flex items-center justify-center pt-0.5 rounded font-iran-sans;
|
||||
@apply text-sm md:text-base xl:text-[1.1em];
|
||||
@apply h-7 lg:h-[2.2rem];
|
||||
@apply px-3 lg:px-4;
|
||||
}
|
||||
|
||||
span:nth-child(1) {
|
||||
@apply bg-[#019907] text-white;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
@apply bg-[#F2F2F2] text-[#999999] -mr-2;
|
||||
}
|
||||
|
||||
span:nth-child(n + 3) {
|
||||
@apply text-[#B2B2B2] font-vazir;
|
||||
@apply text-[0.7em] lg:text-lg;
|
||||
}
|
||||
|
||||
span:nth-child(4) {
|
||||
@apply relative flex items-center gap-1.5 lg:gap-4;
|
||||
|
||||
&::before {
|
||||
@apply content-['_'] w-1.5 h-1.5 bg-neutral-200 rounded-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@apply w-full text-[#7F7F7F] font-vazir text-lg leading-[2.3rem];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="sort">
|
||||
<h3>
|
||||
<i class="isax isax-document-filter"></i>
|
||||
<span>{{ $t('sortBy') }}:</span>
|
||||
</h3>
|
||||
<ul>
|
||||
<li v-for="(btn, key) in btns.sort" :key="key">
|
||||
<Button v-bind="btn.props" @click="sort(key)" :active="sorted == key" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { btns } = inject('init')
|
||||
let { comments } = inject('data')
|
||||
const sorted = ref('createdAt')
|
||||
const sort = (key) => {
|
||||
sorted.value = key
|
||||
comments = comments.sort((a, b) => {
|
||||
return key == 'createdAt' ?
|
||||
new Date(b.createdAt) - new Date(a.createdAt) :
|
||||
b.point - a.point
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product .comments .sort {
|
||||
@apply flex gap-4;
|
||||
|
||||
h3 {
|
||||
@apply flex items-center gap-2 text-[#333333] ml-9;
|
||||
|
||||
i {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-lg font-medium font-iran-sans;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply flex;
|
||||
|
||||
button {
|
||||
@apply h-10 py-1.5 font-iran-sans font-medium text-lg text-[#B2B2B2] #{!important};
|
||||
|
||||
&[active=true] {
|
||||
@apply text-primary-600 #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="summary">
|
||||
<h2>{{ $t('buyersComments') }}</h2>
|
||||
<div>
|
||||
<i class="isax isax-star-15"></i>
|
||||
<bdi>5 / {{ product.stars }}</bdi>
|
||||
</div>
|
||||
<p>{{ $t('buyersCommentsDesc') }}</p>
|
||||
<Button v-bind="btns.submit.props" @click="handleClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { btns } = inject('init')
|
||||
const product = inject('data')
|
||||
const { dialog } = inject('service')
|
||||
|
||||
const handleClick = () => {
|
||||
dialog.show(resolveComponent('ProductCommentDialogForm'), product)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product .comments>.summary {
|
||||
@apply flex w-[18.3rem] flex-col gap-4;
|
||||
|
||||
h2 {
|
||||
@apply flex gap-3 items-center text-primary-600 text-xl font-bold font-vazir;
|
||||
|
||||
&::before {
|
||||
@apply content-['_'] h-6 w-[0.3rem] rounded-full bg-primary-600;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
@apply flex gap-2 items-center mt-6;
|
||||
|
||||
i {
|
||||
@apply text-xl text-[#FFB628];
|
||||
}
|
||||
|
||||
bdi {
|
||||
@apply text-[#191919] text-xl font-normal font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@apply text-[#999999] font-vazir;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply h-[3.4rem] text-xl font-medium font-iran-sans #{!important};
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<section class="tab-comments">
|
||||
<Button :label="$t('commentsCount', { count: 4 })" text @click="handleClick" />
|
||||
<Swiper>
|
||||
<SwiperSlide v-for="(item, i) in items" :key="i">
|
||||
<ProductCommentCard :data="item" />
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product .tab-comments {
|
||||
@apply flex flex-col gap-1 items-start;
|
||||
|
||||
&>button {
|
||||
@apply mr-3 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-[#47B556];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.swiper {
|
||||
@apply w-full h-[13.1rem] px-6;
|
||||
|
||||
.swiper-wrapper {
|
||||
@apply w-max z-0;
|
||||
|
||||
.swiper-slide {
|
||||
@apply h-max w-max ml-2.5 last:ml-0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<section class="comments">
|
||||
<ProductCommentSummary />
|
||||
<div>
|
||||
<ProductCommentSort />
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="(comment, i) in comments" :key="i">
|
||||
<ProductCommentItem :data="comment" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { comments } = inject('data')
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product .comments {
|
||||
@apply w-full flex gap-[11.4rem] lg:px-6;
|
||||
|
||||
&>div:not([class]) {
|
||||
@apply flex flex-col gap-3 lg:gap-8 overflow-hidden grow;
|
||||
|
||||
&>button {
|
||||
@apply lg:hidden w-10 h-5 p-0 mr-6;
|
||||
|
||||
.p-button-label {
|
||||
@apply text-[0.7em] rtl text-primary-600;
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
&>ul {
|
||||
@apply flex lg:flex-col gap-9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="product-comment-form">
|
||||
<div>
|
||||
<div>
|
||||
<h2>{{ $t('yourComment') }}</h2>
|
||||
<Button v-bind="closeBtn.props" @click="dialog.close()" />
|
||||
</div>
|
||||
<p>{{ $t('aboutTheProdcut', { title }) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="(item, key) in fields" :key="key">
|
||||
<component :is="item.is" ref="chips" :id="key" v-bind="item.props" v-model="form[key]" />
|
||||
<label :for="key">{{ item.label }}</label>
|
||||
<template v-if="['positive', 'negative'].includes(key)">
|
||||
<Button icon="isax isax-add" severity="secondary" text @click="addChip(i)" />
|
||||
</template>
|
||||
<small v-if="errors[key]">{{ errors[key] }}</small>
|
||||
</li>
|
||||
<li>
|
||||
<label> {{ $t('yourScore') }} : </label>
|
||||
<ul>
|
||||
<li v-for="i in 5" :key="i" @click="form.point = i">
|
||||
<img src="/icons/star.svg" :class="starsClass(i)" />
|
||||
</li>
|
||||
</ul>
|
||||
<small v-if="errors.point">{{ errors.point }}</small>
|
||||
</li>
|
||||
<li>
|
||||
<label>{{ $t('description') }} :</label>
|
||||
<Textarea v-model="form.content" :placeholder="$t('writeDesc')" />
|
||||
<small v-if="errors.content">{{ errors.content }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Button :label="$t('submitComment')" :loading="loading" @click="submit()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import init from '../../../../initialize/productCommentForm.js'
|
||||
const { closeBtn, fields } = init()
|
||||
const { toast, t } = inject('service')
|
||||
const dialog = inject('dialogRef')
|
||||
const { title, id } = dialog.value.data
|
||||
const chips = ref()
|
||||
|
||||
const form = reactive({})
|
||||
const errors = ref({});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const submit = async () => {
|
||||
if (Object.keys(errors.value).length < 1) {
|
||||
loading.value = true
|
||||
|
||||
const { status, error } = await useFetch(`/api/comments/${id}`, {
|
||||
headers: { Authorization: useCookie('token') }, method: 'post', body: form
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
toast.add({
|
||||
life: 3000, severity: 'success', summary: t('success'), detail: t('yourCommentSummited')
|
||||
})
|
||||
dialog.value.close()
|
||||
} else {
|
||||
if (error.value.statusCode == 422)
|
||||
{errors.value = error.value.data}
|
||||
else
|
||||
toast.add({
|
||||
life: 3000, severity: 'error', summary: t('error'), detail: error.value.message
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const addChip = (i) => {
|
||||
chips.value[i].addItem(new Event('keydown'), chips.value[i].$data.inputValue, true)
|
||||
}
|
||||
|
||||
const starsClass = (i) => (i > (form?.point || 0) ? 'opacity-25' : '')
|
||||
|
||||
|
||||
watch(computed(() => form), (value, old) => {
|
||||
Object.keys(errors.value).forEach(key => {
|
||||
if (value[key] != old[key]) delete errors.value[key]
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-comment-form {
|
||||
@apply w-full lg:w-[40.1rem] lg:h-[48.6rem] bg-white rounded-t-[0.6rem] lg:rounded-[0.6rem] flex flex-col relative;
|
||||
|
||||
&::before {
|
||||
@apply content-['_'] absolute inset-x-0 bottom-[4.5rem] h-3 shadow-[0px_-2px_5px_#0000000a] lg:shadow-none z-0;
|
||||
}
|
||||
|
||||
&>div:nth-of-type(1) {
|
||||
@apply flex flex-col gap-1.5 lg:gap-2 p-6 lg:px-10 lg:py-8 lg:border-b-2 border-[#E5E5E5];
|
||||
|
||||
&>div {
|
||||
@apply flex justify-between items-center;
|
||||
|
||||
h2 {
|
||||
@apply text-zinc-800 text-sm lg:text-xl font-bold font-vazir;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply -mx-4 -my-3;
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-[1.4em] lg:text-3xl text-[#191919];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@apply text-[#7F7F7F] text-[0.7em] lg:text-lg font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
@apply lg:h-[29.9rem] grow p-6 lg:py-8 lg:px-10 lg:overflow-y-auto;
|
||||
|
||||
&>ul {
|
||||
@apply w-full flex flex-col items-center gap-[1.9rem] lg:gap-6 lg:py-1.5 lg:pr-14 lg:pl-16;
|
||||
|
||||
&>li:nth-of-type(-n + 3) {
|
||||
@apply w-[21.4rem] lg:w-[27.1rem] lg:min-h-[3.5rem] relative mt-1.5 lg:mt-3 flex items-center [&_button]:first:hidden;
|
||||
|
||||
button {
|
||||
@apply text-2xl absolute left-0 top-0 text-[#7F7F7F];
|
||||
}
|
||||
|
||||
label {
|
||||
@apply w-max absolute right-3 top-4 lg:top-3.5 px-1 bg-white transition-['top'];
|
||||
@apply text-neutral-400 text-sm lg:text-base font-medium font-vazir cursor-text;
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
|
||||
&>input {
|
||||
@apply h-[3.4rem] pl-10 rtl w-full rounded-[0.6rem] border shadow-none;
|
||||
@apply text-zinc-800 font-vazir;
|
||||
}
|
||||
|
||||
.p-chips {
|
||||
@apply w-full max-w-full rounded-[0.6rem];
|
||||
|
||||
ul {
|
||||
@apply pl-8 w-full h-max grow flex-row-reverse;
|
||||
|
||||
input {
|
||||
@apply w-full rtl text-zinc-800 font-vazir;
|
||||
}
|
||||
|
||||
.p-chips-token {
|
||||
@apply ltr my-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(input:focus),
|
||||
&:has(.p-chips-token-label),
|
||||
&:has(input:not(:placeholder-shown)) {
|
||||
label {
|
||||
@apply -top-2 h-4;
|
||||
@apply text-zinc-800 text-xs font-normal font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
small{
|
||||
@apply absolute -bottom-6 left-0 ;
|
||||
}
|
||||
}
|
||||
|
||||
&>li:nth-of-type(4) {
|
||||
@apply w-[21.4rem] lg:w-[27.1rem] flex items-center gap-4 lg:mt-2;
|
||||
|
||||
label {
|
||||
@apply text-[#4C4C4C] text-sm lg:text-xl font-medium font-vazir;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply flex gap-1.5;
|
||||
|
||||
img {
|
||||
@apply w-[1.1rem] h-[1.1rem] lg:w-6 lg:h-6 cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>li:nth-of-type(5) {
|
||||
@apply flex flex-col gap-4 relative;
|
||||
|
||||
label {
|
||||
@apply text-[#4C4C4C] text-sm lg:text-xl font-medium font-vazir;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@apply w-[21.4rem] h-[12.1rem] lg:w-[27.1rem] lg:h-[12.1rem] p-4 rounded-[0.6rem] border border-[#E5E5E5];
|
||||
|
||||
&::placeholder {
|
||||
@apply text-[#999999] text-xs lg:text-lg font-normal font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>li::after {
|
||||
content: attr(error);
|
||||
@apply text-red-500 text-sm absolute -bottom-6 left-0;
|
||||
}
|
||||
|
||||
small {
|
||||
@apply text-red-500 font-vazir mr-auto;
|
||||
}
|
||||
|
||||
li:has(small) * {
|
||||
@apply border-red-500 #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>button {
|
||||
@apply w-[21.4rem] lg:w-[35.1rem] h-[2.9rem] my-6 self-center lg:h-14 lg:m-10 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply font-bold font-vazir leading-tight text-sm lg:text-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="product-comments">
|
||||
<div>
|
||||
<Button icon="isax isax-arrow-right-1" link @click="$emit('close')" />
|
||||
<h2>{{ $t("commentsCount", { count: 5 }) }}</h2>
|
||||
<Button icon="isax isax-document-filter" link @click="handleClick" />
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<ProductCommentItem :data="item" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Button :label="$t('submitComment')" icon="isax isax-message" rounded @click="handleClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const items = reactive([{}, {}, {}, {}, {}, {}, {}, {}, {}]);
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-comments {
|
||||
@apply w-full bg-white rounded-t-[0.6rem] flex flex-col gap-10 relative px-6 pt-6 overflow-hidden;
|
||||
|
||||
&>div:nth-of-type(1) {
|
||||
@apply w-full flex items-center gap-3;
|
||||
|
||||
&>h2 {
|
||||
@apply text-[#333333] text-sm font-medium font-vazir ml-auto;
|
||||
}
|
||||
|
||||
&>button {
|
||||
@apply -mx-4 -my-3;
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-[#333333] text-[1.4em];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
@apply h-full overflow-y-auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
&>ul {
|
||||
@apply h-max flex flex-col gap-6;
|
||||
}
|
||||
}
|
||||
|
||||
&>button {
|
||||
@apply absolute left-6 bottom-5 w-[8.3rem] h-12 px-[1.4rem] #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-xs font-vazir;
|
||||
}
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-xl;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="product-comments-sort">
|
||||
<div>
|
||||
<Button icon="pi pi-times" link @click="$emit('close')" />
|
||||
<h2>{{ $t("sortBy") }}</h2>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-for="(item, key) in items" :key="key">
|
||||
<h3 @click="events.select(key)" v-ripple>
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<i v-if="item.selected" class="pi pi-check"></i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { t } = useI18n();
|
||||
const items = reactive({
|
||||
newest: { title: t("newest"), selected: true },
|
||||
mostUseful: { title: t("mostUseful") },
|
||||
});
|
||||
|
||||
const events = reactive({
|
||||
select: (key) => {
|
||||
items.newest.selected = false;
|
||||
items.mostUseful.selected = false;
|
||||
items[key].selected = true;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.product-comments-sort {
|
||||
@apply w-full flex flex-col gap-9 p-6 bg-white rounded-t-[0.6rem];
|
||||
|
||||
&>div {
|
||||
@apply flex items-center gap-3;
|
||||
|
||||
&>button {
|
||||
@apply -mx-4 -my-3 text-[#333333] #{!important};
|
||||
}
|
||||
|
||||
&>h2 {
|
||||
@apply text-[#333333] text-sm font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
&>ul {
|
||||
@apply w-full flex flex-col gap-4;
|
||||
|
||||
&>li {
|
||||
@apply flex items-center justify-between pb-4 first:border-b text-[#333333];
|
||||
|
||||
h3 {
|
||||
@apply text-xs font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user