wahnsinn vibe
This commit is contained in:
16
frontend/shared/package.json
Normal file
16
frontend/shared/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@shop/shared",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./api": "./src/api.ts",
|
||||
"./types": "./src/types.ts",
|
||||
"./i18n": "./src/i18n/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7"
|
||||
}
|
||||
}
|
||||
60
frontend/shared/src/api.ts
Normal file
60
frontend/shared/src/api.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import axios, { type AxiosInstance } from "axios";
|
||||
|
||||
const STORAGE_KEY_ACCESS = "shop_access_token";
|
||||
const STORAGE_KEY_REFRESH = "shop_refresh_token";
|
||||
|
||||
export function createApi(baseURL: string): AxiosInstance {
|
||||
// 10 min — LLM plan calls over many items on a local CPU can take several minutes.
|
||||
const api = axios.create({ baseURL, timeout: 600000 });
|
||||
|
||||
api.interceptors.request.use((cfg) => {
|
||||
const token = localStorage.getItem(STORAGE_KEY_ACCESS);
|
||||
if (token) {
|
||||
cfg.headers = cfg.headers || {};
|
||||
cfg.headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
return cfg;
|
||||
});
|
||||
|
||||
api.interceptors.response.use(
|
||||
(r) => r,
|
||||
async (err) => {
|
||||
const original = err.config;
|
||||
if (err.response?.status === 401 && !original._retry) {
|
||||
const refresh = localStorage.getItem(STORAGE_KEY_REFRESH);
|
||||
if (refresh) {
|
||||
original._retry = true;
|
||||
try {
|
||||
const resp = await axios.post(`${baseURL}/api/auth/refresh`, {
|
||||
refresh_token: refresh,
|
||||
});
|
||||
localStorage.setItem(STORAGE_KEY_ACCESS, resp.data.access_token);
|
||||
localStorage.setItem(STORAGE_KEY_REFRESH, resp.data.refresh_token);
|
||||
original.headers.Authorization = `Bearer ${resp.data.access_token}`;
|
||||
return api(original);
|
||||
} catch {
|
||||
localStorage.removeItem(STORAGE_KEY_ACCESS);
|
||||
localStorage.removeItem(STORAGE_KEY_REFRESH);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
export function saveTokens(access: string, refresh: string): void {
|
||||
localStorage.setItem(STORAGE_KEY_ACCESS, access);
|
||||
localStorage.setItem(STORAGE_KEY_REFRESH, refresh);
|
||||
}
|
||||
|
||||
export function clearTokens(): void {
|
||||
localStorage.removeItem(STORAGE_KEY_ACCESS);
|
||||
localStorage.removeItem(STORAGE_KEY_REFRESH);
|
||||
}
|
||||
|
||||
export function hasAccess(): boolean {
|
||||
return !!localStorage.getItem(STORAGE_KEY_ACCESS);
|
||||
}
|
||||
59
frontend/shared/src/i18n/de.ts
Normal file
59
frontend/shared/src/i18n/de.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export const de: Record<string, string> = {
|
||||
// common
|
||||
"common.loading": "Lädt...",
|
||||
"common.save": "Speichern",
|
||||
"common.cancel": "Abbrechen",
|
||||
"common.delete": "Löschen",
|
||||
"common.edit": "Bearbeiten",
|
||||
"common.confirm": "Bestätigen",
|
||||
"common.back": "Zurück",
|
||||
"common.name": "Name",
|
||||
"common.email": "E-Mail",
|
||||
"common.password": "Passwort",
|
||||
"common.total": "Gesamt",
|
||||
"common.empty": "Keine Einträge",
|
||||
|
||||
// shop
|
||||
"shop.title": "Shop",
|
||||
"shop.home": "Startseite",
|
||||
"shop.categories": "Kategorien",
|
||||
"shop.cart": "Warenkorb",
|
||||
"shop.account": "Mein Konto",
|
||||
"shop.orders": "Meine Bestellungen",
|
||||
"shop.login": "Anmelden",
|
||||
"shop.register": "Registrieren",
|
||||
"shop.logout": "Abmelden",
|
||||
"shop.search_placeholder": "Was suchst du? (z.B. 'grüner Pulli')",
|
||||
"shop.add_to_cart": "In den Warenkorb",
|
||||
"shop.in_stock": "Auf Lager",
|
||||
"shop.out_of_stock": "Ausverkauft",
|
||||
"shop.checkout": "Zur Kasse",
|
||||
"shop.order_placed": "Bestellung aufgegeben",
|
||||
"shop.empty_cart": "Warenkorb ist leer",
|
||||
"shop.continue_shopping": "Weiter einkaufen",
|
||||
"shop.delivery_address": "Lieferadresse",
|
||||
"shop.payment_method": "Zahlungsart",
|
||||
"shop.place_order": "Jetzt bestellen",
|
||||
"shop.street": "Straße",
|
||||
"shop.zip": "PLZ",
|
||||
"shop.city": "Stadt",
|
||||
"shop.country": "Land",
|
||||
"shop.no_products": "Keine Produkte gefunden",
|
||||
|
||||
// admin
|
||||
"admin.title": "Admin",
|
||||
"admin.dashboard": "Dashboard",
|
||||
"admin.products": "Produkte",
|
||||
"admin.categories": "Kategorien",
|
||||
"admin.orders": "Bestellungen",
|
||||
"admin.settings": "Einstellungen",
|
||||
"admin.chat_placeholder":
|
||||
"Gib einen Befehl oder wirf JSON rein (z.B. 'setze den Shopnamen auf TEST123')",
|
||||
"admin.send": "Senden",
|
||||
"admin.proposals": "Vorschläge",
|
||||
"admin.confirm_all": "Alle bestätigen",
|
||||
"admin.missing": "Fehlt",
|
||||
"admin.add_product": "Produkt hinzufügen",
|
||||
"admin.add_category": "Kategorie hinzufügen",
|
||||
"admin.new_status": "Neuer Status",
|
||||
};
|
||||
56
frontend/shared/src/i18n/en.ts
Normal file
56
frontend/shared/src/i18n/en.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export const en: Record<string, string> = {
|
||||
"common.loading": "Loading...",
|
||||
"common.save": "Save",
|
||||
"common.cancel": "Cancel",
|
||||
"common.delete": "Delete",
|
||||
"common.edit": "Edit",
|
||||
"common.confirm": "Confirm",
|
||||
"common.back": "Back",
|
||||
"common.name": "Name",
|
||||
"common.email": "Email",
|
||||
"common.password": "Password",
|
||||
"common.total": "Total",
|
||||
"common.empty": "No entries",
|
||||
|
||||
"shop.title": "Shop",
|
||||
"shop.home": "Home",
|
||||
"shop.categories": "Categories",
|
||||
"shop.cart": "Cart",
|
||||
"shop.account": "My account",
|
||||
"shop.orders": "My orders",
|
||||
"shop.login": "Log in",
|
||||
"shop.register": "Sign up",
|
||||
"shop.logout": "Log out",
|
||||
"shop.search_placeholder": "What are you looking for? (e.g. 'green sweater')",
|
||||
"shop.add_to_cart": "Add to cart",
|
||||
"shop.in_stock": "In stock",
|
||||
"shop.out_of_stock": "Out of stock",
|
||||
"shop.checkout": "Checkout",
|
||||
"shop.order_placed": "Order placed",
|
||||
"shop.empty_cart": "Cart is empty",
|
||||
"shop.continue_shopping": "Continue shopping",
|
||||
"shop.delivery_address": "Delivery address",
|
||||
"shop.payment_method": "Payment method",
|
||||
"shop.place_order": "Place order",
|
||||
"shop.street": "Street",
|
||||
"shop.zip": "ZIP",
|
||||
"shop.city": "City",
|
||||
"shop.country": "Country",
|
||||
"shop.no_products": "No products found",
|
||||
|
||||
"admin.title": "Admin",
|
||||
"admin.dashboard": "Dashboard",
|
||||
"admin.products": "Products",
|
||||
"admin.categories": "Categories",
|
||||
"admin.orders": "Orders",
|
||||
"admin.settings": "Settings",
|
||||
"admin.chat_placeholder":
|
||||
"Enter a command or paste JSON (e.g. 'set the shop name to TEST123')",
|
||||
"admin.send": "Send",
|
||||
"admin.proposals": "Proposals",
|
||||
"admin.confirm_all": "Confirm all",
|
||||
"admin.missing": "Missing",
|
||||
"admin.add_product": "Add product",
|
||||
"admin.add_category": "Add category",
|
||||
"admin.new_status": "New status",
|
||||
};
|
||||
26
frontend/shared/src/i18n/index.ts
Normal file
26
frontend/shared/src/i18n/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { I18nText, Locale } from "../types";
|
||||
import { de } from "./de";
|
||||
import { en } from "./en";
|
||||
|
||||
const dicts: Record<Locale, Record<string, string>> = { de, en };
|
||||
|
||||
let current: Locale = (localStorage.getItem("locale") as Locale) || "de";
|
||||
|
||||
export function setLocale(loc: Locale) {
|
||||
current = loc;
|
||||
localStorage.setItem("locale", loc);
|
||||
}
|
||||
|
||||
export function getLocale(): Locale {
|
||||
return current;
|
||||
}
|
||||
|
||||
export function t(key: string): string {
|
||||
return dicts[current]?.[key] || dicts.de[key] || key;
|
||||
}
|
||||
|
||||
export function pickI18n(txt: I18nText | undefined | null, loc?: Locale): string {
|
||||
if (!txt) return "";
|
||||
const l = loc || current;
|
||||
return txt[l] || txt.de || txt.en || "";
|
||||
}
|
||||
3
frontend/shared/src/index.ts
Normal file
3
frontend/shared/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./types";
|
||||
export * from "./api";
|
||||
export * as i18n from "./i18n";
|
||||
89
frontend/shared/src/types.ts
Normal file
89
frontend/shared/src/types.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
export type Locale = "de" | "en";
|
||||
|
||||
export interface I18nText {
|
||||
de?: string;
|
||||
en?: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
email: string;
|
||||
name: string;
|
||||
role: "customer" | "admin";
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
id: number;
|
||||
slug: string;
|
||||
name: I18nText;
|
||||
parent_id: number | null;
|
||||
sort_order: number;
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
sku: string;
|
||||
name: I18nText;
|
||||
description: I18nText;
|
||||
price: number;
|
||||
currency: string;
|
||||
stock: number;
|
||||
active: boolean;
|
||||
image_url: string;
|
||||
category_id: number | null;
|
||||
attributes: Record<string, any>;
|
||||
_score?: number;
|
||||
}
|
||||
|
||||
export interface CartItem {
|
||||
product_id: number;
|
||||
qty: number;
|
||||
name: I18nText;
|
||||
price: number;
|
||||
image_url: string;
|
||||
line_total: number;
|
||||
}
|
||||
|
||||
export interface Cart {
|
||||
items: CartItem[];
|
||||
subtotal: number;
|
||||
}
|
||||
|
||||
export interface OrderItem {
|
||||
product_id: number;
|
||||
sku: string;
|
||||
name: I18nText;
|
||||
price: number;
|
||||
qty: number;
|
||||
line_total: number;
|
||||
}
|
||||
|
||||
export interface Order {
|
||||
id: number;
|
||||
user_id: number | null;
|
||||
status: string;
|
||||
total: number;
|
||||
currency: string;
|
||||
address: Record<string, string>;
|
||||
payment: Record<string, any>;
|
||||
items: OrderItem[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface ProposalCard {
|
||||
tool: string;
|
||||
args: Record<string, any>;
|
||||
missing: string[];
|
||||
preview: string;
|
||||
notes?: string;
|
||||
schema: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
role: string;
|
||||
user_id: number;
|
||||
}
|
||||
Reference in New Issue
Block a user