Added vue frontend project, fixed swagger path
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { Clock, Heart, MapPin, Sparkles, Star, X } from 'lucide-vue-next';
|
||||
|
||||
interface SwipeItem {
|
||||
id: number;
|
||||
type: 'food' | 'movie';
|
||||
title: string;
|
||||
subtitle: string;
|
||||
rating: number;
|
||||
location?: string;
|
||||
duration?: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
const cards: SwipeItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'food',
|
||||
title: 'Italian Restaurant',
|
||||
subtitle: 'Authentic pasta & pizza',
|
||||
rating: 4.8,
|
||||
location: '2.3 km away',
|
||||
image: 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=800&q=80',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'movie',
|
||||
title: 'Dune: Part Two',
|
||||
subtitle: 'Sci-Fi Epic',
|
||||
rating: 4.9,
|
||||
duration: '2h 46m',
|
||||
image: 'https://images.unsplash.com/photo-1536440136628-849c177e76a1?w=800&q=80',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'food',
|
||||
title: 'Sushi House',
|
||||
subtitle: 'Fresh Japanese cuisine',
|
||||
rating: 4.7,
|
||||
location: '1.8 km away',
|
||||
image: 'https://images.unsplash.com/photo-1579584425555-c3ce17fd4351?w=800&q=80',
|
||||
},
|
||||
];
|
||||
|
||||
const currentIndex = ref(0);
|
||||
const swipeDirection = ref<'left' | 'right' | null>(null);
|
||||
const pointerStartX = ref<number | null>(null);
|
||||
|
||||
const currentCard = computed(() => cards[currentIndex.value]);
|
||||
|
||||
function nextCard() {
|
||||
currentIndex.value = (currentIndex.value + 1) % cards.length;
|
||||
}
|
||||
|
||||
function handleSwipe(direction: 'left' | 'right') {
|
||||
swipeDirection.value = direction;
|
||||
window.setTimeout(() => {
|
||||
nextCard();
|
||||
swipeDirection.value = null;
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function onPointerDown(event: PointerEvent) {
|
||||
pointerStartX.value = event.clientX;
|
||||
}
|
||||
|
||||
function onPointerUp(event: PointerEvent) {
|
||||
if (pointerStartX.value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = event.clientX - pointerStartX.value;
|
||||
pointerStartX.value = null;
|
||||
|
||||
if (deltaX > 60) {
|
||||
handleSwipe('right');
|
||||
} else if (deltaX < -60) {
|
||||
handleSwipe('left');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-[18px] border border-white/[0.06] bg-[#16161F] p-5 shadow-[0_4px_20px_rgba(0,0,0,0.4)]">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-[12px] border border-purple-500/20 bg-gradient-to-br from-purple-500/20 to-blue-500/20">
|
||||
<Sparkles class="h-[18px] w-[18px] text-purple-400" :stroke-width="2" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-[15px] font-semibold text-white">Vote Now</h3>
|
||||
<p class="text-[11px] text-zinc-500">Swipe to decide</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
v-for="(_, i) in cards"
|
||||
:key="i"
|
||||
:class="[
|
||||
'h-1.5 rounded-full transition-all',
|
||||
i === currentIndex ? 'w-6 bg-gradient-to-r from-purple-500 to-blue-500' : 'w-1.5 bg-zinc-700',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative mb-4 h-[360px]">
|
||||
<div
|
||||
class="absolute inset-0 cursor-grab overflow-hidden rounded-[18px] shadow-xl active:cursor-grabbing"
|
||||
:class="swipeDirection ? 'transition-all duration-200 ease-out opacity-0 scale-95' : ''"
|
||||
@pointerdown="onPointerDown"
|
||||
@pointerup="onPointerUp"
|
||||
>
|
||||
<img :src="currentCard.image" :alt="currentCard.title" class="h-full w-full object-cover" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/50 to-transparent" />
|
||||
|
||||
<div class="absolute top-4 right-4 flex items-center gap-1.5 rounded-full border border-white/10 bg-black/60 px-3 py-1.5 backdrop-blur-md">
|
||||
<Star class="h-3.5 w-3.5 fill-amber-400 text-amber-400" :stroke-width="2" />
|
||||
<span class="text-[13px] font-bold text-white">{{ currentCard.rating }}</span>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 p-5">
|
||||
<div class="mb-3">
|
||||
<span class="mb-3 inline-flex items-center gap-1.5 rounded-lg border border-purple-500/20 bg-purple-500/20 px-2.5 py-1.5 text-[11px] font-semibold text-purple-300 backdrop-blur-sm">
|
||||
{{ currentCard.type === 'food' ? 'Restaurant' : 'Movie' }}
|
||||
</span>
|
||||
<h4 class="mb-1 text-[24px] font-bold leading-tight text-white">{{ currentCard.title }}</h4>
|
||||
<p class="mb-3 text-[14px] text-zinc-300">{{ currentCard.subtitle }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-[12px] text-zinc-400">
|
||||
<div v-if="currentCard.location" class="flex items-center gap-1.5">
|
||||
<MapPin class="h-3.5 w-3.5" :stroke-width="2" />
|
||||
<span class="font-medium">{{ currentCard.location }}</span>
|
||||
</div>
|
||||
<div v-if="currentCard.duration" class="flex items-center gap-1.5">
|
||||
<Clock class="h-3.5 w-3.5" :stroke-width="2" />
|
||||
<span class="font-medium">{{ currentCard.duration }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="swipeDirection" class="pointer-events-none absolute inset-0 z-10 flex items-center justify-center">
|
||||
<div
|
||||
:class="[
|
||||
'flex h-24 w-24 items-center justify-center rounded-full border-[3px] backdrop-blur-sm shadow-2xl',
|
||||
swipeDirection === 'left' ? 'border-rose-500 bg-rose-500/20' : 'border-emerald-500 bg-emerald-500/20',
|
||||
]"
|
||||
>
|
||||
<X v-if="swipeDirection === 'left'" class="h-12 w-12 text-rose-500" :stroke-width="3" />
|
||||
<Heart
|
||||
v-else
|
||||
class="h-12 w-12 fill-emerald-500 text-emerald-500"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
@click="handleSwipe('left')"
|
||||
class="group flex h-14 w-14 items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.06] transition-all hover:border-rose-500/30 hover:bg-rose-500/10 active:scale-95"
|
||||
>
|
||||
<X class="h-6 w-6 text-zinc-400 transition-colors group-hover:text-rose-400" :stroke-width="2.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="handleSwipe('right')"
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-purple-500 to-blue-500 shadow-[0_8px_24px_rgba(139,92,246,0.35)] transition-all hover:from-purple-600 hover:to-blue-600 hover:shadow-[0_12px_32px_rgba(139,92,246,0.5)] active:scale-95"
|
||||
>
|
||||
<Heart class="h-7 w-7 text-white" :stroke-width="2.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user