mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
update
This commit is contained in:
@@ -51,7 +51,9 @@ func main() {
|
||||
// Optional migrations for eshop-specific tables only (will be added later)
|
||||
runMigrations, _ := strconv.ParseBool(os.Getenv("RUN_MIGRATIONS"))
|
||||
if runMigrations {
|
||||
log.Println("[eshop] RUN_MIGRATIONS is true, but no eshop-specific migrations are defined yet")
|
||||
if err := database.MigrateDB(dbInstance); err != nil {
|
||||
log.Fatalf("[eshop] Failed to run database migrations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Gin router with a similar hardened stack as the main backend
|
||||
|
||||
@@ -15,9 +15,6 @@ COPY . .
|
||||
|
||||
# Build app
|
||||
ENV NODE_ENV=production
|
||||
ENV GENERATE_SOURCEMAP=false
|
||||
ENV CI=true
|
||||
ENV TSC_COMPILE_ON_ERROR=true
|
||||
|
||||
RUN npm run build
|
||||
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
<body>
|
||||
<noscript>Pro zobrazení e-shopu je potřeba povolit JavaScript.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+1627
-14329
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,11 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
@@ -15,18 +16,22 @@
|
||||
"@stripe/react-stripe-js": "^5.4.1",
|
||||
"@stripe/stripe-js": "^8.5.3",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.13.6",
|
||||
"dompurify": "^3.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-router-dom": "^6.30.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-router-dom": "^5.3.3"
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getProduct, addToCart, EshopProductVariant } from '../services/eshopApi';
|
||||
import { Box, Heading, Text, Image, Badge, HStack, VStack, Button, Select, useToast } from '@chakra-ui/react';
|
||||
import { sanitizeRichHtml } from '../utils/sanitizeHtml';
|
||||
|
||||
const formatPrice = (cents: number, currency: string) => {
|
||||
const value = cents / 100;
|
||||
@@ -20,6 +21,7 @@ const ProductDetailPage: React.FC = () => {
|
||||
enabled: !!slug,
|
||||
});
|
||||
const [variantId, setVariantId] = useState<number | undefined>(undefined);
|
||||
const descriptionHtml = React.useMemo(() => sanitizeRichHtml(data?.description_html), [data?.description_html]);
|
||||
|
||||
if (isLoading) return <Text>Načítání produktu…</Text>;
|
||||
if (isError || !data) return <Text>Produkt nebyl nalezen.</Text>;
|
||||
@@ -76,9 +78,9 @@ const ProductDetailPage: React.FC = () => {
|
||||
<Button colorScheme="blue" onClick={handleAddToCart} maxW="260px">
|
||||
Přidat do košíku
|
||||
</Button>
|
||||
{data.description_html && (
|
||||
{descriptionHtml && (
|
||||
<Box mt={4} fontSize="sm" color="gray.700">
|
||||
<div dangerouslySetInnerHTML={{ __html: data.description_html }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: descriptionHtml }} />
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const ADDITIONAL_TAGS = ['iframe'];
|
||||
const ADDITIONAL_ATTRS = ['allow', 'allowfullscreen', 'class', 'rel', 'style', 'target'];
|
||||
|
||||
export function sanitizeRichHtml(html: string | null | undefined): string {
|
||||
const sanitized = DOMPurify.sanitize(html ?? '', {
|
||||
USE_PROFILES: { html: true },
|
||||
ADD_TAGS: ADDITIONAL_TAGS,
|
||||
ADD_ATTR: ADDITIONAL_ATTRS,
|
||||
});
|
||||
|
||||
if (typeof window === 'undefined' || !sanitized) {
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
const template = window.document.createElement('template');
|
||||
template.innerHTML = sanitized;
|
||||
|
||||
template.content.querySelectorAll<HTMLAnchorElement>('a[target="_blank"]').forEach((anchor) => {
|
||||
const rel = new Set((anchor.getAttribute('rel') ?? '').split(/\s+/).filter(Boolean));
|
||||
rel.add('noopener');
|
||||
rel.add('noreferrer');
|
||||
anchor.setAttribute('rel', Array.from(rel).join(' '));
|
||||
});
|
||||
|
||||
return template.innerHTML;
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": "src",
|
||||
"types": []
|
||||
"types": ["vite/client", "vitest/globals"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
function buildProcessEnv(mode: string, env: Record<string, string>, base: string) {
|
||||
const processEnv: Record<string, string> = {
|
||||
NODE_ENV: mode === 'production' ? 'production' : mode,
|
||||
PUBLIC_URL: base === '/' ? '' : base.replace(/\/$/, ''),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (key.startsWith('REACT_APP_')) {
|
||||
processEnv[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return processEnv;
|
||||
}
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
const base = env.PUBLIC_URL ? (env.PUBLIC_URL.endsWith('/') ? env.PUBLIC_URL : `${env.PUBLIC_URL}/`) : '/';
|
||||
const processEnv = buildProcessEnv(mode, env, base);
|
||||
|
||||
return {
|
||||
base,
|
||||
publicDir: 'public',
|
||||
plugins: [react()],
|
||||
define: {
|
||||
'process.env': JSON.stringify(processEnv),
|
||||
global: 'globalThis',
|
||||
},
|
||||
build: {
|
||||
outDir: 'build',
|
||||
assetsDir: 'static',
|
||||
emptyOutDir: true,
|
||||
sourcemap: false,
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 3100,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8082',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
preview: {
|
||||
host: '0.0.0.0',
|
||||
port: 4174,
|
||||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user