Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes to product and checkout modal #100

Merged
merged 8 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ export default defineConfig({
output: "static",
vite: {
optimizeDeps: {
exclude: ["@resvg/resvg-js"],
exclude: ["@resvg/resvg-js", "fsevents"],
},
ssr: {
noExternal: [],
}
},
scopedStyleStrategy: "where",
outDir: "./dist",
Expand Down
13,199 changes: 5,772 additions & 7,427 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,38 @@
"lint": "eslint ."
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/rss": "^4.0.7",
"@astrojs/check": "^0.9.4",
"@astrojs/react": "^4.2.0",
"@astrojs/rss": "^4.0.11",
"@resvg/resvg-js": "^2.6.2",
"@strapi/blocks-react-renderer": "^1.0.1",
"@tabler/icons-react": "^3.29.0",
"astro": "5.0.0-beta.5",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.21",
"astro": "^5.3.0",
"dotenv": "^16.4.7",
"fuse.js": "^7.0.0",
"github-slugger": "^2.0.0",
"marked": "^15.0.3",
"react": "^18.0.21",
"react-dom": "^18.0.21",
"remark-collapse": "^0.1.2",
"remark-toc": "^9.0.0",
"satori": "^0.10.14",
"tailwindcss": "^3.4.6",
"typescript": "^5.5.3"
},
"devDependencies": {
"@astrojs/react": "^3.6.2",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.1",
"@astrojs/sitemap": "^3.2.1",
"@astrojs/tailwind": "^6.0.0",
"@tailwindcss/typography": "^0.5.13",
"@types/github-slugger": "^1.3.0",
"@types/react": "^18.3.3",
"@typescript-eslint/parser": "^6.19.0",
"@typescript-eslint/parser": "^8.24.1",
"astro-eslint-parser": "^0.16.2",
"eslint": "^8.56.0",
"eslint": "^9.20.1",
"eslint-plugin-astro": "^0.31.3",
"prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.5",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"prettier-plugin-tailwindcss": "^0.6.5"
}
}
40 changes: 40 additions & 0 deletions src/components/PageCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { FC } from 'react';

interface Props {
href: string;
title: string;
description: string;
icon?: string;
}

const PageCard: FC<Props> = ({ href, title, description, icon = "📄" }) => {
return (
<li className="w-full mb-4">
<a
href={href}
className="block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-accent-500 dark:hover:border-accent-500 transition-all duration-300"
>
<div className="flex items-start space-x-4">
<span className="text-2xl" role="img" aria-label="page icon">
{icon}
</span>
<div className="flex-1">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2 group-hover:text-accent-500">
{title}
</h3>
<p className="text-gray-600 dark:text-gray-300">
{description}
</p>
</div>
<div className="text-accent-500 dark:text-accent-400">
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</a>
</li>
);
}

