diff --git a/src/Imprink.Application/Imprink.Application.csproj b/src/Imprink.Application/Imprink.Application.csproj index 17480c4..bde7f6a 100644 --- a/src/Imprink.Application/Imprink.Application.csproj +++ b/src/Imprink.Application/Imprink.Application.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/webui/auth0-templates/page.js b/webui/auth0-templates/page.js new file mode 100644 index 0000000..e14237a --- /dev/null +++ b/webui/auth0-templates/page.js @@ -0,0 +1,143 @@ +'use client'; + +import { useState } from 'react'; +import { loadStripe } from '@stripe/stripe-js'; +import { Elements } from '@stripe/react-stripe-js'; +import PaymentForm from './components/PaymentForm'; +import './globals.css'; + +const stripePromise = loadStripe(''); + +const products = [ + { id: '1', name: 'Premium Widget', price: 2999, description: 'High-quality widget for professionals' }, + { id: '2', name: 'Standard Widget', price: 1999, description: 'Reliable widget for everyday use' }, + { id: '3', name: 'Basic Widget', price: 999, description: 'Entry-level widget for beginners' } +]; + +export default function Home() { + const [selectedProduct, setSelectedProduct] = useState(null); + const [clientSecret, setClientSecret] = useState(''); + const [orderId, setOrderId] = useState(''); + const [loading, setLoading] = useState(false); + + const handleProductSelect = async (product) => { + setLoading(true); + setSelectedProduct(product); + + const newOrderId = Math.floor(Math.random() * 10000).toString(); + setOrderId(newOrderId); + + try { + const response = await fetch('https://impr.ink/api/stripe/create-payment-intent', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + amount: product.price, + orderId: newOrderId + }), + }); + + const data = await response.json(); + + if (data.clientSecret) { + setClientSecret(data.clientSecret); + } else { + console.error('Error creating payment intent:', data.error); + } + } catch (error) { + console.error('Error:', error); + } finally { + setLoading(false); + } + }; + + const handlePaymentSuccess = () => { + setSelectedProduct(null); + setClientSecret(''); + setOrderId(''); + }; + + const handleBackToProducts = () => { + setSelectedProduct(null); + setClientSecret(''); + setOrderId(''); + }; + + const appearance = { + theme: 'stripe', + variables: { + colorPrimary: '#0570de', + colorBackground: '#ffffff', + colorText: '#30313d', + colorDanger: '#df1b41', + fontFamily: 'Ideal Sans, system-ui, sans-serif', + spacingUnit: '2px', + borderRadius: '4px', + }, + }; + + const options = { + clientSecret, + appearance, + }; + + return ( +
+
+

🛍️ Stripe Payment Demo

+

Select a product to purchase

+
+ + {!selectedProduct ? ( +
+

Products

+
+ {products.map((product) => ( +
+

{product.name}

+

{product.description}

+

${(product.price / 100).toFixed(2)}

+ +
+ ))} +
+
+ ) : ( +
+
+

Order Summary

+
+

Product: {selectedProduct.name}

+

Order ID: {orderId}

+

Amount: ${(selectedProduct.price / 100).toFixed(2)}

+
+
+ + {clientSecret && ( + + + + )} + + +
+ )} +
+ ); +} \ No newline at end of file diff --git a/webui/next.config.mjs b/webui/next.config.mjs index 0b5a886..dd9ee23 100644 --- a/webui/next.config.mjs +++ b/webui/next.config.mjs @@ -1,14 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - webpack: (config, { dev }) => { - if (dev) { - config.watchOptions = { - poll: 1000, - aggregateTimeout: 300, - } - } - return config - }, -} +const nextConfig = {} export default nextConfig; \ No newline at end of file diff --git a/webui/package-lock.json b/webui/package-lock.json index dfadc04..6befedc 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -9,10 +9,13 @@ "version": "0.1.0", "dependencies": { "@auth0/nextjs-auth0": "^4.6.1", + "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", + "@emotion/serialize": "^1.3.3", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.1.1", "@mui/material": "^7.1.1", + "@mui/material-nextjs": "^7.1.1", "@mui/x-data-grid": "^8.5.2", "@mui/x-date-pickers": "^8.5.2", "@stripe/react-stripe-js": "^3.7.0", @@ -910,6 +913,41 @@ } } }, + "node_modules/@mui/material-nextjs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/material-nextjs/-/material-nextjs-7.1.1.tgz", + "integrity": "sha512-6/tjmViYMI7XIqDTqK+n4t5B07YfVDq72emdBy/o8FLHsV7u477Ro0Aago2MQu8FrBQWDvzvvRkynIb02GjDBQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.11.4", + "@emotion/server": "^11.11.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "next": "^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/cache": { + "optional": true + }, + "@emotion/server": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/private-theming": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz", diff --git a/webui/package.json b/webui/package.json index 98006bd..4db9571 100644 --- a/webui/package.json +++ b/webui/package.json @@ -10,10 +10,13 @@ }, "dependencies": { "@auth0/nextjs-auth0": "^4.6.1", + "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", + "@emotion/serialize": "^1.3.3", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.1.1", "@mui/material": "^7.1.1", + "@mui/material-nextjs": "^7.1.1", "@mui/x-data-grid": "^8.5.2", "@mui/x-date-pickers": "^8.5.2", "@stripe/react-stripe-js": "^3.7.0", diff --git a/webui/src/app/components/ThemeToggleButton.js b/webui/src/app/components/ThemeToggleButton.js new file mode 100644 index 0000000..54f0f8d --- /dev/null +++ b/webui/src/app/components/ThemeToggleButton.js @@ -0,0 +1,25 @@ +'use client'; + +import { IconButton } from '@mui/material'; +import { useTheme } from './theme/ThemeContext'; + +export default function ThemeToggleButton() { + const { isDarkMode, toggleTheme } = useTheme(); + + return ( + + {isDarkMode ? '🌙' : '☀️'} + + ); +} \ No newline at end of file diff --git a/webui/src/app/components/theme/MuiThemeProvider.js b/webui/src/app/components/theme/MuiThemeProvider.js new file mode 100644 index 0000000..87678ce --- /dev/null +++ b/webui/src/app/components/theme/MuiThemeProvider.js @@ -0,0 +1,18 @@ +'use client'; + +import { ThemeProvider } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import { darkTheme } from './darkTheme'; +import { lightTheme } from './lightTheme'; +import { useTheme } from './ThemeContext'; + +export default function MuiThemeProvider({ children }) { + const { isDarkMode } = useTheme(); + + return ( + + + {children} + + ); +} \ No newline at end of file diff --git a/webui/src/app/components/theme/ThemeContext.js b/webui/src/app/components/theme/ThemeContext.js new file mode 100644 index 0000000..ae25725 --- /dev/null +++ b/webui/src/app/components/theme/ThemeContext.js @@ -0,0 +1,27 @@ +'use client'; + +import { createContext, useContext, useState } from 'react'; + +const ThemeContext = createContext({ isDarkMode: true }); + +export function ThemeContextProvider({ children }) { + const [isDarkMode, setIsDarkMode] = useState(true); + + const toggleTheme = () => { + setIsDarkMode(!isDarkMode); + }; + + return ( + + {children} + + ); +} + +export function useTheme() { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeContextProvider'); + } + return context; +} \ No newline at end of file diff --git a/webui/src/app/components/theme/darkTheme.js b/webui/src/app/components/theme/darkTheme.js new file mode 100644 index 0000000..c20539d --- /dev/null +++ b/webui/src/app/components/theme/darkTheme.js @@ -0,0 +1,279 @@ +'use client' + +import { createTheme } from '@mui/material/styles'; + +export const darkTheme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#6366f1', + light: '#818cf8', + dark: '#4f46e5', + contrastText: '#ffffff', + }, + secondary: { + main: '#f59e0b', + light: '#fbbf24', + dark: '#d97706', + contrastText: '#000000', + }, + background: { + default: '#0f0f23', + paper: '#1a1a2e', + }, + surface: { + main: '#16213e', + }, + text: { + primary: '#f8fafc', + secondary: '#cbd5e1', + }, + error: { + main: '#ef4444', + light: '#f87171', + dark: '#dc2626', + }, + warning: { + main: '#f59e0b', + light: '#fbbf24', + dark: '#d97706', + }, + info: { + main: '#06b6d4', + light: '#22d3ee', + dark: '#0891b2', + }, + success: { + main: '#10b981', + light: '#34d399', + dark: '#059669', + }, + divider: '#334155', + }, + typography: { + fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + h1: { + fontSize: '2.5rem', + fontWeight: 700, + lineHeight: 1.2, + letterSpacing: '-0.025em', + }, + h2: { + fontSize: '2rem', + fontWeight: 600, + lineHeight: 1.3, + letterSpacing: '-0.025em', + }, + h3: { + fontSize: '1.5rem', + fontWeight: 600, + lineHeight: 1.4, + letterSpacing: '-0.015em', + }, + h4: { + fontSize: '1.25rem', + fontWeight: 600, + lineHeight: 1.4, + }, + h5: { + fontSize: '1.125rem', + fontWeight: 600, + lineHeight: 1.5, + }, + h6: { + fontSize: '1rem', + fontWeight: 600, + lineHeight: 1.5, + }, + body1: { + fontSize: '1rem', + lineHeight: 1.6, + fontWeight: 400, + }, + body2: { + fontSize: '0.875rem', + lineHeight: 1.6, + fontWeight: 400, + }, + button: { + fontSize: '0.875rem', + fontWeight: 500, + textTransform: 'none', + letterSpacing: '0.025em', + }, + }, + shape: { + borderRadius: 12, + }, + shadows: [ + 'none', + '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', + '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + '0 25px 50px -12px rgba(0, 0, 0, 0.25)', + ], + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + scrollbarWidth: 'thin', + scrollbarColor: '#6366f1 #1a1a2e', + '&::-webkit-scrollbar': { + width: '8px', + }, + '&::-webkit-scrollbar-track': { + background: '#1a1a2e', + }, + '&::-webkit-scrollbar-thumb': { + background: '#6366f1', + borderRadius: '4px', + }, + }, + }, + }, + MuiButton: { + styleOverrides: { + root: { + borderRadius: '8px', + padding: '10px 24px', + fontSize: '0.875rem', + fontWeight: 500, + boxShadow: 'none', + '&:hover': { + boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)', + transform: 'translateY(-1px)', + }, + '&:active': { + transform: 'translateY(0)', + }, + }, + contained: { + background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', + '&:hover': { + background: 'linear-gradient(135deg, #5b21b6 0%, #7c3aed 100%)', + }, + }, + outlined: { + borderWidth: '1.5px', + '&:hover': { + borderWidth: '1.5px', + backgroundColor: 'rgba(99, 102, 241, 0.08)', + }, + }, + }, + }, + MuiTextField: { + styleOverrides: { + root: { + '& .MuiOutlinedInput-root': { + borderRadius: '8px', + backgroundColor: 'rgba(255, 255, 255, 0.02)', + '&:hover .MuiOutlinedInput-notchedOutline': { + borderColor: '#6366f1', + }, + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: '#6366f1', + borderWidth: '2px', + }, + }, + }, + }, + }, + MuiCard: { + styleOverrides: { + root: { + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(99, 102, 241, 0.1)', + backdropFilter: 'blur(20px)', + '&:hover': { + border: '1px solid rgba(99, 102, 241, 0.2)', + transform: 'translateY(-2px)', + boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)', + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + backgroundImage: 'none', + backgroundColor: '#1a1a2e', + border: '1px solid rgba(99, 102, 241, 0.1)', + }, + }, + }, + MuiAppBar: { + styleOverrides: { + root: { + background: 'rgba(26, 26, 46, 0.8)', + backdropFilter: 'blur(20px)', + borderBottom: '1px solid rgba(99, 102, 241, 0.1)', + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)', + }, + }, + }, + MuiDrawer: { + styleOverrides: { + paper: { + background: 'linear-gradient(180deg, #1a1a2e 0%, #16213e 100%)', + border: 'none', + borderRight: '1px solid rgba(99, 102, 241, 0.1)', + }, + }, + }, + MuiChip: { + styleOverrides: { + root: { + background: 'rgba(99, 102, 241, 0.1)', + color: '#818cf8', + border: '1px solid rgba(99, 102, 241, 0.2)', + '&:hover': { + background: 'rgba(99, 102, 241, 0.2)', + }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: 'none', + fontWeight: 500, + fontSize: '0.875rem', + '&.Mui-selected': { + color: '#6366f1', + }, + }, + }, + }, + MuiTabs: { + styleOverrides: { + indicator: { + background: 'linear-gradient(90deg, #6366f1, #8b5cf6)', + height: '3px', + borderRadius: '3px', + }, + }, + }, + }, +}); \ No newline at end of file diff --git a/webui/src/app/components/theme/lightTheme.js b/webui/src/app/components/theme/lightTheme.js new file mode 100644 index 0000000..39592b1 --- /dev/null +++ b/webui/src/app/components/theme/lightTheme.js @@ -0,0 +1,279 @@ +'use client' + +import { createTheme } from '@mui/material/styles'; + +export const lightTheme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#6366f1', + light: '#818cf8', + dark: '#4f46e5', + contrastText: '#ffffff', + }, + secondary: { + main: '#f59e0b', + light: '#fbbf24', + dark: '#d97706', + contrastText: '#000000', + }, + background: { + default: '#f8fafc', + paper: '#ffffff', + }, + surface: { + main: '#f1f5f9', + }, + text: { + primary: '#0f172a', + secondary: '#475569', + }, + error: { + main: '#ef4444', + light: '#f87171', + dark: '#dc2626', + }, + warning: { + main: '#f59e0b', + light: '#fbbf24', + dark: '#d97706', + }, + info: { + main: '#06b6d4', + light: '#22d3ee', + dark: '#0891b2', + }, + success: { + main: '#10b981', + light: '#34d399', + dark: '#059669', + }, + divider: '#e2e8f0', + }, + typography: { + fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + h1: { + fontSize: '2.5rem', + fontWeight: 700, + lineHeight: 1.2, + letterSpacing: '-0.025em', + }, + h2: { + fontSize: '2rem', + fontWeight: 600, + lineHeight: 1.3, + letterSpacing: '-0.025em', + }, + h3: { + fontSize: '1.5rem', + fontWeight: 600, + lineHeight: 1.4, + letterSpacing: '-0.015em', + }, + h4: { + fontSize: '1.25rem', + fontWeight: 600, + lineHeight: 1.4, + }, + h5: { + fontSize: '1.125rem', + fontWeight: 600, + lineHeight: 1.5, + }, + h6: { + fontSize: '1rem', + fontWeight: 600, + lineHeight: 1.5, + }, + body1: { + fontSize: '1rem', + lineHeight: 1.6, + fontWeight: 400, + }, + body2: { + fontSize: '0.875rem', + lineHeight: 1.6, + fontWeight: 400, + }, + button: { + fontSize: '0.875rem', + fontWeight: 500, + textTransform: 'none', + letterSpacing: '0.025em', + }, + }, + shape: { + borderRadius: 12, + }, + shadows: [ + 'none', + '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', + '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', + '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', + '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + '0 25px 50px -12px rgba(0, 0, 0, 0.15)', + ], + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + scrollbarWidth: 'thin', + scrollbarColor: '#6366f1 #f1f5f9', + '&::-webkit-scrollbar': { + width: '8px', + }, + '&::-webkit-scrollbar-track': { + background: '#f1f5f9', + }, + '&::-webkit-scrollbar-thumb': { + background: '#6366f1', + borderRadius: '4px', + }, + }, + }, + }, + MuiButton: { + styleOverrides: { + root: { + borderRadius: '8px', + padding: '10px 24px', + fontSize: '0.875rem', + fontWeight: 500, + boxShadow: 'none', + '&:hover': { + boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)', + transform: 'translateY(-1px)', + }, + '&:active': { + transform: 'translateY(0)', + }, + }, + contained: { + background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', + '&:hover': { + background: 'linear-gradient(135deg, #5b21b6 0%, #7c3aed 100%)', + }, + }, + outlined: { + borderWidth: '1.5px', + '&:hover': { + borderWidth: '1.5px', + backgroundColor: 'rgba(99, 102, 241, 0.08)', + }, + }, + }, + }, + MuiTextField: { + styleOverrides: { + root: { + '& .MuiOutlinedInput-root': { + borderRadius: '8px', + backgroundColor: 'rgba(241, 245, 249, 0.5)', + '&:hover .MuiOutlinedInput-notchedOutline': { + borderColor: '#6366f1', + }, + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: '#6366f1', + borderWidth: '2px', + }, + }, + }, + }, + }, + MuiCard: { + styleOverrides: { + root: { + background: 'linear-gradient(145deg, #ffffff 0%, #f8fafc 100%)', + border: '1px solid rgba(99, 102, 241, 0.1)', + backdropFilter: 'blur(20px)', + '&:hover': { + border: '1px solid rgba(99, 102, 241, 0.2)', + transform: 'translateY(-2px)', + boxShadow: '0 20px 40px rgba(99, 102, 241, 0.15)', + }, + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + backgroundImage: 'none', + backgroundColor: '#ffffff', + border: '1px solid rgba(99, 102, 241, 0.1)', + }, + }, + }, + MuiAppBar: { + styleOverrides: { + root: { + background: 'rgba(255, 255, 255, 0.8)', + backdropFilter: 'blur(20px)', + borderBottom: '1px solid rgba(99, 102, 241, 0.1)', + boxShadow: '0 8px 32px rgba(99, 102, 241, 0.1)', + color: '#0f172a', + }, + }, + }, + MuiDrawer: { + styleOverrides: { + paper: { + background: 'linear-gradient(180deg, #ffffff 0%, #f8fafc 100%)', + border: 'none', + borderRight: '1px solid rgba(99, 102, 241, 0.1)', + }, + }, + }, + MuiChip: { + styleOverrides: { + root: { + background: 'rgba(99, 102, 241, 0.1)', + border: '1px solid rgba(99, 102, 241, 0.2)', + '&:hover': { + background: 'rgba(99, 102, 241, 0.2)', + }, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: 'none', + fontWeight: 500, + fontSize: '0.875rem', + '&.Mui-selected': { + color: '#6366f1', + }, + }, + }, + }, + MuiTabs: { + styleOverrides: { + indicator: { + background: 'linear-gradient(90deg, #6366f1, #8b5cf6)', + height: '3px', + borderRadius: '3px', + }, + }, + }, + }, +}); \ No newline at end of file diff --git a/webui/src/app/layout.js b/webui/src/app/layout.js index 799eaf5..3e9fc1e 100644 --- a/webui/src/app/layout.js +++ b/webui/src/app/layout.js @@ -1,14 +1,27 @@ -export const metadata = { - title: 'Stripe Payment Demo', - description: 'Stripe payment integration demo with Next.js App Router', -} +import { Inter } from 'next/font/google'; +import MuiThemeProvider from './components/theme/MuiThemeProvider'; +import { ThemeContextProvider } from './components/theme/ThemeContext'; +import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter"; -export default function RootLayout({ children }) { +const inter = Inter({ subsets: ['latin'] }); + +export const metadata = { + title: 'Imprink', + description: 'Turn your dreams into colorful realities!', +}; + +export default function RootLayout({children}) { return ( - - {children} - + + + + + {children} + + + + - ) + ); } \ No newline at end of file diff --git a/webui/src/app/page.js b/webui/src/app/page.js index e14237a..e3f550b 100644 --- a/webui/src/app/page.js +++ b/webui/src/app/page.js @@ -1,143 +1,105 @@ 'use client'; -import { useState } from 'react'; -import { loadStripe } from '@stripe/stripe-js'; -import { Elements } from '@stripe/react-stripe-js'; -import PaymentForm from './components/PaymentForm'; -import './globals.css'; - -const stripePromise = loadStripe(''); - -const products = [ - { id: '1', name: 'Premium Widget', price: 2999, description: 'High-quality widget for professionals' }, - { id: '2', name: 'Standard Widget', price: 1999, description: 'Reliable widget for everyday use' }, - { id: '3', name: 'Basic Widget', price: 999, description: 'Entry-level widget for beginners' } -]; - -export default function Home() { - const [selectedProduct, setSelectedProduct] = useState(null); - const [clientSecret, setClientSecret] = useState(''); - const [orderId, setOrderId] = useState(''); - const [loading, setLoading] = useState(false); - - const handleProductSelect = async (product) => { - setLoading(true); - setSelectedProduct(product); - - const newOrderId = Math.floor(Math.random() * 10000).toString(); - setOrderId(newOrderId); - - try { - const response = await fetch('https://impr.ink/api/stripe/create-payment-intent', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - amount: product.price, - orderId: newOrderId - }), - }); - - const data = await response.json(); - - if (data.clientSecret) { - setClientSecret(data.clientSecret); - } else { - console.error('Error creating payment intent:', data.error); - } - } catch (error) { - console.error('Error:', error); - } finally { - setLoading(false); - } - }; - - const handlePaymentSuccess = () => { - setSelectedProduct(null); - setClientSecret(''); - setOrderId(''); - }; - - const handleBackToProducts = () => { - setSelectedProduct(null); - setClientSecret(''); - setOrderId(''); - }; - - const appearance = { - theme: 'stripe', - variables: { - colorPrimary: '#0570de', - colorBackground: '#ffffff', - colorText: '#30313d', - colorDanger: '#df1b41', - fontFamily: 'Ideal Sans, system-ui, sans-serif', - spacingUnit: '2px', - borderRadius: '4px', - }, - }; - - const options = { - clientSecret, - appearance, - }; +import { + Box, + Container, + Typography, + Button, + Card, + CardContent, + TextField, + Chip, + AppBar, + Toolbar, + Grid, +} from '@mui/material'; +import ThemeToggleButton from './components/ThemeToggleButton'; +export default function HomePage() { return ( -
-
-

🛍️ Stripe Payment Demo

-

Select a product to purchase

-
+ <> + + + + Modern App + + + + + - {!selectedProduct ? ( -
-

Products

-
- {products.map((product) => ( -
-

{product.name}

-

{product.description}

-

${(product.price / 100).toFixed(2)}

- -
- ))} -
-
- ) : ( -
-
-

Order Summary

-
-

Product: {selectedProduct.name}

-

Order ID: {orderId}

-

Amount: ${(selectedProduct.price / 100).toFixed(2)}

-
-
+ + + + Welcome to Modern UI + + + Experience the beauty of modern Material-UI theming + + + + + + - {clientSecret && ( - - - - )} + + + + + + Beautiful Cards + + + Cards with modern gradients, hover effects, and perfect spacing that make your content shine. + + + + + + + + + + - -
- )} -
+ + + + + Form Elements + + + + + + + + + + + + + ); } \ No newline at end of file diff --git a/webui/src/lib/api.js b/webui/src/lib/api.js index 0b1cb3c..8cf8595 100644 --- a/webui/src/lib/api.js +++ b/webui/src/lib/api.js @@ -5,4 +5,18 @@ const api = axios.create({ withCredentials: true, }); +api.interceptors.request.use((config) => { + if (typeof window !== 'undefined') { + const token = localStorage.getItem('access_token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } else { + console.log("Token not found in localStorage, please auth!"); + } + } + return config; +}, (error) => { + return Promise.reject(error); +}); + export default api; \ No newline at end of file