|
|
@@ -0,0 +1,101 @@
|
|
|
+<template>
|
|
|
+ <div class="mt-8 pt-6 border-t border-border/50 relative z-10">
|
|
|
+ <!-- Submit Form -->
|
|
|
+ <div v-if="!order.review_text" class="bg-primary/5 border border-primary/20 rounded-2xl p-6">
|
|
|
+ <div class="flex items-center gap-2 mb-4">
|
|
|
+ <Star class="w-4 h-4 text-primary" />
|
|
|
+ <h4 class="font-bold text-sm">{{ t('orders.review.writeTitle') }}</h4>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex gap-2 mb-4">
|
|
|
+ <button
|
|
|
+ v-for="i in 5"
|
|
|
+ :key="i"
|
|
|
+ @click="rating = i"
|
|
|
+ class="transition-transform hover:scale-110"
|
|
|
+ >
|
|
|
+ <Star
|
|
|
+ class="w-6 h-6"
|
|
|
+ :class="i <= rating ? 'text-yellow-400 fill-yellow-400' : 'text-muted-foreground/30'"
|
|
|
+ />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <textarea
|
|
|
+ v-model="reviewText"
|
|
|
+ :placeholder="t('orders.review.placeholder')"
|
|
|
+ class="w-full bg-background/50 border border-border/50 rounded-xl p-4 text-sm focus:outline-none focus:border-primary/50 transition-colors resize-none mb-4"
|
|
|
+ rows="3"
|
|
|
+ ></textarea>
|
|
|
+
|
|
|
+ <button
|
|
|
+ @click="submit"
|
|
|
+ :disabled="isSubmitting || rating === 0 || reviewText.length < 2"
|
|
|
+ class="bg-primary text-primary-foreground px-6 py-2 rounded-xl text-sm font-bold hover:bg-primary/90 transition-all disabled:opacity-50 disabled:scale-100 transform active:scale-95 flex items-center gap-2"
|
|
|
+ >
|
|
|
+ <Loader2 v-if="isSubmitting" class="w-4 h-4 animate-spin" />
|
|
|
+ {{ t('orders.review.submit') }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Feedback after submission -->
|
|
|
+ <div v-else class="flex flex-col gap-3">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <div class="flex gap-0.5">
|
|
|
+ <Star
|
|
|
+ v-for="i in 5"
|
|
|
+ :key="i"
|
|
|
+ class="w-3.5 h-3.5"
|
|
|
+ :class="i <= order.rating ? 'text-yellow-400 fill-yellow-400' : 'text-muted-foreground/30'"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <span v-if="!order.review_approved" class="text-[10px] bg-amber-500/10 text-amber-500 px-2 py-0.5 rounded-full font-bold uppercase tracking-wider">
|
|
|
+ {{ t('orders.review.pending') }}
|
|
|
+ </span>
|
|
|
+ <span v-else class="text-[10px] bg-emerald-500/10 text-emerald-500 px-2 py-0.5 rounded-full font-bold uppercase tracking-wider">
|
|
|
+ {{ t('orders.review.approved') }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p class="text-sm text-foreground/80 italic leading-relaxed">"{{ order.review_text }}"</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref } from 'vue';
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
+import { Star, Loader2 } from 'lucide-vue-next';
|
|
|
+import { submitOrderReview } from '@/lib/api';
|
|
|
+import { toast } from 'vue-sonner';
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ order: any
|
|
|
+}>();
|
|
|
+
|
|
|
+const emit = defineEmits(['updated']);
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
+const rating = ref(0);
|
|
|
+const reviewText = ref('');
|
|
|
+const isSubmitting = ref(false);
|
|
|
+
|
|
|
+async function submit() {
|
|
|
+ if (rating.value === 0 || reviewText.value.length < 2) return;
|
|
|
+
|
|
|
+ isSubmitting.value = true;
|
|
|
+ try {
|
|
|
+ await submitOrderReview(props.order.id, {
|
|
|
+ rating: rating.value,
|
|
|
+ review_text: reviewText.value
|
|
|
+ });
|
|
|
+ toast.success(t('orders.review.success'));
|
|
|
+ emit('updated');
|
|
|
+ } catch (e: any) {
|
|
|
+ toast.error(e.message || 'Failed to submit review');
|
|
|
+ } finally {
|
|
|
+ isSubmitting.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|