Skip to content

Commit

Permalink
Merge pull request #100 from calimania/feat/product-hunt
Browse files Browse the repository at this point in the history
Fixes to product and checkout modal
  • Loading branch information
dvidsilva authored Feb 21, 2025
2 parents 16a10f6 + adbec16 commit 6dac0be
Show file tree
Hide file tree
Showing 17 changed files with 6,018 additions and 7,714 deletions.
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

0 comments on commit 6dac0be

Please sign in to comment.