export default PageCard;
4 changes: 2 additions & 2 deletions src/components/SubscriptionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const SubscriptionModal: React.FC<Props> = ({
<h2 className="text-lg font-semibold text-gray-800 flex items-center">
{error ? title : `🎉 ${title} ✨`}
</h2>

<button
onClick={() => {
try {
Expand All @@ -42,7 +42,7 @@ const SubscriptionModal: React.FC<Props> = ({
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="icon icon-tabler icons-tabler-outline icon-tabler-x"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,107 @@
import React, { useEffect, useState } from 'react';
import { ProductForm } from "scripts/ui.product"
import {type FC, useEffect, useState } from 'react';
import { createPaymentLink, type PaymentLinkOptions, type Price } from "scripts/ui.product"

interface Props {
prices: any;
prices: Price[];
product: any;
}

const CheckoutModal = ({ prices, product }: Props) => {
const CheckoutModal: FC<Props> = ({ prices, product }: Props) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedPrice, setSelectedPrice] = useState(0);
const [selectedPriceId, setSelectedPriceId] = useState('');
const [quantity, setQuantity] = useState(1);
const [tip, setTip] = useState(0);
const [total, setTotal] = useState(0);
const handlePriceChange = (event: any) => {
const price = event.target.value;
setSelectedPrice(price);
updateTotal(price, quantity);
};
const [selectedPrice, setSelectedPrice] = useState({} as Price);

const [options, setOptions] = useState({
totalPrice: 0,
product: product.id,
prices: [],
stripe_test: product?.data.Name?.match(/test/i),
includes_shipping: !product?.data.Name?.match(/digital/i),
} as PaymentLinkOptions);


// Calculate total whenever inputs change
useEffect(() => {
if (isModalOpen) {
ProductForm(); // Initialize ProductForm when modal is open
const priceId = selectedPriceId;
const price = prices.find((p: Price) => p.STRIPE_ID == priceId);
const basePrice = price?.Price || 0;
const subtotal = basePrice * quantity;
const newTotal = subtotal + tip;

const option_prices = [
{
quantity,
price: String(selectedPriceId),
currency: 'usd',
} as Price,
];

if (tip > 0) {
option_prices.push({
unit_amount: String(tip),
currency: 'usd',
product: product?.data.SKU,
} as Price);
}
}, [isModalOpen]);

setOptions((prevOptions) => ({
...prevOptions,
totalPrice: newTotal,
prices: option_prices,

} as PaymentLinkOptions));
setTotal(Number(newTotal));
setSelectedPrice(price);

}, [selectedPriceId, quantity, tip]);


const redirectToPaymentLink = async (event: any) => {
event.preventDefault();
console.log('submit event', { options });
// @TODO: prevent redirect when price or product is missing
const a = await createPaymentLink(options);
console.log({ a });
};

const handlePriceChange = (event: any) => {
const price = event.target.value as string;
setSelectedPriceId(price);
};

const handleQuantityChange = (event: any) => {
const newQuantity = event.target.value;
const newQuantity = event.target.value as number;
setQuantity(newQuantity);
updateTotal(selectedPrice, newQuantity);
};

const handleCustomPriceChange = (event: any) => {
const handleTipChange = (event: any) => {
const customPrice = event.target.value;
updateTotal(customPrice, quantity);
};

const updateTotal = (price: number, quantity: number) => {
const newTotal = price * quantity;
setTotal(newTotal);
setTip(parseInt(customPrice, 10));
};

return (
<>
{/* Modal Trigger Button */}
<button
className="w-full mb-5 flex items-center justify-center rounded-md border border-transparent bg-sky-500 px-8 py-3 text-base font-medium text-white hover:bg-accent-700 focus:outline-none focus:ring-2 focus:ring-accent-500 focus:ring-offset-2"
onClick={() => { setIsModalOpen(true) }}
>
Continue to Payment
</button>
{isModalOpen && (
<div className="modal-overlay fixed inset-0 bg-gray-900 bg-opacity-70 flex justify-center items-center z-50">
<div className="modal-overlay fixed inset-0 bg-gray-900 bg-opacity-70 flex justify-center items-center z-50"
onClick={(e) => (e.target as Element).classList.contains('modal-overlay') && setIsModalOpen(false)}>
<div className="modal-content bg-skin-card p-6 rounded-lg max-w-lg w-full">
<button
className="absolute top-4 right-4 text-gray-500"
onClick={() => setIsModalOpen(false)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-x">
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
className="icon icon-tabler icons-tabler-outline icon-tabler-x">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M18 6l-12 12" />
<path d="M6 6l12 12" />
Expand All @@ -63,12 +116,14 @@ const CheckoutModal = ({ prices, product }: Props) => {
<div className="hidden md:flex items-center justify-between">
<h3 className="text-2xl font-medium text-gray-900 dark:text-white">
Available Options
<br />
<small className="text-sm text-gray-500">Pick an option to see more details</small>
<br />
</h3>
<span className="text-sm text-gray-500">Select your preferred option</span>
</div>

<div className="m-4">
<label htmlFor="price"><strong>Price Options</strong></label>
<label htmlFor="price"><strong>Product Options</strong></label>
<select
id="price"
name="price"
Expand All @@ -86,7 +141,7 @@ const CheckoutModal = ({ prices, product }: Props) => {
</div>

<div className="m-4">
<label htmlFor="quantity"><strong>Quantity</strong></label>
<label htmlFor="quantity"><strong>Quantity multiplier</strong></label>
<input
type="number"
id="quantity"
Expand All @@ -98,14 +153,14 @@ const CheckoutModal = ({ prices, product }: Props) => {
</div>

<div className="m-4">
<label htmlFor="custom-price"><strong>Custom Price</strong></label>
<label htmlFor="custom-price"><strong>Tip || Custom Price</strong></label>
<input
type="number"
id="custom-price"
placeholder="0"
name="custom-price"
className="w-full rounded-md border-2 border-solid border-gray-300 py-2 pl-3 pr-10 text-base focus:border-accent-500 focus:outline-none focus:ring-accent-500 dark:border-gray-600 dark:bg-gray-700"
onChange={handleCustomPriceChange}
onChange={handleTipChange}
/>
</div>

Expand All @@ -116,36 +171,26 @@ const CheckoutModal = ({ prices, product }: Props) => {

<div className="m-4">
<button
disabled
disabled={total <= 0}
data-action-button="submit"
type="submit"
className="flex w-full items-center justify-center rounded-md border border-transparent bg-sky-500 px-8 py-3 text-base font-medium text-white hover:bg-accent-700 focus:outline-none focus:ring-2 focus:ring-accent-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
onClick={redirectToPaymentLink}
>
Continue to Payment
</button>

<ol className="mt-4 text-sm text-gray-500 hidden md:flex flex-col ">
<li className="mb-2">We use Stripe to collect payments, and send payouts to the artists.</li>
<li className="mb-2">You'll be redirected to a Stripe page to continue, and we'll notify the artists after a successful transaction.</li>
<li className="mb-2">Currently, we don't support a shopping cart. You can get one or multiple units of the same <em>Price</em> per transaction.</li>
<li className="mb-2">We notify the artist after a successful transaction and forward your shipping details and email to them.</li>
<li className="mb-2">The artists will reach out with tracking details or follow-up steps to complete the transaction.</li>
</ol>
</div>
<div className="m-4">
<p className="text-md">
{total > 0 ? selectedPrice?.Description || 'Continue using custom price' : 'Please select a price and quantity to continue'}
</p>
</div>
</form>
</div>
</div>
)}

{/* Modal Trigger Button */}
<button
className="w-full mb-5 flex items-center justify-center rounded-md border border-transparent bg-sky-500 px-8 py-3 text-base font-medium text-white hover:bg-accent-700 focus:outline-none focus:ring-2 focus:ring-accent-500 focus:ring-offset-2"
onClick={() => {setIsModalOpen(true)}}
>
Continue to Payment
</button>
</>
);
};

export default CheckoutModal;
export default CheckoutModal;
28 changes: 17 additions & 11 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import type { Site, SocialObjects } from "./types";

const BASE_URL = ((process.env.BASE_URL || import.meta.env.BASE_URL) as string);

const url = BASE_URL.startsWith('http') ? BASE_URL : 'https://markket.place/';

console.log({ url, BASE_URL });
// Loading .env files to populate the configuration
const {
BASE_URL,
PUBLIC_STRIPE_PUBLISHABLE_KEY,
STRAPI_URL,
STORE_SLUG,
COLOR_PRIMARY,
COLOR_ACCENT,
POSTHOG_ID,
} = import.meta.env;

/**
* @type {{[string]: string}} Global Configuration attributes for the markket instance
*/
export const markketplace = {
STRAPI_URL: (import.meta.env.STRAPI_URL || '').replace(/\/$/, '') || 'https://api.markket.place',
STORE_SLUG: import.meta.env.STORE_SLUG as string || 'fika',
STRAPI_URL: (STRAPI_URL || '').replace(/\/$/, '') || 'https://api.markket.place',
STORE_SLUG: (STORE_SLUG as string) || 'fika',
colors: {
primary: import.meta.env.COLOR_PRIMARY as string || '#fbda0c',
accent: import.meta.env.COLOR_ACCENT as string || '#38b2ac',
primary: COLOR_PRIMARY as string || '#fbda0c',
accent: COLOR_ACCENT as string || '#38b2ac',
},
POSTHOG_ID: import.meta.env.POSTHOG_ID as string || '',
url,
POSTHOG_ID: POSTHOG_ID as string || '',
url: BASE_URL.startsWith('http') ? BASE_URL : 'https://markket.place/',
STRIPE_PUBLISHABLE_KEY: PUBLIC_STRIPE_PUBLISHABLE_KEY || '',
};

/**
Expand Down
2 changes: 2 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface ImportMetaEnv {
readonly STORE_DESCRIPTION: string;
readonly STORE_TITLE: string;
readonly STORE_OG_IMAGE: string;
readonly PUBLIC_BASE_URL: string;
readonly PUBLIC_STRIPE_PUBLISHABLE_KEY: string;
}

export interface ImportMeta {
Expand Down
Loading