diff --git a/README.md b/README.md
index 8a368d5..065725e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# ShiftOryx
-Το ShiftOryx είναι multi-tenant SaaS για δημιουργία, διαχείριση και δημοσίευση προγραμμάτων βαρδιών. Ο πρώτος κλάδος είναι τα πρατήρια καυσίμων, αλλά η αρχιτεκτονική επιτρέπει μελλοντική χρήση από άλλες επιχειρήσεις με βάρδιες.
+Το ShiftOryx είναι multi-tenant SaaS για δημιουργία, διαχείριση και δημοσίευση προγραμμάτων βαρδιών για καταστήματα και επιχειρήσεις με βάρδιες.
Προηγούμενα/compatibility ονόματα: `GasStation Shift Manager`, `GasStationProject`, `GasStation-main`.
diff --git a/docs/SHIFTORYX_MASTER_ROADMAP.md b/docs/SHIFTORYX_MASTER_ROADMAP.md
index 44bd8e1..6d8b164 100644
--- a/docs/SHIFTORYX_MASTER_ROADMAP.md
+++ b/docs/SHIFTORYX_MASTER_ROADMAP.md
@@ -40,7 +40,7 @@ Current domain status: `PURCHASED_NOT_CONFIGURED`. Ownership is confirmed, but D
Όνομα: ShiftOryx.
-Το ShiftOryx είναι multi-tenant SaaS για δημιουργία, διαχείριση και δημοσίευση προγραμμάτων βαρδιών. Αρχικός κλάδος είναι τα πρατήρια καυσίμων, με δυνατότητα επέκτασης σε άλλες επιχειρήσεις με βάρδιες.
+Το ShiftOryx είναι multi-tenant SaaS για δημιουργία, διαχείριση και δημοσίευση προγραμμάτων βαρδιών για καταστήματα και επιχειρήσεις με βάρδιες.
Κύρια URLs:
diff --git a/docs/project-brain.md b/docs/project-brain.md
index 3b2084c..fad562e 100644
--- a/docs/project-brain.md
+++ b/docs/project-brain.md
@@ -14,7 +14,7 @@
## Product Direction
-Το ShiftOryx είναι multi-tenant SaaS για προγράμματα βαρδιών. Ξεκινά από πρατήρια καυσίμων αλλά ο domain model πρέπει να παραμένει επεκτάσιμος.
+Το ShiftOryx είναι multi-tenant SaaS για προγράμματα βαρδιών καταστημάτων και επιχειρήσεων, με επεκτάσιμο domain model.
Current pilot:
diff --git a/docs/project-status-report.md b/docs/project-status-report.md
index e8b6e9b..68da34a 100644
--- a/docs/project-status-report.md
+++ b/docs/project-status-report.md
@@ -16,7 +16,7 @@ Master product direction: `ShiftOryx - Master Product, Technical Architecture &
## 1. Executive Summary
-Το ShiftOryx είναι multi-tenant SaaS κατεύθυνση για προγράμματα βαρδιών, με πρώτο κλάδο τα πρατήρια καυσίμων. Το σημερινό BP Kallis pilot καλύπτει τη δημιουργία, επεξεργασία, αποθήκευση, δημόσια προβολή και εξαγωγή εβδομαδιαίων και μηνιαίων προγραμμάτων.
+Το ShiftOryx είναι multi-tenant SaaS πλατφόρμα για προγράμματα βαρδιών καταστημάτων και επιχειρήσεων. Το σημερινό BP Kallis pilot καλύπτει τη δημιουργία, επεξεργασία, αποθήκευση, δημόσια προβολή και εξαγωγή εβδομαδιαίων και μηνιαίων προγραμμάτων.
Η εφαρμογή έχει δύο βασικές εμπειρίες:
diff --git a/docs/saas-security-qa-checklist.md b/docs/saas-security-qa-checklist.md
index cef9989..5171e55 100644
--- a/docs/saas-security-qa-checklist.md
+++ b/docs/saas-security-qa-checklist.md
@@ -47,7 +47,7 @@ Use this checklist before enabling tenant-gated access or deploying SaaS foundat
- `0` memberships shows:
```text
-Δεν υπάρχει ενεργό πρατήριο συνδεδεμένο με αυτόν τον λογαριασμό.
+Δεν υπάρχει ενεργό κατάστημα συνδεδεμένο με αυτόν τον λογαριασμό.
```
- `1` membership redirects to the tenant domain.
@@ -55,7 +55,7 @@ Use this checklist before enabling tenant-gated access or deploying SaaS foundat
- Tenant denial shows:
```text
-Δεν έχετε πρόσβαση σε αυτό το πρατήριο.
+Δεν έχετε πρόσβαση σε αυτό το κατάστημα.
```
## Tenant Gate
diff --git a/package.json b/package.json
index fe8cda0..71d17a4 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,8 @@
"security:hardening": "node scripts/validate-security-hardening.mjs",
"security:scan": "npm run security:hardening && npm run security:integrity && npm run security:audit && npm run security:cve",
"qa:central-portal-isolation": "node scripts/validate-central-portal-isolation.mjs",
- "qa:auth-broker-negative": "node scripts/test-auth-broker-negative-matrix.mjs"
+ "qa:auth-broker-negative": "node scripts/test-auth-broker-negative-matrix.mjs",
+ "qa:product-terminology": "node scripts/validate-product-terminology.mjs"
},
"dependencies": {
"@dnd-kit/core": "^6.2.0",
diff --git a/scripts/validate-product-terminology.mjs b/scripts/validate-product-terminology.mjs
new file mode 100644
index 0000000..1f24680
--- /dev/null
+++ b/scripts/validate-product-terminology.mjs
@@ -0,0 +1,61 @@
+import fs from 'node:fs';
+import path from 'node:path';
+
+const SRC_DIR = path.resolve(process.cwd(), 'src');
+
+// Terms that must NOT appear in runtime customer-facing code
+const FORBIDDEN_PATTERNS = [
+ { pattern: /πρατήρ/i, name: 'Greek fuel station term (πρατήριο / πρατήρια / πρατηρίου)' },
+ { pattern: /\bfuel station\b/i, name: 'English fuel station term' },
+ { pattern: /\bgas station\b/i, name: 'English gas station term (customer-facing)' },
+];
+
+// Explicit allowlist for non-customer-facing technical compatibility identifiers
+const ALLOWLIST = [
+ 'FUEL_STATION', // Category enum identifier
+];
+
+function scanDirectory(dir, fileList = []) {
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ scanDirectory(fullPath, fileList);
+ } else if (/\.(jsx?|tsx?|html|css)$/.test(entry.name)) {
+ fileList.push(fullPath);
+ }
+ }
+ return fileList;
+}
+
+const files = scanDirectory(SRC_DIR);
+let violations = 0;
+
+for (const file of files) {
+ const content = fs.readFileSync(file, 'utf8');
+ const lines = content.split('\n');
+
+ lines.forEach((line, index) => {
+ // Strip allowed technical enum identifiers before checking
+ let strippedLine = line;
+ for (const allowed of ALLOWLIST) {
+ strippedLine = strippedLine.replaceAll(allowed, '');
+ }
+
+ for (const { pattern, name } of FORBIDDEN_PATTERNS) {
+ if (pattern.test(strippedLine)) {
+ console.error(
+ `[TERMINOLOGY VIOLATION] ${name} found in ${path.relative(process.cwd(), file)}:${index + 1}\n > ${line.trim()}`
+ );
+ violations++;
+ }
+ }
+ });
+}
+
+if (violations > 0) {
+ console.error(`\nProduct terminology validation failed with ${violations} violation(s).`);
+ process.exit(1);
+} else {
+ console.log(`Product terminology validation passed. Scanned ${files.length} runtime files with zero customer-facing fuel station terms.`);
+}
diff --git a/scripts/validate-saas-foundation.mjs b/scripts/validate-saas-foundation.mjs
index f751156..36438fd 100644
--- a/scripts/validate-saas-foundation.mjs
+++ b/scripts/validate-saas-foundation.mjs
@@ -86,8 +86,8 @@ assert(dockerfile.includes('nginx'), 'Dockerfile must serve the production build
assert(compose.includes('gasstation-frontend'), 'docker-compose must define the frontend service.');
assert(nginx.includes('try_files $uri $uri/ /index.html'), 'Nginx must provide SPA fallback.');
assert(saasSecurityChecklist.includes('Αν υπάρχει λογαριασμός με αυτό το email'), 'SaaS security QA must cover generic forgot-password messaging.');
-assert(saasSecurityChecklist.includes('Δεν υπάρχει ενεργό πρατήριο'), 'SaaS security QA must cover no-access messaging.');
-assert(saasSecurityChecklist.includes('Δεν έχετε πρόσβαση σε αυτό το πρατήριο'), 'SaaS security QA must cover tenant denied messaging.');
+assert(saasSecurityChecklist.includes('Δεν υπάρχει ενεργό κατάστημα'), 'SaaS security QA must cover no-access messaging.');
+assert(saasSecurityChecklist.includes('Δεν έχετε πρόσβαση σε αυτό το κατάστημα'), 'SaaS security QA must cover tenant denied messaging.');
assert(saasSecurityChecklist.includes('tenantMemberships/{uid}_{tenantId}'), 'SaaS security QA must cover uid-based memberships.');
assert(saasSecurityChecklist.includes('VITE_ENABLE_TENANT_GATE=false'), 'SaaS security QA must document the pilot-safe tenant gate flag.');
assert(saasSecurityChecklist.includes('No service account JSON'), 'SaaS security QA must cover secret commit prevention.');
@@ -138,10 +138,10 @@ assert(repositories.includes('tenantTokenRequestsRepository'), 'Repository expor
assert(tenantAccessService.includes('listActiveTenantAccessForUser'), 'Tenant access service must load active memberships by uid.');
assert(tenantAccessService.includes('verifyTenantAccessForHost'), 'Tenant access service must prepare tenant host membership checks.');
assert(tenantAccessService.includes('getTenantById(hostContext.tenantSlug)'), 'Tenant host access must read tenants/{tenantSlug} directly to satisfy membership-based Firestore rules.');
-assert(tenantAccessService.includes('Δεν υπάρχει ενεργό πρατήριο'), 'Tenant access service must include safe no-access message.');
+assert(tenantAccessService.includes('Δεν υπάρχει ενεργό κατάστημα'), 'Tenant access service must include safe no-access message.');
assert(
tenantAccessService.includes('TENANT_ACCESS_DENIED_MESSAGE') &&
- tenantAuthorization.includes('Δεν έχετε πρόσβαση σε αυτό το πρατήριο'),
+ tenantAuthorization.includes('Δεν έχετε πρόσβαση σε αυτό το κατάστημα'),
'Tenant access service must use the shared safe denied message.',
);
assert(!tenantAccessService.includes('@'), 'Tenant access service must not hardcode email access rules.');
diff --git a/scripts/validate-tenant-authorization.mjs b/scripts/validate-tenant-authorization.mjs
index 56691e7..ab335e3 100644
--- a/scripts/validate-tenant-authorization.mjs
+++ b/scripts/validate-tenant-authorization.mjs
@@ -12,7 +12,7 @@ function assert(condition, message) {
function assertDenied(result, reason, message) {
assert(result.allowed === false, `${message}: expected denied.`);
assert(result.reason === reason, `${message}: expected reason ${reason}, got ${result.reason}.`);
- assert(result.message === 'Δεν έχετε πρόσβαση σε αυτό το πρατήριο.', `${message}: expected safe Greek denial.`);
+ assert(result.message === 'Δεν έχετε πρόσβαση σε αυτό το κατάστημα.', `${message}: expected safe Greek denial.`);
}
function assertAllowed(result, message) {
diff --git a/src/App.jsx b/src/App.jsx
index cd36250..1ca230f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -88,8 +88,8 @@ export default function App() {
page = (
diff --git a/src/components/auth/TenantGate.jsx b/src/components/auth/TenantGate.jsx
index e87ca63..bdb303f 100644
--- a/src/components/auth/TenantGate.jsx
+++ b/src/components/auth/TenantGate.jsx
@@ -127,7 +127,7 @@ export default function TenantGate({ children, hostContext: providedHostContext,
}, [hostContext.hostname, hostContext.mode, routePath, shouldBypassGate]);
if (state.status === 'checking') {
- return ;
+ return ;
}
if (state.status === 'denied') {
diff --git a/src/components/portal/InvalidHostNotice.jsx b/src/components/portal/InvalidHostNotice.jsx
index a77d34a..84e7480 100644
--- a/src/components/portal/InvalidHostNotice.jsx
+++ b/src/components/portal/InvalidHostNotice.jsx
@@ -19,8 +19,8 @@ export default function InvalidHostNotice({ hostContext }) {
>
diff --git a/src/hooks/useSchedulerStore.js b/src/hooks/useSchedulerStore.js
index 947c5cf..a994072 100644
--- a/src/hooks/useSchedulerStore.js
+++ b/src/hooks/useSchedulerStore.js
@@ -136,7 +136,7 @@ function getCurrentYearMonth() {
function getPublicTenantId() {
const tenantSlug = getCurrentTenantHostContext()?.tenantSlug;
if (!tenantSlug) {
- throw new Error('Δεν βρέθηκε tenant context για το πρατήριο.');
+ throw new Error('Δεν βρέθηκε tenant context για το κατάστημα.');
}
return tenantSlug;
}
diff --git a/src/scheduler-engine/generateSchedule.ts b/src/scheduler-engine/generateSchedule.ts
index c2db584..8f0d19e 100644
--- a/src/scheduler-engine/generateSchedule.ts
+++ b/src/scheduler-engine/generateSchedule.ts
@@ -46,7 +46,7 @@ export function generateSchedule(input: GenerateScheduleInput): GenerateSchedule
};
}
- // 2. Check if input matches legacy 4-6 Fuel Station topology
+ // 2. Check if input matches legacy 4-6 Store topology
const hasBaseRoles =
enabledEmployees.length >= 4 &&
enabledEmployees.length <= 6 &&
diff --git a/src/services/tenantAccessService.js b/src/services/tenantAccessService.js
index 2127065..049dab4 100644
--- a/src/services/tenantAccessService.js
+++ b/src/services/tenantAccessService.js
@@ -10,7 +10,7 @@ import {
} from '../utils/tenantHostContext';
export const TENANT_ACCESS_MESSAGES = {
- noAccess: 'Δεν υπάρχει ενεργό πρατήριο συνδεδεμένο με αυτόν τον λογαριασμό.',
+ noAccess: 'Δεν υπάρχει ενεργό κατάστημα συνδεδεμένο με αυτόν τον λογαριασμό.',
denied: TENANT_ACCESS_DENIED_MESSAGE,
};
diff --git a/src/services/tenantAuthorization.js b/src/services/tenantAuthorization.js
index 24684a3..dad06ac 100644
--- a/src/services/tenantAuthorization.js
+++ b/src/services/tenantAuthorization.js
@@ -1,7 +1,7 @@
export const ACTIVE_MEMBERSHIP_STATUS = 'ACTIVE';
export const INACTIVE_MEMBERSHIP_STATUSES = ['INACTIVE', 'SUSPENDED', 'EXPIRED', 'REVOKED'];
export const ADMIN_TENANT_ROLES = ['OWNER'];
-export const TENANT_ACCESS_DENIED_MESSAGE = 'Δεν έχετε πρόσβαση σε αυτό το πρατήριο.';
+export const TENANT_ACCESS_DENIED_MESSAGE = 'Δεν έχετε πρόσβαση σε αυτό το κατάστημα.';
function normalizeToken(value) {
return String(value || '').trim().toUpperCase();
diff --git a/src/utils/portalHelpers.js b/src/utils/portalHelpers.js
index cd7eafb..080d9dc 100644
--- a/src/utils/portalHelpers.js
+++ b/src/utils/portalHelpers.js
@@ -35,7 +35,7 @@ export const VALID_BUSINESS_CATEGORIES = Object.freeze([
export const BUSINESS_CATEGORY_OPTIONS = Object.freeze([
{ id: 'OTHER', label: 'Άλλη Επιχείρηση' },
- { id: 'FUEL_STATION', label: 'Πρατήριο Καυσίμων' },
+ { id: 'FUEL_STATION', label: 'Κατάστημα' },
{ id: 'CAFE', label: 'Καφέ / Bistro' },
{ id: 'RESTAURANT', label: 'Εστιατόριο / Εστίαση' },
{ id: 'HAIR_SALON', label: 'Κομμωτήριο / Barber' },