Browse Source

Updates using qwen3coder output

master
Larry Luangrath 2 weeks ago
parent
commit
44faf6c456
  1. 1
      .gitignore
  2. 2821
      package-lock.json
  3. 23
      package.json
  4. 55
      server/index.js
  5. 265
      src/App.css
  6. 145
      src/App.js
  7. 35
      src/components/Cart.js
  8. 82
      src/components/CheckoutForm.js
  9. 17
      src/components/Header.js
  10. 17
      src/components/ProductItem.js
  11. 21
      src/components/ProductList.js

1
.gitignore vendored

@ -21,3 +21,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env

2821
package-lock.json generated

File diff suppressed because it is too large Load Diff

23
package.json

@ -3,18 +3,20 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@stripe/react-stripe-js": "^5.2.0",
"@stripe/stripe-js": "^8.0.0",
"concurrently": "^9.2.1",
"express": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-scripts": "5.0.1",
"stripe": "^19.1.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"server": "node server/index.js",
"dev": "concurrently \"npm run server\" \"npm run start\"",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
@ -35,5 +37,14 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"devDependencies": {
"dotenv": "^17.2.3",
"react-scripts": "^5.0.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0"
},
"proxy": "http://localhost:4000"
}

55
server/index.js

@ -0,0 +1,55 @@
require('dotenv').config();
const express = require('express');
const stripe = require('stripe')(process.env.REACT_APP_STRIPE_SKEY);
const app = express();
app.use(express.json());
app.post('/api/create-payment-intent', async (req, res) => {
try {
const { amount, currency = 'usd', items } = req.body;
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: currency,
metadata: {
integration_check: 'accept_a_payment',
items: JSON.stringify(items)
}
});
res.send({
clientSecret: paymentIntent.client_secret
});
} catch (error) {
console.error('Error creating payment intent:', error);
res.status(400).send({ error: error.message });
}
});
// Additional endpoints for order management
app.post('/api/create-order', async (req, res) => {
try {
// Process order and save to database
const order = {
items: req.body.items,
total: req.body.total,
status: 'pending',
createdAt: new Date()
};
// Save to your database
// const savedOrder = await saveOrderToDatabase(order);
res.json({ order });
} catch (error) {
res.status(400).json({ error: 'Failed to create order' });
}
});
app.listen(process.env.SERVER_PORT || 5000, () => {
console.log('test', process.env.SERVER_PORT);
console.log(`Server running on port ${process.env.SERVER_PORT || 5000}`);
});

265
src/App.css

