Compare commits

..

9 Commits

Author SHA1 Message Date
sebseb7
9df5642a6e refactor: reimplement category page display with a recursive tree structure and add configurator image. 2025-12-13 02:14:20 +01:00
sebseb7
a50dd086c3 feat: Include 'Kategorien' in Nginx location regex for HTML content. 2025-12-06 21:02:22 +01:00
sebseb7
e88370ff3e feat: add Categories page with refined layout and translation support 2025-12-06 14:29:33 +01:00
sebseb7
5d3e0832fe doc 2025-12-01 13:11:24 +01:00
sebseb7
3347ba2754 Add missing auth translations and update components to use i18n keys
- Added new translation keys to de/auth.js:
  - resetPassword section (title, button, success, invalidToken, error, emailSent, emailError)
  - errors section (fillAllFields, invalidEmail, passwordsNotMatch, passwordsNotMatchShort, enterEmail, loginFailed, registerFailed, googleLoginFailed, emailExists)
  - success section (registerComplete)
  - newPassword, backToHome keys

- Updated ResetPassword.js to use translation keys instead of hardcoded German strings
- Updated LoginComponent.js to use translation keys instead of hardcoded German strings
- translate-i18n.js generated translations for other languages
2025-12-01 13:02:03 +01:00
sebseb7
013a38ca98 fix: update caniuse-lite version and enhance SPA routing for resetPassword
- Updated caniuse-lite to version 1.0.30001757 in package-lock.json for improved compatibility.
- Added functionality to copy index.html to the resetPassword directory for better SPA routing in production environments.
2025-12-01 12:50:41 +01:00
sebseb7
2d6c8ff25f feat(Orders): add tracking shipment link and update translations
- Implemented a tracking shipment link in the OrdersTab component for DHL deliveries, enhancing user experience by allowing direct access to shipment tracking.
- Added 'trackShipment' translation key across multiple languages to support the new feature.
- Updated existing translations for consistency and improved localization in the orders module.
2025-11-29 14:05:59 +01:00
sebseb7
d2ac8d3fc1 feat(Orders): update order status translations and colors
- Refactored order status translations to use English keys for 'new', 'shipped', and 'delivered'.
- Updated corresponding status colors to maintain consistency in the UI.
- Adjusted the display logic to reflect the new status keys in the OrdersTab component.
2025-11-29 13:45:39 +01:00
sebseb7
8928b3f283 feat(Orders): add 'paid' status and update translations across multiple languages
- Introduced 'paid' status to the orders system, enhancing order tracking capabilities.
- Updated translations for 'paid' status in various languages including German, Spanish, French, and more.
- Adjusted related UI components to reflect the new status and ensure consistent user experience across the application.
2025-11-29 13:21:35 +01:00
59 changed files with 1729 additions and 606 deletions

154
docs/nginx.conf Normal file
View File

@@ -0,0 +1,154 @@
server {
client_max_body_size 64M;
listen 443 ssl;
http2 on;
server_name example.de;
ssl_certificate /etc/letsencrypt/live/example.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.de/privkey.pem;
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types
text/css
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
image/svg+xml;
index index.html;
root /example/dist;
error_log logs/error.log info;
access_log logs/access.log combined;
location /socket.io/ {
proxy_pass http://localhost:9303/socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 3600s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
send_timeout 3600s;
proxy_buffering off;
proxy_cache off;
keepalive_timeout 65;
keepalive_requests 100;
}
location /api/ {
proxy_pass http://localhost:9303/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header User-Agent $http_user_agent;
proxy_set_header Content-Type $content_type;
proxy_set_header Content-Length $content_length;
proxy_set_header X-API-Key $http_x_api_key;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_buffering off;
client_max_body_size 10M;
}
location ^~ /Kategorie/ {
types {}
default_type text/html;
}
location ^~ /Artikel/ {
types {}
default_type text/html;
}
location = /sitemap.xml {
types {}
default_type application/xml;
}
location ~ ^/(datenschutz|impressum|batteriegesetzhinweise|widerrufsrecht|sitemap|agb|Kategorien|Konfigurator|404|profile|resetPassword|thc-test|filiale|aktionen|presseverleih|payment/success)(/|$) {
types {}
default_type text/html;
}
location = /404 {
error_page 404 =404 /404-big.html;
return 404;
}
location = /404-big.html {
internal;
alias /home/seb/src/growheads_de/dist/404;
default_type text/html;
}
error_page 404 /404.html;
location = /404.html {
internal;
default_type text/html;
return 404 '<!doctype html><html><body>
<script>
if (!navigator.userAgent.includes("bot")) { location.href="/404"; }
</script>
</body></html>';
}
location ~* \.(js|css)\?.*$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
}
location ~* \.(js|css)$ {
if ($uri ~ "\.[a-f0-9]{7,}\.(js|css)$") {
expires 1y;
add_header Cache-Control "public, immutable";
break;
}
expires 1d;
add_header Cache-Control "public";
add_header Vary Accept-Encoding;
}
location ~* \.(ttf|otf|woff|woff2|eot)$ {
expires 1y;
add_header Cache-Control "public";
add_header Access-Control-Allow-Origin "*";
}
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 1y;
add_header Cache-Control "public";
add_header Vary Accept-Encoding;
}
location = /prerender.css {
expires 1w;
add_header Cache-Control "public";
add_header Vary Accept-Encoding;
}
location /assets/ {
expires 1y;
add_header Cache-Control "public";
add_header Vary Accept-Encoding;
}
}

6
package-lock.json generated
View File

