Files
website/website/resources/js/Pages/Checkout/Show.vue
Claude Dev 67ea1ef22a Switch entire UI to dark mode by default
Update all 26 Vue files (3 layouts, 4 components, 19 pages) and the
Blade root template to use a dark color scheme: gray-950 backgrounds,
gray-900 cards, gray-800 borders, light text, and adjusted status
badges/flash messages for dark backgrounds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 07:24:23 -05:00

192 lines
8.7 KiB
Vue

<script setup>
import { ref, computed } from 'vue';
import { useForm, Link } from '@inertiajs/vue3';
import AppLayout from '@/Layouts/AppLayout.vue';
defineOptions({ layout: AppLayout });
const props = defineProps({
plan: Object,
paymentMethods: Array,
intent: Object,
stripeKey: String,
});
const selectedGateway = ref('stripe');
const selectedPaymentMethod = ref(props.paymentMethods?.[0]?.id || '');
const couponCode = ref('');
const couponApplied = ref(false);
const couponDiscount = ref(0);
const couponError = ref('');
const total = computed(() => {
const price = parseFloat(props.plan.price);
return Math.max(0, price - couponDiscount.value).toFixed(2);
});
const form = useForm({
gateway: 'stripe',
payment_method_id: props.paymentMethods?.[0]?.id || '',
coupon_code: '',
});
const applyCoupon = async () => {
couponError.value = '';
couponApplied.value = false;
try {
const response = await fetch('/checkout/apply-coupon', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content,
'Accept': 'application/json',
},
body: JSON.stringify({
code: couponCode.value,
plan_id: props.plan.id,
}),
});
const data = await response.json();
if (data.valid) {
couponApplied.value = true;
couponDiscount.value = data.discount;
} else {
couponError.value = data.message || 'Invalid coupon.';
}
} catch {
couponError.value = 'Failed to validate coupon.';
}
};
const submit = () => {
form.gateway = selectedGateway.value;
form.payment_method_id = selectedPaymentMethod.value;
form.coupon_code = couponApplied.value ? couponCode.value : '';
form.post(`/checkout/${props.plan.id}`);
};
</script>
<template>
<div>
<div class="mb-4">
<Link href="/plans" class="text-sm text-blue-400 hover:text-blue-300">&larr; Back to Plans</Link>
</div>
<h1 class="text-2xl font-bold text-white mb-6">Checkout</h1>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Order Summary -->
<div class="lg:col-span-1 order-2 lg:order-1">
<div class="bg-gray-900 rounded-lg border border-gray-800 shadow-sm p-6">
<h2 class="text-lg font-semibold text-white mb-4">Order Summary</h2>
<div class="space-y-3">
<div class="flex justify-between text-sm">
<span class="text-gray-400">{{ plan.name }}</span>
<span class="text-white">${{ parseFloat(plan.price).toFixed(2) }}</span>
</div>
<div class="flex justify-between text-sm text-gray-500">
<span>Billing Cycle</span>
<span class="capitalize">{{ plan.billing_cycle }}</span>
</div>
<div v-if="couponApplied" class="flex justify-between text-sm text-green-400">
<span>Discount</span>
<span>-${{ couponDiscount.toFixed(2) }}</span>
</div>
<hr class="border-gray-800">
<div class="flex justify-between font-semibold text-white">
<span>Total</span>
<span>${{ total }}/{{ plan.billing_cycle }}</span>
</div>
</div>
</div>
</div>
<!-- Checkout Form -->
<div class="lg:col-span-2 order-1 lg:order-2">
<form @submit.prevent="submit" class="space-y-6">
<!-- Payment Gateway -->
<div class="bg-gray-900 rounded-lg border border-gray-800 shadow-sm p-6">
<h2 class="text-lg font-semibold text-white mb-4">Payment Method</h2>
<div class="space-y-3">
<label class="flex items-center p-3 border rounded-md cursor-pointer" :class="selectedGateway === 'stripe' ? 'border-blue-500 bg-blue-900/20' : 'border-gray-700'">
<input v-model="selectedGateway" type="radio" value="stripe" class="mr-3">
<span class="text-sm font-medium text-gray-200">Credit / Debit Card (Stripe)</span>
</label>
<label class="flex items-center p-3 border rounded-md cursor-pointer" :class="selectedGateway === 'paypal' ? 'border-blue-500 bg-blue-900/20' : 'border-gray-700'">
<input v-model="selectedGateway" type="radio" value="paypal" class="mr-3">
<span class="text-sm font-medium text-gray-200">PayPal</span>
</label>
</div>
<!-- Saved Payment Methods (Stripe) -->
<div v-if="selectedGateway === 'stripe' && paymentMethods.length > 0" class="mt-4">
<label class="block text-sm font-medium text-gray-300 mb-2">Select Card</label>
<select v-model="selectedPaymentMethod" class="w-full rounded-md bg-gray-800 border-gray-700 text-gray-100 text-sm">
<option v-for="pm in paymentMethods" :key="pm.id" :value="pm.id">
{{ pm.brand }} ending in {{ pm.last_four }} ({{ pm.exp_month }}/{{ pm.exp_year }})
<template v-if="pm.is_default"> - Default</template>
</option>
</select>
</div>
<div v-if="selectedGateway === 'stripe' && paymentMethods.length === 0" class="mt-4">
<p class="text-sm text-gray-500">
You have no saved payment methods.
<Link href="/billing" class="text-blue-400 hover:text-blue-300">Add one first</Link>.
</p>
</div>
</div>
<!-- Coupon -->
<div class="bg-gray-900 rounded-lg border border-gray-800 shadow-sm p-6">
<h2 class="text-lg font-semibold text-white mb-4">Coupon Code</h2>
<div class="flex gap-3">
<input
v-model="couponCode"
type="text"
placeholder="Enter coupon code"
class="flex-1 rounded-md bg-gray-800 border-gray-700 text-gray-100 text-sm placeholder-gray-500"
:disabled="couponApplied"
>
<button
type="button"
@click="applyCoupon"
:disabled="!couponCode || couponApplied"
class="px-4 py-2 bg-gray-800 text-sm font-medium text-gray-300 rounded-md hover:bg-gray-700 border border-gray-700 disabled:opacity-50"
>
{{ couponApplied ? 'Applied' : 'Apply' }}
</button>
</div>
<p v-if="couponError" class="mt-2 text-sm text-red-400">{{ couponError }}</p>
<p v-if="couponApplied" class="mt-2 text-sm text-green-400">Coupon applied successfully!</p>
</div>
<!-- Errors -->
<div v-if="form.errors && Object.keys(form.errors).length" class="rounded-md bg-red-900/50 border border-red-800 p-4">
<ul class="list-disc list-inside text-sm text-red-300">
<li v-for="(error, field) in form.errors" :key="field">{{ error }}</li>
</ul>
</div>
<!-- Submit -->
<button
type="submit"
:disabled="form.processing || (selectedGateway === 'stripe' && !selectedPaymentMethod)"
class="w-full px-6 py-3 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 disabled:opacity-50"
>
<span v-if="form.processing">Processing...</span>
<span v-else>Subscribe for ${{ total }}/{{ plan.billing_cycle }}</span>
</button>
</form>
</div>
</div>
</div>
</template>