wahnsinn vibe

This commit is contained in:
Marek Lenczewski
2026-04-16 19:42:06 +02:00
parent 9c5da44f64
commit e3e88cc58e
127 changed files with 9456 additions and 3 deletions

View 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"
}
}

View 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);
}

View 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",
};

View 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",
};

View 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 || "";
}

View File

@@ -0,0 +1,3 @@
export * from "./types";
export * from "./api";
export * as i18n from "./i18n";

View 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;
}