Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bfdc64d13 | ||
|
|
d0bbcfca7c | ||
|
|
69b7a22d11 | ||
|
|
f755180bf8 | ||
|
|
a6c9f72efb | ||
|
|
8c7e31dd19 | ||
|
|
2be38141b5 |
@ -29,8 +29,8 @@ class StripeController extends Controller
|
||||
$clientSessionId = Str::uuid();
|
||||
|
||||
$priceIds = [
|
||||
6 => 'price_1S51zxGaZ3yeYkzWYb0wSt4j',
|
||||
18 => 'price_1S5464GaZ3yeYkzWh8RuJfab',
|
||||
6 => env('STRIPE_6_PRICE'),
|
||||
18 => env('STRIPE_18_PRICE'),
|
||||
];
|
||||
|
||||
if (!isset($priceIds[$count])) {
|
||||
@ -76,7 +76,7 @@ class StripeController extends Controller
|
||||
$userForm = $request->input('userForm');
|
||||
$dateAppointment = $request->input('selectedDate');
|
||||
$clientSessionId = Str::uuid();
|
||||
$priceId = 'price_1S5ifuGaZ3yeYkzWsgrOTpgT';
|
||||
$priceId = env('STRIPE_BOOKING');
|
||||
|
||||
try {
|
||||
$session = Session::create([
|
||||
@ -112,27 +112,75 @@ class StripeController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
public function validatePayment(Request $request)
|
||||
public function validatePayment(Request $request)
|
||||
{
|
||||
$clientSessionId = $request->query('client_session_id');
|
||||
|
||||
$payment = Payment::where('client_session_id', $clientSessionId)
|
||||
->where('status', 'succeeded')
|
||||
->first();
|
||||
if (!$clientSessionId) {
|
||||
return response()->json(['error' => 'Client session ID is required'], 400);
|
||||
}
|
||||
|
||||
if ($payment) {
|
||||
// Si la vérification réussit, retournez le nombre de tirages.
|
||||
$payment = Payment::where('client_session_id', $clientSessionId)->first();
|
||||
|
||||
if (!$payment) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Payment not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// If payment is already succeeded in our database
|
||||
if ($payment->status === 'succeeded') {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'drawCount' => $payment->draw_count,
|
||||
'cached' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// Si la vérification échoue, retournez une erreur.
|
||||
// If payment is pending, check with Stripe directly to handle race condition
|
||||
if ($payment->status === 'pending') {
|
||||
try {
|
||||
Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
|
||||
$session = Session::retrieve($clientSessionId);
|
||||
|
||||
// Check if payment is completed on Stripe side
|
||||
if ($session->payment_status === 'paid' && $session->status === 'complete') {
|
||||
// Update our payment record and mark as succeeded
|
||||
$payment->update(['status' => 'succeeded']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'drawCount' => $payment->draw_count,
|
||||
'updated' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// Payment not completed yet, return pending status
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Payment is still being processed.',
|
||||
'status' => 'pending',
|
||||
], 202);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Stripe validation failed: ' . $e->getMessage(), [
|
||||
'client_session_id' => $clientSessionId,
|
||||
'payment_id' => $payment->id
|
||||
]);
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Payment validation error.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Payment failed or has other status
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Paiement non validé.',
|
||||
], 404);
|
||||
'message' => 'Payment failed or cancelled.',
|
||||
'status' => $payment->status,
|
||||
], 402);
|
||||
}
|
||||
|
||||
public function getCards(Request $request)
|
||||
|
||||
@ -17,6 +17,25 @@ class HandleInertiaRequests extends Middleware
|
||||
*/
|
||||
protected $rootView = 'app';
|
||||
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* This override prevents cache issues when users navigate back from external
|
||||
* sites (like Stripe checkout) using the browser's back button.
|
||||
*/
|
||||
public function handle(\Illuminate\Http\Request $request, \Closure $next): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
$response = parent::handle($request, $next);
|
||||
|
||||
// Add headers to prevent browser caching of Inertia responses
|
||||
// This fixes the issue where back-button navigation from Stripe shows raw JSON
|
||||
$response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
||||
$response->headers->set('Pragma', 'no-cache');
|
||||
$response->headers->set('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current asset version.
|
||||
*
|
||||
|
||||
@ -73,7 +73,9 @@ class CardRepository implements CardRepositoryInterface
|
||||
'name' => $card->name,
|
||||
'image_url' => $card->image_url,
|
||||
'orientation' => $isReversed ? 'reversed' : 'upright',
|
||||
'description' => $isReversed ? $card->description_reversed : $card->description_upright,
|
||||
'description' => $card->description,
|
||||
'description_reversed' => $card->description_reversed,
|
||||
'description_upright' => $card->description_upright,
|
||||
'symbolism' => $card->symbolism,
|
||||
'created_at' => now(),
|
||||
];
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"php": "^8.2",
|
||||
"guzzlehttp/guzzle": "^7.10",
|
||||
"inertiajs/inertia-laravel": "^2.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/framework": "^11.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"laravel/wayfinder": "^0.1.9",
|
||||
@ -26,8 +26,8 @@
|
||||
"laravel/sail": "^1.41",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^4.0",
|
||||
"pestphp/pest-plugin-laravel": "^4.0"
|
||||
"pestphp/pest": "^3.0",
|
||||
"pestphp/pest-plugin-laravel": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
1392
composer.lock
generated
1392
composer.lock
generated
File diff suppressed because it is too large
Load Diff
BIN
public/build.zip
Normal file
BIN
public/build.zip
Normal file
Binary file not shown.
@ -37,15 +37,15 @@ const activeItemStyles = computed(
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Accueil',
|
||||
title: 'Home',
|
||||
href: '/', // Example route
|
||||
},
|
||||
{
|
||||
title: 'Tirage d’oracle',
|
||||
title: 'Reading cards',
|
||||
href: '/oracle-draw', // Example route
|
||||
},
|
||||
{
|
||||
title: 'Consultations',
|
||||
title: 'Booking',
|
||||
href: '/consultations', // Example route
|
||||
},
|
||||
{
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
<div class="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
||||
<button
|
||||
@click="goToOffers"
|
||||
@click="goToBooking"
|
||||
class="gold-trail-btn relative inline-flex h-12 min-w-[160px] items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-[#f59e0b] to-[#eab308] px-8 font-bold tracking-wide text-[#1e1b4b] shadow-lg transition-all duration-300 hover:shadow-[#f59e0b]/40"
|
||||
>
|
||||
<span class="relative z-10">Book a Consultation</span>
|
||||
@ -38,14 +38,7 @@
|
||||
class="absolute inset-0 translate-x-[-100%] -skew-x-12 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-[100%]"
|
||||
></span>
|
||||
</button>
|
||||
<button
|
||||
@click="goToBooking"
|
||||
class="inline-flex h-12 min-w-[160px] items-center justify-center rounded-full border-2 border-[#4c1d95] bg-transparent px-8 font-bold text-white backdrop-blur-sm transition-all hover:bg-[#4c1d95]"
|
||||
>
|
||||
Book a Consultation
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-xs text-white/70 backdrop-blur-sm">Secure payment</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
class="absolute inset-0 translate-x-[-100%] -skew-x-12 transform bg-gradient-to-r from-transparent via-white/20 to-transparent transition-transform duration-1000 group-hover:translate-x-[100%]"
|
||||
></span>
|
||||
|
||||
<span class="relative z-10 flex items-center gap-2 truncate"> Reveal my free reading </span>
|
||||
<span class="relative z-10 flex items-center gap-2 truncate"> Reveal your inner power</span>
|
||||
|
||||
<!-- Animated arrow icon -->
|
||||
</button>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
class="relative z-10 mb-16 transform text-center text-4xl font-bold text-[var(--c-white)] transition-all duration-700 hover:scale-105 md:text-5xl"
|
||||
>
|
||||
<span class="relative inline-block">
|
||||
Explorez Nos Lectures
|
||||
Explore Our <span class="text-[var(--c-gold)]">Offerings</span>
|
||||
<span class="absolute -bottom-2 left-1/4 h-1 w-1/2 bg-gradient-to-r from-transparent via-[var(--c-gold)] to-transparent"></span>
|
||||
</span>
|
||||
</h2>
|
||||
@ -51,7 +51,9 @@
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 text-center">
|
||||
<h3 class="text-2xl font-bold text-[var(--c-white)] transition-colors duration-300 group-hover:text-purple-300">Free Reading</h3>
|
||||
<h3 class="text-2xl font-bold text-[var(--c-white)] transition-colors duration-300 group-hover:text-purple-300">
|
||||
Reveal your inner power
|
||||
</h3>
|
||||
<div class="relative mt-2 inline-block">
|
||||
<p class="text-5xl font-bold text-[var(--c-gold)] transition-transform duration-300 group-hover:scale-110">Free</p>
|
||||
<div
|
||||
@ -89,9 +91,13 @@
|
||||
</ul>
|
||||
|
||||
<button
|
||||
class="relative z-10 mt-4 flex h-12 w-full items-center justify-center overflow-hidden rounded-full border border-[var(--c-purple)]/40 bg-[#2d1b69] px-8 font-bold tracking-wide text-[var(--c-white)] transition-all duration-300 group-hover:bg-[#3d2485] group-hover:shadow-lg"
|
||||
class="relative z-10 mt-4 flex h-12 w-full items-center justify-center overflow-hidden rounded-full border border-[var(--c-purple)]/40 px-8 font-bold tracking-wide text-[var(--c-white)] transition-all duration-300 group-hover:shadow-lg"
|
||||
:class="hasUsedFreeDraw ? 'bg-[var(--c-gold)]/80 group-hover:bg-[var(--c-gold)]' : 'bg-[#2d1b69] group-hover:bg-[#3d2485]'"
|
||||
@click="handleFreeClick"
|
||||
>
|
||||
<span class="relative z-10 transition-transform duration-300 group-hover:translate-x-1">BEGIN</span>
|
||||
<span class="relative z-10 transition-transform duration-300 group-hover:translate-x-1">
|
||||
{{ hasUsedFreeDraw ? 'VIEW MY READING' : 'BEGIN' }}
|
||||
</span>
|
||||
<svg
|
||||
class="relative z-10 ml-2 h-5 w-5 -translate-x-2 transform opacity-0 transition-all duration-300 group-hover:translate-x-0 group-hover:opacity-100"
|
||||
fill="none"
|
||||
@ -111,7 +117,7 @@
|
||||
class="group relative flex scale-105 flex-col gap-6 overflow-hidden rounded-2xl bg-gradient-to-br from-[#4c1d95] to-[#371a70] p-8 shadow-lg ring-2 ring-[var(--c-gold)] transition-all duration-500 hover:-translate-y-3 hover:scale-[1.03] hover:shadow-2xl"
|
||||
>
|
||||
<!-- Popular badge with animation -->
|
||||
<div class="absolute top-4 right-4 z-10">
|
||||
<div class="absolute top-4 right-4 z-10 hidden sm:block">
|
||||
<div class="relative">
|
||||
<div class="absolute -inset-1 animate-pulse rounded-full bg-[var(--c-gold)] opacity-75 blur"></div>
|
||||
<div class="relative rounded-full bg-[var(--c-gold)] px-3 py-1 text-xs font-bold text-[var(--c-purple)]">POPULAIRE</div>
|
||||
@ -175,6 +181,7 @@
|
||||
|
||||
<button
|
||||
class="relative z-10 mt-4 flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-[var(--c-gold)] to-yellow-600 px-8 font-bold tracking-wide text-[var(--c-purple)] transition-all duration-300 group-hover:from-yellow-600 group-hover:to-yellow-400 group-hover:shadow-lg"
|
||||
@click="goToTirage"
|
||||
>
|
||||
<span class="relative z-10 transition-transform duration-300 group-hover:translate-x-1">DISCOVER</span>
|
||||
<svg
|
||||
@ -255,6 +262,7 @@
|
||||
|
||||
<button
|
||||
class="relative z-10 mt-4 flex h-12 w-full items-center justify-center overflow-hidden rounded-full border border-[var(--c-purple)]/40 bg-[#2d1b69] px-8 font-bold tracking-wide text-[var(--c-white)] transition-all duration-300 group-hover:bg-[#3d2485] group-hover:shadow-lg"
|
||||
@click="goToTirage"
|
||||
>
|
||||
<span class="relative z-10 transition-transform duration-300 group-hover:translate-x-1">EXPLORE</span>
|
||||
<svg
|
||||
@ -275,12 +283,42 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTarotStore } from '@/stores/tarot';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
|
||||
const tarotStore = useTarotStore();
|
||||
|
||||
// Define features for each tier
|
||||
const freeFeatures = ['Single‑card Reading', 'General Interpretation', 'Quick Tips'];
|
||||
|
||||
const featuredFeatures = ['six‑card Reading', 'Personalized Analysis', 'Tailored Recommendations'];
|
||||
|
||||
const premiumFeatures = ['Eigtheen‑card reading', 'In‑depth exploration', 'Complete strategy'];
|
||||
|
||||
// Check if user has already used their free draw (either card is saved, or draws remaining is 0)
|
||||
const hasUsedFreeDraw = computed(() => tarotStore.freeDrawCard !== null || tarotStore.freeDrawsRemaining === 0);
|
||||
|
||||
// Navigate to tirage page
|
||||
const goToTirage = () => {
|
||||
router.visit('/tirage');
|
||||
};
|
||||
|
||||
// Navigate to free card result (to view previous draw)
|
||||
const goToFreeResult = () => {
|
||||
router.visit('/resultat-gratuit');
|
||||
};
|
||||
|
||||
// Handle free tier button click
|
||||
const handleFreeClick = () => {
|
||||
if (hasUsedFreeDraw.value) {
|
||||
goToFreeResult();
|
||||
} else {
|
||||
goToTirage();
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => console.log(tarotStore.freeDrawCard));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
"
|
||||
></div>
|
||||
<div>
|
||||
<p class="text-lg font-bold text-white transition-colors duration-300 group-hover:text-[#f59e0b]">Sophie Dubois</p>
|
||||
<p class="text-lg font-bold text-white transition-colors duration-300 group-hover:text-[#f59e0b]">Sophia Williams</p>
|
||||
<div class="flex text-[#f59e0b]">
|
||||
<svg class="h-5 w-5 transform transition-all duration-300 hover:scale-125" fill="currentColor" viewBox="0 0 256 256">
|
||||
<path
|
||||
@ -52,8 +52,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="testimonial-quote relative text-white/80 italic">
|
||||
"L'Oracle de Kris Saint Ange a été une révélation. Ses conseils m'ont aidée à prendre des décisions importantes avec
|
||||
confiance."
|
||||
"Kris Saint Ange's Oracle was a revelation. Her guidance helped me make important decisions with confidence."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -74,7 +73,7 @@
|
||||
"
|
||||
></div>
|
||||
<div>
|
||||
<p class="text-lg font-bold text-white transition-colors duration-300 group-hover:text-[#f59e0b]">Jean Martin</p>
|
||||
<p class="text-lg font-bold text-white transition-colors duration-300 group-hover:text-[#f59e0b]">John Martin</p>
|
||||
<div class="flex text-[#f59e0b]">
|
||||
<svg class="h-5 w-5 transform transition-all duration-300 hover:scale-125" fill="currentColor" viewBox="0 0 256 256">
|
||||
<path
|
||||
@ -109,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="testimonial-quote relative text-white/80 italic">
|
||||
"Une expérience enrichissante. Les lectures sont précises et offrent une perspective unique sur les défis."
|
||||
"An enriching experience. The readings are accurate and offer a unique perspective on challenges."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -130,7 +129,7 @@
|
||||
"
|
||||
></div>
|
||||
<div>
|
||||
<p class="text-lg font-bold text-white transition-colors duration-300 group-hover:text-[#f59e0b]">Isabelle Lefevre</p>
|
||||
<p class="text-lg font-bold text-white transition-colors duration-300 group-hover:text-[#f59e0b]">Isabella Smith</p>
|
||||
<div class="flex text-[#f59e0b]">
|
||||
<svg class="h-5 w-5 transform transition-all duration-300 hover:scale-125" fill="currentColor" viewBox="0 0 256 256">
|
||||
<path
|
||||
@ -161,7 +160,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="testimonial-quote relative text-white/80 italic">
|
||||
"Je suis impressionnée par la profondeur des interprétations. L'Oracle est un outil précieux pour la croissance personnelle."
|
||||
"I'm impressed by the depth of the interpretations. The Oracle is a valuable tool for personal growth."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -27,8 +27,6 @@ const remainingDeckSize = computed(() => Math.max(0, deckSize - revealedCards.va
|
||||
const isFinished = computed(() => !!props.drawnCards && revealedCards.value.length >= props.drawnCards.length);
|
||||
const shouldAutoDraw = computed(() => props.drawnCards?.length > 15);
|
||||
|
||||
console.log(props.drawnCards?.length);
|
||||
|
||||
// Auto-draw cards with beautiful animation
|
||||
const startAutoDraw = async () => {
|
||||
if (!props.drawnCards || isAutoDrawing.value) return;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center gap-6 md:gap-8 rounded-2xl bg-gradient-to-br from-purple-900/40 to-purple-800/30 border border-purple-700/30 p-6 md:p-8 shadow-2xl transition-all duration-500 hover:shadow-3xl md:flex-row backdrop-blur-sm hover:border-purple-600/50 group"
|
||||
class="flex flex-col items-center gap-6 md:gap-8 rounded-2xl bg-gradient-to-br from-purple-900/40 to-purple-800/30 border border-purple-700/30 p-6 md:p-8 shadow-2xl transition-all duration-500 hover:shadow-3xl md:flex-row backdrop-blur-sm hover:border-purple-600/50 group card-container"
|
||||
>
|
||||
<!-- Card Image Container -->
|
||||
<div class="relative">
|
||||
<!-- Card Image with Glow Effect -->
|
||||
<div
|
||||
class="h-56 w-36 sm:h-64 sm:w-40 md:h-72 md:w-48 flex-shrink-0 rounded-xl bg-cover bg-center bg-no-repeat transition-all duration-500 group-hover:scale-105 relative overflow-hidden"
|
||||
class="h-56 w-36 sm:h-64 sm:w-40 md:h-72 md:w-48 flex-shrink-0 rounded-xl bg-cover bg-center bg-no-repeat transition-all duration-500 group-hover:scale-105 relative overflow-hidden card-image"
|
||||
:style="{
|
||||
'background-image': `url(${imageUrl || '/cards/' + (cardNumber + 1) + '.png'})`,
|
||||
transform: orientation === 'reversed' ? 'rotate(180deg)' : 'none'
|
||||
@ -19,9 +19,7 @@
|
||||
<div
|
||||
class="absolute inset-0 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"
|
||||
:style="{
|
||||
'box-shadow': orientation === 'reversed'
|
||||
? '0 0 30px 10px rgba(239, 68, 68, 0.4)'
|
||||
: '0 0 30px 10px rgba(168, 85, 247, 0.4)'
|
||||
'box-shadow': '0 0 30px 10px rgba(168, 85, 247, 0.4)'
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
@ -29,40 +27,99 @@
|
||||
<!-- Orientation Badge -->
|
||||
<div
|
||||
v-if="orientation === 'reversed'"
|
||||
class="absolute top-3 right-3 bg-gradient-to-r from-red-600 to-red-700 text-red-100 text-xs font-semibold px-3 py-1.5 rounded-full border border-red-500/30 shadow-lg"
|
||||
class="absolute top-3 right-3 bg-gradient-to-r from-purple-600 to-purple-700 text-purple-100 text-xs font-semibold px-3 py-1.5 rounded-full border border-purple-500/30 shadow-lg"
|
||||
>
|
||||
Inversée
|
||||
Reversed
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="absolute top-3 right-3 bg-gradient-to-r from-purple-600 to-purple-700 text-purple-100 text-xs font-semibold px-3 py-1.5 rounded-full border border-purple-500/30 shadow-lg"
|
||||
>
|
||||
Droite
|
||||
Upright
|
||||
</div>
|
||||
|
||||
<!-- Card Number Badge -->
|
||||
<div class="absolute -bottom-2 -left-2 bg-gradient-to-r from-purple-700 to-indigo-700 text-purple-100 text-sm font-bold w-10 h-10 rounded-full flex items-center justify-center border-2 border-purple-900/50 shadow-lg">
|
||||
<div
|
||||
class="absolute -bottom-2 -left-2 text-purple-100 text-sm font-bold w-10 h-10 rounded-full flex items-center justify-center border-2 border-purple-900/50 shadow-lg card-number bg-gradient-to-r from-purple-700 to-indigo-700"
|
||||
>
|
||||
{{ cardNumber }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Content -->
|
||||
<div class="flex flex-col gap-4 text-center md:text-left flex-1">
|
||||
<!-- Card Number -->
|
||||
<p class="text-purple-300/70 text-xs sm:text-sm tracking-widest uppercase font-semibold">
|
||||
Carte #{{ cardNumber }}
|
||||
</p>
|
||||
<div class="flex flex-col gap-6 text-center md:text-left flex-1 w-full">
|
||||
<!-- Card Header -->
|
||||
<div class="space-y-2">
|
||||
<p class="text-purple-300/70 text-xs sm:text-sm tracking-widest uppercase font-semibold">
|
||||
Card #{{ cardNumber }}
|
||||
</p>
|
||||
|
||||
<!-- Card Name -->
|
||||
<h3 class="text-2xl sm:text-3xl font-black bg-gradient-to-r from-purple-200 to-purple-100 bg-clip-text text-transparent leading-tight">
|
||||
{{ name }}
|
||||
</h3>
|
||||
<h3
|
||||
class="text-2xl sm:text-3xl font-black bg-clip-text text-transparent leading-tight bg-gradient-to-r from-purple-200 to-purple-100"
|
||||
>
|
||||
{{ name }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="text-purple-300/90 text-base sm:text-lg leading-relaxed font-medium" v-html="description"></div>
|
||||
<!-- Description Content -->
|
||||
<div class="space-y-4">
|
||||
<!-- Description Header -->
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-lg font-bold flex items-center gap-2 text-purple-200">
|
||||
<span class="text-purple-400">
|
||||
{{ orientation === 'reversed' ? '🔄' : '📖' }}
|
||||
</span>
|
||||
{{ orientation === 'reversed' ? 'Reversed Meaning' : 'Upright Meaning' }}
|
||||
</div>
|
||||
<span class="text-xs px-2 py-1 rounded-full bg-purple-800/30 text-purple-300">
|
||||
{{ orientation === 'reversed' ? 'Challenge' : 'Positive' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Description Content -->
|
||||
<div class="min-h-[200px] max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
|
||||
<div class="space-y-4 animate-fadeIn">
|
||||
<div
|
||||
class="text-purple-300/90 leading-relaxed font-medium pl-4 border-l-2 border-purple-600/50 p-3 rounded-r-lg bg-gradient-to-r from-purple-900/10 to-transparent"
|
||||
v-html="orientation === 'reversed' ? description_reversed : description_upright"
|
||||
></div>
|
||||
|
||||
<!-- Key Points -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm opacity-80 text-purple-300">
|
||||
Key Points:
|
||||
</h4>
|
||||
<ul class="space-y-1 text-sm pl-4 text-purple-300/80">
|
||||
<li v-if="orientation === 'reversed'">
|
||||
• Represents challenges or blockages
|
||||
</li>
|
||||
<li v-if="orientation === 'reversed'">
|
||||
• Indicates reversed or delayed energy
|
||||
</li>
|
||||
<li v-if="orientation !== 'reversed'">
|
||||
• Represents the card's natural energy
|
||||
</li>
|
||||
<li v-if="orientation !== 'reversed'">
|
||||
• Indicates direct manifestation
|
||||
</li>
|
||||
<li>
|
||||
• Traditional tarot interpretation
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Decorative Divider -->
|
||||
<div class="w-16 h-0.5 bg-gradient-to-r from-purple-600 to-transparent rounded-full mt-2 mx-auto md:mx-0"></div>
|
||||
<div class="w-16 h-0.5 rounded-full mt-2 bg-gradient-to-r from-purple-600 to-transparent"></div>
|
||||
|
||||
<!-- Footer Note -->
|
||||
<p class="text-xs opacity-70 italic text-purple-300/70">
|
||||
{{ orientation === 'reversed'
|
||||
? 'This reversed card suggests special attention is needed'
|
||||
: 'This upright card indicates smooth, manifested energy' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Hover Effect Background -->
|
||||
@ -76,14 +133,17 @@ interface Props {
|
||||
name: string;
|
||||
imageUrl: string;
|
||||
orientation?: string;
|
||||
description: string;
|
||||
description_upright: string;
|
||||
description_reversed: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
orientation: 'upright'
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
.card-container {
|
||||
animation: cardAppear 0.6s ease-out;
|
||||
}
|
||||
|
||||
@ -98,36 +158,74 @@ div {
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom shadow for hover effect */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.shadow-3xl {
|
||||
box-shadow:
|
||||
0 25px 50px -12px rgba(0, 0, 0, 0.5),
|
||||
0 0 30px rgba(168, 85, 247, 0.3);
|
||||
}
|
||||
|
||||
/* Smooth transitions for all interactive elements */
|
||||
* {
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(168, 85, 247, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(168, 85, 247, 0.4);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(168, 85, 247, 0.6);
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
.card-container * {
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for description */
|
||||
.text-base::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
/* Card hover effects */
|
||||
.card-image:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
:deep(strong),
|
||||
:deep(b) {
|
||||
font-weight: 700 !important; /* Forces bold weight */
|
||||
color: white; /* Optional: Makes it fully white vs the 80% opacity body text */
|
||||
}
|
||||
|
||||
.text-base::-webkit-scrollbar-track {
|
||||
background: rgba(168, 85, 247, 0.1);
|
||||
border-radius: 2px;
|
||||
/* Optional: If you want to style lists inside description too */
|
||||
:deep(ul),
|
||||
:deep(ol) {
|
||||
padding-left: 1.5rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.text-base::-webkit-scrollbar-thumb {
|
||||
background: rgba(168, 85, 247, 0.4);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.text-base::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(168, 85, 247, 0.6);
|
||||
:deep(li) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
></div>
|
||||
<div
|
||||
class="absolute -bottom-2 -left-2 flex h-8 w-8 items-center justify-center rounded-full border-2 border-[var(--color-border)]/60 bg-[var(--secondary)] text-xs font-black text-[var(--foreground)] shadow"
|
||||
:title="`Carte #${cardNumber}`"
|
||||
:title="`Card #${cardNumber}`"
|
||||
>
|
||||
{{ cardNumber }}
|
||||
</div>
|
||||
@ -23,7 +23,7 @@
|
||||
class="absolute top-2 right-2 rounded-full px-2 py-0.5 text-[10px] font-bold tracking-wide text-white shadow"
|
||||
:class="orientation === 'reversed' ? 'bg-red-600/90' : 'bg-[var(--primary)]'"
|
||||
>
|
||||
{{ orientation === 'reversed' ? 'Inversée' : 'Droite' }}
|
||||
{{ orientation === 'reversed' ? 'Reversed' : 'Upright' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
{{ name }}
|
||||
</h3>
|
||||
<p class="mt-1 text-xs uppercase tracking-wider text-[var(--muted-foreground)]">
|
||||
Carte #{{ cardNumber }}
|
||||
Card #{{ cardNumber }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -55,7 +55,7 @@
|
||||
@click="collapsed = !collapsed"
|
||||
class="rounded-full border border-[var(--color-border)]/60 bg-[var(--secondary)]/60 px-3 py-1 text-xs font-semibold text-[var(--foreground)] transition-colors hover:bg-[var(--secondary)]"
|
||||
>
|
||||
{{ collapsed ? 'Lire plus' : 'Réduire' }}
|
||||
{{ collapsed ? 'Read more' : 'Show less' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -36,9 +36,10 @@ onMounted(() => {
|
||||
<div class="flex items-center justify-between px-2 py-2 whitespace-nowrap sm:px-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
v-if="!isMobileMenuOpen"
|
||||
src="/logo-success.webp"
|
||||
alt="Logo"
|
||||
class="h-12 w-32 transition-transform duration-300 hover:scale-105 sm:h-14 sm:w-36"
|
||||
class="h-12 w-32 transition-transform duration-300 hover:scale-105 sm:w-36"
|
||||
/>
|
||||
</div>
|
||||
<nav class="hidden items-center gap-6 md:flex lg:gap-10">
|
||||
@ -80,7 +81,11 @@ onMounted(() => {
|
||||
class="pointer-events-none absolute inset-0 translate-x-[-120%] -skew-x-12 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-[120%]"
|
||||
></span>
|
||||
</button>
|
||||
<button class="p-2 text-white transition-colors hover:text-[var(--c-gold)] md:hidden" @click="toggleMobileMenu">
|
||||
<button
|
||||
class="p-2 text-white transition-colors hover:text-[var(--c-gold)] md:hidden"
|
||||
@click="toggleMobileMenu"
|
||||
v-if="!isMobileMenuOpen"
|
||||
>
|
||||
<svg class="h-7 w-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
@ -109,19 +114,19 @@ onMounted(() => {
|
||||
class="border-b border-white/10 py-5 text-xl font-semibold text-white transition-colors duration-300 hover:text-[var(--c-gold)]"
|
||||
href="/"
|
||||
@click="toggleMobileMenu"
|
||||
>Accueil</Link
|
||||
>Home</Link
|
||||
>
|
||||
<Link
|
||||
class="border-b border-white/10 py-5 text-xl font-semibold text-white transition-colors duration-300 hover:text-[var(--c-gold)]"
|
||||
href="/tirage"
|
||||
@click="toggleMobileMenu"
|
||||
>Tirages</Link
|
||||
>Readings</Link
|
||||
>
|
||||
<Link
|
||||
class="border-b border-white/10 py-5 text-xl font-semibold text-white transition-colors duration-300 hover:text-[var(--c-gold)]"
|
||||
href="/#testimonials"
|
||||
@click="toggleMobileMenu"
|
||||
>Témoignages</Link
|
||||
>Testimonials</Link
|
||||
>
|
||||
<button
|
||||
class="mystic-btn mystic-btn-primary group relative mt-8 inline-flex h-14 w-full items-center justify-center overflow-hidden rounded-full px-6 text-lg font-bold text-white shadow-lg transition-all duration-300"
|
||||
@ -130,7 +135,7 @@ onMounted(() => {
|
||||
toggleMobileMenu();
|
||||
"
|
||||
>
|
||||
<span class="relative z-10">Réserver une Session</span>
|
||||
<span class="relative z-10">Book a Session</span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
@ -399,6 +404,18 @@ input:focus {
|
||||
width: 85%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
/* Ensure close button doesn't overlap */
|
||||
.mobile-menu-close {
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Add top padding to menu content */
|
||||
.mobile-menu-content {
|
||||
padding-top: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
||||
@ -14,20 +14,31 @@
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="mb-8 rounded-lg bg-red-50 p-6 text-center">
|
||||
<div class="mb-2 font-medium text-red-700">Erreur</div>
|
||||
<p class="text-red-600">{{ error }}</p>
|
||||
<div v-else-if="error" class="mb-8 rounded-lg border border-red-500/30 bg-red-900/30 p-6 text-center">
|
||||
<div class="mb-2 font-medium text-red-300">Error</div>
|
||||
<p class="text-red-200">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="!hasCard" class="rounded-lg bg-gray-50 p-8 text-center">
|
||||
<p class="text-gray-600">Aucune carte n'a été trouvée pour votre session.</p>
|
||||
<!-- Empty State - No card found, offer to do a new reading -->
|
||||
<div
|
||||
v-else-if="!hasCard"
|
||||
class="rounded-3xl border border-white/10 bg-gradient-to-br from-[var(--c-purple)]/20 to-[var(--c-gold)]/10 p-8 text-center shadow-2xl backdrop-blur-lg"
|
||||
>
|
||||
<div class="mb-4 text-6xl">🔮</div>
|
||||
<h3 class="mb-4 text-2xl font-bold text-white">No Reading Found</h3>
|
||||
<p class="mb-6 text-white/70">Your previous reading could not be retrieved. Would you like to do a new free reading?</p>
|
||||
<button
|
||||
@click="resetAndNewReading"
|
||||
class="mystic-btn mystic-btn-primary group relative inline-flex h-12 min-w-[200px] items-center justify-center overflow-hidden rounded-full px-8 text-base font-bold text-white shadow-2xl transition-all duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<span class="relative z-10">Start New Reading</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Single Card Display -->
|
||||
<div v-else class="mb-8 md:mb-12">
|
||||
<div
|
||||
class="relative overflow-hidden rounded-3xl border border-[var(--c-purple)]/30 bg-gradient-to-br from-[var(--card)] to-[var(--card)]/90 p-6 shadow-2xl ring-1 ring-[var(--c-purple)]/40 md:p-10"
|
||||
class="relative overflow-hidden rounded-3xl border border-white/10 bg-gradient-to-br from-[var(--c-purple)]/20 to-[var(--c-gold)]/10 p-6 shadow-2xl backdrop-blur-lg md:p-10"
|
||||
>
|
||||
<!-- Ambient glows -->
|
||||
<div class="pointer-events-none absolute -top-20 -left-20 h-56 w-56 rounded-full bg-[var(--c-purple)]/25 blur-3xl"></div>
|
||||
@ -36,9 +47,6 @@
|
||||
<!-- Card Header -->
|
||||
<div class="relative z-10 mb-6 text-center">
|
||||
<h2 class="text-3xl font-black text-white md:text-4xl">{{ card.name }}</h2>
|
||||
<div v-if="card.orientation" class="mt-2 text-sm text-white/70">
|
||||
<div v-html="card.orientation === 'reversed' ? 'Inversée' : 'Droite'"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card Content -->
|
||||
@ -51,11 +59,6 @@
|
||||
:alt="card.name"
|
||||
class="absolute inset-0 h-full w-full rounded-2xl object-cover"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-x-0 bottom-0 rounded-b-2xl bg-gradient-to-t from-black/80 to-transparent p-4 text-center"
|
||||
>
|
||||
<p class="text-sm font-bold text-white">{{ card.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -65,24 +68,14 @@
|
||||
<div class="prose prose-invert max-w-none text-white/80" v-html="card.description"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<h3 class="mb-3 text-lg font-black text-white">
|
||||
{{ card.orientation === 'reversed' ? 'Signification Inversée' : 'Signification Droite' }}
|
||||
</h3>
|
||||
<div
|
||||
class="prose prose-invert max-w-none text-white/80"
|
||||
v-html="card.orientation === 'reversed' ? card.description_reversed : card.description_upright"
|
||||
></div>
|
||||
<div v-if="card.description_upright" class="mb-8">
|
||||
<h3 class="mb-3 text-lg font-black text-white">Upright Meaning</h3>
|
||||
<div class="prose prose-invert max-w-none text-white/80" v-html="card.description_upright"></div>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-3 text-lg font-black text-white">
|
||||
{{ card.orientation === 'reversed' ? 'Signification Droite' : 'Signification Inversée' }}
|
||||
</h3>
|
||||
<div
|
||||
class="prose prose-invert max-w-none text-white/80"
|
||||
v-html="card.orientation === 'reversed' ? card.description_upright : card.description_reversed"
|
||||
></div>
|
||||
<div v-if="card.description_reversed" class="mb-8">
|
||||
<h3 class="mb-3 text-lg font-black text-white">Reversed Meaning</h3>
|
||||
<div class="prose prose-invert max-w-none text-white/80" v-html="card.description_reversed"></div>
|
||||
</div>
|
||||
|
||||
<!-- Symbolism -->
|
||||
@ -194,6 +187,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import LandingLayout from '@/layouts/app/LandingLayout.vue';
|
||||
import { useTarotStore } from '@/stores/tarot';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import axios from 'axios';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
@ -202,6 +196,7 @@ const goToBooking = () => {
|
||||
router.visit('/rendez-vous');
|
||||
};
|
||||
|
||||
const tarotStore = useTarotStore();
|
||||
const card = ref<any>(null);
|
||||
const error = ref<string | null>(null);
|
||||
const loading = ref(true);
|
||||
@ -212,16 +207,35 @@ const cardId = params.get('id');
|
||||
const hasCard = computed(() => card.value !== null);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await axios.get(`/api/get-card/${cardId}`);
|
||||
card.value = response.data.cards;
|
||||
} catch (err: any) {
|
||||
console.error('Card fetch error:', err);
|
||||
error.value = err.response?.data?.message || 'Failed to get cards from the server. Please contact support.';
|
||||
} finally {
|
||||
// First, check if we have a persisted free draw card in the store
|
||||
if (tarotStore.freeDrawCard) {
|
||||
card.value = tarotStore.freeDrawCard;
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: If no persisted card but we have an ID in the URL, try to fetch from API
|
||||
if (cardId) {
|
||||
try {
|
||||
const response = await axios.get(`/api/get-card/${cardId}`);
|
||||
card.value = response.data.cards;
|
||||
} catch (err: any) {
|
||||
console.error('Card fetch error:', err);
|
||||
error.value = err.response?.data?.message || 'Failed to get cards from the server. Please contact support.';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
// No card in store and no ID in URL - just stop loading
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Reset the store and navigate to do a new reading
|
||||
const resetAndNewReading = () => {
|
||||
tarotStore.clearFreeDrawCard();
|
||||
router.visit('/tirage');
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
/* Button styles */
|
||||
@ -267,4 +281,21 @@ onMounted(async () => {
|
||||
.mystic-btn:hover::before {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
:deep(strong),
|
||||
:deep(b) {
|
||||
font-weight: 700 !important; /* Forces bold weight */
|
||||
color: white; /* Optional: Makes it fully white vs the 80% opacity body text */
|
||||
}
|
||||
|
||||
/* Optional: If you want to style lists inside description too */
|
||||
:deep(ul),
|
||||
:deep(ol) {
|
||||
padding-left: 1.5rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
:deep(li) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useTarotStore } from '@/stores/tarot';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { loadStripe } from '@stripe/stripe-js';
|
||||
import axios from 'axios';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
|
||||
const tarotStore = useTarotStore();
|
||||
const isSelectionScreen = ref(true);
|
||||
@ -87,6 +88,15 @@ const redirectToWisePayment = async (count: number) => {
|
||||
// Computed to disable the free draw button if used
|
||||
const isFreeDrawUsed = computed(() => tarotStore.freeDrawsRemaining <= 0);
|
||||
|
||||
// Redirect to FreeCardResult.vue with card id
|
||||
function handleViewMyReading() {
|
||||
if (tarotStore.freeDrawCard && tarotStore.freeDrawCard.id !== undefined) {
|
||||
router.visit(`/resultat-gratuit?id=${tarotStore.freeDrawCard.id}`);
|
||||
} else {
|
||||
router.visit('/resultat-gratuit');
|
||||
}
|
||||
}
|
||||
|
||||
// Hover state for cards
|
||||
const hoveredCard = ref(null);
|
||||
|
||||
@ -97,187 +107,221 @@ const setHover = (index) => {
|
||||
const clearHover = () => {
|
||||
hoveredCard.value = null;
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
console.log(tarotStore.freeDrawCard);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<section class="px-4 py-16 sm:py-20">
|
||||
<h2 class="mb-12 text-center text-3xl font-bold text-white md:text-4xl lg:text-5xl">
|
||||
Explore Our <span class="text-[var(--c-gold)]">Readings</span>
|
||||
</h2>
|
||||
<section class="flex min-h-screen flex-col items-center justify-center px-4 py-16 sm:py-20">
|
||||
<div class="mx-auto w-full max-w-7xl">
|
||||
<h2 class="mb-12 text-center text-3xl font-bold text-white md:text-4xl lg:text-5xl">
|
||||
Explore Our <span class="text-[var(--c-gold)]">Readings</span>
|
||||
</h2>
|
||||
|
||||
<p
|
||||
v-if="isFreeDrawUsed"
|
||||
class="mx-auto mb-8 max-w-2xl rounded-lg border border-[var(--c-gold)]/30 bg-gray-900 p-4 text-center text-lg font-semibold text-[var(--c-gold)]"
|
||||
>
|
||||
You've used your free print! Choose a paid option to continue.
|
||||
</p>
|
||||
|
||||
<div class="mx-auto grid max-w-6xl grid-cols-1 gap-6 md:gap-8 lg:grid-cols-3">
|
||||
<!-- Free draw -->
|
||||
<div
|
||||
class="group relative flex flex-col gap-6 rounded-2xl border border-gray-800 bg-gradient-to-b from-gray-900 to-black p-6 shadow-lg transition-all duration-500 hover:-translate-y-3 hover:shadow-xl md:p-8"
|
||||
:class="{ 'opacity-100': !isFreeDrawUsed, 'opacity-80': isFreeDrawUsed }"
|
||||
@mouseenter="setHover(1)"
|
||||
@mouseleave="clearHover"
|
||||
<p
|
||||
v-if="isFreeDrawUsed"
|
||||
class="mx-auto mb-8 max-w-2xl rounded-lg border border-[var(--c-gold)]/30 bg-gray-900 p-4 text-center text-lg font-semibold text-[var(--c-gold)]"
|
||||
>
|
||||
<!-- Radiant background effect on hover -->
|
||||
You've used your free print! Choose a paid option to continue.
|
||||
</p>
|
||||
|
||||
<div class="mx-auto grid max-w-6xl grid-cols-1 gap-6 md:gap-8 lg:grid-cols-3 lg:items-stretch">
|
||||
<!-- Free draw -->
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[var(--c-purple)]/0 via-[var(--c-purple)]/10 to-[var(--c-purple)]/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
></div>
|
||||
|
||||
<!-- Decorative elements -->
|
||||
<div class="absolute top-0 left-0 h-1 w-full bg-gradient-to-r from-transparent via-[var(--c-gold)] to-transparent"></div>
|
||||
|
||||
<div class="relative z-10 text-center">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--c-purple)] to-[var(--c-purple)]/80 transition-transform duration-300 group-hover:scale-110"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-white"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
|
||||
></path>
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline>
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12"></polyline>
|
||||
<polyline points="21 12 16.5 14.6 16.5 19.79"></polyline>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white md:text-2xl">Free Reading</h3>
|
||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">Free</p>
|
||||
<p class="mt-4 text-sm text-gray-300 md:text-base">Single‑card Reading General Interpretation Quick Tips</p>
|
||||
</div>
|
||||
<button
|
||||
:disabled="isFreeDrawUsed"
|
||||
class="group relative mt-2 flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-6 font-bold tracking-wide text-white transition-all duration-300 hover:from-[var(--c-purple)] hover:to-[var(--c-purple)]/80 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
@click="handleSelection(1)"
|
||||
class="group relative flex flex-col rounded-2xl border border-gray-800 bg-gradient-to-b from-gray-900 to-black p-6 shadow-lg transition-all duration-500 hover:-translate-y-3 hover:shadow-xl md:p-8"
|
||||
:class="{ 'opacity-100': !isFreeDrawUsed, 'opacity-80': isFreeDrawUsed }"
|
||||
@mouseenter="setHover(1)"
|
||||
@mouseleave="clearHover"
|
||||
>
|
||||
<span class="relative z-10">Begin</span>
|
||||
<!-- Radiant background effect on hover -->
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-r from-[var(--c-gold)]/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[var(--c-purple)]/0 via-[var(--c-purple)]/10 to-[var(--c-purple)]/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
></div>
|
||||
<svg
|
||||
v-if="!isFreeDrawUsed"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="relative z-10 ml-2 h-5 w-5 transition-transform duration-300 group-hover:translate-x-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Premium option -->
|
||||
<div
|
||||
class="group relative flex transform flex-col gap-6 rounded-2xl bg-gradient-to-b from-[var(--c-purple)] to-[#1a1035] p-6 shadow-xl transition-all duration-500 hover:-translate-y-3 hover:shadow-2xl md:scale-105 md:p-8"
|
||||
@mouseenter="setHover(2)"
|
||||
@mouseleave="clearHover"
|
||||
>
|
||||
<!-- Premium badge -->
|
||||
<div class="absolute -top-3 left-1/2 -translate-x-1/2 transform">
|
||||
<span class="rounded-full bg-[var(--c-gold)] px-3 py-1 text-xs font-bold text-black uppercase">Populaire</span>
|
||||
</div>
|
||||
<!-- Decorative elements -->
|
||||
<div class="absolute top-0 left-0 h-1 w-full bg-gradient-to-r from-transparent via-[var(--c-gold)] to-transparent"></div>
|
||||
|
||||
<!-- Radiant background effect on hover -->
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[var(--c-gold)]/0 via-[var(--c-gold)]/10 to-[var(--c-gold)]/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
></div>
|
||||
|
||||
<div class="relative z-10 text-center">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--c-gold)] to-[var(--c-gold)]/80 transition-transform duration-300 group-hover:scale-110"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-black"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
<div class="relative z-10 text-center">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--c-purple)] to-[var(--c-purple)]/80 transition-transform duration-300 group-hover:scale-110"
|
||||
>
|
||||
<line x1="8" y1="12" x2="16" y2="12"></line>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-white"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
|
||||
></path>
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline>
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12"></polyline>
|
||||
<polyline points="21 12 16.5 14.6 16.5 19.79"></polyline>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white md:text-2xl">Reveal your inner power</h3>
|
||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">Free</p>
|
||||
<p class="mt-4 text-sm text-gray-300 md:text-base">Single‑card Reading Interpretation</p>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white md:text-2xl">Insight Profile</h3>
|
||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">9.99 €</p>
|
||||
<p class="mt-4 text-sm text-gray-200 md:text-base">six‑card Reading Personalized Analysis Tailored Recommendations</p>
|
||||
</div>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<button
|
||||
class="group relative flex h-12 flex-1 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--c-gold)] bg-transparent px-4 font-bold tracking-wide text-[var(--c-gold)] transition-all duration-300 hover:bg-[var(--c-gold)] hover:text-black"
|
||||
@click="handleSelection(6, 'stripe')"
|
||||
title="Pay with Stripe"
|
||||
class="group relative mt-auto flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-6 font-bold tracking-wide text-white transition-all duration-300 hover:from-[var(--c-purple)] hover:to-[var(--c-purple)]/80 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
@click="isFreeDrawUsed ? handleViewMyReading() : handleSelection(1)"
|
||||
>
|
||||
<span class="relative text-sm">Discover</span>
|
||||
<span class="relative z-10 transition-transform duration-300 group-hover:translate-x-1">
|
||||
{{ isFreeDrawUsed ? 'VIEW MY READING' : 'BEGIN' }}
|
||||
</span>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-r from-[var(--c-gold)]/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
></div>
|
||||
<svg
|
||||
v-if="!isFreeDrawUsed"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="relative z-10 ml-2 h-5 w-5 transition-transform duration-300 group-hover:translate-x-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Premium plus option -->
|
||||
<div
|
||||
class="group relative flex flex-col gap-6 rounded-2xl border border-gray-800 bg-gradient-to-b from-gray-900 to-black p-6 shadow-lg transition-all duration-500 hover:-translate-y-3 hover:shadow-xl md:p-8"
|
||||
@mouseenter="setHover(3)"
|
||||
@mouseleave="clearHover"
|
||||
>
|
||||
<!-- Radiant background effect on hover -->
|
||||
<!-- Premium option -->
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[var(--c-gold)]/0 via-[var(--c-gold)]/10 to-[var(--c-gold)]/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
></div>
|
||||
class="group relative flex transform flex-col rounded-2xl bg-gradient-to-b from-[var(--c-purple)] to-[#1a1035] p-6 shadow-xl transition-all duration-500 hover:-translate-y-3 hover:shadow-2xl md:scale-105 md:p-8"
|
||||
@mouseenter="setHover(2)"
|
||||
@mouseleave="clearHover"
|
||||
>
|
||||
<!-- Premium badge -->
|
||||
<div class="absolute -top-3 left-1/2 -translate-x-1/2 transform">
|
||||
<span class="rounded-full bg-[var(--c-gold)] px-3 py-1 text-xs font-bold text-black uppercase">Populaire</span>
|
||||
</div>
|
||||
|
||||
<!-- Gold corner accents -->
|
||||
<div class="absolute top-0 left-0 h-6 w-6 rounded-tl-lg border-t-2 border-l-2 border-[var(--c-gold)]"></div>
|
||||
<div class="absolute top-0 right-0 h-6 w-6 rounded-tr-lg border-t-2 border-r-2 border-[var(--c-gold)]"></div>
|
||||
<div class="absolute bottom-0 left-0 h-6 w-6 rounded-bl-lg border-b-2 border-l-2 border-[var(--c-gold)]"></div>
|
||||
<div class="absolute right-0 bottom-0 h-6 w-6 rounded-br-lg border-r-2 border-b-2 border-[var(--c-gold)]"></div>
|
||||
<!-- Radiant background effect on hover -->
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[var(--c-gold)]/0 via-[var(--c-gold)]/10 to-[var(--c-gold)]/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
></div>
|
||||
|
||||
<div class="relative z-10 text-center">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--c-purple)] to-[var(--c-purple)]/80 transition-transform duration-300 group-hover:scale-110"
|
||||
<div class="relative z-10 text-center">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--c-gold)] to-[var(--c-gold)]/80 transition-transform duration-300 group-hover:scale-110"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-black"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="8" y1="12" x2="16" y2="12"></line>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white md:text-2xl">Insight Profile</h3>
|
||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">9.99 €</p>
|
||||
<p class="mt-4 text-sm text-gray-200 md:text-base">six‑card Reading Personalized Analysis Tailored Recommendations</p>
|
||||
</div>
|
||||
<div class="mt-auto flex gap-2">
|
||||
<button
|
||||
:disabled="loading"
|
||||
class="group relative flex h-12 w-full items-center justify-center overflow-hidden rounded-full border-2 border-[var(--c-gold)] bg-transparent px-4 font-bold tracking-wide text-[var(--c-gold)] transition-all duration-300 hover:bg-[var(--c-gold)] hover:text-black disabled:cursor-not-allowed disabled:opacity-50"
|
||||
@click="handleSelection(6, 'stripe')"
|
||||
title="Pay with Stripe"
|
||||
>
|
||||
<svg
|
||||
v-if="loading"
|
||||
class="mr-2 h-5 w-5 animate-spin text-[var(--c-gold)]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-white"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<polygon
|
||||
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
||||
></polygon>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="relative">{{ loading ? 'Processing...' : 'Discover' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white md:text-2xl">Full card spread</h3>
|
||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">99€</p>
|
||||
<p class="mt-4 text-sm text-gray-300 md:text-base">Eigtheen‑card reading In‑depth exploration Complete strategy</p>
|
||||
</div>
|
||||
<div class="mt-2 flex gap-2">
|
||||
<button
|
||||
class="group relative flex h-12 flex-1 items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-4 font-bold tracking-wide text-white transition-all duration-300 hover:bg-gray-800"
|
||||
@click="handleSelection(18, 'stripe')"
|
||||
title="Pay with Stripe"
|
||||
>
|
||||
<span class="relative text-sm">Explore</span>
|
||||
</button>
|
||||
|
||||
<!-- Premium plus option -->
|
||||
<div
|
||||
class="group relative flex flex-col rounded-2xl bg-gradient-to-b from-gray-900 to-black p-6 shadow-lg transition-all duration-500 hover:-translate-y-3 hover:shadow-xl md:p-8"
|
||||
@mouseenter="setHover(3)"
|
||||
@mouseleave="clearHover"
|
||||
>
|
||||
<!-- Radiant background effect on hover -->
|
||||
<div
|
||||
class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[var(--c-gold)]/0 via-[var(--c-gold)]/10 to-[var(--c-gold)]/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100"
|
||||
></div>
|
||||
|
||||
<!-- Decorative elements -->
|
||||
<div class="absolute top-0 left-0 h-1 w-full bg-gradient-to-r from-transparent via-[var(--c-gold)] to-transparent"></div>
|
||||
|
||||
<div class="relative z-10 text-center">
|
||||
<div class="mb-4 flex justify-center">
|
||||
<div
|
||||
class="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--c-purple)] to-[var(--c-purple)]/80 transition-transform duration-300 group-hover:scale-110"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-white"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polygon
|
||||
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
|
||||
></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-white md:text-2xl">Full card spread</h3>
|
||||
<p class="mt-2 text-4xl font-bold text-[var(--c-gold)] md:text-5xl">15.90 €</p>
|
||||
<p class="mt-4 text-sm text-gray-300 md:text-base">Eigtheen‑card reading In‑depth exploration Complete strategy</p>
|
||||
</div>
|
||||
<div class="mt-auto flex gap-2">
|
||||
<button
|
||||
:disabled="loading"
|
||||
class="group relative flex h-12 w-full items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-gray-800 to-gray-900 px-4 font-bold tracking-wide text-white transition-all duration-300 hover:from-[var(--c-purple)] hover:to-[var(--c-purple)]/80 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
@click="handleSelection(18, 'stripe')"
|
||||
title="Pay with Stripe"
|
||||
>
|
||||
<svg
|
||||
v-if="loading"
|
||||
class="mr-2 h-5 w-5 animate-spin text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="relative">{{ loading ? 'Processing...' : 'Explore' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -292,9 +336,6 @@ const clearHover = () => {
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* Custom glow effect */
|
||||
|
||||
@ -124,6 +124,8 @@ onMounted(async () => {
|
||||
:image-url="card.image_url"
|
||||
:orientation="card.orientation"
|
||||
:description="card.description"
|
||||
:description_upright="card.description_upright"
|
||||
:description_reversed="card.description_reversed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -63,6 +63,10 @@ const handleSelection = async (count: number) => {
|
||||
});
|
||||
if (response.data.success) {
|
||||
cards.value = response.data.cards;
|
||||
// Store the free draw card for persistence (so user can view it again later)
|
||||
if (response.data.cards.length > 0) {
|
||||
tarotStore.setFreeDrawCard(response.data.cards[0]);
|
||||
}
|
||||
// Only use the free draw after a successful API call
|
||||
tarotStore.useFreeDraw();
|
||||
isSelectionScreen.value = false;
|
||||
|
||||
@ -2,16 +2,13 @@
|
||||
<LandingLayout>
|
||||
<main class="flex flex-grow items-center justify-center">
|
||||
<div class="w-full max-w-2xl px-4 py-20 sm:px-6 lg:px-8">
|
||||
<div class="relative flex flex-col items-center overflow-hidden rounded-3xl border border-[var(--c-purple)]/30 bg-gradient-to-br from-[var(--card)] to-[var(--card)]/90 p-12 text-center shadow-2xl ring-1 ring-[var(--c-purple)]/40">
|
||||
<div class="pointer-events-none absolute -left-16 -top-16 h-40 w-40 rounded-full bg-[var(--c-purple)]/20 blur-3xl"></div>
|
||||
<div
|
||||
class="relative flex flex-col items-center overflow-hidden rounded-3xl border border-[var(--c-purple)]/30 bg-gradient-to-br from-[var(--c-purple)]/20 to-[var(--c-gold)]/10 p-6 p-12 text-center shadow-2xl ring-1 ring-[var(--c-purple)]/40 backdrop-blur-lg"
|
||||
>
|
||||
<div class="pointer-events-none absolute -top-16 -left-16 h-40 w-40 rounded-full bg-[var(--c-purple)]/20 blur-3xl"></div>
|
||||
<div class="pointer-events-none absolute -right-16 -bottom-16 h-40 w-40 rounded-full bg-[var(--c-gold)]/15 blur-3xl"></div>
|
||||
<div class="relative z-10 mb-8 flex h-20 w-20 items-center justify-center rounded-full bg-red-500/80 shadow-lg">
|
||||
<svg
|
||||
class="h-10 w-10 text-white"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg class="h-10 w-10 text-white" fill="currentColor" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"
|
||||
></path>
|
||||
@ -22,9 +19,7 @@
|
||||
Une erreur est survenue lors du traitement de votre paiement, ou vous avez annulé la transaction.
|
||||
</p>
|
||||
<div class="mt-10 w-full border-t border-[var(--linen)]"></div>
|
||||
<p class="mt-10 text-lg font-medium text-white/90">
|
||||
Veuillez réessayer ou contactez le support si le problème persiste.
|
||||
</p>
|
||||
<p class="mt-10 text-lg font-medium text-white/90">Veuillez réessayer ou contactez le support si le problème persiste.</p>
|
||||
<button
|
||||
@click="tryAgain"
|
||||
class="group relative mt-8 flex h-12 max-w-[480px] min-w-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-full border border-white/30 bg-transparent px-8 text-base font-bold tracking-wide text-white transition-all hover:bg-white/10"
|
||||
|
||||
@ -17,7 +17,7 @@ const maxPolls = 30; // Poll for 5 minutes (30 * 10 seconds)
|
||||
// Poll payment status every 10 seconds
|
||||
const checkPaymentStatus = async () => {
|
||||
try {
|
||||
const endpoint = props.paymentProvider === 'wise' ? '/wise/validate-payment' : '/stripe/validate-payment';
|
||||
const endpoint = props.paymentProvider === 'wise' ? '/wise/validate-payment' : '/api/validate-payment';
|
||||
|
||||
const response = await axios.get(endpoint, {
|
||||
params: { client_session_id: props.clientSessionId },
|
||||
|
||||
@ -2,37 +2,32 @@
|
||||
<LandingLayout>
|
||||
<main class="flex flex-grow items-center justify-center">
|
||||
<div class="w-full max-w-2xl px-4 py-20 sm:px-6 lg:px-8">
|
||||
<div class="relative flex flex-col items-center overflow-hidden rounded-3xl border border-[var(--c-purple)]/30 bg-gradient-to-br from-[var(--card)] to-[var(--card)]/90 p-12 text-center shadow-2xl ring-1 ring-[var(--c-purple)]/40">
|
||||
<div class="pointer-events-none absolute -left-16 -top-16 h-40 w-40 rounded-full bg-[var(--c-purple)]/20 blur-3xl"></div>
|
||||
<div
|
||||
class="relative flex flex-col items-center overflow-hidden rounded-3xl border border-[var(--c-purple)]/30 bg-gradient-to-br from-[var(--c-purple)]/20 to-[var(--c-gold)]/10 p-12 text-center shadow-2xl ring-1 ring-[var(--c-purple)]/40"
|
||||
>
|
||||
<div class="pointer-events-none absolute -top-16 -left-16 h-40 w-40 rounded-full bg-[var(--c-purple)]/20 blur-3xl"></div>
|
||||
<div class="pointer-events-none absolute -right-16 -bottom-16 h-40 w-40 rounded-full bg-[var(--c-gold)]/15 blur-3xl"></div>
|
||||
<div class="relative z-10 mb-8 flex h-20 w-20 items-center justify-center rounded-full bg-[var(--c-gold)] shadow-lg">
|
||||
<svg
|
||||
class="h-10 w-10 text-[var(--c-purple)]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg class="h-10 w-10 text-[var(--c-purple)]" fill="currentColor" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-4xl font-black text-white md:text-5xl">Paiement Réussi</h1>
|
||||
<p class="mx-auto mt-4 max-w-md text-lg text-white/80">
|
||||
Votre transaction a été complétée avec succès. L'univers vous attend.
|
||||
</p>
|
||||
<p class="mx-auto mt-4 max-w-md text-lg text-white/80">Votre transaction a été complétée avec succès. L'univers vous attend.</p>
|
||||
<div class="mt-10 w-full border-t border-[var(--linen)]"></div>
|
||||
<p v-if="loading" class="mt-10 text-lg font-medium text-white/90">Vérification de votre paiement...</p>
|
||||
<p v-else class="mt-10 text-lg font-medium text-white/90">
|
||||
Vous pouvez maintenant procéder à votre tirage de cartes.
|
||||
</p>
|
||||
<p v-else class="mt-10 text-lg font-medium text-white/90">Vous pouvez maintenant procéder à votre tirage de cartes.</p>
|
||||
<button
|
||||
@click="proceedToDraw"
|
||||
:disabled="loading"
|
||||
class="group relative mt-8 flex h-12 max-w-[480px] min-w-[200px] cursor-pointer items-center justify-center overflow-hidden rounded-full bg-gradient-to-r from-[var(--c-gold)] to-yellow-400 px-8 text-base font-bold tracking-wide text-[var(--c-purple)] shadow-lg transition-all duration-300 hover:shadow-[var(--c-gold)]/40 disabled:opacity-60"
|
||||
>
|
||||
<span class="relative z-10">Tirer les cartes</span>
|
||||
<span class="absolute inset-0 translate-x-[-100%] -skew-x-12 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-[100%]"></span>
|
||||
<span
|
||||
class="absolute inset-0 translate-x-[-100%] -skew-x-12 bg-gradient-to-r from-transparent via-white/30 to-transparent transition-transform duration-700 group-hover:translate-x-[100%]"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,7 +61,7 @@ onMounted(async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la validation du paiement:', error);
|
||||
alert('Erreur de validation. Veuillez réessayer.');
|
||||
|
||||
router.visit('/cancel');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
||||
@ -1,40 +1,78 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useTarotStore = defineStore('tarot', () => {
|
||||
// State
|
||||
const freeDrawsRemaining = ref(1);
|
||||
const paidDrawsRemaining = ref(0);
|
||||
// Interface pour la carte tirée
|
||||
interface DrawnCard {
|
||||
id: number;
|
||||
name: string;
|
||||
image_url: string;
|
||||
orientation: 'upright' | 'reversed';
|
||||
description: string;
|
||||
description_upright?: string;
|
||||
description_reversed?: string;
|
||||
symbolism?: Record<string, string>;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// Actions
|
||||
function useFreeDraw() {
|
||||
if (freeDrawsRemaining.value > 0) {
|
||||
freeDrawsRemaining.value--;
|
||||
return true;
|
||||
export const useTarotStore = defineStore(
|
||||
'tarot',
|
||||
() => {
|
||||
// State
|
||||
const freeDrawsRemaining = ref(1);
|
||||
const paidDrawsRemaining = ref(0);
|
||||
const freeDrawCard = ref<DrawnCard | null>(null);
|
||||
|
||||
// Actions
|
||||
function useFreeDraw() {
|
||||
if (freeDrawsRemaining.value > 0) {
|
||||
freeDrawsRemaining.value--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Modified usePaidDraw to handle the correct number of cards and reset the state
|
||||
function usePaidDraw(count: number) {
|
||||
if (paidDrawsRemaining.value >= count) {
|
||||
// Since the draws are 'used', we set the remaining to 0.
|
||||
// This assumes a user pays for a set number of cards in one go.
|
||||
paidDrawsRemaining.value = 0;
|
||||
return true;
|
||||
// Store the free draw card for persistence
|
||||
function setFreeDrawCard(card: DrawnCard) {
|
||||
freeDrawCard.value = card;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addPaidDraws(count: number) {
|
||||
paidDrawsRemaining.value += count;
|
||||
}
|
||||
// Clear the free draw card (e.g., for a new session)
|
||||
function clearFreeDrawCard() {
|
||||
freeDrawCard.value = null;
|
||||
freeDrawsRemaining.value = 1;
|
||||
}
|
||||
|
||||
return {
|
||||
freeDrawsRemaining,
|
||||
paidDrawsRemaining,
|
||||
useFreeDraw,
|
||||
usePaidDraw,
|
||||
addPaidDraws,
|
||||
};
|
||||
});
|
||||
// Modified usePaidDraw to handle the correct number of cards and reset the state
|
||||
function usePaidDraw(count: number) {
|
||||
if (paidDrawsRemaining.value >= count) {
|
||||
// Since the draws are 'used', we set the remaining to 0.
|
||||
// This assumes a user pays for a set number of cards in one go.
|
||||
paidDrawsRemaining.value = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addPaidDraws(count: number) {
|
||||
paidDrawsRemaining.value += count;
|
||||
}
|
||||
|
||||
return {
|
||||
freeDrawsRemaining,
|
||||
paidDrawsRemaining,
|
||||
freeDrawCard,
|
||||
useFreeDraw,
|
||||
setFreeDrawCard,
|
||||
clearFreeDrawCard,
|
||||
usePaidDraw,
|
||||
addPaidDraws,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'tarot-storage',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
pick: ['freeDrawsRemaining', 'freeDrawCard'],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user