@@ -4554,9 +4554,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
"version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"dev": true,
"funding": [
{

View File

@@ -28,7 +28,7 @@ class CategoryService {
const cacheKey = `${categoryId}_${language}`;
return null;
}
async get(categoryId, language = "de") {
const cacheKey = `${categoryId}_${language}`;
return null;
@@ -159,6 +159,7 @@ const Batteriegesetzhinweise =
const Widerrufsrecht = require("./src/pages/Widerrufsrecht.js").default;
const Sitemap = require("./src/pages/Sitemap.js").default;
const PrerenderSitemap = require("./src/PrerenderSitemap.js").default;
const PrerenderCategoriesPage = require("./src/PrerenderCategoriesPage.js").default;
const AGB = require("./src/pages/AGB.js").default;
const NotFound404 = require("./src/pages/NotFound404.js").default;
@@ -189,7 +190,7 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
try {
const productDetails = await fetchProductDetails(workerSocket, productSeoName);
const actualSeoName = productDetails.product.seoName || productSeoName;
const productComponent = React.createElement(PrerenderProduct, {
productData: productDetails,
@@ -205,7 +206,7 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
}, shopConfig.baseUrl, shopConfig);
// Get category info from categoryMap if available
const categoryInfo = productDetails.product.categoryId ? categoryMap[productDetails.product.categoryId] : null;
const jsonLdScript = generateProductJsonLd({
...productDetails.product,
seoName: actualSeoName,
@@ -234,9 +235,9 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
success,
workerId
};
results.push(result);
// Call progress callback if provided
if (progressCallback) {
progressCallback(result);
@@ -252,14 +253,14 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
error: error.message,
workerId
};
results.push(result);
// Call progress callback if provided
if (progressCallback) {
progressCallback(result);
}
setTimeout(processNextProduct, 25);
}
};
@@ -291,16 +292,16 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
const barLength = 30;
const filledLength = Math.round((barLength * current) / total);
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
// @note Single line progress update to prevent flickering
const truncatedName = productName ? ` - ${productName.substring(0, 25)}${productName.length > 25 ? '...' : ''}` : '';
// Build worker stats on one line
let workerStats = '';
for (let i = 0; i < Math.min(maxWorkers, 8); i++) { // Limit to 8 workers to fit on screen
workerStats += `W${i + 1}:${workerCounts[i]}/${workerSuccess[i]} `;
}
// Single line update without complex cursor movements
process.stdout.write(`\r [${bar}] ${percentage}% (${current}/${total})${truncatedName}\n ${workerStats}${current < total ? '\x1b[1A' : '\n'}`);
};
@@ -308,26 +309,26 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
// Split products among workers
const productsPerWorker = Math.ceil(allProductsArray.length / maxWorkers);
const workerPromises = [];
// Initial progress bar
updateProgressBar(0, totalProducts);
for (let i = 0; i < maxWorkers; i++) {
const start = i * productsPerWorker;
const end = Math.min(start + productsPerWorker, allProductsArray.length);
const productsForWorker = allProductsArray.slice(start, end);
if (productsForWorker.length > 0) {
const promise = renderProductWorker(productsForWorker, i + 1, (result) => {
// Progress callback - called each time a product is completed
completedProducts++;
progressResults.push(result);
lastProductName = result.productName;
// Update per-worker counters
const workerIndex = result.workerId - 1; // Convert to 0-based index
workerCounts[workerIndex]++;
if (result.success) {
totalSuccessCount++;
workerSuccess[workerIndex]++;
@@ -335,11 +336,11 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
// Don't log errors immediately to avoid interfering with progress bar
// Errors will be shown after completion
}
// Update progress bar with worker stats
updateProgressBar(completedProducts, totalProducts, lastProductName);
}, categoryMap);
workerPromises.push(promise);
}
}
@@ -347,10 +348,10 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
try {
// Wait for all workers to complete
await Promise.all(workerPromises);
// Ensure final progress update
updateProgressBar(totalProducts, totalProducts, lastProductName);
// Show any errors that occurred
const errorResults = progressResults.filter(r => !r.success && r.error);
if (errorResults.length > 0) {
@@ -359,7 +360,7 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
console.log(` - ${result.productSeoName}: ${result.error}`);
});
}
return totalSuccessCount;
} catch (error) {
console.error('Error in parallel rendering:', error);
@@ -422,6 +423,14 @@ const renderApp = async (categoryData, socket) => {
process.exit(1);
}
// Copy index.html to resetPassword (no file extension) for SPA routing
if (config.isProduction) {
const indexPath = path.resolve(__dirname, config.outputDir, "index.html");
const resetPasswordPath = path.resolve(__dirname, config.outputDir, "resetPassword");
fs.copyFileSync(indexPath, resetPasswordPath);
console.log(`✅ Copied index.html to ${resetPasswordPath}`);
}
// Render static pages
console.log("\n📄 Rendering static pages...");
@@ -457,6 +466,13 @@ const renderApp = async (categoryData, socket) => {
description: "Sitemap page",
needsCategoryData: true,
},
{
component: PrerenderCategoriesPage,
path: "/Kategorien",
filename: "Kategorien",
description: "Categories page",
needsCategoryData: true,
},
{ component: AGB, path: "/agb", filename: "agb", description: "AGB page" },
{ component: NotFound404, path: "/404", filename: "404", description: "404 Not Found page" },
{
@@ -551,8 +567,7 @@ const renderApp = async (categoryData, socket) => {
try {
productData = await fetchCategoryProducts(socket, category.id);
console.log(
` ✅ Found ${
productData.products ? productData.products.length : 0
` ✅ Found ${productData.products ? productData.products.length : 0
} products`
);
@@ -636,7 +651,7 @@ const renderApp = async (categoryData, socket) => {
const totalProducts = allProducts.size;
const numCPUs = os.cpus().length;
const maxWorkers = Math.min(numCPUs, totalProducts, 8); // Cap at 8 workers to avoid overwhelming the server
// Create category map for breadcrumbs
const categoryMap = {};
allCategories.forEach(category => {
@@ -645,11 +660,11 @@ const renderApp = async (categoryData, socket) => {
seoName: category.seoName
};
});
console.log(
`\n📦 Rendering ${totalProducts} individual product pages using ${maxWorkers} parallel workers...`
);
const productPagesRendered = await renderProductsInParallel(
Array.from(allProducts),
maxWorkers,
@@ -701,21 +716,21 @@ const renderApp = async (categoryData, socket) => {
// Generate products.xml (Google Shopping feed) in parallel to sitemap.xml
if (allProductsData.length > 0) {
console.log("\n🛒 Generating products.xml (Google Shopping feed)...");
try {
const productsXml = generateProductsXml(allProductsData, shopConfig.baseUrl, shopConfig);
const productsXmlPath = path.resolve(__dirname, config.outputDir, "products.xml");
// Write with explicit UTF-8 encoding
fs.writeFileSync(productsXmlPath, productsXml, { encoding: 'utf8' });
console.log(`✅ products.xml generated: ${productsXmlPath}`);
console.log(` - Products included: ${allProductsData.length}`);
console.log(` - Format: Google Shopping RSS 2.0 feed`);
console.log(` - Encoding: UTF-8`);
console.log(` - Includes: title, description, price, availability, images`);
// Verify the file is valid UTF-8
try {
const verification = fs.readFileSync(productsXmlPath, 'utf8');
@@ -723,18 +738,18 @@ const renderApp = async (categoryData, socket) => {
} catch (verifyError) {
console.log(` - File verification: ⚠️ ${verifyError.message}`);
}
// Validate XML against Google Shopping schema
try {
const ProductsXmlValidator = require('./scripts/validate-products-xml.cjs');
const validator = new ProductsXmlValidator(productsXmlPath);
const validationResults = await validator.validate();
if (validationResults.valid) {
console.log(` - Schema validation: ✅ Valid Google Shopping RSS 2.0`);
} else {
console.log(` - Schema validation: ⚠️ ${validationResults.summary.errorCount} errors, ${validationResults.summary.warningCount} warnings`);
// Show first few errors for quick debugging
if (validationResults.errors.length > 0) {
console.log(` - First error: ${validationResults.errors[0].message}`);
@@ -743,7 +758,7 @@ const renderApp = async (categoryData, socket) => {
} catch (validationError) {
console.log(` - Schema validation: ⚠️ Validation failed: ${validationError.message}`);
}
} catch (error) {
console.error(`❌ Error generating products.xml: ${error.message}`);
console.log("\n⚠ Skipping products.xml generation due to errors");
@@ -754,18 +769,18 @@ const renderApp = async (categoryData, socket) => {
// Generate llms.txt (LLM-friendly markdown sitemap) and category-specific files
console.log("\n🤖 Generating LLM sitemap files...");
try {
// Generate main llms.txt overview file
const llmsTxt = generateLlmsTxt(allCategories, allProductsData, shopConfig.baseUrl, shopConfig);
const llmsTxtPath = path.resolve(__dirname, config.outputDir, "llms.txt");
fs.writeFileSync(llmsTxtPath, llmsTxt, { encoding: 'utf8' });
console.log(`✅ Main llms.txt generated: ${llmsTxtPath}`);
console.log(` - Static pages: 8 pages`);
console.log(` - Categories: ${allCategories.length} with links to detailed files`);
console.log(` - File size: ${Math.round(llmsTxt.length / 1024)}KB`);
// Group products by category for category-specific files
const productsByCategory = {};
allProductsData.forEach((product) => {
@@ -775,20 +790,20 @@ const renderApp = async (categoryData, socket) => {
}
productsByCategory[categoryId].push(product);
});
// Generate category-specific LLM files with pagination
let categoryFilesGenerated = 0;
let totalCategoryProducts = 0;
let totalPaginatedFiles = 0;
for (const category of allCategories) {
if (category.seoName) {
const categoryProducts = productsByCategory[category.id] || [];
const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-');
// Generate all paginated files for this category
const categoryPages = generateAllCategoryLlmsPages(category, categoryProducts, shopConfig.baseUrl, shopConfig);
// Write each paginated file
for (const page of categoryPages) {
const pagePath = path.resolve(__dirname, config.outputDir, page.fileName);
@@ -806,22 +821,22 @@ const renderApp = async (categoryData, socket) => {
console.log(` ✅ llms-${categorySlug}-page-*.txt - ${categoryProducts.length} products across ${pageCount} pages (${Math.round(totalSize / 1024)}KB total)`);
console.log(` 📋 ${productList.fileName} - ${productList.productCount} products (${Math.round(productList.content.length / 1024)}KB)`);
categoryFilesGenerated++;
totalCategoryProducts += categoryProducts.length;
}
}
console.log(` 📄 Total paginated files generated: ${totalPaginatedFiles}`);
console.log(` 📦 Total products across all categories: ${totalCategoryProducts}`);
try {
const verification = fs.readFileSync(llmsTxtPath, 'utf8');
console.log(` - File verification: ✅ All files valid UTF-8`);
} catch (verifyError) {
console.log(` - File verification: ⚠️ ${verifyError.message}`);
}
} catch (error) {
console.error(`❌ Error generating LLM sitemap files: ${error.message}`);
console.log("\n⚠ Skipping LLM sitemap generation due to errors");
@@ -841,7 +856,7 @@ const fetchCategoryDataAndRender = () => {
const socket = io(socketUrl, {
path: "/socket.io/",
transports: [ "websocket"],
transports: ["websocket"],
reconnection: false,
timeout: 10000,
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

View File

@@ -7,6 +7,7 @@ const imagesToConvert = [
{ src: 'seeds.jpg', dest: 'seeds.avif' },
{ src: 'cutlings.jpg', dest: 'cutlings.avif' },
{ src: 'gg.png', dest: 'gg.avif' },
{ src: 'konfigurator.png', dest: 'konfigurator.avif' },
{ src: 'maps.png', dest: 'maps.avif' }
];
@@ -18,6 +19,7 @@ const run = async () => {
const inputPath = path.join(imagesDir, image.src);
const outputPath = path.join(imagesDir, image.dest);
console.log('d');
if (!fs.existsSync(inputPath)) {
console.warn(`⚠️ Input file not found: ${inputPath}`);
continue;
@@ -45,8 +47,8 @@ const run = async () => {
hasError = true;
}
} else {
// Silent skip if already up to date to keep logs clean, or use verbose flag
// console.log(`Skipping ${image.src} (already up to date)`);
// Silent skip if already up to date to keep logs clean, or use verbose flag
// console.log(`Skipping ${image.src} (already up to date)`);
}
}
@@ -55,4 +57,5 @@ const run = async () => {
}
};
console.log('dsfs');
run();

View File

@@ -50,6 +50,7 @@ const Datenschutz = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/D
const AGB = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/AGB.js"));
//const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js")); <Route path="/404" element={<NotFound404 />} />
const Sitemap = lazy(() => import(/* webpackChunkName: "sitemap" */ "./pages/Sitemap.js"));
const CategoriesPage = lazy(() => import(/* webpackChunkName: "categories" */ "./pages/CategoriesPage.js"));
const Impressum = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Impressum.js"));
const Batteriegesetzhinweise = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Batteriegesetzhinweise.js"));
const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Widerrufsrecht.js"));
@@ -260,19 +261,19 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
{/* Category page - Render Content in parallel */}
<Route
path="/Kategorie/:categoryId"
element={<Content/>}
element={<Content />}
/>
{/* Single product page */}
<Route
path="/Artikel/:seoName"
element={<ProductDetail/>}
element={<ProductDetail />}
/>
{/* Search page - Render Content in parallel */}
<Route path="/search" element={<Content/>} />
<Route path="/search" element={<Content />} />
{/* Profile page */}
<Route path="/profile" element={<ProfilePage/>} />
<Route path="/profile" element={<ProfilePage />} />
{/* Payment success page for Mollie redirects */}
<Route path="/payment/success" element={<PaymentSuccess />} />
@@ -280,22 +281,23 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
{/* Reset password page */}
<Route
path="/resetPassword"
element={<ResetPassword/>}
element={<ResetPassword />}
/>
{/* Admin page */}
<Route path="/admin" element={<AdminPage/>} />
<Route path="/admin" element={<AdminPage />} />
{/* Admin Users page */}
<Route path="/admin/users" element={<UsersPage/>} />
<Route path="/admin/users" element={<UsersPage />} />
{/* Admin Server Logs page */}
<Route path="/admin/logs" element={<ServerLogsPage/>} />
<Route path="/admin/logs" element={<ServerLogsPage />} />
{/* Legal pages */}
<Route path="/datenschutz" element={<Datenschutz />} />
<Route path="/agb" element={<AGB />} />
<Route path="/sitemap" element={<Sitemap />} />
<Route path="/Kategorien" element={<CategoriesPage />} />
<Route path="/impressum" element={<Impressum />} />
<Route
path="/batteriegesetzhinweise"
@@ -304,7 +306,7 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
{/* Grow Tent Configurator */}
<Route path="/Konfigurator" element={<GrowTentKonfigurator/>} />
<Route path="/Konfigurator" element={<GrowTentKonfigurator />} />
{/* Separate pages that are truly different */}
<Route path="/presseverleih" element={<PresseverleihPage />} />
@@ -457,11 +459,11 @@ const App = () => {
<ProductContextProvider>
<CategoryContextProvider>
<CssBaseline />
<AppContent
currentTheme={currentTheme}
dynamicTheme={dynamicTheme}
onThemeChange={handleThemeChange}
/>
<AppContent
currentTheme={currentTheme}
dynamicTheme={dynamicTheme}
onThemeChange={handleThemeChange}
/>
</CategoryContextProvider>
</ProductContextProvider>
</ThemeProvider>

View File

@@ -0,0 +1,118 @@
import React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import LegalPage from './pages/LegalPage.js';
import CategoryBox from './components/CategoryBox.js';
const PrerenderCategoriesPage = ({ categoryData }) => {
// Helper function to recursively collect all categories from the tree
const collectAllCategories = (categoryNode, categories = [], level = 0) => {
if (!categoryNode) return categories;
// Add current category (skip root category 209)
if (categoryNode.id !== 209 && categoryNode.seoName) {
categories.push({
id: categoryNode.id,
name: categoryNode.name,
seoName: categoryNode.seoName,
level: level
});
}
// Recursively add children
if (categoryNode.children) {
for (const child of categoryNode.children) {
collectAllCategories(child, categories, level + 1);
}
}
return categories;
};
// The categoryData passed prop is the root tree (id: 209)
const rootTree = categoryData;
const renderLevel1Section = (l1Node) => {
// Collect all descendants (excluding the L1 node itself, which collectAllCategories would include first)
const descendants = collectAllCategories(l1Node).slice(1);
return (
<Paper
key={l1Node.id}
elevation={1}
sx={{
p: 2,
mb: 3,
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { xs: 'flex-start', md: 'flex-start' },
gap: 3
}}
>
{/* Level 1 Header/Box */}
<Box sx={{
minWidth: '150px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1
}}>
<CategoryBox
id={l1Node.id}
name={l1Node.name}
seoName={l1Node.seoName}
sx={{
boxShadow: 4,
width: '150px',
height: '150px'
}}
/>
</Box>
{/* Descendants area */}
<Box sx={{ flex: 1 }}>
<Box sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2
}}>
{descendants.map((cat) => (
<CategoryBox
key={cat.id}
id={cat.id}
name={cat.name}
seoName={cat.seoName}
sx={{
width: '100px',
height: '100px',
minWidth: '100px',
minHeight: '100px',
boxShadow: 1,
fontSize: '0.9rem'
}}
/>
))}
</Box>
</Box>
</Paper>
);
};
const content = (
<Box>
<Box>
{rootTree && rootTree.children && rootTree.children.map((child) => (
renderLevel1Section(child)
))}
{(!rootTree || !rootTree.children || rootTree.children.length === 0) && (
<Typography>Keine Kategorien gefunden.</Typography>
)}
</Box>
</Box>
);
return <LegalPage title="Kategorien" content={content} />;
};
export default PrerenderCategoriesPage;

View File

@@ -352,6 +352,9 @@ class Footer extends Component {
<Typography variant="body2" sx={{ fontSize: { xs: '11px', md: '14px' }, lineHeight: 1.5 }}>
© {new Date().getFullYear()} <StyledDomainLink href="https://growheads.de" target="_blank" rel="noopener noreferrer">GrowHeads.de</StyledDomainLink>
</Typography>
<Typography variant="body2" sx={{ fontSize: { xs: '9px', md: '9px' }, lineHeight: 1.5, mt: 1 }}>
<StyledDomainLink href="https://telegraf.growheads.de" target="_blank" rel="noreferrer">Telegraf - sicherer Chat mit unseren Mitarbeitern</StyledDomainLink>
</Typography>
</Box>
</Stack>
</Box>

View File

@@ -175,12 +175,12 @@ export class LoginComponent extends Component {
const { location, navigate } = this.props;
if (!email || !password) {
this.setState({ error: 'Bitte füllen Sie alle Felder aus' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.fillAllFields') : 'Bitte füllen Sie alle Felder aus' });
return;
}
if (!this.validateEmail(email)) {
this.setState({ error: 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.invalidEmail') : 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
return;
}
@@ -238,7 +238,7 @@ export class LoginComponent extends Component {
} else {
this.setState({
loading: false,
error: response.message || 'Anmeldung fehlgeschlagen'
error: response.message || (this.props.t ? this.props.t('auth.errors.loginFailed') : 'Anmeldung fehlgeschlagen')
});
}
});
@@ -248,22 +248,22 @@ export class LoginComponent extends Component {
const { email, password, confirmPassword } = this.state;
if (!email || !password || !confirmPassword) {
this.setState({ error: 'Bitte füllen Sie alle Felder aus' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.fillAllFields') : 'Bitte füllen Sie alle Felder aus' });
return;
}
if (!this.validateEmail(email)) {
this.setState({ error: 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.invalidEmail') : 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
return;
}
if (password !== confirmPassword) {
this.setState({ error: 'Passwörter stimmen nicht überein' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.passwordsNotMatchShort') : 'Passwörter stimmen nicht überein' });
return;
}
if (password.length < 8) {
this.setState({ error: 'Das Passwort muss mindestens 8 Zeichen lang sein' });
this.setState({ error: this.props.t ? this.props.t('auth.passwordMinLength') : 'Das Passwort muss mindestens 8 Zeichen lang sein' });
return;
}
@@ -274,14 +274,14 @@ export class LoginComponent extends Component {
if (response.success) {
this.setState({
loading: false,
success: 'Registrierung erfolgreich. Sie können sich jetzt anmelden.',
success: this.props.t ? this.props.t('auth.success.registerComplete') : 'Registrierung erfolgreich. Sie können sich jetzt anmelden.',
tabValue: 0 // Switch to login tab
});
} else {
let errorMessage = 'Registrierung fehlgeschlagen';
let errorMessage = this.props.t ? this.props.t('auth.errors.registerFailed') : 'Registrierung fehlgeschlagen';
if (response.cause === 'emailExists') {
errorMessage = 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an.';
errorMessage = this.props.t ? this.props.t('auth.errors.emailExists') : 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an.';
} else if (response.message) {
errorMessage = response.message;
}
@@ -322,12 +322,12 @@ export class LoginComponent extends Component {
const { email } = this.state;
if (!email) {
this.setState({ error: 'Bitte geben Sie Ihre E-Mail-Adresse ein' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.enterEmail') : 'Bitte geben Sie Ihre E-Mail-Adresse ein' });
return;
}
if (!this.validateEmail(email)) {
this.setState({ error: 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
this.setState({ error: this.props.t ? this.props.t('auth.errors.invalidEmail') : 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
return;
}
@@ -342,12 +342,12 @@ export class LoginComponent extends Component {
if (response.success) {
this.setState({
loading: false,
success: 'Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.'
success: this.props.t ? this.props.t('auth.resetPassword.emailSent') : 'Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.'
});
} else {
this.setState({
loading: false,
error: response.message || 'Fehler beim Senden der E-Mail'
error: response.message || (this.props.t ? this.props.t('auth.resetPassword.emailError') : 'Fehler beim Senden der E-Mail')
});
}
});
@@ -408,7 +408,7 @@ export class LoginComponent extends Component {
} else {
this.setState({
loading: false,
error: 'Google-Anmeldung fehlgeschlagen',
error: this.props.t ? this.props.t('auth.errors.googleLoginFailed') : 'Google-Anmeldung fehlgeschlagen',
showGoogleAuth: false // Reset Google auth state on failed login
});
}
@@ -418,7 +418,7 @@ export class LoginComponent extends Component {
handleGoogleLoginError = (error) => {
console.error('Google Login Error:', error);
this.setState({
error: 'Google-Anmeldung fehlgeschlagen',
error: this.props.t ? this.props.t('auth.errors.googleLoginFailed') : 'Google-Anmeldung fehlgeschlagen',
showGoogleAuth: false, // Reset Google auth state on error
loading: false
});

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { Link } from 'react-router-dom';
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
@@ -27,7 +28,7 @@ class SharedCarousel extends React.Component {
constructor(props) {
super(props);
const { i18n } = props;
// Don't load categories in constructor - will be loaded in componentDidMount with correct language
this.state = {
categories: [],
@@ -41,7 +42,7 @@ class SharedCarousel extends React.Component {
componentDidMount() {
this._isMounted = true;
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n.language;
// ALWAYS reload categories to ensure correct language
console.log("SharedCarousel componentDidMount: ALWAYS RELOADING categories for language", currentLanguage);
window.categoryService.get(209, currentLanguage).then((response) => {
@@ -60,12 +61,12 @@ class SharedCarousel extends React.Component {
componentDidUpdate(prevProps) {
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
if(prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
this.setState({ categories: [] },() => {
window.categoryService.get(209,this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
if (prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
this.setState({ categories: [] }, () => {
window.categoryService.get(209, this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
console.log("response", response);
if (response.children && response.children.length > 0) {
this.originalCategories = response.children;
this.originalCategories = response.children;
this.categories = [...response.children, ...response.children];
this.setState({ categories: this.categories });
this.startAutoScroll();
@@ -123,7 +124,7 @@ class SharedCarousel extends React.Component {
showScrollbarFlash = () => {
this.clearScrollbarTimer();
this.setState({ showScrollbar: true });
this.scrollbarTimer = setTimeout(() => {
if (this._isMounted) {
this.setState({ showScrollbar: false });
@@ -133,7 +134,7 @@ class SharedCarousel extends React.Component {
handleAutoScroll = () => {
if (!this.autoScrollActive || this.originalCategories.length === 0) return;
this.translateX -= AUTO_SCROLL_SPEED;
this.updateTrackTransform();
@@ -172,7 +173,7 @@ class SharedCarousel extends React.Component {
scrollBy = (direction) => {
if (this.originalCategories.length === 0) return;
// direction: 1 = left (scroll content right), -1 = right (scroll content left)
const originalItemCount = this.originalCategories.length;
const maxScroll = ITEM_WIDTH * originalItemCount;
@@ -189,7 +190,7 @@ class SharedCarousel extends React.Component {
}
this.updateTrackTransform();
// Force scrollbar to update immediately after wrap-around
if (this.state.showScrollbar) {
this.forceUpdate();
@@ -204,11 +205,11 @@ class SharedCarousel extends React.Component {
const originalItemCount = this.originalCategories.length;
const viewportWidth = 1080; // carousel container max-width
const itemsInView = Math.floor(viewportWidth / ITEM_WIDTH);
// Calculate which item is currently at the left edge (first visible)
// Map translateX directly to item index using the same logic as scrollBy
let currentItemIndex;
if (this.translateX === 0) {
// At the beginning - item 0 is visible
currentItemIndex = 0;
@@ -221,10 +222,10 @@ class SharedCarousel extends React.Component {
// Normal negative scrolling - calculate which item is at left edge
currentItemIndex = Math.floor(Math.abs(this.translateX) / ITEM_WIDTH);
}
// Ensure we stay within bounds
currentItemIndex = Math.max(0, Math.min(currentItemIndex, originalItemCount - 1));
// Calculate scrollbar position: 0% when item 0 is first visible, 100% when last item is first visible
const lastPossibleFirstItem = Math.max(0, originalItemCount - itemsInView);
const thumbPosition = lastPossibleFirstItem > 0 ? Math.min((currentItemIndex / lastPossibleFirstItem) * 100, 100) : 0;
@@ -268,25 +269,41 @@ class SharedCarousel extends React.Component {
const { t } = this.props;
const { categories } = this.state;
if(!categories || categories.length === 0) {
if (!categories || categories.length === 0) {
return null;
}
return (
<Box sx={{ mt: 3 }}>
<Typography
variant="h4"
component="h1"
<Box
component={Link}
to="/Kategorien"
sx={{
mb: 2,
fontFamily: "SwashingtonCP",
display: "flex",
alignItems: "center",
justifyContent: "center",
textDecoration: "none",
color: "primary.main",
textAlign: "center",
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
mb: 2,
transition: "all 0.3s ease",
"&:hover": {
transform: "translateX(5px)",
color: "primary.dark"
}
}}
>
{t('navigation.categories')}
</Typography>
<Typography
variant="h4"
component="span"
sx={{
fontFamily: "SwashingtonCP",
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
}}
>
{t('navigation.categories')}
</Typography>
<ChevronRight sx={{ fontSize: "2.5rem", ml: 1 }} />
</Box>
<div
className="carousel-wrapper"
@@ -394,7 +411,7 @@ class SharedCarousel extends React.Component {
</div>
))}
</div>
{/* Virtual Scrollbar */}
{this.renderVirtualScrollbar()}
</div>

View File

@@ -32,9 +32,9 @@ class CategoryList extends Component {
console.log(" i18n.language:", this.props.i18n?.language);
console.log(" sessionStorage i18nextLng:", typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('i18nextLng') : 'N/A');
console.log(" localStorage i18nextLng:", typeof localStorage !== 'undefined' ? localStorage.getItem('i18nextLng') : 'N/A');
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n.language;
// ALWAYS reload categories to ensure correct language
console.log("CategoryList componentDidMount: ALWAYS RELOADING categories for language", currentLanguage);
this.setState({ categories: [] }); // Clear any cached categories
@@ -53,15 +53,15 @@ class CategoryList extends Component {
componentDidUpdate(prevProps) {
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
if(prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
if (prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
this.setState({
categories: [],
activeCategoryId: null
},() => {
window.categoryService.get(209,this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
}, () => {
window.categoryService.get(209, this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
console.log("response", response);
if (response.children && response.children.length > 0) {
this.setState({
this.setState({
categories: response.children,
activeCategoryId: this.setLevel1CategoryId(this.props.activeCategoryId)
});
@@ -69,14 +69,14 @@ class CategoryList extends Component {
});
});
}
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
this.setLevel1CategoryId(this.props.activeCategoryId);
}
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
this.setLevel1CategoryId(this.props.activeCategoryId);
}
}
setLevel1CategoryId = (input) => {
if(input) {
if (input) {
const language = this.props.languageContext?.currentLanguage || this.props.i18n.language;
const categoryTreeCache = window.categoryService.getSync(209, language);
@@ -136,7 +136,7 @@ class CategoryList extends Component {
this.setState({ activeCategoryId: null });
}
handleMobileMenuToggle = () => {
this.setState(prevState => ({
@@ -173,141 +173,141 @@ class CategoryList extends Component {
py: 0.5, // Add vertical padding to prevent border clipping
}}
>
<Button
component={Link}
to="/"
color="inherit"
size="small"
aria-label="Zur Startseite"
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
sx={{
fontSize: "0.75rem",
textTransform: "none",
whiteSpace: "nowrap",
opacity: 0.9,
mx: isMobile ? 0 : 0.5,
my: 0.25,
minWidth: isMobile ? "100%" : "auto",
borderRadius: 1,
justifyContent: isMobile ? "flex-start" : "center",
transition: "all 0.2s ease",
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
position: "relative",
...(activeCategoryId === null && {
bgcolor: "#fff",
textShadow: "none",
opacity: 1,
}),
"&:hover": {
opacity: 1,
bgcolor: "#fff",
textShadow: "none",
"& .MuiSvgIcon-root": {
color: "#2e7d32 !important",
},
"& .bold-text": {
color: "#2e7d32 !important",
},
"& .thin-text": {
color: "transparent !important",
},
<Button
component={Link}
to="/"
color="inherit"
size="small"
aria-label="Zur Startseite"
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
sx={{
fontSize: "0.75rem",
textTransform: "none",
whiteSpace: "nowrap",
opacity: 0.9,
mx: isMobile ? 0 : 0.5,
my: 0.25,
minWidth: isMobile ? "100%" : "auto",
borderRadius: 1,
justifyContent: isMobile ? "flex-start" : "center",
transition: "all 0.2s ease",
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
position: "relative",
...(activeCategoryId === null && {
bgcolor: "#fff",
textShadow: "none",
opacity: 1,
}),
"&:hover": {
opacity: 1,
bgcolor: "#fff",
textShadow: "none",
"& .MuiSvgIcon-root": {
color: "#2e7d32 !important",
},
}}
>
<HomeIcon sx={{
fontSize: "1rem",
mr: isMobile ? 1 : 0,
color: activeCategoryId === null ? "#2e7d32" : "inherit"
}} />
{isMobile && (
<Box sx={{ position: "relative", display: "inline-block" }}>
{/* Bold text (always rendered to set width) */}
<Box
className="bold-text"
sx={{
fontWeight: "bold",
color: activeCategoryId === null ? "#2e7d32" : "transparent",
position: "relative",
zIndex: 2,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
{/* Thin text (positioned on top) */}
<Box
className="thin-text"
sx={{
fontWeight: "400",
color: activeCategoryId === null ? "transparent" : "inherit",
position: "absolute",
top: 0,
left: 0,
zIndex: 1,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
"& .bold-text": {
color: "#2e7d32 !important",
},
"& .thin-text": {
color: "transparent !important",
},
},
}}
>
<HomeIcon sx={{
fontSize: "1rem",
mr: isMobile ? 1 : 0,
color: activeCategoryId === null ? "#2e7d32" : "inherit"
}} />
{isMobile && (
<Box sx={{ position: "relative", display: "inline-block" }}>
{/* Bold text (always rendered to set width) */}
<Box
className="bold-text"
sx={{
fontWeight: "bold",
color: activeCategoryId === null ? "#2e7d32" : "transparent",
position: "relative",
zIndex: 2,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
)}
</Button>
{/* Thin text (positioned on top) */}
<Box
className="thin-text"
sx={{
fontWeight: "400",
color: activeCategoryId === null ? "transparent" : "inherit",
position: "absolute",
top: 0,
left: 0,
zIndex: 1,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
</Box>
)}
</Button>
<Button
component={Link}
to="/Kategorie/neu"
color="inherit"
size="small"
aria-label="Neuheiten"
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
sx={{
fontSize: "0.75rem",
textTransform: "none",
whiteSpace: "nowrap",
opacity: 0.9,
mx: isMobile ? 0 : 0.5,
my: 0.25,
minWidth: isMobile ? "100%" : "auto",
borderRadius: 1,
justifyContent: isMobile ? "flex-start" : "center",
transition: "all 0.2s ease",
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
position: "relative"
}}
>
<FiberNewIcon sx={{
fontSize: "1rem",
mr: isMobile ? 1 : 0
}} />
{isMobile && (
<Box sx={{ position: "relative", display: "inline-block" }}>
{/* Bold text (always rendered to set width) */}
<Box
className="bold-text"
sx={{
fontWeight: "bold",
color: "transparent",
position: "relative",
zIndex: 2,
}}
>
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
</Box>
{/* Thin text (positioned on top) */}
<Box
className="thin-text"
sx={{
fontWeight: "400",
color: "inherit",
position: "absolute",
top: 0,
left: 0,
zIndex: 1,
}}
>
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
</Box>
<Button
component={Link}
to="/Kategorie/neu"
color="inherit"
size="small"
aria-label="Neuheiten"
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
sx={{
fontSize: "0.75rem",
textTransform: "none",
whiteSpace: "nowrap",
opacity: 0.9,
mx: isMobile ? 0 : 0.5,
my: 0.25,
minWidth: isMobile ? "100%" : "auto",
borderRadius: 1,
justifyContent: isMobile ? "flex-start" : "center",
transition: "all 0.2s ease",
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
position: "relative"
}}
>
<FiberNewIcon sx={{
fontSize: "1rem",
mr: isMobile ? 1 : 0
}} />
{isMobile && (
<Box sx={{ position: "relative", display: "inline-block" }}>
{/* Bold text (always rendered to set width) */}
<Box
className="bold-text"
sx={{
fontWeight: "bold",
color: "transparent",
position: "relative",
zIndex: 2,
}}
>
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
</Box>
)}
</Button>
{/* Thin text (positioned on top) */}
<Box
className="thin-text"
sx={{
fontWeight: "400",
color: "inherit",
position: "absolute",
top: 0,
left: 0,
zIndex: 1,
}}
>
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
</Box>
</Box>
)}
</Button>
{categories.length > 0 ? (
@@ -385,100 +385,100 @@ class CategoryList extends Component {
);
})}
</>
) : ( !isMobile && (
<Typography
variant="caption"
color="inherit"
sx={{
display: "inline-flex",
alignItems: "center",
height: "33px", // Match small button height
px: 1,
fontSize: "0.75rem",
opacity: 0.9,
}}
>
&nbsp;
</Typography>
)
)}
<Button
component={Link}
to="/Konfigurator"
) : (!isMobile && (
<Typography
variant="caption"
color="inherit"
size="small"
aria-label="Zur Startseite"
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
sx={{
display: "inline-flex",
alignItems: "center",
height: "33px", // Match small button height
px: 1,
fontSize: "0.75rem",
textTransform: "none",
whiteSpace: "nowrap",
opacity: 0.9,
mx: isMobile ? 0 : 0.5,
my: 0.25,
minWidth: isMobile ? "100%" : "auto",
borderRadius: 1,
justifyContent: isMobile ? "flex-start" : "center",
transition: "all 0.2s ease",
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
position: "relative",
...(activeCategoryId === null && {
bgcolor: "#fff",
textShadow: "none",
opacity: 1,
}),
"&:hover": {
opacity: 1,
bgcolor: "#fff",
textShadow: "none",
"& .MuiSvgIcon-root": {
color: "#2e7d32 !important",
},
"& .bold-text": {
color: "#2e7d32 !important",
},
"& .thin-text": {
color: "transparent !important",
},
},
}}
>
<SettingsIcon sx={{
fontSize: "1rem",
mr: isMobile ? 1 : 0,
color: activeCategoryId === null ? "#2e7d32" : "inherit"
}} />
{isMobile && (
<Box sx={{ position: "relative", display: "inline-block" }}>
{/* Bold text (always rendered to set width) */}
<Box
className="bold-text"
sx={{
fontWeight: "bold",
color: activeCategoryId === null ? "#2e7d32" : "transparent",
position: "relative",
zIndex: 2,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
{/* Thin text (positioned on top) */}
<Box
className="thin-text"
sx={{
fontWeight: "400",
color: activeCategoryId === null ? "transparent" : "inherit",
position: "absolute",
top: 0,
left: 0,
zIndex: 1,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
&nbsp;
</Typography>
)
)}
<Button
component={Link}
to="/Konfigurator"
color="inherit"
size="small"
aria-label="Zur Startseite"
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
sx={{
fontSize: "0.75rem",
textTransform: "none",
whiteSpace: "nowrap",
opacity: 0.9,
mx: isMobile ? 0 : 0.5,
my: 0.25,
minWidth: isMobile ? "100%" : "auto",
borderRadius: 1,
justifyContent: isMobile ? "flex-start" : "center",
transition: "all 0.2s ease",
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
position: "relative",
...(activeCategoryId === null && {
bgcolor: "#fff",
textShadow: "none",
opacity: 1,
}),
"&:hover": {
opacity: 1,
bgcolor: "#fff",
textShadow: "none",
"& .MuiSvgIcon-root": {
color: "#2e7d32 !important",
},
"& .bold-text": {
color: "#2e7d32 !important",
},
"& .thin-text": {
color: "transparent !important",
},
},
}}
>
<SettingsIcon sx={{
fontSize: "1rem",
mr: isMobile ? 1 : 0,
color: activeCategoryId === null ? "#2e7d32" : "inherit"
}} />
{isMobile && (
<Box sx={{ position: "relative", display: "inline-block" }}>
{/* Bold text (always rendered to set width) */}
<Box
className="bold-text"
sx={{
fontWeight: "bold",
color: activeCategoryId === null ? "#2e7d32" : "transparent",
position: "relative",
zIndex: 2,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
)}
</Button>
{/* Thin text (positioned on top) */}
<Box
className="thin-text"
sx={{
fontWeight: "400",
color: activeCategoryId === null ? "transparent" : "inherit",
position: "absolute",
top: 0,
left: 0,
zIndex: 1,
}}
>
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
</Box>
</Box>
)}
</Button>
</Box>
);
@@ -516,11 +516,11 @@ class CategoryList extends Component {
>
<Container maxWidth="lg" sx={{ px: 2 }}>
{/* Toggle Button */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
py: 1,
cursor: "pointer",
"&:hover": {
@@ -530,7 +530,7 @@ class CategoryList extends Component {
onClick={this.handleMobileMenuToggle}
role="button"
tabIndex={0}
aria-label={this.props.t ?
aria-label={this.props.t ?
(mobileMenuOpen ? this.props.t('navigation.categoriesClose') : this.props.t('navigation.categoriesOpen')) :
(mobileMenuOpen ? "Kategorien schließen" : "Kategorien öffnen")
}
@@ -541,11 +541,11 @@ class CategoryList extends Component {
}
}}
>
<Typography variant="subtitle2" color="inherit" sx={{
<Typography variant="subtitle2" color="inherit" sx={{
fontWeight: "bold",
textShadow: "0 1px 2px rgba(0,0,0,0.3)"
}}>
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
</Typography>
<Box sx={{ display: "flex", alignItems: "center" }}>
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}

View File

@@ -31,6 +31,7 @@ const getStatusTranslation = (status, t) => {
new: t ? t('orders.status.new') : "in Bearbeitung",
pending: t ? t('orders.status.pending') : "Neu",
processing: t ? t('orders.status.processing') : "in Bearbeitung",
paid: t ? t('orders.status.paid') : "Bezahlt",
cancelled: t ? t('orders.status.cancelled') : "Storniert",
shipped: t ? t('orders.status.shipped') : "Verschickt",
delivered: t ? t('orders.status.delivered') : "Geliefert",
@@ -39,29 +40,23 @@ const getStatusTranslation = (status, t) => {
};
const statusEmojis = {
"in Bearbeitung": "⚙️",
new: "⚙️",
pending: "⏳",
processing: "🔄",
paid: "🏦",
cancelled: "❌",
Verschickt: "🚚",
Geliefert: "✅",
Storniert: "❌",
Retoure: "↩️",
"Teil Retoure": "↪️",
"Teil geliefert": "⚡",
shipped: "🚚",
delivered: "✅",
};
const statusColors = {
"in Bearbeitung": "#ed6c02", // orange
new: "#ed6c02", // orange
pending: "#ff9800", // orange for pending
processing: "#2196f3", // blue for processing
paid: "#2e7d32", // green
cancelled: "#d32f2f", // red for cancelled
Verschickt: "#2e7d32", // green
Geliefert: "#2e7d32", // green
Storniert: "#d32f2f", // red
Retoure: "#9c27b0", // purple
"Teil Retoure": "#9c27b0", // purple
"Teil geliefert": "#009688", // teal
shipped: "#2e7d32", // green
delivered: "#2e7d32", // green
};
const currencyFormatter = new Intl.NumberFormat("de-DE", {
@@ -229,11 +224,11 @@ const OrdersTab = ({ orderIdFromHash, t }) => {
display: "flex",
alignItems: "center",
gap: "8px",
color: getStatusColor(displayStatus),
color: getStatusColor(order.status),
}}
>
<span style={{ fontSize: "1.2rem" }}>
{getStatusEmoji(displayStatus)}
{getStatusEmoji(order.status)}
</span>
<Typography
variant="body2"
@@ -243,6 +238,18 @@ const OrdersTab = ({ orderIdFromHash, t }) => {
{displayStatus}
</Typography>
</Box>
{order.delivery_method === 'DHL' && order.trackingCode && (
<Box sx={{ mt: 0.5 }}>
<a
href={`https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=${order.trackingCode}`}
target="_blank"
rel="noopener noreferrer"
style={{ fontSize: '0.85rem', color: '#d40511' }}
>
📦 {t ? t('orders.trackShipment') : 'Sendung verfolgen'}
</a>
</Box>
)}
</TableCell>
<TableCell>
{order.items

View File

@@ -5,6 +5,7 @@ export default {
"profile": "الملف الشخصي",
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"newPassword": "كلمة المرور الجديدة",
"confirmPassword": "تأكيد كلمة المرور",
"forgotPassword": "هل نسيت كلمة المرور؟",
"loginWithGoogle": "تسجيل الدخول باستخدام جوجل",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "سياسة الخصوصية",
"passwordMinLength": "يجب أن تكون كلمة المرور 8 أحرف على الأقل",
"newPasswordMinLength": "يجب أن تكون كلمة المرور الجديدة 8 أحرف على الأقل",
"backToHome": "العودة إلى الصفحة الرئيسية",
"menu": {
"profile": "الملف الشخصي",
"myProfile": "ملفي الشخصي",
@@ -21,5 +23,28 @@ export default {
"settings": "الإعدادات",
"adminDashboard": "لوحة تحكم المسؤول",
"adminUsers": "مستخدمو المسؤول"
},
"resetPassword": {
"title": "إعادة تعيين كلمة المرور",
"button": "إعادة تعيين كلمة المرور",
"success": "تم إعادة تعيين كلمة المرور بنجاح! سيتم توجيهك لتسجيل الدخول قريبًا...",
"invalidToken": "لم يتم العثور على رمز صالح. يرجى استخدام الرابط من بريدك الإلكتروني.",
"error": "حدث خطأ أثناء إعادة تعيين كلمة المرور",
"emailSent": "تم إرسال رابط لإعادة تعيين كلمة المرور إلى بريدك الإلكتروني.",
"emailError": "حدث خطأ أثناء إرسال البريد الإلكتروني"
},
"errors": {
"fillAllFields": "يرجى ملء جميع الحقول",
"invalidEmail": "يرجى إدخال بريد إلكتروني صالح",
"passwordsNotMatch": "كلمات المرور غير متطابقة",
"passwordsNotMatchShort": "كلمات المرور غير متطابقة",
"enterEmail": "يرجى إدخال بريدك الإلكتروني",
"loginFailed": "فشل تسجيل الدخول",
"registerFailed": "فشل التسجيل",
"googleLoginFailed": "فشل تسجيل الدخول عبر جوجل",
"emailExists": "يوجد مستخدم بهذا البريد الإلكتروني بالفعل. يرجى استخدام بريد إلكتروني آخر أو تسجيل الدخول."
},
"success": {
"registerComplete": "تم التسجيل بنجاح. يمكنك الآن تسجيل الدخول."
}
};

View File

@@ -3,7 +3,8 @@ export default {
"new": "قيد التنفيذ",
"pending": "جديد",
"processing": "قيد التنفيذ",
"cancelled": لغاة",
"paid": دفوع",
"cancelled": "ملغي",
"shipped": "تم الشحن",
"delivered": "تم التوصيل",
"return": "إرجاع",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "إلغاء الطلب"
},
"noOrders": "لم تقم بوضع أي طلبات بعد.",
"trackShipment": "تتبع الشحنة",
"details": {
"title": "تفاصيل الطلب: {{orderId}}",
"deliveryAddress": "عنوان التوصيل",
@@ -36,14 +38,13 @@ export default {
"item": "العنصر",
"quantity": "الكمية",
"price": "السعر",
"vat": "ضريبة القيمة المضافة",
"total": "الإجمالي",
"cancelOrder": "إلغاء الطلب"
},
"cancelConfirm": {
"title": "إلغاء الطلب",
"message": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟",
"confirm": "إلغاء الطلب",
"confirm": "إلغاء",
"cancelling": "جارٍ الإلغاء..."
},
"processing": "يتم إكمال الطلب..."

View File

@@ -5,14 +5,16 @@ export default {
"profile": "Профил",
"email": "Имейл",
"password": "Парола",
"newPassword": "Нова парола",
"confirmPassword": "Потвърдете паролата",
"forgotPassword": "Забравена парола?",
"loginWithGoogle": "Вход с Google",
"or": "ИЛИ",
"privacyAccept": "С натискане на \"Вход с Google\" приемам",
"privacyAccept": "С натискането на \"Вход с Google\" приемам",
"privacyPolicy": "Политиката за поверителност",
"passwordMinLength": "Паролата трябва да е поне 8 символа",
"newPasswordMinLength": "Новата парола трябва да е поне 8 символа",
"backToHome": "Обратно към началната страница",
"menu": {
"profile": "Профил",
"myProfile": "Моят профил",
@@ -21,5 +23,28 @@ export default {
"settings": "Настройки",
"adminDashboard": "Админ табло",
"adminUsers": "Админ потребители"
},
"resetPassword": {
"title": "Нулиране на парола",
"button": "Нулиране на парола",
"success": "Вашата парола беше успешно нулирана! Скоро ще бъдете пренасочени към вход...",
"invalidToken": "Няма валиден токен. Моля, използвайте линка от имейла си.",
"error": "Грешка при нулиране на паролата",
"emailSent": "Линк за нулиране на паролата беше изпратен на вашия имейл.",
"emailError": "Грешка при изпращане на имейла"
},
"errors": {
"fillAllFields": "Моля, попълнете всички полета",
"invalidEmail": "Моля, въведете валиден имейл адрес",
"passwordsNotMatch": "Паролите не съвпадат",
"passwordsNotMatchShort": "Паролите не съвпадат",
"enterEmail": "Моля, въведете вашия имейл адрес",
"loginFailed": "Входът не бе успешен",
"registerFailed": "Регистрацията не бе успешна",
"googleLoginFailed": "Вход с Google не бе успешен",
"emailExists": "Потребител с този имейл вече съществува. Моля, използвайте друг имейл или влезте в системата."
},
"success": {
"registerComplete": "Регистрацията беше успешна. Сега можете да влезете."
}
};

View File

@@ -1,14 +1,15 @@
export default {
"status": {
"new": "В процес",
"pending": "Нова",
"processing": "В процес",
"cancelled": "Отменена",
"shipped": "Изпратена",
"delivered": "Доставена",
"new": "в процес",
"pending": "Ново",
"processing": "в процес",
"paid": "Платено",
"cancelled": "Отменено",
"shipped": "Изпратено",
"delivered": "Доставено",
"return": "Връщане",
"partialReturn": "Частично връщане",
"partialDelivered": "Частично доставена"
"partialDelivered": "Частично доставено"
},
"table": {
"orderNumber": "Номер на поръчка",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Отмени поръчката"
},
"noOrders": "Все още не сте направили поръчки.",
"trackShipment": "Проследи пратката",
"details": {
"title": "Подробности за поръчка: {{orderId}}",
"deliveryAddress": "Адрес за доставка",
@@ -36,15 +38,14 @@ export default {
"item": "Артикул",
"quantity": "Количество",
"price": "Цена",
"vat": "ДДС",
"total": "Общо",
"cancelOrder": "Отмени поръчката"
},
"cancelConfirm": {
"title": "Отмяна на поръчка",
"message": "Сигурни ли сте, че искате да отмените тази поръчка?",
"confirm": "Отмени поръчката",
"cancelling": "Отмяна..."
"title": "Отмени поръчката",
"message": "Сигурни ли сте, че искате да отмените тази поръчка?",
"confirm": "Отмени",
"cancelling": "Отмяна..."
},
"processing": "Поръчката се обработва...",
"processing": "Поръчката се обработва..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Heslo",
"newPassword": "Nové heslo",
"confirmPassword": "Potvrdit heslo",
"forgotPassword": "Zapomněli jste heslo?",
"loginWithGoogle": "Přihlásit se přes Google",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Zásadami ochrany osobních údajů",
"passwordMinLength": "Heslo musí mít alespoň 8 znaků",
"newPasswordMinLength": "Nové heslo musí mít alespoň 8 znaků",
"backToHome": "Zpět na domovskou stránku",
"menu": {
"profile": "Profil",
"myProfile": "Můj profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Nastavení",
"adminDashboard": "Admin Dashboard",
"adminUsers": "Admin Users"
},
"resetPassword": {
"title": "Obnovení hesla",
"button": "Obnovit heslo",
"success": "Vaše heslo bylo úspěšně obnoveno! Brzy budete přesměrováni na přihlášení...",
"invalidToken": "Nebyl nalezen platný token. Použijte prosím odkaz z vašeho e-mailu.",
"error": "Chyba při obnově hesla",
"emailSent": "Odkaz pro obnovení hesla byl odeslán na vaši e-mailovou adresu.",
"emailError": "Chyba při odesílání e-mailu"
},
"errors": {
"fillAllFields": "Vyplňte prosím všechna pole",
"invalidEmail": "Zadejte platnou e-mailovou adresu",
"passwordsNotMatch": "Hesla se neshodují",
"passwordsNotMatchShort": "Hesla se neshodují",
"enterEmail": "Zadejte prosím svou e-mailovou adresu",
"loginFailed": "Přihlášení selhalo",
"registerFailed": "Registrace selhala",
"googleLoginFailed": "Přihlášení přes Google selhalo",
"emailExists": "Uživatel s touto e-mailovou adresou již existuje. Použijte prosím jinou e-mailovou adresu nebo se přihlaste."
},
"success": {
"registerComplete": "Registrace byla úspěšná. Nyní se můžete přihlásit."
}
};

View File

@@ -1,50 +1,51 @@
export default {
"status": {
"new": "Probíhá",
"pending": "Nová",
"processing": "Probíhá",
"cancelled": "Zrušeno",
"shipped": "Odesláno",
"delivered": "Doručeno",
"return": "Vrácení",
"partialReturn": "Částečné vrácení",
"partialDelivered": "Částečně doručeno"
"new": "probíhá",
"pending": "Nové",
"processing": "probíhá",
"paid": "Zaplaceno",
"cancelled": "Zrušeno",
"shipped": "Odesláno",
"delivered": "Doručeno",
"return": "Vrácení",
"partialReturn": "Částečné vrácení",
"partialDelivered": "Částečně doručeno"
},
"table": {
"orderNumber": "Číslo objednávky",
"date": "Datum",
"status": "Stav",
"items": "Položky",
"total": "Celkem",
"actions": "Akce",
"viewDetails": "Zobrazit detaily"
"orderNumber": "Číslo objednávky",
"date": "Datum",
"status": "Stav",
"items": "Položky",
"total": "Celkem",
"actions": "Akce",
"viewDetails": "Zobrazit detaily"
},
"tooltips": {
"viewDetails": "Zobrazit detaily",
"cancelOrder": "Zrušit objednávku"
"viewDetails": "Zobrazit detaily",
"cancelOrder": "Zrušit objednávku"
},
"noOrders": "Ještě jste neprovedli žádné objednávky.",
"noOrders": "Ještě jste neprovedli žádné objednávky.",
"trackShipment": "Sledovat zásilku",
"details": {
"title": "Detaily objednávky: {{orderId}}",
"deliveryAddress": "Dodací adresa",
"invoiceAddress": "Fakturační adresa",
"orderDetails": "Detaily objednávky",
"deliveryMethod": "Způsob doručení:",
"paymentMethod": "Způsob platby:",
"notSpecified": "Nespecifikováno",
"orderedItems": "Objednané položky",
"item": "Položka",
"quantity": "Množství",
"price": "Cena",
"vat": "DPH",
"total": "Celkem",
"cancelOrder": "Zrušit objednávku"
"title": "Detaily objednávky: {{orderId}}",
"deliveryAddress": "Dodací adresa",
"invoiceAddress": "Fakturační adresa",
"orderDetails": "Detaily objednávky",
"deliveryMethod": "Způsob doručení:",
"paymentMethod": "Způsob platby:",
"notSpecified": "Nespecifikováno",
"orderedItems": "Objednané položky",
"item": "Položka",
"quantity": "Množství",
"price": "Cena",
"total": "Celkem",
"cancelOrder": "Zrušit objednávku"
},
"cancelConfirm": {
"title": "Zrušit objednávku",
"message": "Opravdu chcete tuto objednávku zrušit?",
"confirm": "Zrušit objednávku",
"confirm": "Zrušit",
"cancelling": "Rušení..."
},
"processing": "Objednávka se dokončuje...",
"processing": "Objednávka se dokončuje..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "E-Mail",
"password": "Passwort",
"newPassword": "Neues Passwort",
"confirmPassword": "Passwort bestätigen",
"forgotPassword": "Passwort vergessen?",
"loginWithGoogle": "Mit Google anmelden",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Datenschutzbestimmungen",
"passwordMinLength": "Das Passwort muss mindestens 8 Zeichen lang sein",
"newPasswordMinLength": "Das neue Passwort muss mindestens 8 Zeichen lang sein",
"backToHome": "Zurück zur Startseite",
"menu": {
"profile": "Profil",
"myProfile": "Mein Profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Einstellungen",
"adminDashboard": "Admin Dashboard",
"adminUsers": "Admin Users"
},
"resetPassword": {
"title": "Passwort zurücksetzen",
"button": "Passwort zurücksetzen",
"success": "Ihr Passwort wurde erfolgreich zurückgesetzt! Sie werden in Kürze zur Anmeldung weitergeleitet...",
"invalidToken": "Kein gültiger Token gefunden. Bitte verwenden Sie den Link aus Ihrer E-Mail.",
"error": "Fehler beim Zurücksetzen des Passworts",
"emailSent": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.",
"emailError": "Fehler beim Senden der E-Mail"
},
"errors": {
"fillAllFields": "Bitte füllen Sie alle Felder aus",
"invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"passwordsNotMatch": "Die Passwörter stimmen nicht überein",
"passwordsNotMatchShort": "Passwörter stimmen nicht überein",
"enterEmail": "Bitte geben Sie Ihre E-Mail-Adresse ein",
"loginFailed": "Anmeldung fehlgeschlagen",
"registerFailed": "Registrierung fehlgeschlagen",
"googleLoginFailed": "Google-Anmeldung fehlgeschlagen",
"emailExists": "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an."
},
"success": {
"registerComplete": "Registrierung erfolgreich. Sie können sich jetzt anmelden."
}
};

View File

@@ -3,6 +3,7 @@ export default {
"new": "in Bearbeitung",
"pending": "Neu",
"processing": "in Bearbeitung",
"paid": "Bezahlt",
"cancelled": "Storniert",
"shipped": "Verschickt",
"delivered": "Geliefert",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Bestellung stornieren"
},
"noOrders": "Sie haben noch keine Bestellungen aufgegeben.",
"trackShipment": "Sendung verfolgen",
"details": {
"title": "Bestelldetails: {{orderId}}",
"deliveryAddress": "Lieferadresse",

View File

@@ -5,14 +5,16 @@ export default {
"profile": "Προφίλ",
"email": "Email",
"password": "Κωδικός",
"newPassword": "Νέος κωδικός",
"confirmPassword": "Επιβεβαίωση κωδικού",
"forgotPassword": "Ξεχάσατε τον κωδικό;",
"loginWithGoogle": "Σύνδεση με Google",
"or": "Ή",
"privacyAccept": "Κάνοντας κλικ στο \"Σύνδεση με Google\" αποδέχομαι την",
"privacyPolicy": "Πολιτική Απορρήτου",
"privacyPolicy": "Πολιτική απορρήτου",
"passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
"newPasswordMinLength": "Ο νέος κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
"backToHome": "Επιστροφή στην αρχική σελίδα",
"menu": {
"profile": "Προφίλ",
"myProfile": "Το προφίλ μου",
@@ -21,5 +23,28 @@ export default {
"settings": "Ρυθμίσεις",
"adminDashboard": "Πίνακας διαχείρισης",
"adminUsers": "Διαχειριστές"
},
"resetPassword": {
"title": "Επαναφορά κωδικού",
"button": "Επαναφορά κωδικού",
"success": "Ο κωδικός σας επαναφέρθηκε με επιτυχία! Θα ανακατευθυνθείτε στη σύνδεση σύντομα...",
"invalidToken": "Δεν βρέθηκε έγκυρο διακριτικό. Παρακαλώ χρησιμοποιήστε τον σύνδεσμο από το email σας.",
"error": "Σφάλμα κατά την επαναφορά του κωδικού",
"emailSent": "Ένας σύνδεσμος για επαναφορά του κωδικού σας έχει σταλεί στη διεύθυνση email σας.",
"emailError": "Σφάλμα κατά την αποστολή του email"
},
"errors": {
"fillAllFields": "Παρακαλώ συμπληρώστε όλα τα πεδία",
"invalidEmail": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση email",
"passwordsNotMatch": "Οι κωδικοί δεν ταιριάζουν",
"passwordsNotMatchShort": "Οι κωδικοί δεν ταιριάζουν",
"enterEmail": "Παρακαλώ εισάγετε τη διεύθυνση email σας",
"loginFailed": "Η σύνδεση απέτυχε",
"registerFailed": "Η εγγραφή απέτυχε",
"googleLoginFailed": "Η σύνδεση με Google απέτυχε",
"emailExists": "Υπάρχει ήδη χρήστης με αυτή τη διεύθυνση email. Παρακαλώ χρησιμοποιήστε άλλη διεύθυνση ή συνδεθείτε."
},
"success": {
"registerComplete": "Η εγγραφή ολοκληρώθηκε με επιτυχία. Μπορείτε τώρα να συνδεθείτε."
}
};

View File

@@ -1,10 +1,11 @@
export default {
"status": {
"new": "Σε εξέλιξη",
"new": "σε εξέλιξη",
"pending": "Νέο",
"processing": "Σε εξέλιξη",
"processing": "σε εξέλιξη",
"paid": "Πληρωμένο",
"cancelled": "Ακυρώθηκε",
"shipped": "Απεστάλη",
"shipped": "Απεσταλμένο",
"delivered": "Παραδόθηκε",
"return": "Επιστροφή",
"partialReturn": "Μερική επιστροφή",
@@ -24,10 +25,11 @@ export default {
"cancelOrder": "Ακύρωση παραγγελίας"
},
"noOrders": "Δεν έχετε κάνει ακόμα καμία παραγγελία.",
"trackShipment": "Παρακολούθηση αποστολής",
"details": {
"title": "Λεπτομέρειες παραγγελίας: {{orderId}}",
"deliveryAddress": "Διεύθυνση παράδοσης",
"invoiceAddress": "Διεύθυνση τιμολόγησης",
"invoiceAddress": "Διεύθυνση τιμολογίου",
"orderDetails": "Λεπτομέρειες παραγγελίας",
"deliveryMethod": "Τρόπος παράδοσης:",
"paymentMethod": "Τρόπος πληρωμής:",
@@ -36,15 +38,14 @@ export default {
"item": "Είδος",
"quantity": "Ποσότητα",
"price": "Τιμή",
"vat": "ΦΠΑ",
"total": "Σύνολο",
"cancelOrder": "Ακύρωση παραγγελίας"
},
"cancelConfirm": {
"title": "Ακύρωση παραγγελίας",
"message": "Είστε σίγουροι ότι θέλετε να ακυρώσετε αυτήν την παραγγελία;",
"confirm": "Ακύρωση παραγγελίας",
"cancelling": "Ακύρωση..."
"message": "Είστε σίγουροι ότι θέλετε να ακυρώσετε αυτή την παραγγελία;",
"confirm": "Ακύρωση",
"cancelling": "Ακύρωση σε εξέλιξη..."
},
"processing": "Η παραγγελία ολοκληρώνεται..."
};

View File

@@ -5,14 +5,16 @@ export default {
"profile": "Profile", // Profil
"email": "Email", // E-Mail
"password": "Password", // Passwort
"newPassword": "New password", // Neues Passwort
"confirmPassword": "Confirm password", // Passwort bestätigen
"forgotPassword": "Forgot password?", // Passwort vergessen?
"loginWithGoogle": "Sign in with Google", // Mit Google anmelden
"or": "OR", // ODER
"privacyAccept": "By clicking \"Sign in with Google\" I accept the", // Mit dem Click auf "Mit Google anmelden" akzeptiere ich die
"privacyPolicy": "Privacy Policy", // Datenschutzbestimmungen
"privacyAccept": "By clicking on \"Sign in with Google\" I accept the", // Mit dem Click auf "Mit Google anmelden" akzeptiere ich die
"privacyPolicy": "Privacy policy", // Datenschutzbestimmungen
"passwordMinLength": "The password must be at least 8 characters long", // Das Passwort muss mindestens 8 Zeichen lang sein
"newPasswordMinLength": "The new password must be at least 8 characters long", // Das neue Passwort muss mindestens 8 Zeichen lang sein
"backToHome": "Back to homepage", // Zurück zur Startseite
"menu": {
"profile": "Profile", // Profil
"myProfile": "My profile", // Mein Profil
@@ -21,5 +23,28 @@ export default {
"settings": "Settings", // Einstellungen
"adminDashboard": "Admin Dashboard", // Admin Dashboard
"adminUsers": "Admin Users" // Admin Users
},
"resetPassword": {
"title": "Reset password", // Passwort zurücksetzen
"button": "Reset password", // Passwort zurücksetzen
"success": "Your password has been reset successfully! You will be redirected to login shortly...", // Ihr Passwort wurde erfolgreich zurückgesetzt! Sie werden in Kürze zur Anmeldung weitergeleitet...
"invalidToken": "No valid token found. Please use the link from your email.", // Kein gültiger Token gefunden. Bitte verwenden Sie den Link aus Ihrer E-Mail.
"error": "Error resetting password", // Fehler beim Zurücksetzen des Passworts
"emailSent": "A link to reset your password has been sent to your email address.", // Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.
"emailError": "Error sending email" // Fehler beim Senden der E-Mail
},
"errors": {
"fillAllFields": "Please fill in all fields", // Bitte füllen Sie alle Felder aus
"invalidEmail": "Please enter a valid email address", // Bitte geben Sie eine gültige E-Mail-Adresse ein
"passwordsNotMatch": "The passwords do not match", // Die Passwörter stimmen nicht überein
"passwordsNotMatchShort": "Passwords do not match", // Passwörter stimmen nicht überein
"enterEmail": "Please enter your email address", // Bitte geben Sie Ihre E-Mail-Adresse ein
"loginFailed": "Login failed", // Anmeldung fehlgeschlagen
"registerFailed": "Registration failed", // Registrierung fehlgeschlagen
"googleLoginFailed": "Google login failed", // Google-Anmeldung fehlgeschlagen
"emailExists": "A user with this email address already exists. Please use another email address or log in." // Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an.
},
"success": {
"registerComplete": "Registration successful. You can now log in." // Registrierung erfolgreich. Sie können sich jetzt anmelden.
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "In progress", // in Bearbeitung
"new": "in progress", // in Bearbeitung
"pending": "New", // Neu
"processing": "In progress", // in Bearbeitung
"processing": "in progress", // in Bearbeitung
"paid": "Paid", // Bezahlt
"cancelled": "Cancelled", // Storniert
"shipped": "Shipped", // Verschickt
"delivered": "Delivered", // Geliefert
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Cancel order" // Bestellung stornieren
},
"noOrders": "You have not placed any orders yet.", // Sie haben noch keine Bestellungen aufgegeben.
"trackShipment": "Track shipment", // Sendung verfolgen
"details": {
"title": "Order details: {{orderId}}", // Bestelldetails: {{orderId}}
"deliveryAddress": "Delivery address", // Lieferadresse
@@ -36,15 +38,14 @@ export default {
"item": "Item", // Artikel
"quantity": "Quantity", // Menge
"price": "Price", // Preis
"vat": "VAT", // MwSt.
"total": "Total", // Gesamt
"cancelOrder": "Cancel order" // Bestellung stornieren
},
"cancelConfirm": {
"title": "Cancel Order",
"message": "Are you sure you want to cancel this order?",
"confirm": "Cancel Order",
"cancelling": "Cancelling..."
"title": "Cancel order", // Bestellung stornieren
"message": "Are you sure you want to cancel this order?", // Sind Sie sicher, dass Sie diese Bestellung stornieren möchten?
"confirm": "Cancel", // Stornieren
"cancelling": "Cancelling..." // Wird storniert...
},
"processing": "Order is being completed...", // Bestellung wird abgeschlossen...
"processing": "Order is being completed..." // Bestellung wird abgeschlossen...
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Perfil",
"email": "Correo electrónico",
"password": "Contraseña",
"newPassword": "Nueva contraseña",
"confirmPassword": "Confirmar contraseña",
"forgotPassword": "¿Olvidaste tu contraseña?",
"loginWithGoogle": "Iniciar sesión con Google",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Política de privacidad",
"passwordMinLength": "La contraseña debe tener al menos 8 caracteres",
"newPasswordMinLength": "La nueva contraseña debe tener al menos 8 caracteres",
"backToHome": "Volver a la página principal",
"menu": {
"profile": "Perfil",
"myProfile": "Mi perfil",
@@ -21,5 +23,28 @@ export default {
"settings": "Configuración",
"adminDashboard": "Panel de administración",
"adminUsers": "Usuarios administradores"
},
"resetPassword": {
"title": "Restablecer contraseña",
"button": "Restablecer contraseña",
"success": "¡Tu contraseña ha sido restablecida con éxito! Serás redirigido para iniciar sesión en breve...",
"invalidToken": "No se encontró un token válido. Por favor, usa el enlace de tu correo electrónico.",
"error": "Error al restablecer la contraseña",
"emailSent": "Se ha enviado un enlace para restablecer tu contraseña a tu dirección de correo electrónico.",
"emailError": "Error al enviar el correo electrónico"
},
"errors": {
"fillAllFields": "Por favor, completa todos los campos",
"invalidEmail": "Por favor, introduce una dirección de correo electrónico válida",
"passwordsNotMatch": "Las contraseñas no coinciden",
"passwordsNotMatchShort": "Las contraseñas no coinciden",
"enterEmail": "Por favor, introduce tu dirección de correo electrónico",
"loginFailed": "Error al iniciar sesión",
"registerFailed": "Error al registrarse",
"googleLoginFailed": "Error al iniciar sesión con Google",
"emailExists": "Ya existe un usuario con esta dirección de correo electrónico. Por favor, usa otra dirección de correo electrónico o inicia sesión."
},
"success": {
"registerComplete": "Registro exitoso. Ahora puedes iniciar sesión."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "En progreso",
"new": "en progreso",
"pending": "Nuevo",
"processing": "En progreso",
"processing": "en progreso",
"paid": "Pagado",
"cancelled": "Cancelado",
"shipped": "Enviado",
"delivered": "Entregado",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Cancelar pedido"
},
"noOrders": "Aún no has realizado ningún pedido.",
"trackShipment": "Rastrear envío",
"details": {
"title": "Detalles del pedido: {{orderId}}",
"deliveryAddress": "Dirección de entrega",
@@ -36,15 +38,14 @@ export default {
"item": "Artículo",
"quantity": "Cantidad",
"price": "Precio",
"vat": "IVA",
"total": "Total",
"cancelOrder": "Cancelar pedido"
},
"cancelConfirm": {
"title": "Cancelar pedido",
"message": "¿Estás seguro de que deseas cancelar este pedido?",
"confirm": "Cancelar pedido",
"message": "¿Está seguro de que desea cancelar este pedido?",
"confirm": "Cancelar",
"cancelling": "Cancelando..."
},
"processing": "El pedido se está completando...",
"processing": "El pedido se está completando..."
};

View File

@@ -1,10 +1,11 @@
export default {
"login": "Connexion",
"register": "S'inscrire",
"register": "Inscription",
"logout": "Déconnexion",
"profile": "Profil",
"email": "Email",
"password": "Mot de passe",
"newPassword": "Nouveau mot de passe",
"confirmPassword": "Confirmer le mot de passe",
"forgotPassword": "Mot de passe oublié ?",
"loginWithGoogle": "Se connecter avec Google",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Politique de confidentialité",
"passwordMinLength": "Le mot de passe doit contenir au moins 8 caractères",
"newPasswordMinLength": "Le nouveau mot de passe doit contenir au moins 8 caractères",
"backToHome": "Retour à la page d'accueil",
"menu": {
"profile": "Profil",
"myProfile": "Mon profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Paramètres",
"adminDashboard": "Tableau de bord Admin",
"adminUsers": "Utilisateurs Admin"
},
"resetPassword": {
"title": "Réinitialiser le mot de passe",
"button": "Réinitialiser le mot de passe",
"success": "Votre mot de passe a été réinitialisé avec succès ! Vous serez redirigé vers la connexion sous peu...",
"invalidToken": "Aucun jeton valide trouvé. Veuillez utiliser le lien de votre email.",
"error": "Erreur lors de la réinitialisation du mot de passe",
"emailSent": "Un lien pour réinitialiser votre mot de passe a été envoyé à votre adresse email.",
"emailError": "Erreur lors de l'envoi de l'email"
},
"errors": {
"fillAllFields": "Veuillez remplir tous les champs",
"invalidEmail": "Veuillez entrer une adresse email valide",
"passwordsNotMatch": "Les mots de passe ne correspondent pas",
"passwordsNotMatchShort": "Les mots de passe ne correspondent pas",
"enterEmail": "Veuillez entrer votre adresse email",
"loginFailed": "Échec de la connexion",
"registerFailed": "Échec de l'inscription",
"googleLoginFailed": "Échec de la connexion Google",
"emailExists": "Un utilisateur avec cette adresse email existe déjà. Veuillez utiliser une autre adresse email ou vous connecter."
},
"success": {
"registerComplete": "Inscription réussie. Vous pouvez maintenant vous connecter."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "En cours",
"new": "en cours",
"pending": "Nouveau",
"processing": "En cours",
"processing": "en cours",
"paid": "Payé",
"cancelled": "Annulé",
"shipped": "Expédié",
"delivered": "Livré",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Annuler la commande"
},
"noOrders": "Vous n'avez pas encore passé de commandes.",
"trackShipment": "Suivre l'envoi",
"details": {
"title": "Détails de la commande : {{orderId}}",
"deliveryAddress": "Adresse de livraison",
@@ -36,14 +38,13 @@ export default {
"item": "Article",
"quantity": "Quantité",
"price": "Prix",
"vat": "TVA",
"total": "Total",
"cancelOrder": "Annuler la commande"
},
"cancelConfirm": {
"title": "Annuler la commande",
"message": "Êtes-vous sûr de vouloir annuler cette commande ?",
"confirm": "Annuler la commande",
"confirm": "Annuler",
"cancelling": "Annulation en cours..."
},
"processing": "La commande est en cours de traitement..."

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Lozinka",
"newPassword": "Nova lozinka",
"confirmPassword": "Potvrdi lozinku",
"forgotPassword": "Zaboravili ste lozinku?",
"loginWithGoogle": "Prijavite se putem Googlea",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Pravila privatnosti",
"passwordMinLength": "Lozinka mora imati najmanje 8 znakova",
"newPasswordMinLength": "Nova lozinka mora imati najmanje 8 znakova",
"backToHome": "Natrag na početnu stranicu",
"menu": {
"profile": "Profil",
"myProfile": "Moj profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Postavke",
"adminDashboard": "Admin nadzorna ploča",
"adminUsers": "Admin korisnici"
},
"resetPassword": {
"title": "Resetiraj lozinku",
"button": "Resetiraj lozinku",
"success": "Vaša lozinka je uspješno resetirana! Uskoro ćete biti preusmjereni na prijavu...",
"invalidToken": "Nije pronađen valjani token. Molimo koristite link iz vaše e-pošte.",
"error": "Pogreška pri resetiranju lozinke",
"emailSent": "Link za resetiranje lozinke poslan je na vašu e-mail adresu.",
"emailError": "Pogreška pri slanju e-pošte"
},
"errors": {
"fillAllFields": "Molimo ispunite sva polja",
"invalidEmail": "Molimo unesite valjanu e-mail adresu",
"passwordsNotMatch": "Lozinke se ne podudaraju",
"passwordsNotMatchShort": "Lozinke se ne podudaraju",
"enterEmail": "Molimo unesite vašu e-mail adresu",
"loginFailed": "Prijava nije uspjela",
"registerFailed": "Registracija nije uspjela",
"googleLoginFailed": "Prijava putem Googlea nije uspjela",
"emailExists": "Korisnik s ovom e-mail adresom već postoji. Molimo koristite drugu e-mail adresu ili se prijavite."
},
"success": {
"registerComplete": "Registracija uspješna. Sada se možete prijaviti."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "U tijeku",
"new": "u tijeku",
"pending": "Novo",
"processing": "U tijeku",
"processing": "u tijeku",
"paid": "Plaćeno",
"cancelled": "Otkazano",
"shipped": "Poslano",
"delivered": "Isporučeno",
@@ -23,7 +24,8 @@ export default {
"viewDetails": "Pogledaj detalje",
"cancelOrder": "Otkaži narudžbu"
},
"noOrders": "Još niste napravili nijednu narudžbu.",
"noOrders": "Još niste izvršili nijednu narudžbu.",
"trackShipment": "Prati pošiljku",
"details": {
"title": "Detalji narudžbe: {{orderId}}",
"deliveryAddress": "Adresa dostave",
@@ -36,15 +38,14 @@ export default {
"item": "Artikl",
"quantity": "Količina",
"price": "Cijena",
"vat": "PDV",
"total": "Ukupno",
"cancelOrder": "Otkaži narudžbu"
},
"cancelConfirm": {
"title": "Otkaži narudžbu",
"message": "Jeste li sigurni da želite otkazati ovu narudžbu?",
"confirm": "Otkaži narudžbu",
"confirm": "Otkaži",
"cancelling": "Otkazivanje..."
},
"processing": "Narudžba se dovršava...",
"processing": "Narudžba se obrađuje..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Jelszó",
"newPassword": "Új jelszó",
"confirmPassword": "Jelszó megerősítése",
"forgotPassword": "Elfelejtett jelszó?",
"loginWithGoogle": "Bejelentkezés Google-lal",
@@ -13,13 +14,37 @@ export default {
"privacyPolicy": "Adatvédelmi szabályzatot",
"passwordMinLength": "A jelszónak legalább 8 karakter hosszúnak kell lennie",
"newPasswordMinLength": "Az új jelszónak legalább 8 karakter hosszúnak kell lennie",
"backToHome": "Vissza a kezdőlapra",
"menu": {
"profile": "Profil",
"myProfile": "Saját profilom",
"myProfile": "Saját profil",
"checkout": "Pénztár",
"orders": "Rendelések",
"settings": "Beállítások",
"adminDashboard": "Admin Vezérlőpult",
"adminUsers": "Admin Felhasználók"
},
"resetPassword": {
"title": "Jelszó visszaállítása",
"button": "Jelszó visszaállítása",
"success": "A jelszavad sikeresen visszaállítva! Hamarosan átirányítunk a bejelentkezéshez...",
"invalidToken": "Érvényes token nem található. Kérjük, használd az emailedben található linket.",
"error": "Hiba történt a jelszó visszaállítása során",
"emailSent": "Egy link a jelszó visszaállításához elküldésre került az email címedre.",
"emailError": "Hiba történt az email küldése során"
},
"errors": {
"fillAllFields": "Kérjük, tölts ki minden mezőt",
"invalidEmail": "Kérjük, adj meg egy érvényes email címet",
"passwordsNotMatch": "A jelszavak nem egyeznek",
"passwordsNotMatchShort": "A jelszavak nem egyeznek",
"enterEmail": "Kérjük, add meg az email címed",
"loginFailed": "Bejelentkezés sikertelen",
"registerFailed": "Regisztráció sikertelen",
"googleLoginFailed": "Google bejelentkezés sikertelen",
"emailExists": "Már létezik felhasználó ezzel az email címmel. Kérjük, használj másik email címet vagy jelentkezz be."
},
"success": {
"registerComplete": "Sikeres regisztráció. Most már bejelentkezhetsz."
}
};

View File

@@ -1,14 +1,15 @@
export default {
"status": {
"new": "Folyamatban",
"new": "feldolgozás alatt",
"pending": "Új",
"processing": "Folyamatban",
"processing": "feldolgozás alatt",
"paid": "Fizetve",
"cancelled": "Törölve",
"shipped": "Kiszállítva",
"delivered": "Kézbesítve",
"shipped": "Feladva",
"delivered": "Kiszállítva",
"return": "Visszaküldés",
"partialReturn": "Részleges visszaküldés",
"partialDelivered": "Részben kézbesítve"
"partialDelivered": "Részben kiszállítva"
},
"table": {
"orderNumber": "Rendelésszám",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Rendelés törlése"
},
"noOrders": "Még nem adott le rendelést.",
"trackShipment": "Szállítmány követése",
"details": {
"title": "Rendelés részletei: {{orderId}}",
"deliveryAddress": "Szállítási cím",
@@ -36,14 +38,13 @@ export default {
"item": "Termék",
"quantity": "Mennyiség",
"price": "Ár",
"vat": "ÁFA",
"total": "Összesen",
"cancelOrder": "Rendelés törlése"
},
"cancelConfirm": {
"title": "Rendelés törlése",
"message": "Biztosan törölni szeretné ezt a rendelést?",
"confirm": "Rendelés törlése",
"confirm": "Törlés",
"cancelling": "Törlés folyamatban..."
},
"processing": "A rendelés feldolgozása folyamatban..."

View File

@@ -5,21 +5,46 @@ export default {
"profile": "Profilo",
"email": "Email",
"password": "Password",
"newPassword": "Nuova password",
"confirmPassword": "Conferma password",
"forgotPassword": "Password dimenticata?",
"loginWithGoogle": "Accedi con Google",
"or": "O",
"privacyAccept": "Cliccando su \"Accedi con Google\" accetto la",
"privacyPolicy": "Informativa sulla privacy",
"privacyPolicy": "Privacy policy",
"passwordMinLength": "La password deve contenere almeno 8 caratteri",
"newPasswordMinLength": "La nuova password deve contenere almeno 8 caratteri",
"backToHome": "Torna alla homepage",
"menu": {
"profile": "Profilo",
"myProfile": "Il mio profilo",
"checkout": "Pagamento",
"checkout": "Checkout",
"orders": "Ordini",
"settings": "Impostazioni",
"adminDashboard": "Pannello di amministrazione",
"adminUsers": "Utenti amministratori"
"adminDashboard": "Admin Dashboard",
"adminUsers": "Admin Users"
},
"resetPassword": {
"title": "Reimposta password",
"button": "Reimposta password",
"success": "La tua password è stata reimpostata con successo! Verrai reindirizzato alla pagina di accesso a breve...",
"invalidToken": "Nessun token valido trovato. Per favore usa il link nella tua email.",
"error": "Errore durante la reimpostazione della password",
"emailSent": "Un link per reimpostare la password è stato inviato al tuo indirizzo email.",
"emailError": "Errore nell'invio dell'email"
},
"errors": {
"fillAllFields": "Per favore compila tutti i campi",
"invalidEmail": "Per favore inserisci un indirizzo email valido",
"passwordsNotMatch": "Le password non corrispondono",
"passwordsNotMatchShort": "Le password non corrispondono",
"enterEmail": "Per favore inserisci il tuo indirizzo email",
"loginFailed": "Accesso fallito",
"registerFailed": "Registrazione fallita",
"googleLoginFailed": "Accesso con Google fallito",
"emailExists": "Esiste già un utente con questo indirizzo email. Per favore usa un altro indirizzo email o accedi."
},
"success": {
"registerComplete": "Registrazione completata con successo. Ora puoi accedere."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "In lavorazione",
"new": "in lavorazione",
"pending": "Nuovo",
"processing": "In lavorazione",
"processing": "in lavorazione",
"paid": "Pagato",
"cancelled": "Annullato",
"shipped": "Spedito",
"delivered": "Consegnato",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Annulla ordine"
},
"noOrders": "Non hai ancora effettuato ordini.",
"trackShipment": "Traccia spedizione",
"details": {
"title": "Dettagli ordine: {{orderId}}",
"deliveryAddress": "Indirizzo di consegna",
@@ -36,15 +38,14 @@ export default {
"item": "Articolo",
"quantity": "Quantità",
"price": "Prezzo",
"vat": "IVA",
"total": "Totale",
"cancelOrder": "Annulla ordine"
},
"cancelConfirm": {
"title": "Annulla ordine",
"message": "Sei sicuro di voler annullare questo ordine?",
"confirm": "Annulla ordine",
"confirm": "Annulla",
"cancelling": "Annullamento in corso..."
},
"processing": "Ordine in elaborazione..."
"processing": "Ordine in fase di completamento..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Hasło",
"newPassword": "Nowe hasło",
"confirmPassword": "Potwierdź hasło",
"forgotPassword": "Zapomniałeś hasła?",
"loginWithGoogle": "Zaloguj się przez Google",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Politykę prywatności",
"passwordMinLength": "Hasło musi mieć co najmniej 8 znaków",
"newPasswordMinLength": "Nowe hasło musi mieć co najmniej 8 znaków",
"backToHome": "Powrót do strony głównej",
"menu": {
"profile": "Profil",
"myProfile": "Mój profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Ustawienia",
"adminDashboard": "Panel administratora",
"adminUsers": "Użytkownicy administratora"
},
"resetPassword": {
"title": "Resetowanie hasła",
"button": "Resetuj hasło",
"success": "Twoje hasło zostało pomyślnie zresetowane! Wkrótce nastąpi przekierowanie do logowania...",
"invalidToken": "Nie znaleziono ważnego tokenu. Proszę użyć linku z e-maila.",
"error": "Błąd podczas resetowania hasła",
"emailSent": "Link do resetowania hasła został wysłany na Twój adres e-mail.",
"emailError": "Błąd podczas wysyłania e-maila"
},
"errors": {
"fillAllFields": "Proszę wypełnić wszystkie pola",
"invalidEmail": "Proszę podać prawidłowy adres e-mail",
"passwordsNotMatch": "Hasła nie są zgodne",
"passwordsNotMatchShort": "Hasła nie są zgodne",
"enterEmail": "Proszę podać swój adres e-mail",
"loginFailed": "Logowanie nie powiodło się",
"registerFailed": "Rejestracja nie powiodła się",
"googleLoginFailed": "Logowanie przez Google nie powiodło się",
"emailExists": "Użytkownik z tym adresem e-mail już istnieje. Proszę użyć innego adresu e-mail lub się zalogować."
},
"success": {
"registerComplete": "Rejestracja zakończona sukcesem. Możesz się teraz zalogować."
}
};

View File

@@ -1,14 +1,15 @@
export default {
"status": {
"new": "W trakcie realizacji",
"new": "w trakcie realizacji",
"pending": "Nowe",
"processing": "W trakcie realizacji",
"cancelled": "Anulowano",
"shipped": "Wysłano",
"delivered": "Dostarczono",
"processing": "w trakcie realizacji",
"paid": "Opłacone",
"cancelled": "Anulowane",
"shipped": "Wysłane",
"delivered": "Dostarczone",
"return": "Zwrot",
"partialReturn": "Częściowy zwrot",
"partialDelivered": "Częściowo dostarczono"
"partialDelivered": "Częściowo dostarczone"
},
"table": {
"orderNumber": "Numer zamówienia",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Anuluj zamówienie"
},
"noOrders": "Nie złożyłeś jeszcze żadnych zamówień.",
"trackShipment": "Śledź przesyłkę",
"details": {
"title": "Szczegóły zamówienia: {{orderId}}",
"deliveryAddress": "Adres dostawy",
@@ -36,15 +38,14 @@ export default {
"item": "Produkt",
"quantity": "Ilość",
"price": "Cena",
"vat": "VAT",
"total": "Razem",
"cancelOrder": "Anuluj zamówienie"
},
"cancelConfirm": {
"title": "Anuluj zamówienie",
"message": "Czy na pewno chcesz anulować to zamówienie?",
"confirm": "Anuluj zamówienie",
"confirm": "Anuluj",
"cancelling": "Anulowanie..."
},
"processing": "Zamówienie jest realizowane...",
"processing": "Zamówienie jest realizowane..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Parolă",
"newPassword": "Parolă nouă",
"confirmPassword": "Confirmă parola",
"forgotPassword": "Ai uitat parola?",
"loginWithGoogle": "Autentifică-te cu Google",
@@ -12,14 +13,38 @@ export default {
"privacyAccept": "Prin clic pe „Autentifică-te cu Google” accept",
"privacyPolicy": "Politica de confidențialitate",
"passwordMinLength": "Parola trebuie să aibă cel puțin 8 caractere",
"newPasswordMinLength": "Noua parolă trebuie să aibă cel puțin 8 caractere",
"newPasswordMinLength": "Parola nouă trebuie să aibă cel puțin 8 caractere",
"backToHome": "Înapoi la pagina principală",
"menu": {
"profile": "Profil",
"myProfile": "Profilul meu",
"checkout": "Finalizare comandă",
"orders": "Comenzi",
"settings": "Setări",
"adminDashboard": "Panou de administrare",
"adminUsers": "Utilizatori administratori"
"adminDashboard": "Panou Admin",
"adminUsers": "Utilizatori Admin"
},
"resetPassword": {
"title": "Resetează parola",
"button": "Resetează parola",
"success": "Parola ta a fost resetată cu succes! Vei fi redirecționat către autentificare în scurt timp...",
"invalidToken": "Nu a fost găsit un token valid. Te rugăm să folosești linkul din emailul tău.",
"error": "Eroare la resetarea parolei",
"emailSent": "Un link pentru resetarea parolei a fost trimis la adresa ta de email.",
"emailError": "Eroare la trimiterea emailului"
},
"errors": {
"fillAllFields": "Te rugăm să completezi toate câmpurile",
"invalidEmail": "Te rugăm să introduci o adresă de email validă",
"passwordsNotMatch": "Parolele nu coincid",
"passwordsNotMatchShort": "Parolele nu coincid",
"enterEmail": "Te rugăm să introduci adresa ta de email",
"loginFailed": "Autentificare eșuată",
"registerFailed": "Înregistrare eșuată",
"googleLoginFailed": "Autentificare Google eșuată",
"emailExists": "Un utilizator cu această adresă de email există deja. Te rugăm să folosești o altă adresă de email sau să te autentifici."
},
"success": {
"registerComplete": "Înregistrare reușită. Acum te poți autentifica."
}
};

View File

@@ -1,50 +1,51 @@
export default {
"status": {
"new": "În curs",
"pending": "Nou",
"processing": "În curs",
"cancelled": "Anulat",
"shipped": "Expediat",
"delivered": "Livrat",
"return": "Returnare",
"partialReturn": "Returnare parțială",
"partialDelivered": "Livrat parțial"
"new": "în curs",
"pending": "Nou",
"processing": "în curs",
"paid": "Plătit",
"cancelled": "Anulat",
"shipped": "Expediat",
"delivered": "Livrat",
"return": "Returnare",
"partialReturn": "Returnare parțială",
"partialDelivered": "Livrat parțial"
},
"table": {
"orderNumber": "Număr comandă",
"date": "Data",
"status": "Status",
"items": "Articole",
"total": "Total",
"actions": "Acțiuni",
"viewDetails": "Vezi detalii"
"orderNumber": "Număr comandă",
"date": "Data",
"status": "Status",
"items": "Articole",
"total": "Total",
"actions": "Acțiuni",
"viewDetails": "Vezi detalii"
},
"tooltips": {
"viewDetails": "Vezi detalii",
"cancelOrder": "Anulează comanda"
"viewDetails": "Vezi detalii",
"cancelOrder": "Anulează comanda"
},
"noOrders": "Nu ați plasat încă nicio comandă.",
"noOrders": "Nu ați plasat încă nicio comandă.",
"trackShipment": "Urmărește expedierea",
"details": {
"title": "Detalii comandă: {{orderId}}",
"deliveryAddress": "Adresa de livrare",
"invoiceAddress": "Adresa de facturare",
"orderDetails": "Detalii comandă",
"deliveryMethod": "Metoda de livrare:",
"paymentMethod": "Metoda de plată:",
"notSpecified": "Nespecificat",
"orderedItems": "Articole comandate",
"item": "Articol",
"quantity": "Cantitate",
"price": "Preț",
"vat": "TVA",
"total": "Total",
"cancelOrder": "Anulează comanda"
"title": "Detalii comandă: {{orderId}}",
"deliveryAddress": "Adresa de livrare",
"invoiceAddress": "Adresa de facturare",
"orderDetails": "Detalii comandă",
"deliveryMethod": "Metoda de livrare:",
"paymentMethod": "Metoda de plată:",
"notSpecified": "Nespecificat",
"orderedItems": "Articole comandate",
"item": "Articol",
"quantity": "Cantitate",
"price": "Preț",
"total": "Total",
"cancelOrder": "Anulează comanda"
},
"cancelConfirm": {
"title": "Anulează comanda",
"message": "Sigur doriți să anulați această comandă?",
"confirm": "Anulează comanda",
"cancelling": "Se anulează..."
"title": "Anulează comanda",
"message": "Sigur doriți să anulați această comandă?",
"confirm": "Anulează",
"cancelling": "Se anulează..."
},
"processing": "Comanda este în curs de finalizare..."
"processing": "Comanda este în curs de finalizare..."
};

View File

@@ -5,14 +5,16 @@ export default {
"profile": "Профиль",
"email": "Электронная почта",
"password": "Пароль",
"newPassword": "Новый пароль",
"confirmPassword": "Подтвердите пароль",
"forgotPassword": "Забыли пароль?",
"loginWithGoogle": "Войти через Google",
"or": "ИЛИ",
"privacyAccept": "Нажимая «Войти через Google», я принимаю",
"privacyAccept": "Нажимая на \"Войти через Google\", я принимаю",
"privacyPolicy": "Политику конфиденциальности",
"passwordMinLength": "Пароль должен содержать не менее 8 символов",
"newPasswordMinLength": "Новый пароль должен содержать не менее 8 символов",
"backToHome": "Вернуться на главную страницу",
"menu": {
"profile": "Профиль",
"myProfile": "Мой профиль",
@@ -21,5 +23,28 @@ export default {
"settings": "Настройки",
"adminDashboard": "Панель администратора",
"adminUsers": "Пользователи администратора"
},
"resetPassword": {
"title": "Сброс пароля",
"button": "Сбросить пароль",
"success": "Ваш пароль успешно сброшен! Скоро вы будете перенаправлены на страницу входа...",
"invalidToken": "Действительный токен не найден. Пожалуйста, используйте ссылку из вашего письма.",
"error": "Ошибка при сбросе пароля",
"emailSent": "Ссылка для сброса пароля была отправлена на ваш адрес электронной почты.",
"emailError": "Ошибка при отправке письма"
},
"errors": {
"fillAllFields": "Пожалуйста, заполните все поля",
"invalidEmail": "Пожалуйста, введите действительный адрес электронной почты",
"passwordsNotMatch": "Пароли не совпадают",
"passwordsNotMatchShort": "Пароли не совпадают",
"enterEmail": "Пожалуйста, введите ваш адрес электронной почты",
"loginFailed": "Не удалось войти",
"registerFailed": "Не удалось зарегистрироваться",
"googleLoginFailed": "Не удалось войти через Google",
"emailExists": "Пользователь с таким адресом электронной почты уже существует. Пожалуйста, используйте другой адрес электронной почты или войдите в систему."
},
"success": {
"registerComplete": "Регистрация прошла успешно. Теперь вы можете войти."
}
};

View File

@@ -1,14 +1,15 @@
export default {
"status": {
"new": "В процессе",
"new": "в процессе",
"pending": "Новый",
"processing": "В процессе",
"cancelled": "Отменён",
"shipped": "Отправлен",
"delivered": "Доставлен",
"processing": "в процессе",
"paid": "Оплачено",
"cancelled": "Отменено",
"shipped": "Отправлено",
"delivered": "Доставлено",
"return": "Возврат",
"partialReturn": "Частичный возврат",
"partialDelivered": "Частично доставлен"
"partialDelivered": "Частично доставлено"
},
"table": {
"orderNumber": "Номер заказа",
@@ -23,11 +24,12 @@ export default {
"viewDetails": "Просмотреть детали",
"cancelOrder": "Отменить заказ"
},
"noOrders": "Вы ещё не сделали ни одного заказа.",
"noOrders": "Вы еще не сделали ни одного заказа.",
"trackShipment": "Отследить отправление",
"details": {
"title": "Детали заказа: {{orderId}}",
"deliveryAddress": "Адрес доставки",
"invoiceAddress": "Адрес для счёта",
"invoiceAddress": "Адрес для счета",
"orderDetails": "Детали заказа",
"deliveryMethod": "Способ доставки:",
"paymentMethod": "Способ оплаты:",
@@ -36,15 +38,14 @@ export default {
"item": "Товар",
"quantity": "Количество",
"price": "Цена",
"vat": "НДС",
"total": "Итого",
"cancelOrder": "Отменить заказ"
},
"cancelConfirm": {
"title": "Отмена заказа",
"title": "Отменить заказ",
"message": "Вы уверены, что хотите отменить этот заказ?",
"confirm": "Отменить заказ",
"confirm": "Отменить",
"cancelling": "Отмена..."
},
"processing": "Заказ обрабатывается...",
"processing": "Заказ обрабатывается..."
};

View File

@@ -5,14 +5,16 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Heslo",
"newPassword": "Nové heslo",
"confirmPassword": "Potvrdiť heslo",
"forgotPassword": "Zabudli ste heslo?",
"loginWithGoogle": "Prihlásiť sa cez Google",
"or": "ALEBO",
"privacyAccept": "Kliknutím na „Prihlásiť sa cez Google“ súhlasím s",
"privacyPolicy": "Zásadami ochrany osobných údajov",
"privacyPolicy": "zásadami ochrany osobných údajov",
"passwordMinLength": "Heslo musí mať aspoň 8 znakov",
"newPasswordMinLength": "Nové heslo musí mať aspoň 8 znakov",
"backToHome": "Späť na domovskú stránku",
"menu": {
"profile": "Profil",
"myProfile": "Môj profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Nastavenia",
"adminDashboard": "Admin Dashboard",
"adminUsers": "Admin Users"
},
"resetPassword": {
"title": "Obnoviť heslo",
"button": "Obnoviť heslo",
"success": "Vaše heslo bolo úspešne obnovené! Čoskoro budete presmerovaní na prihlásenie...",
"invalidToken": "Nenašiel sa platný token. Použite prosím odkaz z vášho e-mailu.",
"error": "Chyba pri obnove hesla",
"emailSent": "Odkaz na obnovenie hesla bol odoslaný na vašu e-mailovú adresu.",
"emailError": "Chyba pri odosielaní e-mailu"
},
"errors": {
"fillAllFields": "Prosím, vyplňte všetky polia",
"invalidEmail": "Zadajte platnú e-mailovú adresu",
"passwordsNotMatch": "Heslá sa nezhodujú",
"passwordsNotMatchShort": "Heslá sa nezhodujú",
"enterEmail": "Zadajte svoju e-mailovú adresu",
"loginFailed": "Prihlásenie zlyhalo",
"registerFailed": "Registrácia zlyhala",
"googleLoginFailed": "Prihlásenie cez Google zlyhalo",
"emailExists": "Používateľ s touto e-mailovou adresou už existuje. Použite prosím inú e-mailovú adresu alebo sa prihláste."
},
"success": {
"registerComplete": "Registrácia bola úspešná. Teraz sa môžete prihlásiť."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "Prebieha",
"new": "v procese",
"pending": "Nové",
"processing": "Prebieha",
"processing": "v procese",
"paid": "Zaplatené",
"cancelled": "Zrušené",
"shipped": "Odoslané",
"delivered": "Doručené",
@@ -15,7 +16,7 @@ export default {
"date": "Dátum",
"status": "Stav",
"items": "Položky",
"total": "Spolu",
"total": "Celkom",
"actions": "Akcie",
"viewDetails": "Zobraziť detaily"
},
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Zrušiť objednávku"
},
"noOrders": "Ešte ste neuskutočnili žiadne objednávky.",
"trackShipment": "Sledovať zásielku",
"details": {
"title": "Detaily objednávky: {{orderId}}",
"deliveryAddress": "Dodacia adresa",
@@ -36,15 +38,14 @@ export default {
"item": "Položka",
"quantity": "Množstvo",
"price": "Cena",
"vat": "DPH",
"total": "Spolu",
"cancelOrder": "Zrušiť objednávku"
},
"cancelConfirm": {
"title": "Zrušiť objednávku",
"message": "Naozaj chcete zrušiť túto objednávku?",
"confirm": "Zrušiť objednávku",
"message": "Ste si istý, že chcete zrušiť túto objednávku?",
"confirm": "Zrušiť",
"cancelling": "Zrušovanie..."
},
"processing": "Objednávka sa dokončuje..."
"processing": "Objednávka sa spracováva..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "E-pošta",
"password": "Geslo",
"newPassword": "Novo geslo",
"confirmPassword": "Potrdi geslo",
"forgotPassword": "Ste pozabili geslo?",
"loginWithGoogle": "Prijava z Google",
@@ -13,13 +14,37 @@ export default {
"privacyPolicy": "Pravilnik o zasebnosti",
"passwordMinLength": "Geslo mora biti dolgo vsaj 8 znakov",
"newPasswordMinLength": "Novo geslo mora biti dolgo vsaj 8 znakov",
"backToHome": "Nazaj na domačo stran",
"menu": {
"profile": "Profil",
"myProfile": "Moj profil",
"checkout": "Zaključek nakupa",
"orders": "Naročila",
"settings": "Nastavitve",
"adminDashboard": "Nadzorna plošča administratorja",
"adminUsers": "Administratorji"
"adminDashboard": "Administratorska nadzorna plošča",
"adminUsers": "Administratorski uporabniki"
},
"resetPassword": {
"title": "Ponastavi geslo",
"button": "Ponastavi geslo",
"success": "Vaše geslo je bilo uspešno ponastavljeno! Kmalu boste preusmerjeni na prijavo...",
"invalidToken": "Ni najden veljaven žeton. Prosimo, uporabite povezavo iz vašega e-poštnega sporočila.",
"error": "Napaka pri ponastavitvi gesla",
"emailSent": "Povezava za ponastavitev gesla je bila poslana na vaš e-poštni naslov.",
"emailError": "Napaka pri pošiljanju e-pošte"
},
"errors": {
"fillAllFields": "Prosimo, izpolnite vsa polja",
"invalidEmail": "Prosimo, vnesite veljaven e-poštni naslov",
"passwordsNotMatch": "Gesli se ne ujemata",
"passwordsNotMatchShort": "Gesli se ne ujemata",
"enterEmail": "Prosimo, vnesite vaš e-poštni naslov",
"loginFailed": "Prijava ni uspela",
"registerFailed": "Registracija ni uspela",
"googleLoginFailed": "Prijava z Google ni uspela",
"emailExists": "Uporabnik s tem e-poštnim naslovom že obstaja. Prosimo, uporabite drug e-poštni naslov ali se prijavite."
},
"success": {
"registerComplete": "Registracija je bila uspešna. Zdaj se lahko prijavite."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "V teku",
"new": "v teku",
"pending": "Novo",
"processing": "V teku",
"processing": "v teku",
"paid": "Plačano",
"cancelled": "Preklicano",
"shipped": "Poslano",
"delivered": "Dostavljeno",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Prekliči naročilo"
},
"noOrders": "Še niste oddali nobenega naročila.",
"trackShipment": "Sledi pošiljki",
"details": {
"title": "Podrobnosti naročila: {{orderId}}",
"deliveryAddress": "Naslov za dostavo",
@@ -36,15 +38,14 @@ export default {
"item": "Izdelek",
"quantity": "Količina",
"price": "Cena",
"vat": "DDV",
"total": "Skupaj",
"cancelOrder": "Prekliči naročilo"
},
"cancelConfirm": {
"title": "Prekliči naročilo",
"message": "Ste prepričani, da želite preklicati to naročilo?",
"confirm": "Prekliči naročilo",
"confirm": "Prekliči",
"cancelling": "Preklicujem..."
},
"processing": "Naročilo se zaključuje...",
"processing": "Naročilo se zaključuje..."
};

View File

@@ -5,21 +5,46 @@ export default {
"profile": "Profili",
"email": "Email",
"password": "Fjalëkalimi",
"newPassword": "Fjalëkalim i ri",
"confirmPassword": "Konfirmo fjalëkalimin",
"forgotPassword": "Keni harruar fjalëkalimin?",
"loginWithGoogle": "Hyr me Google",
"or": "OSE",
"privacyAccept": "Duke klikuar \"Hyr me Google\" pranoj",
"privacyPolicy": "Politikën e Privatësisë",
"privacyAccept": "Duke klikuar \"Hyr me Google\" unë pranoj",
"privacyPolicy": "Politikën e privatësisë",
"passwordMinLength": "Fjalëkalimi duhet të ketë të paktën 8 karaktere",
"newPasswordMinLength": "Fjalëkalimi i ri duhet të ketë të paktën 8 karaktere",
"backToHome": "Kthehu në faqen kryesore",
"menu": {
"profile": "Profili",
"myProfile": "Profili im",
"checkout": "Përfundimi i porosisë",
"orders": "Porositë",
"settings": "Cilësimet",
"adminDashboard": "Paneli i Administratorit",
"adminUsers": "Përdoruesit e Administratorit"
"adminDashboard": "Paneli i administratorit",
"adminUsers": "Përdoruesit e administratorit"
},
"resetPassword": {
"title": "Rivendos fjalëkalimin",
"button": "Rivendos fjalëkalimin",
"success": "Fjalëkalimi juaj u rivendos me sukses! Do të ridrejtoheni për t'u futur së shpejti...",
"invalidToken": "Nuk u gjet asnjë token i vlefshëm. Ju lutemi përdorni lidhjen nga emaili juaj.",
"error": "Gabim gjatë rivendosjes së fjalëkalimit",
"emailSent": "Një lidhje për rivendosjen e fjalëkalimit është dërguar në adresën tuaj të emailit.",
"emailError": "Gabim gjatë dërgimit të emailit"
},
"errors": {
"fillAllFields": "Ju lutemi plotësoni të gjitha fushat",
"invalidEmail": "Ju lutemi shkruani një adresë emaili të vlefshme",
"passwordsNotMatch": "Fjalëkalimet nuk përputhen",
"passwordsNotMatchShort": "Fjalëkalimet nuk përputhen",
"enterEmail": "Ju lutemi shkruani adresën tuaj të emailit",
"loginFailed": "Hyrja dështoi",
"registerFailed": "Regjistrimi dështoi",
"googleLoginFailed": "Hyrja me Google dështoi",
"emailExists": "Një përdorues me këtë adresë emaili ekziston tashmë. Ju lutemi përdorni një adresë tjetër emaili ose hyni."
},
"success": {
"registerComplete": "Regjistrimi u krye me sukses. Tani mund të hyni."
}
};

View File

@@ -1,13 +1,14 @@
export default {
"status": {
"new": "Në proces",
"new": "në proces",
"pending": "E re",
"processing": "Në proces",
"processing": "në proces",
"paid": "E paguar",
"cancelled": "Anuluar",
"shipped": "Dërguar",
"delivered": "Dorëzuar",
"return": "Kthim",
"partialReturn": "Kthim pjesërisht",
"partialReturn": "Kthim i pjesshëm",
"partialDelivered": "Dorëzuar pjesërisht"
},
"table": {
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Anulo porosinë"
},
"noOrders": "Nuk keni bërë ende asnjë porosi.",
"trackShipment": "Ndjek dërgesën",
"details": {
"title": "Detajet e porosisë: {{orderId}}",
"deliveryAddress": "Adresa e dorëzimit",
@@ -36,15 +38,14 @@ export default {
"item": "Artikulli",
"quantity": "Sasia",
"price": "Çmimi",
"vat": "TVSH",
"total": "Totali",
"cancelOrder": "Anulo porosinë"
},
"cancelConfirm": {
"title": "Anulo Porosinë",
"title": "Anulo porosinë",
"message": "A jeni i sigurt që dëshironi të anuloni këtë porosi?",
"confirm": "Anulo Porosinë",
"confirm": "Anulo",
"cancelling": "Duke anuluar..."
},
"processing": "Porosia po përpunohet...",
"processing": "Porosia po përpunohet..."
};

View File

@@ -5,14 +5,16 @@ export default {
"profile": "Profil",
"email": "Email",
"password": "Lozinka",
"newPassword": "Nova lozinka",
"confirmPassword": "Potvrdi lozinku",
"forgotPassword": "Zaboravili ste lozinku?",
"loginWithGoogle": "Prijavite se sa Google-om",
"loginWithGoogle": "Prijavite se putem Google-a",
"or": "ILI",
"privacyAccept": "Klikom na \"Prijavite se sa Google-om\" prihvatam",
"privacyAccept": "Klikom na \"Prijavite se putem Google-a\" prihvatam",
"privacyPolicy": "Politiku privatnosti",
"passwordMinLength": "Lozinka mora imati najmanje 8 karaktera",
"newPasswordMinLength": "Nova lozinka mora imati najmanje 8 karaktera",
"backToHome": "Nazad na početnu stranicu",
"menu": {
"profile": "Profil",
"myProfile": "Moj profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Podešavanja",
"adminDashboard": "Admin kontrolna tabla",
"adminUsers": "Admin korisnici"
},
"resetPassword": {
"title": "Resetovanje lozinke",
"button": "Resetuj lozinku",
"success": "Vaša lozinka je uspešno resetovana! Uskoro ćete biti preusmereni na prijavu...",
"invalidToken": "Nije pronađen važeći token. Molimo koristite link iz vaše email poruke.",
"error": "Greška pri resetovanju lozinke",
"emailSent": "Link za resetovanje lozinke je poslat na vašu email adresu.",
"emailError": "Greška pri slanju email-a"
},
"errors": {
"fillAllFields": "Molimo popunite sva polja",
"invalidEmail": "Molimo unesite validnu email adresu",
"passwordsNotMatch": "Lozinke se ne poklapaju",
"passwordsNotMatchShort": "Lozinke se ne poklapaju",
"enterEmail": "Molimo unesite vašu email adresu",
"loginFailed": "Prijava nije uspela",
"registerFailed": "Registracija nije uspela",
"googleLoginFailed": "Prijava putem Google-a nije uspela",
"emailExists": "Korisnik sa ovom email adresom već postoji. Molimo koristite drugu email adresu ili se prijavite."
},
"success": {
"registerComplete": "Registracija uspešna. Sada se možete prijaviti."
}
};

View File

@@ -1,13 +1,14 @@
export default {
"status": {
"new": "U toku",
"new": "u toku",
"pending": "Novo",
"processing": "U toku",
"processing": "u toku",
"paid": "Plaćeno",
"cancelled": "Otkazano",
"shipped": "Poslato",
"delivered": "Isporučeno",
"return": "Povraćaj",
"partialReturn": "Delimični povraćaj",
"return": "Povrat",
"partialReturn": "Delimični povrat",
"partialDelivered": "Delimično isporučeno"
},
"table": {
@@ -23,7 +24,8 @@ export default {
"viewDetails": "Pogledaj detalje",
"cancelOrder": "Otkaži porudžbinu"
},
"noOrders": "Još uvek niste napravili nijednu porudžbinu.",
"noOrders": "Još niste napravili nijednu porudžbinu.",
"trackShipment": "Prati pošiljku",
"details": {
"title": "Detalji porudžbine: {{orderId}}",
"deliveryAddress": "Adresa za isporuku",
@@ -36,15 +38,14 @@ export default {
"item": "Artikal",
"quantity": "Količina",
"price": "Cena",
"vat": "PDV",
"total": "Ukupno",
"cancelOrder": "Otkaži porudžbinu"
},
"cancelConfirm": {
"title": "Otkaži porudžbinu",
"message": "Da li ste sigurni da želite da otkažete ovu porudžbinu?",
"confirm": "Otkaži porudžbinu",
"cancelling": "Otkazivanje..."
"confirm": "Otkaži",
"cancelling": "Otkaživanje..."
},
"processing": "Porudžbina se obrađuje...",
"processing": "Porudžbina se obrađuje..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Profil",
"email": "E-post",
"password": "Lösenord",
"newPassword": "Nytt lösenord",
"confirmPassword": "Bekräfta lösenord",
"forgotPassword": "Glömt lösenord?",
"loginWithGoogle": "Logga in med Google",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Integritetspolicy",
"passwordMinLength": "Lösenordet måste vara minst 8 tecken långt",
"newPasswordMinLength": "Det nya lösenordet måste vara minst 8 tecken långt",
"backToHome": "Tillbaka till startsidan",
"menu": {
"profile": "Profil",
"myProfile": "Min profil",
@@ -21,5 +23,28 @@ export default {
"settings": "Inställningar",
"adminDashboard": "Admin Dashboard",
"adminUsers": "Admin Users"
},
"resetPassword": {
"title": "Återställ lösenord",
"button": "Återställ lösenord",
"success": "Ditt lösenord har återställts! Du kommer snart att omdirigeras till inloggningen...",
"invalidToken": "Ingen giltig token hittades. Vänligen använd länken från ditt e-postmeddelande.",
"error": "Fel vid återställning av lösenord",
"emailSent": "En länk för att återställa ditt lösenord har skickats till din e-postadress.",
"emailError": "Fel vid sändning av e-post"
},
"errors": {
"fillAllFields": "Vänligen fyll i alla fält",
"invalidEmail": "Ange en giltig e-postadress",
"passwordsNotMatch": "Lösenorden matchar inte",
"passwordsNotMatchShort": "Lösenorden matchar inte",
"enterEmail": "Ange din e-postadress",
"loginFailed": "Inloggning misslyckades",
"registerFailed": "Registrering misslyckades",
"googleLoginFailed": "Google-inloggning misslyckades",
"emailExists": "En användare med denna e-postadress finns redan. Vänligen använd en annan e-postadress eller logga in."
},
"success": {
"registerComplete": "Registreringen lyckades. Du kan nu logga in."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "Pågående",
"new": "pågående",
"pending": "Ny",
"processing": "Pågående",
"processing": "pågående",
"paid": "Betald",
"cancelled": "Avbruten",
"shipped": "Skickad",
"delivered": "Levererad",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Avbryt order"
},
"noOrders": "Du har inte lagt några beställningar än.",
"trackShipment": "Spåra försändelse",
"details": {
"title": "Orderdetaljer: {{orderId}}",
"deliveryAddress": "Leveransadress",
@@ -36,14 +38,13 @@ export default {
"item": "Artikel",
"quantity": "Antal",
"price": "Pris",
"vat": "Moms",
"total": "Totalt",
"cancelOrder": "Avbryt order"
},
"cancelConfirm": {
"title": "Avbryt order",
"message": "Är du säker på att du vill avbryta denna order?",
"confirm": "Avbryt order",
"confirm": "Avbryt",
"cancelling": "Avbryter..."
},
"processing": "Ordern behandlas..."

View File

@@ -4,15 +4,17 @@ export default {
"logout": ıkış Yap",
"profile": "Profil",
"email": "E-posta",
"password": "Şifre",
"confirmPassword": "Şifreyi Onayla",
"forgotPassword": "Şifrenizi mi unuttunuz?",
"password": "Parola",
"newPassword": "Yeni parola",
"confirmPassword": "Parolayı onayla",
"forgotPassword": "Parolanızı mı unuttunuz?",
"loginWithGoogle": "Google ile giriş yap",
"or": "VEYA",
"privacyAccept": "\"Google ile giriş yap\" butonuna tıklayarak",
"privacyPolicy": "Gizlilik Politikasını",
"passwordMinLength": "Şifre en az 8 karakter uzunluğunda olmalıdır",
"newPasswordMinLength": "Yeni şifre en az 8 karakter uzunluğunda olmalıdır",
"privacyPolicy": "Gizlilik politikasını",
"passwordMinLength": "Parola en az 8 karakter uzunluğunda olmalıdır",
"newPasswordMinLength": "Yeni parola en az 8 karakter uzunluğunda olmalıdır",
"backToHome": "Ana sayfaya dön",
"menu": {
"profile": "Profil",
"myProfile": "Profilim",
@@ -21,5 +23,28 @@ export default {
"settings": "Ayarlar",
"adminDashboard": "Yönetici Paneli",
"adminUsers": "Yönetici Kullanıcılar"
},
"resetPassword": {
"title": "Parolayı sıfırla",
"button": "Parolayı sıfırla",
"success": "Parolanız başarıyla sıfırlandı! Kısa süre içinde giriş sayfasına yönlendirileceksiniz...",
"invalidToken": "Geçerli bir token bulunamadı. Lütfen e-postanızdaki bağlantıyı kullanın.",
"error": "Parola sıfırlama hatası",
"emailSent": "Parolanızı sıfırlamak için bir bağlantı e-posta adresinize gönderildi.",
"emailError": "E-posta gönderme hatası"
},
"errors": {
"fillAllFields": "Lütfen tüm alanları doldurun",
"invalidEmail": "Lütfen geçerli bir e-posta adresi girin",
"passwordsNotMatch": "Parolalar eşleşmiyor",
"passwordsNotMatchShort": "Parolalar eşleşmiyor",
"enterEmail": "Lütfen e-posta adresinizi girin",
"loginFailed": "Giriş başarısız",
"registerFailed": "Kayıt başarısız",
"googleLoginFailed": "Google ile giriş başarısız",
"emailExists": "Bu e-posta adresiyle zaten bir kullanıcı mevcut. Lütfen başka bir e-posta adresi kullanın veya giriş yapın."
},
"success": {
"registerComplete": "Kayıt başarılı. Artık giriş yapabilirsiniz."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "Devam ediyor",
"new": "işlemde",
"pending": "Yeni",
"processing": "Devam ediyor",
"processing": "işlemde",
"paid": "Ödendi",
"cancelled": "İptal edildi",
"shipped": "Gönderildi",
"delivered": "Teslim edildi",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Siparişi iptal et"
},
"noOrders": "Henüz sipariş vermediniz.",
"trackShipment": "Gönderiyi takip et",
"details": {
"title": "Sipariş detayları: {{orderId}}",
"deliveryAddress": "Teslimat adresi",
@@ -31,20 +33,19 @@ export default {
"orderDetails": "Sipariş detayları",
"deliveryMethod": "Teslimat yöntemi:",
"paymentMethod": "Ödeme yöntemi:",
"notSpecified": "Belirtilmemiş",
"notSpecified": "Belirtilmedi",
"orderedItems": "Sipariş edilen ürünler",
"item": "Ürün",
"quantity": "Adet",
"quantity": "Miktar",
"price": "Fiyat",
"vat": "KDV",
"total": "Toplam",
"cancelOrder": "Siparişi iptal et"
},
"cancelConfirm": {
"title": "Siparişi İptal Et",
"title": "Siparişi iptal et",
"message": "Bu siparişi iptal etmek istediğinizden emin misiniz?",
"confirm": "Siparişi İptal Et",
"confirm": "İptal et",
"cancelling": "İptal ediliyor..."
},
"processing": "Sipariş tamamlanıyor...",
"processing": "Sipariş tamamlanıyor..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "Профіль",
"email": "Електронна пошта",
"password": "Пароль",
"newPassword": "Новий пароль",
"confirmPassword": "Підтвердити пароль",
"forgotPassword": "Забули пароль?",
"loginWithGoogle": "Увійти через Google",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "Політику конфіденційності",
"passwordMinLength": "Пароль повинен містити щонайменше 8 символів",
"newPasswordMinLength": "Новий пароль повинен містити щонайменше 8 символів",
"backToHome": "Повернутися на головну сторінку",
"menu": {
"profile": "Профіль",
"myProfile": "Мій профіль",
@@ -21,5 +23,28 @@ export default {
"settings": "Налаштування",
"adminDashboard": "Панель адміністратора",
"adminUsers": "Адміністратори"
},
"resetPassword": {
"title": "Скинути пароль",
"button": "Скинути пароль",
"success": "Ваш пароль успішно скинуто! Незабаром ви будете перенаправлені для входу...",
"invalidToken": "Дійсний токен не знайдено. Будь ласка, використовуйте посилання з вашої електронної пошти.",
"error": "Помилка при скиданні пароля",
"emailSent": "Посилання для скидання пароля надіслано на вашу електронну адресу.",
"emailError": "Помилка надсилання електронної пошти"
},
"errors": {
"fillAllFields": "Будь ласка, заповніть усі поля",
"invalidEmail": "Будь ласка, введіть дійсну електронну адресу",
"passwordsNotMatch": "Паролі не співпадають",
"passwordsNotMatchShort": "Паролі не співпадають",
"enterEmail": "Будь ласка, введіть вашу електронну адресу",
"loginFailed": "Не вдалося увійти",
"registerFailed": "Не вдалося зареєструватися",
"googleLoginFailed": "Не вдалося увійти через Google",
"emailExists": "Користувач з цією електронною адресою вже існує. Будь ласка, використовуйте іншу адресу або увійдіть."
},
"success": {
"registerComplete": "Реєстрація успішна. Тепер ви можете увійти."
}
};

View File

@@ -1,8 +1,9 @@
export default {
"status": {
"new": "В процесі",
"new": "в процесі",
"pending": "Новий",
"processing": "В процесі",
"processing": "в процесі",
"paid": "Оплачено",
"cancelled": "Скасовано",
"shipped": "Відправлено",
"delivered": "Доставлено",
@@ -24,6 +25,7 @@ export default {
"cancelOrder": "Скасувати замовлення"
},
"noOrders": "Ви ще не робили замовлень.",
"trackShipment": "Відстежити відправлення",
"details": {
"title": "Деталі замовлення: {{orderId}}",
"deliveryAddress": "Адреса доставки",
@@ -36,15 +38,14 @@ export default {
"item": "Товар",
"quantity": "Кількість",
"price": "Ціна",
"vat": "ПДВ",
"total": "Всього",
"cancelOrder": "Скасувати замовлення"
},
"cancelConfirm": {
"title": "Скасувати замовлення",
"message": "Ви впевнені, що хочете скасувати це замовлення?",
"confirm": "Скасувати замовлення",
"cancelling": "Скасування..."
"confirm": "Скасувати",
"cancelling": "Скасовується..."
},
"processing": "Замовлення обробляється...",
"processing": "Замовлення обробляється..."
};

View File

@@ -5,6 +5,7 @@ export default {
"profile": "个人资料",
"email": "电子邮件",
"password": "密码",
"newPassword": "新密码",
"confirmPassword": "确认密码",
"forgotPassword": "忘记密码?",
"loginWithGoogle": "使用 Google 登录",
@@ -13,6 +14,7 @@ export default {
"privacyPolicy": "隐私政策",
"passwordMinLength": "密码长度至少为8个字符",
"newPasswordMinLength": "新密码长度至少为8个字符",
"backToHome": "返回首页",
"menu": {
"profile": "个人资料",
"myProfile": "我的资料",
@@ -21,5 +23,28 @@ export default {
"settings": "设置",
"adminDashboard": "管理员面板",
"adminUsers": "管理员用户"
},
"resetPassword": {
"title": "重置密码",
"button": "重置密码",
"success": "您的密码已成功重置!您将很快被重定向到登录页面...",
"invalidToken": "未找到有效的令牌。请使用您邮箱中的链接。",
"error": "重置密码时出错",
"emailSent": "重置密码的链接已发送到您的电子邮件地址。",
"emailError": "发送电子邮件时出错"
},
"errors": {
"fillAllFields": "请填写所有字段",
"invalidEmail": "请输入有效的电子邮件地址",
"passwordsNotMatch": "密码不匹配",
"passwordsNotMatchShort": "密码不匹配",
"enterEmail": "请输入您的电子邮件地址",
"loginFailed": "登录失败",
"registerFailed": "注册失败",
"googleLoginFailed": "Google 登录失败",
"emailExists": "该电子邮件地址已被注册。请使用其他电子邮件地址或登录。"
},
"success": {
"registerComplete": "注册成功。您现在可以登录。"
}
};

View File

@@ -3,6 +3,7 @@ export default {
"new": "进行中",
"pending": "新订单",
"processing": "进行中",
"paid": "已付款",
"cancelled": "已取消",
"shipped": "已发货",
"delivered": "已送达",
@@ -23,27 +24,27 @@ export default {
"viewDetails": "查看详情",
"cancelOrder": "取消订单"
},
"noOrders": "您尚未下过任何订单。",
"noOrders": "您还没有下过任何订单。",
"trackShipment": "跟踪发货",
"details": {
"title": "订单详情: {{orderId}}",
"deliveryAddress": "收货地址",
"invoiceAddress": "发票地址",
"orderDetails": "订单详情",
"deliveryMethod": "配送方式:",
"paymentMethod": "支付方式:",
"deliveryMethod": "配送方式",
"paymentMethod": "支付方式",
"notSpecified": "未指定",
"orderedItems": "订购商品",
"item": "商品",
"quantity": "数量",
"price": "价格",
"vat": "增值税",
"total": "总计",
"cancelOrder": "取消订单"
},
"cancelConfirm": {
"title": "取消订单",
"message": "您确定要取消此订单吗?",
"confirm": "取消订单",
"confirm": "取消",
"cancelling": "正在取消..."
},
"processing": "订单正在处理..."

231
src/pages/CategoriesPage.js Normal file
View File

@@ -0,0 +1,231 @@
import React, { Component } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress';
import Paper from '@mui/material/Paper';
import LegalPage from './LegalPage.js';
import CategoryBox from '../components/CategoryBox.js';
import { withI18n } from '../i18n/withTranslation.js';
// Helper function to recursively collect all categories from the tree
const collectAllCategories = (categoryNode, categories = [], level = 0) => {
if (!categoryNode) return categories;
// Add current category (skip root category 209)
if (categoryNode.id !== 209 && categoryNode.seoName) {
categories.push({
id: categoryNode.id,
name: categoryNode.name,
seoName: categoryNode.seoName,
level: level
});
}
// Recursively add children
if (categoryNode.children) {
for (const child of categoryNode.children) {
collectAllCategories(child, categories, level + 1);
}
}
return categories;
};
// Check for cached data - handle both browser and prerender environments
const getProductCache = () => {
if (typeof window !== "undefined" && window.productCache) {
return window.productCache;
}
if (
typeof global !== "undefined" &&
global.window &&
global.window.productCache
) {
return global.window.productCache;
}
return null;
};
// Initialize categories from cache if available (for prerendering)
const initializeCategoryTree = (language = 'de') => {
// Try synchronous get from service first if available
if (typeof window !== "undefined" && window.categoryService) {
const syncData = window.categoryService.getSync(209, language);
if (syncData) return syncData;
}
const productCache = getProductCache();
// Fallback to productCache checks (mostly for prerender context if service isn't init)
const cacheKey = `categoryTree_209_${language}`; // Note: Service uses simpler keys, might mismatch if strictly relying on this
// Check old style cache just in case
if (productCache && productCache[cacheKey]) {
const cached = productCache[cacheKey];
if (cached.categoryTree) return cached.categoryTree;
}
return null;
};
class CategoriesPage extends Component {
constructor(props) {
super(props);
// Use languageContext if available, otherwise fallback to i18n or 'de'
const currentLanguage = props.languageContext?.currentLanguage || props.i18n?.language || 'de';
const initialTree = initializeCategoryTree(currentLanguage);
console.log("CategoriesPage constructor: currentLanguage =", currentLanguage);
this.state = {
categoryTree: initialTree,
loading: !initialTree
};
}
componentDidMount() {
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
// If we don't have data yet, or if we want to ensure freshness/socket connection
if (!this.state.categoryTree) {
this.fetchCategories(currentLanguage);
}
}
componentDidUpdate(prevProps) {
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
const prevLanguage = prevProps.languageContext?.currentLanguage || prevProps.i18n?.language || 'de';
if (currentLanguage !== prevLanguage) {
console.log(`CategoriesPage: Language changed from ${prevLanguage} to ${currentLanguage}. Refetching.`);
this.setState({ loading: true, categoryTree: [] }); // Clear tree to force re-render/loading state
this.fetchCategories(currentLanguage);
}
}
fetchCategories = (language) => {
// Use categoryService which handles caching and translated vs untranslated responses correctly
console.log(`CategoriesPage: Fetching categories for ${language} using categoryService`);
window.categoryService.get(209, language).then((tree) => {
if (tree) {
this.setState({
categoryTree: tree,
loading: false
});
} else {
console.error('Failed to fetch categories via service');
this.setState({ loading: false });
}
}).catch(err => {
console.error("Error in categoryService:", err);
this.setState({ loading: false });
});
};
renderLevel1Section = (l1Node) => {
// Collect all descendants (excluding the L1 node itself, which collectAllCategories would include first)
const descendants = collectAllCategories(l1Node).slice(1);
return (
<Paper
key={l1Node.id}
elevation={1}
sx={{
p: 2,
mb: 3,
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { xs: 'flex-start', md: 'flex-start' },
gap: 3
}}
>
{/* Level 1 Header/Box */}
<Box sx={{
minWidth: '150px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1
}}>
<CategoryBox
id={l1Node.id}
name={l1Node.name}
seoName={l1Node.seoName}
sx={{
boxShadow: 4,
width: '150px',
height: '150px'
}}
/>
<Typography
variant="h6"
sx={{
textAlign: 'center',
fontWeight: 'bold',
display: { xs: 'block', md: 'none' } // Only show text below box on mobile if needed, or rely on box text
}}
>
{/* Box already has text, so maybe no extra text needed here */}
</Typography>
</Box>
{/* Descendants area */}
<Box sx={{ flex: 1 }}>
<Box sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2
}}>
{descendants.map((cat) => (
<CategoryBox
key={cat.id}
id={cat.id}
name={cat.name}
seoName={cat.seoName}
sx={{
width: '100px',
height: '100px',
minWidth: '100px',
minHeight: '100px',
boxShadow: 1,
transition: 'transform 0.2s',
'&:hover': { transform: 'scale(1.05)', boxShadow: 3 },
fontSize: '0.9rem' // Smaller text for smaller boxes
}}
/>
))}
</Box>
</Box>
</Paper>
);
};
render() {
const { t } = this.props;
const { categoryTree, loading } = this.state;
const content = (
<Box>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</Box>
) : (
<Box>
{categoryTree && categoryTree.children && categoryTree.children.map((child) => (
this.renderLevel1Section(child)
))}
{(!categoryTree || !categoryTree.children || categoryTree.children.length === 0) && (
<Typography>Keine Kategorien gefunden.</Typography>
)}
</Box>
)}
</Box>
);
return <LegalPage title={t ? t('navigation.categories') : 'Kategorien'} content={content} />;
}
}
export default withI18n()(CategoriesPage);

View File

@@ -30,28 +30,28 @@ const ResetPassword = () => {
const tokenFromUrl = urlParams.get('token');
if (!tokenFromUrl) {
setError('Kein gültiger Token gefunden. Bitte verwenden Sie den Link aus Ihrer E-Mail.');
setError(t('auth.resetPassword.invalidToken'));
} else {
setToken(tokenFromUrl);
}
}, [location]);
}, [location, t]);
const handleSubmit = (e) => {
e.preventDefault();
// Validation
if (!newPassword || !confirmPassword) {
setError('Bitte füllen Sie alle Felder aus');
setError(t('auth.errors.fillAllFields'));
return;
}
if (newPassword.length < 8) {
setError('Das Passwort muss mindestens 8 Zeichen lang sein');
setError(t('auth.passwordMinLength'));
return;
}
if (newPassword !== confirmPassword) {
setError('Die Passwörter stimmen nicht überein');
setError(t('auth.errors.passwordsNotMatch'));
return;
}
@@ -76,7 +76,7 @@ const ResetPassword = () => {
}
}, 3000);
} else {
setError(response.message || 'Fehler beim Zurücksetzen des Passworts');
setError(response.message || t('auth.resetPassword.error'));
}
});
};
@@ -87,7 +87,7 @@ const ResetPassword = () => {
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<LockResetIcon sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} />
<Typography component="h1" variant="h5" gutterBottom>
Passwort zurücksetzen
{t('auth.resetPassword.title')}
</Typography>
{!token ? (
@@ -97,7 +97,7 @@ const ResetPassword = () => {
) : success ? (
<Box sx={{ width: '100%', mt: 2 }}>
<Alert severity="success" sx={{ mb: 2 }}>
Ihr Passwort wurde erfolgreich zurückgesetzt! Sie werden in Kürze zur Anmeldung weitergeleitet...
{t('auth.resetPassword.success')}
</Alert>
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={24} />
@@ -116,7 +116,7 @@ const ResetPassword = () => {
required
fullWidth
name="newPassword"
label="Neues Passwort"
label={t('auth.newPassword')}
type="password"
id="newPassword"
autoComplete="new-password"
@@ -130,7 +130,7 @@ const ResetPassword = () => {
required
fullWidth
name="confirmPassword"
label={t ? t('auth.confirmPassword') : 'Passwort bestätigen'}
label={t('auth.confirmPassword')}
type="password"
id="confirmPassword"
autoComplete="new-password"
@@ -154,7 +154,7 @@ const ResetPassword = () => {
{loading ? (
<CircularProgress size={24} color="inherit" />
) : (
'Passwort zurücksetzen'
t('auth.resetPassword.button')
)}
</Button>
@@ -164,7 +164,7 @@ const ResetPassword = () => {
onClick={() => navigate('/')}
sx={{ color: '#2e7d32' }}
>
Zurück zur Startseite
{t('auth.backToHome')}
</Button>
</Box>
</Box>