@ -1,38 +1,263 @@
.App {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.header {
background-color: #333;
color: white;
padding: 1rem;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.cart-count {
background-color: #ff4d4d;
border-radius: 50%;
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
margin-left: 0.5rem;
}
.main-content {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-top: 2rem;
}
.product-list h2, .cart h2 {
margin-bottom: 1rem;
color: #333;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
.product-item {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
text-align: center;
background: #f9f9f9;
}
.App-logo {
height: 40vmin;
pointer-events: none;
.product-item img {
max-width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
.product-item h3 {
margin: 0.5rem 0;
font-size: 1.2rem;
}
.price {
font-weight: bold;
color: #007bff;
font-size: 1.1rem;
}
.description {
color: #666;
font-size: 0.9rem;
margin: 0.5rem 0;
}
.product-item button {
background-color: #007bff;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-top: 0.5rem;
}
.product-item button:hover {
background-color: #0056b3;
}
.cart {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
background: #f9f9f9;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
.cart-items {
list-style: none;
padding: 0;
}
.cart-item {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.quantity-controls {
display: flex;
align-items: center;
}
.quantity-controls button {
width: 25px;
height: 25px;
margin: 0 0.2rem;
border: 1px solid #ccc;
background: white;
cursor: pointer;
}
.cart-total {
margin: 1rem 0;
padding-top: 1rem;
border-top: 2px solid #333;
text-align: right;
}
.checkout-btn {
width: 100%;
padding: 0.75rem;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
}
.checkout-btn:hover {
background-color: #218838;
}
.App-link {
color: #61dafb;
.loading {
text-align: center;
padding: 2rem;
font-size: 1.2rem;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
to {
transform: rotate(360deg);
.products-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.close-modal {
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.close-modal:hover {
background: #5a6268;
}
.form-row {
margin-bottom: 1rem;
}
.form-row label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.submit-button {
width: 100%;
padding: 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background 0.3s;
}
.submit-button:hover:not(:disabled) {
background: #0056b3;
}
.submit-button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.error-message {
color: #dc3545;
margin-top: 0.5rem;
padding: 0.5rem;
background: #f8d7da;
border-radius: 4px;
}
.order-complete {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
z-index: 1001;
}
.order-complete button {
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

145
src/App.js

@ -1,25 +1,134 @@
import logo from './logo.svg';
import React, { useState, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import ProductList from './components/ProductList';
import Cart from './components/Cart';
import Header from './components/Header';
import CheckoutForm from './components/CheckoutForm';
import './App.css';
function App() {
// Initialize Stripe (use your publishable key)
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PKEY);
const App = () => {
console.log('stripe', process.env.REACT_APP_STRIPE_PKEY)
const [products, setProducts] = useState([]);
const [cart, setCart] = useState([]);
const [loading, setLoading] = useState(true);
const [isCheckoutOpen, setIsCheckoutOpen] = useState(false);
const [orderComplete, setOrderComplete] = useState(false);
// Mock product data
const mockProducts = [
{ id: 1, name: 'Product 1', price: 29.99, image: '/placeholder1.jpg', description: 'Description 1' },
{ id: 2, name: 'Product 2', price: 39.99, image: '/placeholder2.jpg', description: 'Description 2' },
{ id: 3, name: 'Product 3', price: 19.99, image: '/placeholder3.jpg', description: 'Description 3' },
{ id: 4, name: 'Product 4', price: 49.99, image: '/placeholder4.jpg', description: 'Description 4' },
];
useEffect(() => {
// Simulate API fetch
setTimeout(() => {
setProducts(mockProducts);
setLoading(false);
}, 1000);
}, []);
const addToCart = (product) => {
setCart(prevCart => {
const existingItem = prevCart.find(item => item.id === product.id);
if (existingItem) {
return prevCart.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prevCart, { ...product, quantity: 1 }];
});
};
const removeFromCart = (productId) => {
setCart(prevCart => prevCart.filter(item => item.id !== productId));
};
const updateQuantity = (productId, quantity) => {
if (quantity < 1) return;
setCart(prevCart =>
prevCart.map(item =>
item.id === productId ? { ...item, quantity } : item
)
);
};
const cartTotal = cart.reduce((total, item) => total + (item.price * item.quantity), 0);
const handleCheckout = () => {
setIsCheckoutOpen(true);
};
const handlePaymentSuccess = () => {
setIsCheckoutOpen(false);
setOrderComplete(true);
setCart([]); // Clear cart after successful payment
};
const handlePaymentError = () => {
setIsCheckoutOpen(false);
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<Header cartCount={cart.length} />
<main className="main-content">
{loading ? (
<div className="loading">Loading products...</div>
) : (
<>
<ProductList products={products} onAddToCart={addToCart} />
<Cart
items={cart}
onRemove={removeFromCart}
onUpdateQuantity={updateQuantity}
total={cartTotal}
onCheckout={handleCheckout}
/>
</>
)}
</main>
{/* Stripe Checkout Modal */}
{isCheckoutOpen && (
<div className="modal-overlay">
<div className="modal-content">
<h2>Checkout</h2>
<Elements stripe={stripePromise}>
<CheckoutForm
amount={cartTotal * 100} // Stripe expects amount in cents
onPaymentSuccess={handlePaymentSuccess}
onPaymentError={handlePaymentError}
cartItems={cart}
/>
</Elements>
<button
className="close-modal"
onClick={() => setIsCheckoutOpen(false)}
>
Close
</button>
</div>
</div>
)}
{orderComplete && (
<div className="order-complete">
<h2>Order Successful!</h2>
<p>Thank you for your purchase. Your order is being processed.</p>
<button onClick={() => setOrderComplete(false)}>Continue Shopping</button>
</div>
)}
</div>
);
}
};
export default App;
export default App;

35
src/components/Cart.js

@ -0,0 +1,35 @@
import React from 'react';
const Cart = ({ items, onRemove, onUpdateQuantity, onCheckout, total }) => {
return (
<div className="cart">
<h2>Shopping Cart</h2>
{items.length === 0 ? (
<p>Your cart is empty</p>
) : (
<>
<ul className="cart-items">
{items.map(item => (
<li key={item.id} className="cart-item">
<span>{item.name}</span>
<span>${(item.price * item.quantity).toFixed(2)}</span>
<div className="quantity-controls">
<button onClick={() => onUpdateQuantity(item.id, item.quantity - 1)}>-</button>
<span>{item.quantity}</span>
<button onClick={() => onUpdateQuantity(item.id, item.quantity + 1)}>+</button>
</div>
<button onClick={() => onRemove(item.id)}>Remove</button>
</li>
))}
</ul>
<div className="cart-total">
<strong>Total: ${total.toFixed(2)}</strong>
</div>
<button onClick={() => onCheckout()} className="checkout-btn">Proceed to Checkout</button>
</>
)}
</div>
);
};
export default Cart;

82
src/components/CheckoutForm.js

@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { useStripe, useElements, CardElement } from '@stripe/react-stripe-js';
const CheckoutForm = ({ amount, onPaymentSuccess, onPaymentError, cartItems }) => {
const stripe = useStripe();
const elements = useElements();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
setLoading(true);
setError(null);
try {
// Create payment intent on your backend
const response = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: amount,
currency: 'usd',
items: cartItems
}),
});
const { clientSecret } = await response.json();
// Confirm card payment
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: 'Customer Name',
},
},
});
if (result.error) {
setError(result.error.message);
onPaymentError();
} else {
// Payment successful
onPaymentSuccess();
}
} catch (error) {
setError('Payment processing failed. Please try again.');
onPaymentError();
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div className="form-row">
<label htmlFor="card-element">
Credit or debit card
</label>
<CardElement id="card-element" />
{error && <div className="error-message">{error}</div>}
</div>
<button
type="submit"
disabled={!stripe || loading}
className="submit-button"
>
{loading ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}`}
</button>
</form>
);
};
export default CheckoutForm;

17
src/components/Header.js

@ -0,0 +1,17 @@
import React from 'react';
const Header = ({ cartCount }) => {
return (
<header className="header">
<div className="header-content">
<h1>My Store</h1>
<div className="cart-icon">
<span>Cart</span>
{cartCount > 0 && <span className="cart-count">{cartCount}</span>}
</div>
</div>
</header>
);
};
export default Header;

17
src/components/ProductItem.js

@ -0,0 +1,17 @@
import React from 'react';
const ProductItem = ({ product, onAddToCart }) => {
return (
<div className="product-item">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p className="price">${product.price.toFixed(2)}</p>
<p className="description">{product.description}</p>
<button onClick={() => onAddToCart(product)}>
Add to Cart
</button>
</div>
);
};
export default ProductItem;

21
src/components/ProductList.js

@ -0,0 +1,21 @@
import React from 'react';
import ProductItem from './ProductItem';
const ProductList = ({ products, onAddToCart }) => {
return (
<div className="product-list">
<h2>Products</h2>
<div className="products-grid">
{products.map(product => (
<ProductItem
key={product.id}
product={product}
onAddToCart={onAddToCart}
/>
))}
</div>
</div>
);
};
export default ProductList;
Loading…
Cancel
Save