From d4f01c3f73cb7cc9eef4833cc53a4c68e72b2164 Mon Sep 17 00:00:00 2001
From: lumijiez <59575049+lumijiez@users.noreply.github.com>
Date: Mon, 23 Jun 2025 20:39:46 +0300
Subject: [PATCH] Add MUI theming and an axios interceptor
---
.../Imprink.Application.csproj | 4 +
webui/auth0-templates/page.js | 143 +++++++++
webui/next.config.mjs | 12 +-
webui/package-lock.json | 38 +++
webui/package.json | 3 +
webui/src/app/components/ThemeToggleButton.js | 25 ++
.../app/components/theme/MuiThemeProvider.js | 18 ++
.../src/app/components/theme/ThemeContext.js | 27 ++
webui/src/app/components/theme/darkTheme.js | 279 ++++++++++++++++++
webui/src/app/components/theme/lightTheme.js | 279 ++++++++++++++++++
webui/src/app/layout.js | 31 +-
webui/src/app/page.js | 230 ++++++---------
webui/src/lib/api.js | 14 +
13 files changed, 949 insertions(+), 154 deletions(-)
create mode 100644 webui/auth0-templates/page.js
create mode 100644 webui/src/app/components/ThemeToggleButton.js
create mode 100644 webui/src/app/components/theme/MuiThemeProvider.js
create mode 100644 webui/src/app/components/theme/ThemeContext.js
create mode 100644 webui/src/app/components/theme/darkTheme.js
create mode 100644 webui/src/app/components/theme/lightTheme.js
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 (
+
+
+
+ {!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 (
-
-
+ <>
+
+
+
+ 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