mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-15 20:13:50 +00:00
Merge pull request #44 from jcreek/catalog-improvements
Catalog improvements
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let product;
|
export let product;
|
||||||
|
|
||||||
|
const defaultPrice = product.prices[0];
|
||||||
|
const isSubscription = defaultPrice.type === 'recurring';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href="/products/{product.id}" class="max-w-[384px] mx-auto">
|
<a href="/products/{product.id}" class="max-w-[384px] mx-auto">
|
||||||
@@ -13,7 +16,7 @@
|
|||||||
<div class="mt-5 flex items-center justify-between">
|
<div class="mt-5 flex items-center justify-between">
|
||||||
<div class="">
|
<div class="">
|
||||||
<h6 class="font-medium text-xl leading-8 text-black mb-2">{product.name}</h6>
|
<h6 class="font-medium text-xl leading-8 text-black mb-2">{product.name}</h6>
|
||||||
<h6 class="font-semibold text-xl leading-8 text-indigo-600">£{product.actualPrice}</h6>
|
<h6 class="font-semibold text-xl leading-8 text-indigo-600">£{product.actualPrice} {isSubscription ? `/ ${defaultPrice.interval}` : ''}</h6>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="p-2 min-[400px]:p-4 rounded-full bg-white border border-gray-300 flex items-center justify-center group shadow-sm shadow-transparent transition-all duration-500 hover:shadow-gray-200 hover:border-gray-400 hover:bg-gray-50"
|
class="p-2 min-[400px]:p-4 rounded-full bg-white border border-gray-300 flex items-center justify-center group shadow-sm shadow-transparent transition-all duration-500 hover:shadow-gray-200 hover:border-gray-400 hover:bg-gray-50"
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
|
export type ProductInfo = {
|
||||||
|
product: Stripe.Product;
|
||||||
|
price: Stripe.Price;
|
||||||
|
isSubscription: boolean;
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userId = session.user.id;
|
const userId = session.user.id;
|
||||||
|
const userEmail = session.user.email;
|
||||||
|
|
||||||
const formData = new URLSearchParams(await request.text());
|
const formData = new URLSearchParams(await request.text());
|
||||||
const items: { priceId: string; quantity: number }[] = [];
|
const items: { priceId: string; quantity: number }[] = [];
|
||||||
@@ -26,6 +27,7 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const checkoutSession = await stripeClient.checkout.sessions.create({
|
const checkoutSession = await stripeClient.checkout.sessions.create({
|
||||||
|
customer_email: userEmail,
|
||||||
line_items: lineItems,
|
line_items: lineItems,
|
||||||
mode: 'payment',
|
mode: 'payment',
|
||||||
success_url: `${request.headers.get('origin')}/checkout/success`,
|
success_url: `${request.headers.get('origin')}/checkout/success`,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { hasProductAccess } from '$lib/utils/supabase/admin';
|
import { hasProductAccess } from '$lib/utils/supabase/admin';
|
||||||
|
import type { ProductInfo } from '$lib/types/Products/ProductInfo';
|
||||||
|
|
||||||
export const load = async ({ params, locals: { safeGetSession }, fetch }) => {
|
export const load = async ({ params, locals: { safeGetSession }, fetch }) => {
|
||||||
const { session } = await safeGetSession();
|
const { session } = await safeGetSession();
|
||||||
@@ -8,7 +9,7 @@ export const load = async ({ params, locals: { safeGetSession }, fetch }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productResponse = await fetch(`/products/${params.productId}`);
|
const productResponse = await fetch(`/products/${params.productId}`);
|
||||||
const product = await productResponse.json();
|
const { product, isSubscription, price }: ProductInfo = await productResponse.json();
|
||||||
|
|
||||||
// Create the item object
|
// Create the item object
|
||||||
const item = {
|
const item = {
|
||||||
@@ -17,17 +18,13 @@ export const load = async ({ params, locals: { safeGetSession }, fetch }) => {
|
|||||||
categoryDescription: product.description,
|
categoryDescription: product.description,
|
||||||
imgAlt: product.description,
|
imgAlt: product.description,
|
||||||
imgSrc: product.images[0],
|
imgSrc: product.images[0],
|
||||||
price: 0,
|
|
||||||
priceId: product.default_price,
|
priceId: product.default_price,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
isSubscription: product.isSubscription
|
isSubscription: isSubscription,
|
||||||
|
price: (price.unit_amount ? price.unit_amount / 100 : NaN),
|
||||||
|
interval: price.recurring ? price.recurring.interval : null,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the price of the product
|
|
||||||
const priceResponse = await fetch(`/products/prices/${product.default_price}`);
|
|
||||||
const price = await priceResponse.json();
|
|
||||||
// Set the price of the product
|
|
||||||
item.price = price.unit_amount / 100;
|
|
||||||
|
|
||||||
// Check if the user has access to the 'EXAMPLE PRODUCT' product
|
// Check if the user has access to the 'EXAMPLE PRODUCT' product
|
||||||
const hasPurchasedProduct = await hasProductAccess(session.user.id, params.productId);
|
const hasPurchasedProduct = await hasProductAccess(session.user.id, params.productId);
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
<h6
|
<h6
|
||||||
class="font-manrope font-semibold text-2xl leading-9 text-gray-900 pr-5 sm:border-r border-gray-200 mr-5"
|
class="font-manrope font-semibold text-2xl leading-9 text-gray-900 pr-5 sm:border-r border-gray-200 mr-5"
|
||||||
>
|
>
|
||||||
£{item.price}
|
£{item.price} {item.isSubscription ? `/ ${item.interval}` : ''}
|
||||||
</h6>
|
</h6>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { stripe as stripeClient } from '$lib/utils/stripe';
|
import { stripe as stripeClient } from '$lib/utils/stripe';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
import { type ProductInfo } from '$lib/types/Products/ProductInfo';
|
||||||
|
|
||||||
export const GET = async ({ params }) => {
|
export const GET = async ({ params }) => {
|
||||||
const { productId } = params;
|
const { productId } = params;
|
||||||
const product = await stripeClient.products.retrieve(productId);
|
const product = await stripeClient.products.retrieve(productId);
|
||||||
|
|
||||||
const price = await stripeClient.prices.retrieve(product.default_price);
|
const price = await stripeClient.prices.retrieve(product.default_price as string);
|
||||||
|
|
||||||
const isSubscription = price.type === ('recurring' as Stripe.Price.type);
|
const isSubscription = price.type === ('recurring' as Stripe.Price.Type);
|
||||||
product.isSubscription = isSubscription;
|
|
||||||
return json(product);
|
const productInfo: ProductInfo = {
|
||||||
|
product,
|
||||||
|
price,
|
||||||
|
isSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(productInfo);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user