Added customer list, coupon list and started working on purchase list

This commit is contained in:
Thastertyn 2025-04-21 19:29:01 +02:00
parent 2fe5dbcb21
commit f2af9dc566
223 changed files with 5027 additions and 3017 deletions

30
frontend/biome.json.old Normal file
View File

@ -0,0 +1,30 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

View File

@ -1,17 +1,17 @@
{ {
"name": "frontend", "name": "frontend",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"format:check": "prettier --check .",
"format": "prettier --write .",
"knip": "knip", "knip": "knip",
"generate-client": "openapi-ts" "generate-client": "openapi-ts",
"format:write": "biome format ./src/ --write",
"format:check": "biome check ./src/"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.9.1", "@hookform/resolvers": "^3.9.1",
@ -62,6 +62,7 @@
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4",
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@faker-js/faker": "^9.3.0", "@faker-js/faker": "^9.3.0",
"@hey-api/client-axios": "^0.6.2", "@hey-api/client-axios": "^0.6.2",

View File

@ -147,6 +147,9 @@ importers:
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3(@types/react@19.1.0)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) version: 5.0.3(@types/react@19.1.0)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
devDependencies: devDependencies:
'@biomejs/biome':
specifier: ^1.9.4
version: 1.9.4
'@eslint/js': '@eslint/js':
specifier: ^9.16.0 specifier: ^9.16.0
version: 9.24.0 version: 9.24.0
@ -365,6 +368,59 @@ packages:
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@biomejs/biome@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
engines: {node: '>=14.21.3'}
hasBin: true
'@biomejs/cli-darwin-arm64@1.9.4':
resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
'@biomejs/cli-darwin-x64@1.9.4':
resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
'@biomejs/cli-linux-arm64-musl@1.9.4':
resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-arm64@1.9.4':
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
'@biomejs/cli-linux-x64-musl@1.9.4':
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-linux-x64@1.9.4':
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
'@biomejs/cli-win32-arm64@1.9.4':
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
'@biomejs/cli-win32-x64@1.9.4':
resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
'@date-fns/tz@1.2.0': '@date-fns/tz@1.2.0':
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
@ -3379,6 +3435,41 @@ snapshots:
'@babel/helper-string-parser': 7.25.9 '@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9 '@babel/helper-validator-identifier': 7.25.9
'@biomejs/biome@1.9.4':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.4
'@biomejs/cli-darwin-x64': 1.9.4
'@biomejs/cli-linux-arm64': 1.9.4
'@biomejs/cli-linux-arm64-musl': 1.9.4
'@biomejs/cli-linux-x64': 1.9.4
'@biomejs/cli-linux-x64-musl': 1.9.4
'@biomejs/cli-win32-arm64': 1.9.4
'@biomejs/cli-win32-x64': 1.9.4
'@biomejs/cli-darwin-arm64@1.9.4':
optional: true
'@biomejs/cli-darwin-x64@1.9.4':
optional: true
'@biomejs/cli-linux-arm64-musl@1.9.4':
optional: true
'@biomejs/cli-linux-arm64@1.9.4':
optional: true
'@biomejs/cli-linux-x64-musl@1.9.4':
optional: true
'@biomejs/cli-linux-x64@1.9.4':
optional: true
'@biomejs/cli-win32-arm64@1.9.4':
optional: true
'@biomejs/cli-win32-x64@1.9.4':
optional: true
'@date-fns/tz@1.2.0': {} '@date-fns/tz@1.2.0': {}
'@esbuild/aix-ppc64@0.25.2': '@esbuild/aix-ppc64@0.25.2':

View File

@ -2,7 +2,7 @@ import {
UserPublic, UserPublic,
UserRegister, UserRegister,
UserUpdate, UserUpdate,
ShopLoginAccessTokenData ShopLoginAccessTokenData,
} from "@/client"; } from "@/client";
import { Shop } from "./mock/models"; import { Shop } from "./mock/models";

View File

@ -3,10 +3,19 @@ import { MockAuthAPI } from "./mock/auth-mock-api";
import { AuthAPI, ShopAPI } from "./api-definition"; import { AuthAPI, ShopAPI } from "./api-definition";
import { MockShopAPI } from "./mock/shop-mock-api"; import { MockShopAPI } from "./mock/shop-mock-api";
import { MockProductAPI } from "./mock/products-mock-api"; import { MockProductAPI } from "./mock/products-mock-api";
import { MockUserAPI } from "./mock/user-mock-api";
import { MockCouponAPI } from "./mock/coupon-mock-api";
import { MockPurchaseAPI } from "./mock/purchase-mock-api";
export const authAPI: AuthAPI = export const authAPI: AuthAPI =
import.meta.env.VITE_USE_MOCK_API === "true" ? MockAuthAPI : RealAuthAPI; import.meta.env.VITE_USE_MOCK_API === "true" ? MockAuthAPI : RealAuthAPI;
export const shopAPI: ShopAPI = MockShopAPI; export const shopAPI: ShopAPI = MockShopAPI;
export const productsAPI = MockProductAPI; export const productsAPI = MockProductAPI;
export const usersAPI = MockUserAPI;
export const couponAPI = MockCouponAPI;
export const purchaseAPI = MockPurchaseAPI;

View File

@ -5,7 +5,6 @@ import { extractSubFromJWT, generateFakeJWT } from "@/utils/jwt";
import { mockDB } from "./db"; import { mockDB } from "./db";
import { MockUser, Shop } from "./models"; import { MockUser, Shop } from "./models";
const db = mockDB; const db = mockDB;
export const MockAuthAPI: AuthAPI = { export const MockAuthAPI: AuthAPI = {
@ -23,7 +22,7 @@ export const MockAuthAPI: AuthAPI = {
...user, ...user,
first_name: user.first_name ?? null, first_name: user.first_name ?? null,
last_name: user.last_name ?? null, last_name: user.last_name ?? null,
profile_picture: user.profile_picture ?? null profile_picture: user.profile_picture ?? null,
}; };
return publicUser; return publicUser;
}, },
@ -37,8 +36,8 @@ export const MockAuthAPI: AuthAPI = {
const newUser: MockUser = { const newUser: MockUser = {
id: userId, id: userId,
uuid: userUUID, uuid: userUUID,
user_role: "customer", user_role: "owner",
status: "customer", status: "owner",
shop_id: null, shop_id: null,
username: data.username, username: data.username,
email: data.email, email: data.email,
@ -49,7 +48,7 @@ export const MockAuthAPI: AuthAPI = {
profile_picture: undefined, profile_picture: undefined,
created_at: now, created_at: now,
updated_at: now, updated_at: now,
last_login: now last_login: now,
}; };
const newShop: Shop = { const newShop: Shop = {
@ -70,16 +69,23 @@ export const MockAuthAPI: AuthAPI = {
city: "", city: "",
state: null, state: null,
postal_code: "", postal_code: "",
country: "" country: "",
} },
}; };
await mockDB.transaction('rw', mockDB.users, mockDB.shops, mockDB.preferences, mockDB.statistics, async () => { await mockDB.transaction(
await mockDB.users.add(newUser); "rw",
await mockDB.preferences.add({ user_id: userId }); mockDB.users,
await mockDB.statistics.add({ user_id: userId, total_spend: 0 }); mockDB.shops,
await mockDB.shops.add(newShop); mockDB.preferences,
}); mockDB.statistics,
async () => {
await mockDB.users.add(newUser);
await mockDB.preferences.add({ user_id: userId });
await mockDB.statistics.add({ user_id: userId, total_spend: 0 });
await mockDB.shops.add(newShop);
},
);
}, },
async loginUser(data: ShopLoginAccessTokenData) { async loginUser(data: ShopLoginAccessTokenData) {
@ -115,7 +121,7 @@ export const MockAuthAPI: AuthAPI = {
phone_number: data.phone_number ?? "", phone_number: data.phone_number ?? "",
username: data.username ?? "", username: data.username ?? "",
first_name: data.first_name ?? undefined, first_name: data.first_name ?? undefined,
last_name: data.last_name ?? undefined last_name: data.last_name ?? undefined,
}); });
} },
}; };

View File

@ -0,0 +1,45 @@
import { mockDB } from "./db";
import { Coupon } from "./models";
export const MockCouponAPI = {
async getAllCoupons(): Promise<Coupon[]> {
return mockDB.coupons.toArray();
},
async getCouponById(id: number): Promise<Coupon | null> {
return (await mockDB.coupons.get(id)) ?? null;
},
async createCoupon(couponData: Omit<Coupon, "id">): Promise<Coupon> {
const id = Date.now();
const newCoupon: Coupon = {
...couponData,
id,
};
await mockDB.coupons.add(newCoupon);
return newCoupon;
},
async updateCoupon(
id: number,
data: Partial<Omit<Coupon, "id">>,
): Promise<Coupon | null> {
const existing = await mockDB.coupons.get(id);
if (!existing) return null;
const updated: Coupon = {
...existing,
...data,
};
await mockDB.coupons.put(updated);
return updated;
},
async deleteCoupon(id: number): Promise<boolean> {
await mockDB.coupons.delete(id);
return true;
},
};

View File

@ -1,5 +1,6 @@
import Dexie, { Table } from "dexie"; import Dexie, { Table } from "dexie";
import { import {
Coupon,
MockUser, MockUser,
MockUserPreferences, MockUserPreferences,
MockUserStatistics, MockUserStatistics,
@ -8,7 +9,9 @@ import {
ProductCategoryJunction, ProductCategoryJunction,
ProductImage, ProductImage,
ProductVariant, ProductVariant,
Shop Purchase,
PurchaseEntry,
Shop,
} from "./models"; } from "./models";
class MockDB extends Dexie { class MockDB extends Dexie {
@ -21,6 +24,9 @@ class MockDB extends Dexie {
product_images!: Table<ProductImage, number>; product_images!: Table<ProductImage, number>;
product_categories!: Table<ProductCategory, number>; product_categories!: Table<ProductCategory, number>;
product_category_junctions!: Table<ProductCategoryJunction, [number, number]>; product_category_junctions!: Table<ProductCategoryJunction, [number, number]>;
coupons!: Table<Coupon, number>;
purchases!: Table<Purchase, number>;
purchase_entries!: Table<PurchaseEntry, number>;
constructor() { constructor() {
super("MockDB"); super("MockDB");
@ -33,7 +39,10 @@ class MockDB extends Dexie {
product_variants: "++id,product_id", product_variants: "++id,product_id",
product_images: "++id,product_id", product_images: "++id,product_id",
product_categories: "++id,parent_category_id", product_categories: "++id,parent_category_id",
product_category_junctions: "[product_id+category_id]" product_category_junctions: "[product_id+category_id]",
coupons: "++id,valid_due,name",
purchases: "++id,user_id,date_purchased",
purchase_entries: "++id,purchase_id,product_id,product_variant_id",
}); });
} }
} }

View File

@ -81,10 +81,23 @@ export interface ProductImage {
alt_text: string; alt_text: string;
} }
export interface ProductCreate {
name: string;
description: string;
price: number;
stock_quantity: number;
image_data: string | undefined;
}
export interface ProductWithDetails extends Product {
images: ProductImage[];
categories: ProductCategory[];
}
export interface ProductVariant { export interface ProductVariant {
id: number; id: number;
product_id: number; product_id: number;
index: number; // used for ordering index: number;
name: string; name: string;
price: number; price: number;
comment?: string; comment?: string;
@ -92,16 +105,36 @@ export interface ProductVariant {
updated_at: string; updated_at: string;
} }
export interface ProductCreate { export interface Coupon {
id: number;
name: string; name: string;
description: string; text: string;
price: number; valid_due: string;
stock_quantity: number; discount_amount: number;
image_data?: string | undefined;
} }
export interface ProductWithDetails extends Product { export interface Purchase {
images: ProductImage[]; id: number;
variants: ProductVariant[]; user_id: number;
categories: ProductCategory[]; used_coupon_id: number | null;
date_purchased: string;
total: number;
}
export interface PurchaseEntry {
id: number;
purchase_id: number;
product_id: number;
product_variant_id: number;
quantity: number;
}
export interface PurchaseWithDetails extends Purchase {
user: MockUser;
coupon?: Coupon | null;
entries: PurchaseEntryWithProduct[];
}
export interface PurchaseEntryWithProduct extends PurchaseEntry {
product: Product;
} }

View File

@ -28,20 +28,20 @@ export const MockProductAPI = {
.toArray(); .toArray();
const rawCategories = await Promise.all( const rawCategories = await Promise.all(
categoryLinks.map((link) => categoryLinks.map((link) =>
mockDB.product_categories.get(link.category_id) mockDB.product_categories.get(link.category_id),
) ),
); );
const categories = rawCategories.filter( const categories = rawCategories.filter(
(c): c is ProductCategory => c !== undefined (c): c is ProductCategory => c !== undefined,
); );
return { return {
...product, ...product,
images, images,
variants, variants,
categories categories,
}; };
}) }),
); );
return detailedProducts; return detailedProducts;
@ -66,18 +66,18 @@ export const MockProductAPI = {
const rawCategories = await Promise.all( const rawCategories = await Promise.all(
categoryLinks.map((link) => categoryLinks.map((link) =>
mockDB.product_categories.get(link.category_id) mockDB.product_categories.get(link.category_id),
) ),
); );
const categories = rawCategories.filter( const categories = rawCategories.filter(
(c): c is ProductCategory => c !== undefined (c): c is ProductCategory => c !== undefined,
); );
return { return {
...product, ...product,
images, images,
variants, variants,
categories categories,
}; };
}, },
@ -93,7 +93,7 @@ export const MockProductAPI = {
...productData, ...productData,
shop_id: user.id, shop_id: user.id,
created_at: now, created_at: now,
updated_at: now updated_at: now,
}); });
if ("image_data" in productData && productData.image_data) { if ("image_data" in productData && productData.image_data) {
@ -103,7 +103,7 @@ export const MockProductAPI = {
product_id: productId, product_id: productId,
image_id: image_id, image_id: image_id,
image_url: productData.image_data, image_url: productData.image_data,
alt_text: `${productData.name} image` alt_text: `${productData.name} image`,
}); });
} }
@ -117,7 +117,7 @@ export const MockProductAPI = {
const updatedProduct = { const updatedProduct = {
...product, ...product,
...data, ...data,
updated_at: new Date().toISOString() updated_at: new Date().toISOString(),
}; };
await mockDB.products.put(updatedProduct); await mockDB.products.put(updatedProduct);
@ -130,7 +130,7 @@ export const MockProductAPI = {
await mockDB.product_images.put({ await mockDB.product_images.put({
...existingImage, ...existingImage,
image_url: data.image_data, image_url: data.image_data,
alt_text: `${data.name ?? product.name} image` alt_text: `${data.name ?? product.name} image`,
}); });
} else { } else {
const image_id = Date.now(); const image_id = Date.now();
@ -139,7 +139,7 @@ export const MockProductAPI = {
product_id: productId, product_id: productId,
image_id: image_id, image_id: image_id,
image_url: data.image_data, image_url: data.image_data,
alt_text: `${data.name ?? product.name} image` alt_text: `${data.name ?? product.name} image`,
}); });
} }
} }
@ -159,5 +159,5 @@ export const MockProductAPI = {
.delete(); .delete();
await mockDB.products.delete(productId); await mockDB.products.delete(productId);
return true; return true;
} },
}; };

View File

@ -0,0 +1,85 @@
import { mockDB } from "./db";
import {
Purchase,
PurchaseWithDetails,
PurchaseEntry,
PurchaseEntryWithProduct,
} from "./models";
export const MockPurchaseAPI = {
async getAllPurchases(): Promise<Purchase[]> {
return mockDB.purchases.toArray();
},
async getPurchaseById(purchaseId: number): Promise<Purchase | null> {
return (await mockDB.purchases.get(purchaseId)) ?? null;
},
async getPurchaseWithDetails(
purchaseId: number,
): Promise<PurchaseWithDetails | null> {
const purchase = await mockDB.purchases.get(purchaseId);
if (!purchase) return null;
const [user, coupon, entries] = await Promise.all([
mockDB.users.get(purchase.user_id),
purchase.used_coupon_id
? mockDB.coupons.get(purchase.used_coupon_id)
: null,
mockDB.purchase_entries.where("purchase_id").equals(purchaseId).toArray(),
]);
if (!user) return null;
const entriesWithProducts: PurchaseEntryWithProduct[] = await Promise.all(
entries.map(async (entry) => {
const product = await mockDB.products.get(entry.product_id);
if (!product) throw new Error("Product not found");
return { ...entry, product };
}),
);
return {
...purchase,
user,
coupon,
entries: entriesWithProducts,
};
},
async getPurchasesByUser(userId: number): Promise<Purchase[]> {
return mockDB.purchases.where("user_id").equals(userId).toArray();
},
async createPurchase(
purchase: Omit<Purchase, "id">,
entries: Omit<PurchaseEntry, "id">[],
): Promise<Purchase> {
const id = Date.now();
const newPurchase: Purchase = {
...purchase,
id,
};
await mockDB.purchases.add(newPurchase);
await mockDB.purchase_entries.bulkAdd(
entries.map((entry) => ({
...entry,
purchase_id: id,
id: Date.now() + Math.random(),
})),
);
return newPurchase;
},
async deletePurchase(purchaseId: number): Promise<boolean> {
await mockDB.purchase_entries
.where("purchase_id")
.equals(purchaseId)
.delete();
await mockDB.purchases.delete(purchaseId);
return true;
},
};

View File

@ -35,7 +35,7 @@ export const MockShopAPI: ShopAPI = {
console.log("Saving data ->", data); console.log("Saving data ->", data);
await db.shops.update(user.id, { await db.shops.update(user.id, {
...data, ...data,
updated_at: new Date().toISOString() updated_at: new Date().toISOString(),
}); });
} },
}; };

View File

@ -0,0 +1,60 @@
import { mockDB } from "./db";
import { MockUser } from "./models";
import { getCurrentUserDirect } from "./utils/currentUser";
export const MockUserAPI = {
async getAllUsers(): Promise<MockUser[]> {
return mockDB.users.toArray();
},
async getUserById(userId: number): Promise<MockUser | null> {
return (await mockDB.users.get(userId)) ?? null;
},
async createUser(
userData: Omit<MockUser, "id" | "created_at" | "updated_at">,
): Promise<MockUser> {
const now = new Date().toISOString();
const id = Date.now();
const newUser: MockUser = {
...userData,
id,
created_at: now,
updated_at: now,
};
await mockDB.users.add(newUser);
return newUser;
},
async updateUser(
userId: number,
data: Partial<Omit<MockUser, "id" | "created_at">>,
): Promise<MockUser | null> {
const existingUser = await mockDB.users.get(userId);
if (!existingUser) return null;
const updatedUser: MockUser = {
...existingUser,
...data,
updated_at: new Date().toISOString(),
};
await mockDB.users.put(updatedUser);
return updatedUser;
},
async deleteUser(userId: number): Promise<boolean> {
await mockDB.preferences.where("user_id").equals(userId).delete();
await mockDB.statistics.where("user_id").equals(userId).delete();
await mockDB.users.delete(userId);
return true;
},
async getCurrentUser(): Promise<MockUser | null> {
const user = await getCurrentUserDirect();
if (!user?.id) return null;
return (await mockDB.users.get(user.id)) ?? null;
},
};

View File

@ -10,4 +10,4 @@ export async function getCurrentUserDirect() {
const user = await mockDB.users.where("uuid").equals(uuid).first(); const user = await mockDB.users.where("uuid").equals(uuid).first();
return user ?? null; return user ?? null;
} }

View File

@ -10,10 +10,10 @@ export const RealAuthAPI: AuthAPI = {
}, },
async loginUser(data) { async loginUser(data) {
return LoginService.dashboardLoginAccessToken({ return LoginService.dashboardLoginAccessToken({
formData: data.formData formData: data.formData,
}); });
}, },
async updateUser(data) { async updateUser(data) {
await UserService.userUpdateUser({ requestBody: data }); await UserService.userUpdateUser({ requestBody: data });
} },
}; };

View File

@ -1,5 +1,5 @@
import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiRequestOptions } from "./ApiRequestOptions";
import type { ApiResult } from './ApiResult'; import type { ApiResult } from "./ApiResult";
export class ApiError extends Error { export class ApiError extends Error {
public readonly url: string; public readonly url: string;
@ -8,14 +8,18 @@ export class ApiError extends Error {
public readonly body: unknown; public readonly body: unknown;
public readonly request: ApiRequestOptions; public readonly request: ApiRequestOptions;
constructor(request: ApiRequestOptions, response: ApiResult, message: string) { constructor(
request: ApiRequestOptions,
response: ApiResult,
message: string,
) {
super(message); super(message);
this.name = 'ApiError'; this.name = "ApiError";
this.url = response.url; this.url = response.url;
this.status = response.status; this.status = response.status;
this.statusText = response.statusText; this.statusText = response.statusText;
this.body = response.body; this.body = response.body;
this.request = request; this.request = request;
} }
} }

View File

@ -6,16 +6,16 @@ export type ApiRequestOptions<T = unknown> = {
readonly headers?: Record<string, unknown>; readonly headers?: Record<string, unknown>;
readonly mediaType?: string; readonly mediaType?: string;
readonly method: readonly method:
| 'DELETE' | "DELETE"
| 'GET' | "GET"
| 'HEAD' | "HEAD"
| 'OPTIONS' | "OPTIONS"
| 'PATCH' | "PATCH"
| 'POST' | "POST"
| 'PUT'; | "PUT";
readonly path?: Record<string, unknown>; readonly path?: Record<string, unknown>;
readonly query?: Record<string, unknown>; readonly query?: Record<string, unknown>;
readonly responseHeader?: string; readonly responseHeader?: string;
readonly responseTransformer?: (data: unknown) => Promise<T>; readonly responseTransformer?: (data: unknown) => Promise<T>;
readonly url: string; readonly url: string;
}; };

View File

@ -4,4 +4,4 @@ export type ApiResult<TData = any> = {
readonly status: number; readonly status: number;
readonly statusText: string; readonly statusText: string;
readonly url: string; readonly url: string;
}; };

View File

@ -1,7 +1,7 @@
export class CancelError extends Error { export class CancelError extends Error {
constructor(message: string) { constructor(message: string) {
super(message); super(message);
this.name = 'CancelError'; this.name = "CancelError";
} }
public get isCancelled(): boolean { public get isCancelled(): boolean {
@ -30,8 +30,8 @@ export class CancelablePromise<T> implements Promise<T> {
executor: ( executor: (
resolve: (value: T | PromiseLike<T>) => void, resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void, reject: (reason?: unknown) => void,
onCancel: OnCancel onCancel: OnCancel,
) => void ) => void,
) { ) {
this._isResolved = false; this._isResolved = false;
this._isRejected = false; this._isRejected = false;
@ -64,15 +64,15 @@ export class CancelablePromise<T> implements Promise<T> {
this.cancelHandlers.push(cancelHandler); this.cancelHandlers.push(cancelHandler);
}; };
Object.defineProperty(onCancel, 'isResolved', { Object.defineProperty(onCancel, "isResolved", {
get: (): boolean => this._isResolved, get: (): boolean => this._isResolved,
}); });
Object.defineProperty(onCancel, 'isRejected', { Object.defineProperty(onCancel, "isRejected", {
get: (): boolean => this._isRejected, get: (): boolean => this._isRejected,
}); });
Object.defineProperty(onCancel, 'isCancelled', { Object.defineProperty(onCancel, "isCancelled", {
get: (): boolean => this._isCancelled, get: (): boolean => this._isCancelled,
}); });
@ -86,13 +86,13 @@ export class CancelablePromise<T> implements Promise<T> {
public then<TResult1 = T, TResult2 = never>( public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
): Promise<TResult1 | TResult2> { ): Promise<TResult1 | TResult2> {
return this.promise.then(onFulfilled, onRejected); return this.promise.then(onFulfilled, onRejected);
} }
public catch<TResult = never>( public catch<TResult = never>(
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
): Promise<T | TResult> { ): Promise<T | TResult> {
return this.promise.catch(onRejected); return this.promise.catch(onRejected);
} }
@ -112,15 +112,15 @@ export class CancelablePromise<T> implements Promise<T> {
cancelHandler(); cancelHandler();
} }
} catch (error) { } catch (error) {
console.warn('Cancellation threw an error', error); console.warn("Cancellation threw an error", error);
return; return;
} }
} }
this.cancelHandlers.length = 0; this.cancelHandlers.length = 0;
if (this._reject) this._reject(new CancelError('Request aborted')); if (this._reject) this._reject(new CancelError("Request aborted"));
} }
public get isCancelled(): boolean { public get isCancelled(): boolean {
return this._isCancelled; return this._isCancelled;
} }
} }

View File

@ -1,32 +1,32 @@
import type { AxiosRequestConfig, AxiosResponse } from 'axios'; import type { AxiosRequestConfig, AxiosResponse } from "axios";
import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiRequestOptions } from "./ApiRequestOptions";
type Headers = Record<string, string>; type Headers = Record<string, string>;
type Middleware<T> = (value: T) => T | Promise<T>; type Middleware<T> = (value: T) => T | Promise<T>;
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>; type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export class Interceptors<T> { export class Interceptors<T> {
_fns: Middleware<T>[]; _fns: Middleware<T>[];
constructor() { constructor() {
this._fns = []; this._fns = [];
} }
eject(fn: Middleware<T>): void { eject(fn: Middleware<T>): void {
const index = this._fns.indexOf(fn); const index = this._fns.indexOf(fn);
if (index !== -1) { if (index !== -1) {
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
} }
} }
use(fn: Middleware<T>): void { use(fn: Middleware<T>): void {
this._fns = [...this._fns, fn]; this._fns = [...this._fns, fn];
} }
} }
export type OpenAPIConfig = { export type OpenAPIConfig = {
BASE: string; BASE: string;
CREDENTIALS: 'include' | 'omit' | 'same-origin'; CREDENTIALS: "include" | "omit" | "same-origin";
ENCODE_PATH?: ((path: string) => string) | undefined; ENCODE_PATH?: ((path: string) => string) | undefined;
HEADERS?: Headers | Resolver<Headers> | undefined; HEADERS?: Headers | Resolver<Headers> | undefined;
PASSWORD?: string | Resolver<string> | undefined; PASSWORD?: string | Resolver<string> | undefined;
@ -41,17 +41,17 @@ export type OpenAPIConfig = {
}; };
export const OpenAPI: OpenAPIConfig = { export const OpenAPI: OpenAPIConfig = {
BASE: '', BASE: "",
CREDENTIALS: 'include', CREDENTIALS: "include",
ENCODE_PATH: undefined, ENCODE_PATH: undefined,
HEADERS: undefined, HEADERS: undefined,
PASSWORD: undefined, PASSWORD: undefined,
TOKEN: undefined, TOKEN: undefined,
USERNAME: undefined, USERNAME: undefined,
VERSION: '0.0.1', VERSION: "0.0.1",
WITH_CREDENTIALS: false, WITH_CREDENTIALS: false,
interceptors: { interceptors: {
request: new Interceptors(), request: new Interceptors(),
response: new Interceptors(), response: new Interceptors(),
}, },
}; };

View File

@ -1,19 +1,24 @@
import axios from 'axios'; import axios from "axios";
import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios'; import type {
AxiosError,
AxiosRequestConfig,
AxiosResponse,
AxiosInstance,
} from "axios";
import { ApiError } from './ApiError'; import { ApiError } from "./ApiError";
import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiRequestOptions } from "./ApiRequestOptions";
import type { ApiResult } from './ApiResult'; import type { ApiResult } from "./ApiResult";
import { CancelablePromise } from './CancelablePromise'; import { CancelablePromise } from "./CancelablePromise";
import type { OnCancel } from './CancelablePromise'; import type { OnCancel } from "./CancelablePromise";
import type { OpenAPIConfig } from './OpenAPI'; import type { OpenAPIConfig } from "./OpenAPI";
export const isString = (value: unknown): value is string => { export const isString = (value: unknown): value is string => {
return typeof value === 'string'; return typeof value === "string";
}; };
export const isStringWithValue = (value: unknown): value is string => { export const isStringWithValue = (value: unknown): value is string => {
return isString(value) && value !== ''; return isString(value) && value !== "";
}; };
export const isBlob = (value: any): value is Blob => { export const isBlob = (value: any): value is Blob => {
@ -33,7 +38,7 @@ export const base64 = (str: string): string => {
return btoa(str); return btoa(str);
} catch (err) { } catch (err) {
// @ts-ignore // @ts-ignore
return Buffer.from(str).toString('base64'); return Buffer.from(str).toString("base64");
} }
}; };
@ -52,8 +57,8 @@ export const getQueryString = (params: Record<string, unknown>): string => {
if (value instanceof Date) { if (value instanceof Date) {
append(key, value.toISOString()); append(key, value.toISOString());
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
value.forEach(v => encodePair(key, v)); value.forEach((v) => encodePair(key, v));
} else if (typeof value === 'object') { } else if (typeof value === "object") {
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
} else { } else {
append(key, value); append(key, value);
@ -62,14 +67,14 @@ export const getQueryString = (params: Record<string, unknown>): string => {
Object.entries(params).forEach(([key, value]) => encodePair(key, value)); Object.entries(params).forEach(([key, value]) => encodePair(key, value));
return qs.length ? `?${qs.join('&')}` : ''; return qs.length ? `?${qs.join("&")}` : "";
}; };
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
const encoder = config.ENCODE_PATH || encodeURI; const encoder = config.ENCODE_PATH || encodeURI;
const path = options.url const path = options.url
.replace('{api-version}', config.VERSION) .replace("{api-version}", config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => { .replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(group)) { if (options.path?.hasOwnProperty(group)) {
return encoder(String(options.path[group])); return encoder(String(options.path[group]));
@ -81,7 +86,9 @@ const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
return options.query ? url + getQueryString(options.query) : url; return options.query ? url + getQueryString(options.query) : url;
}; };
export const getFormData = (options: ApiRequestOptions): FormData | undefined => { export const getFormData = (
options: ApiRequestOptions,
): FormData | undefined => {
if (options.formData) { if (options.formData) {
const formData = new FormData(); const formData = new FormData();
@ -97,7 +104,7 @@ export const getFormData = (options: ApiRequestOptions): FormData | undefined =>
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.forEach(([key, value]) => { .forEach(([key, value]) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach(v => process(key, v)); value.forEach((v) => process(key, v));
} else { } else {
process(key, value); process(key, value);
} }
@ -110,14 +117,20 @@ export const getFormData = (options: ApiRequestOptions): FormData | undefined =>
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>; type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export const resolve = async <T>(options: ApiRequestOptions<T>, resolver?: T | Resolver<T>): Promise<T | undefined> => { export const resolve = async <T>(
if (typeof resolver === 'function') { options: ApiRequestOptions<T>,
resolver?: T | Resolver<T>,
): Promise<T | undefined> => {
if (typeof resolver === "function") {
return (resolver as Resolver<T>)(options); return (resolver as Resolver<T>)(options);
} }
return resolver; return resolver;
}; };
export const getHeaders = async <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>): Promise<Record<string, string>> => { export const getHeaders = async <T>(
config: OpenAPIConfig,
options: ApiRequestOptions<T>,
): Promise<Record<string, string>> => {
const [token, username, password, additionalHeaders] = await Promise.all([ const [token, username, password, additionalHeaders] = await Promise.all([
// @ts-ignore // @ts-ignore
resolve(options, config.TOKEN), resolve(options, config.TOKEN),
@ -130,38 +143,41 @@ export const getHeaders = async <T>(config: OpenAPIConfig, options: ApiRequestOp
]); ]);
const headers = Object.entries({ const headers = Object.entries({
Accept: 'application/json', Accept: "application/json",
...additionalHeaders, ...additionalHeaders,
...options.headers, ...options.headers,
}) })
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.reduce((headers, [key, value]) => ({ .reduce(
...headers, (headers, [key, value]) => ({
[key]: String(value), ...headers,
}), {} as Record<string, string>); [key]: String(value),
}),
{} as Record<string, string>,
);
if (isStringWithValue(token)) { if (isStringWithValue(token)) {
headers['Authorization'] = `Bearer ${token}`; headers["Authorization"] = `Bearer ${token}`;
} }
if (isStringWithValue(username) && isStringWithValue(password)) { if (isStringWithValue(username) && isStringWithValue(password)) {
const credentials = base64(`${username}:${password}`); const credentials = base64(`${username}:${password}`);
headers['Authorization'] = `Basic ${credentials}`; headers["Authorization"] = `Basic ${credentials}`;
} }
if (options.body !== undefined) { if (options.body !== undefined) {
if (options.mediaType) { if (options.mediaType) {
headers['Content-Type'] = options.mediaType; headers["Content-Type"] = options.mediaType;
} else if (isBlob(options.body)) { } else if (isBlob(options.body)) {
headers['Content-Type'] = options.body.type || 'application/octet-stream'; headers["Content-Type"] = options.body.type || "application/octet-stream";
} else if (isString(options.body)) { } else if (isString(options.body)) {
headers['Content-Type'] = 'text/plain'; headers["Content-Type"] = "text/plain";
} else if (!isFormData(options.body)) { } else if (!isFormData(options.body)) {
headers['Content-Type'] = 'application/json'; headers["Content-Type"] = "application/json";
} }
} else if (options.formData !== undefined) { } else if (options.formData !== undefined) {
if (options.mediaType) { if (options.mediaType) {
headers['Content-Type'] = options.mediaType; headers["Content-Type"] = options.mediaType;
} }
} }
@ -183,7 +199,7 @@ export const sendRequest = async <T>(
formData: FormData | undefined, formData: FormData | undefined,
headers: Record<string, string>, headers: Record<string, string>,
onCancel: OnCancel, onCancel: OnCancel,
axiosClient: AxiosInstance axiosClient: AxiosInstance,
): Promise<AxiosResponse<T>> => { ): Promise<AxiosResponse<T>> => {
const controller = new AbortController(); const controller = new AbortController();
@ -213,7 +229,10 @@ export const sendRequest = async <T>(
} }
}; };
export const getResponseHeader = (response: AxiosResponse<unknown>, responseHeader?: string): string | undefined => { export const getResponseHeader = (
response: AxiosResponse<unknown>,
responseHeader?: string,
): string | undefined => {
if (responseHeader) { if (responseHeader) {
const content = response.headers[responseHeader]; const content = response.headers[responseHeader];
if (isString(content)) { if (isString(content)) {
@ -230,50 +249,53 @@ export const getResponseBody = (response: AxiosResponse<unknown>): unknown => {
return undefined; return undefined;
}; };
export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { export const catchErrorCodes = (
options: ApiRequestOptions,
result: ApiResult,
): void => {
const errors: Record<number, string> = { const errors: Record<number, string> = {
400: 'Bad Request', 400: "Bad Request",
401: 'Unauthorized', 401: "Unauthorized",
402: 'Payment Required', 402: "Payment Required",
403: 'Forbidden', 403: "Forbidden",
404: 'Not Found', 404: "Not Found",
405: 'Method Not Allowed', 405: "Method Not Allowed",
406: 'Not Acceptable', 406: "Not Acceptable",
407: 'Proxy Authentication Required', 407: "Proxy Authentication Required",
408: 'Request Timeout', 408: "Request Timeout",
409: 'Conflict', 409: "Conflict",
410: 'Gone', 410: "Gone",
411: 'Length Required', 411: "Length Required",
412: 'Precondition Failed', 412: "Precondition Failed",
413: 'Payload Too Large', 413: "Payload Too Large",
414: 'URI Too Long', 414: "URI Too Long",
415: 'Unsupported Media Type', 415: "Unsupported Media Type",
416: 'Range Not Satisfiable', 416: "Range Not Satisfiable",
417: 'Expectation Failed', 417: "Expectation Failed",
418: 'Im a teapot', 418: "Im a teapot",
421: 'Misdirected Request', 421: "Misdirected Request",
422: 'Unprocessable Content', 422: "Unprocessable Content",
423: 'Locked', 423: "Locked",
424: 'Failed Dependency', 424: "Failed Dependency",
425: 'Too Early', 425: "Too Early",
426: 'Upgrade Required', 426: "Upgrade Required",
428: 'Precondition Required', 428: "Precondition Required",
429: 'Too Many Requests', 429: "Too Many Requests",
431: 'Request Header Fields Too Large', 431: "Request Header Fields Too Large",
451: 'Unavailable For Legal Reasons', 451: "Unavailable For Legal Reasons",
500: 'Internal Server Error', 500: "Internal Server Error",
501: 'Not Implemented', 501: "Not Implemented",
502: 'Bad Gateway', 502: "Bad Gateway",
503: 'Service Unavailable', 503: "Service Unavailable",
504: 'Gateway Timeout', 504: "Gateway Timeout",
505: 'HTTP Version Not Supported', 505: "HTTP Version Not Supported",
506: 'Variant Also Negotiates', 506: "Variant Also Negotiates",
507: 'Insufficient Storage', 507: "Insufficient Storage",
508: 'Loop Detected', 508: "Loop Detected",
510: 'Not Extended', 510: "Not Extended",
511: 'Network Authentication Required', 511: "Network Authentication Required",
...options.errors, ...options.errors,
} };
const error = errors[result.status]; const error = errors[result.status];
if (error) { if (error) {
@ -281,8 +303,8 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult):
} }
if (!result.ok) { if (!result.ok) {
const errorStatus = result.status ?? 'unknown'; const errorStatus = result.status ?? "unknown";
const errorStatusText = result.statusText ?? 'unknown'; const errorStatusText = result.statusText ?? "unknown";
const errorBody = (() => { const errorBody = (() => {
try { try {
return JSON.stringify(result.body, null, 2); return JSON.stringify(result.body, null, 2);
@ -291,8 +313,10 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult):
} }
})(); })();
throw new ApiError(options, result, throw new ApiError(
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` options,
result,
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`,
); );
} }
}; };
@ -305,7 +329,11 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult):
* @returns CancelablePromise<T> * @returns CancelablePromise<T>
* @throws ApiError * @throws ApiError
*/ */
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>, axiosClient: AxiosInstance = axios): CancelablePromise<T> => { export const request = <T>(
config: OpenAPIConfig,
options: ApiRequestOptions<T>,
axiosClient: AxiosInstance = axios,
): CancelablePromise<T> => {
return new CancelablePromise(async (resolve, reject, onCancel) => { return new CancelablePromise(async (resolve, reject, onCancel) => {
try { try {
const url = getUrl(config, options); const url = getUrl(config, options);
@ -314,18 +342,30 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>,
const headers = await getHeaders(config, options); const headers = await getHeaders(config, options);
if (!onCancel.isCancelled) { if (!onCancel.isCancelled) {
let response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient); let response = await sendRequest<T>(
config,
options,
url,
body,
formData,
headers,
onCancel,
axiosClient,
);
for (const fn of config.interceptors.response._fns) { for (const fn of config.interceptors.response._fns) {
response = await fn(response); response = await fn(response);
} }
const responseBody = getResponseBody(response); const responseBody = getResponseBody(response);
const responseHeader = getResponseHeader(response, options.responseHeader); const responseHeader = getResponseHeader(
response,
options.responseHeader,
);
let transformedBody = responseBody; let transformedBody = responseBody;
if (options.responseTransformer && isSuccess(response.status)) { if (options.responseTransformer && isSuccess(response.status)) {
transformedBody = await options.responseTransformer(responseBody) transformedBody = await options.responseTransformer(responseBody);
} }
const result: ApiResult = { const result: ApiResult = {
@ -344,4 +384,4 @@ export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>,
reject(error); reject(error);
} }
}); });
}; };

View File

@ -1,6 +1,6 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export { ApiError } from './core/ApiError'; export { ApiError } from "./core/ApiError";
export { CancelablePromise, CancelError } from './core/CancelablePromise'; export { CancelablePromise, CancelError } from "./core/CancelablePromise";
export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI'; export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI";
export * from './sdk.gen'; export * from "./sdk.gen";
export * from './types.gen'; export * from "./types.gen";

View File

@ -1,438 +1,485 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
import type { CancelablePromise } from './core/CancelablePromise'; import type { CancelablePromise } from "./core/CancelablePromise";
import { OpenAPI } from './core/OpenAPI'; import { OpenAPI } from "./core/OpenAPI";
import { request as __request } from './core/request'; import { request as __request } from "./core/request";
import type { UserGetUserResponse, UserUpdateUserData, UserUpdateUserResponse, UserRegisterData, UserRegisterResponse, UserDeleteUserResponse, DashboardLoginAccessTokenData, DashboardLoginAccessTokenResponse, DashboardRegisterNewShopData, DashboardRegisterNewShopResponse, ShopLoginAccessTokenData, ShopLoginAccessTokenResponse, ShopDeleteUserData, ShopDeleteUserResponse, ShopLogoutResponse, ShopRegisterData, ShopRegisterResponse, ShopUpdateUserData, ShopUpdateUserResponse, UtilsHealthCheckResponse, UtilsTestDbResponse } from './types.gen'; import type {
UserGetUserResponse,
UserUpdateUserData,
UserUpdateUserResponse,
UserRegisterData,
UserRegisterResponse,
UserDeleteUserResponse,
DashboardLoginAccessTokenData,
DashboardLoginAccessTokenResponse,
DashboardRegisterNewShopData,
DashboardRegisterNewShopResponse,
ShopLoginAccessTokenData,
ShopLoginAccessTokenResponse,
ShopDeleteUserData,
ShopDeleteUserResponse,
ShopLogoutResponse,
ShopRegisterData,
ShopRegisterResponse,
ShopUpdateUserData,
ShopUpdateUserResponse,
UtilsHealthCheckResponse,
UtilsTestDbResponse,
} from "./types.gen";
export class DashboardService { export class DashboardService {
/** /**
* Get information about currently logged in user * Get information about currently logged in user
* @returns UserPublic Successful Response * @returns UserPublic Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static userGetUser(): CancelablePromise<UserGetUserResponse> { public static userGetUser(): CancelablePromise<UserGetUserResponse> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'GET', method: "GET",
url: '/user' url: "/user",
}); });
} }
/** /**
* Update user details * Update user details
* @param data The data for the request. * @param data The data for the request.
* @param data.requestBody * @param data.requestBody
* @returns boolean Successful Response * @returns boolean Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static userUpdateUser(data: UserUpdateUserData): CancelablePromise<UserUpdateUserResponse> { public static userUpdateUser(
return __request(OpenAPI, { data: UserUpdateUserData,
method: 'PUT', ): CancelablePromise<UserUpdateUserResponse> {
url: '/user', return __request(OpenAPI, {
body: data.requestBody, method: "PUT",
mediaType: 'application/json', url: "/user",
errors: { body: data.requestBody,
422: 'Validation Error' mediaType: "application/json",
} errors: {
}); 422: "Validation Error",
} },
});
/** }
* Register new user
* @param data The data for the request. /**
* @param data.requestBody * Register new user
* @returns boolean Successful Response * @param data The data for the request.
* @throws ApiError * @param data.requestBody
*/ * @returns boolean Successful Response
public static userRegister(data: UserRegisterData): CancelablePromise<UserRegisterResponse> { * @throws ApiError
return __request(OpenAPI, { */
method: 'POST', public static userRegister(
url: '/user', data: UserRegisterData,
body: data.requestBody, ): CancelablePromise<UserRegisterResponse> {
mediaType: 'application/json', return __request(OpenAPI, {
errors: { method: "POST",
422: 'Validation Error' url: "/user",
} body: data.requestBody,
}); mediaType: "application/json",
} errors: {
422: "Validation Error",
/** },
* Delete user });
* @returns boolean Successful Response }
* @throws ApiError
*/ /**
public static userDeleteUser(): CancelablePromise<UserDeleteUserResponse> { * Delete user
return __request(OpenAPI, { * @returns boolean Successful Response
method: 'DELETE', * @throws ApiError
url: '/user' */
}); public static userDeleteUser(): CancelablePromise<UserDeleteUserResponse> {
} return __request(OpenAPI, {
method: "DELETE",
/** url: "/user",
* Login Access Token });
* OAuth2 compatible token login for the dashboard. }
*
* This endpoint generates an access token required for authenticating future /**
* requests to the dashboard section of the application. The token is valid for * Login Access Token
* a predefined expiration period. * OAuth2 compatible token login for the dashboard.
* *
* - **username**: User's email * This endpoint generates an access token required for authenticating future
* - **password**: User's password * requests to the dashboard section of the application. The token is valid for
* * a predefined expiration period.
* **Note:** This login is restricted to dashboard access only and cannot be *
* used for tenant accounts access to shops * - **username**: User's email
* @param data The data for the request. * - **password**: User's password
* @param data.formData *
* @returns Token Successful Response * **Note:** This login is restricted to dashboard access only and cannot be
* @throws ApiError * used for tenant accounts access to shops
*/ * @param data The data for the request.
public static dashboardLoginAccessToken(data: DashboardLoginAccessTokenData): CancelablePromise<DashboardLoginAccessTokenResponse> { * @param data.formData
return __request(OpenAPI, { * @returns Token Successful Response
method: 'POST', * @throws ApiError
url: '/login/access-token', */
formData: data.formData, public static dashboardLoginAccessToken(
mediaType: 'application/x-www-form-urlencoded', data: DashboardLoginAccessTokenData,
errors: { ): CancelablePromise<DashboardLoginAccessTokenResponse> {
422: 'Validation Error' return __request(OpenAPI, {
} method: "POST",
}); url: "/login/access-token",
} formData: data.formData,
mediaType: "application/x-www-form-urlencoded",
/** errors: {
* Register New Shop 422: "Validation Error",
* @param data The data for the request. },
* @param data.requestBody });
* @returns boolean Successful Response }
* @throws ApiError
*/ /**
public static dashboardRegisterNewShop(data: DashboardRegisterNewShopData): CancelablePromise<DashboardRegisterNewShopResponse> { * Register New Shop
return __request(OpenAPI, { * @param data The data for the request.
method: 'POST', * @param data.requestBody
url: '/shop', * @returns boolean Successful Response
body: data.requestBody, * @throws ApiError
mediaType: 'application/json', */
errors: { public static dashboardRegisterNewShop(
422: 'Validation Error' data: DashboardRegisterNewShopData,
} ): CancelablePromise<DashboardRegisterNewShopResponse> {
}); return __request(OpenAPI, {
} method: "POST",
url: "/shop",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
} }
export class LoginService { export class LoginService {
/** /**
* Login Access Token * Login Access Token
* OAuth2 compatible token login for the dashboard. * OAuth2 compatible token login for the dashboard.
* *
* This endpoint generates an access token required for authenticating future * This endpoint generates an access token required for authenticating future
* requests to the dashboard section of the application. The token is valid for * requests to the dashboard section of the application. The token is valid for
* a predefined expiration period. * a predefined expiration period.
* *
* - **username**: User's email * - **username**: User's email
* - **password**: User's password * - **password**: User's password
* *
* **Note:** This login is restricted to dashboard access only and cannot be * **Note:** This login is restricted to dashboard access only and cannot be
* used for tenant accounts access to shops * used for tenant accounts access to shops
* @param data The data for the request. * @param data The data for the request.
* @param data.formData * @param data.formData
* @returns Token Successful Response * @returns Token Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static dashboardLoginAccessToken(data: DashboardLoginAccessTokenData): CancelablePromise<DashboardLoginAccessTokenResponse> { public static dashboardLoginAccessToken(
return __request(OpenAPI, { data: DashboardLoginAccessTokenData,
method: 'POST', ): CancelablePromise<DashboardLoginAccessTokenResponse> {
url: '/login/access-token', return __request(OpenAPI, {
formData: data.formData, method: "POST",
mediaType: 'application/x-www-form-urlencoded', url: "/login/access-token",
errors: { formData: data.formData,
422: 'Validation Error' mediaType: "application/x-www-form-urlencoded",
} errors: {
}); 422: "Validation Error",
} },
});
/** }
* Login Access Token
* OAuth2 compatible token login, get an access token for future requests /**
* @param data The data for the request. * Login Access Token
* @param data.formData * OAuth2 compatible token login, get an access token for future requests
* @returns Token Successful Response * @param data The data for the request.
* @throws ApiError * @param data.formData
*/ * @returns Token Successful Response
public static shopLoginAccessToken(data: ShopLoginAccessTokenData): CancelablePromise<ShopLoginAccessTokenResponse> { * @throws ApiError
return __request(OpenAPI, { */
method: 'POST', public static shopLoginAccessToken(
url: '/shop/{shop_uuid}/login/access-token', data: ShopLoginAccessTokenData,
formData: data.formData, ): CancelablePromise<ShopLoginAccessTokenResponse> {
mediaType: 'application/x-www-form-urlencoded', return __request(OpenAPI, {
errors: { method: "POST",
422: 'Validation Error' url: "/shop/{shop_uuid}/login/access-token",
} formData: data.formData,
}); mediaType: "application/x-www-form-urlencoded",
} errors: {
422: "Validation Error",
},
});
}
} }
export class ShopService { export class ShopService {
/** /**
* Login Access Token * Login Access Token
* OAuth2 compatible token login, get an access token for future requests * OAuth2 compatible token login, get an access token for future requests
* @param data The data for the request. * @param data The data for the request.
* @param data.formData * @param data.formData
* @returns Token Successful Response * @returns Token Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static shopLoginAccessToken(data: ShopLoginAccessTokenData): CancelablePromise<ShopLoginAccessTokenResponse> { public static shopLoginAccessToken(
return __request(OpenAPI, { data: ShopLoginAccessTokenData,
method: 'POST', ): CancelablePromise<ShopLoginAccessTokenResponse> {
url: '/shop/{shop_uuid}/login/access-token', return __request(OpenAPI, {
formData: data.formData, method: "POST",
mediaType: 'application/x-www-form-urlencoded', url: "/shop/{shop_uuid}/login/access-token",
errors: { formData: data.formData,
422: 'Validation Error' mediaType: "application/x-www-form-urlencoded",
} errors: {
}); 422: "Validation Error",
} },
});
/** }
* Delete user
* @param data The data for the request. /**
* @param data.shopUuid * Delete user
* @returns unknown Successful Response * @param data The data for the request.
* @throws ApiError * @param data.shopUuid
*/ * @returns unknown Successful Response
public static shopDeleteUser(data: ShopDeleteUserData): CancelablePromise<ShopDeleteUserResponse> { * @throws ApiError
return __request(OpenAPI, { */
method: 'DELETE', public static shopDeleteUser(
url: '/shop/{shop_uuid}/user/delete', data: ShopDeleteUserData,
path: { ): CancelablePromise<ShopDeleteUserResponse> {
shop_uuid: data.shopUuid return __request(OpenAPI, {
}, method: "DELETE",
errors: { url: "/shop/{shop_uuid}/user/delete",
422: 'Validation Error' path: {
} shop_uuid: data.shopUuid,
}); },
} errors: {
422: "Validation Error",
/** },
* User logout });
* @returns unknown Successful Response }
* @throws ApiError
*/ /**
public static shopLogout(): CancelablePromise<ShopLogoutResponse> { * User logout
return __request(OpenAPI, { * @returns unknown Successful Response
method: 'DELETE', * @throws ApiError
url: '/shop/{shop_uuid}/user/logout' */
}); public static shopLogout(): CancelablePromise<ShopLogoutResponse> {
} return __request(OpenAPI, {
method: "DELETE",
/** url: "/shop/{shop_uuid}/user/logout",
* Register new user });
* @param data The data for the request. }
* @param data.shopUuid
* @param data.requestBody /**
* @returns unknown Successful Response * Register new user
* @throws ApiError * @param data The data for the request.
*/ * @param data.shopUuid
public static shopRegister(data: ShopRegisterData): CancelablePromise<ShopRegisterResponse> { * @param data.requestBody
return __request(OpenAPI, { * @returns unknown Successful Response
method: 'POST', * @throws ApiError
url: '/shop/{shop_uuid}/user/register', */
path: { public static shopRegister(
shop_uuid: data.shopUuid data: ShopRegisterData,
}, ): CancelablePromise<ShopRegisterResponse> {
body: data.requestBody, return __request(OpenAPI, {
mediaType: 'application/json', method: "POST",
errors: { url: "/shop/{shop_uuid}/user/register",
422: 'Validation Error' path: {
} shop_uuid: data.shopUuid,
}); },
} body: data.requestBody,
mediaType: "application/json",
/** errors: {
* Update user details 422: "Validation Error",
* @param data The data for the request. },
* @param data.requestBody });
* @returns unknown Successful Response }
* @throws ApiError
*/ /**
public static shopUpdateUser(data: ShopUpdateUserData): CancelablePromise<ShopUpdateUserResponse> { * Update user details
return __request(OpenAPI, { * @param data The data for the request.
method: 'PUT', * @param data.requestBody
url: '/shop/{shop_uuid}/user/update', * @returns unknown Successful Response
body: data.requestBody, * @throws ApiError
mediaType: 'application/json', */
errors: { public static shopUpdateUser(
422: 'Validation Error' data: ShopUpdateUserData,
} ): CancelablePromise<ShopUpdateUserResponse> {
}); return __request(OpenAPI, {
} method: "PUT",
url: "/shop/{shop_uuid}/user/update",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
} }
export class UserService { export class UserService {
/** /**
* Get information about currently logged in user * Get information about currently logged in user
* @returns UserPublic Successful Response * @returns UserPublic Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static userGetUser(): CancelablePromise<UserGetUserResponse> { public static userGetUser(): CancelablePromise<UserGetUserResponse> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'GET', method: "GET",
url: '/user' url: "/user",
}); });
} }
/** /**
* Update user details * Update user details
* @param data The data for the request. * @param data The data for the request.
* @param data.requestBody * @param data.requestBody
* @returns boolean Successful Response * @returns boolean Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static userUpdateUser(data: UserUpdateUserData): CancelablePromise<UserUpdateUserResponse> { public static userUpdateUser(
return __request(OpenAPI, { data: UserUpdateUserData,
method: 'PUT', ): CancelablePromise<UserUpdateUserResponse> {
url: '/user', return __request(OpenAPI, {
body: data.requestBody, method: "PUT",
mediaType: 'application/json', url: "/user",
errors: { body: data.requestBody,
422: 'Validation Error' mediaType: "application/json",
} errors: {
}); 422: "Validation Error",
} },
});
/** }
* Register new user
* @param data The data for the request. /**
* @param data.requestBody * Register new user
* @returns boolean Successful Response * @param data The data for the request.
* @throws ApiError * @param data.requestBody
*/ * @returns boolean Successful Response
public static userRegister(data: UserRegisterData): CancelablePromise<UserRegisterResponse> { * @throws ApiError
return __request(OpenAPI, { */
method: 'POST', public static userRegister(
url: '/user', data: UserRegisterData,
body: data.requestBody, ): CancelablePromise<UserRegisterResponse> {
mediaType: 'application/json', return __request(OpenAPI, {
errors: { method: "POST",
422: 'Validation Error' url: "/user",
} body: data.requestBody,
}); mediaType: "application/json",
} errors: {
422: "Validation Error",
/** },
* Delete user });
* @returns boolean Successful Response }
* @throws ApiError
*/ /**
public static userDeleteUser(): CancelablePromise<UserDeleteUserResponse> { * Delete user
return __request(OpenAPI, { * @returns boolean Successful Response
method: 'DELETE', * @throws ApiError
url: '/user' */
}); public static userDeleteUser(): CancelablePromise<UserDeleteUserResponse> {
} return __request(OpenAPI, {
method: "DELETE",
/** url: "/user",
* Delete user });
* @param data The data for the request. }
* @param data.shopUuid
* @returns unknown Successful Response /**
* @throws ApiError * Delete user
*/ * @param data The data for the request.
public static shopDeleteUser(data: ShopDeleteUserData): CancelablePromise<ShopDeleteUserResponse> { * @param data.shopUuid
return __request(OpenAPI, { * @returns unknown Successful Response
method: 'DELETE', * @throws ApiError
url: '/shop/{shop_uuid}/user/delete', */
path: { public static shopDeleteUser(
shop_uuid: data.shopUuid data: ShopDeleteUserData,
}, ): CancelablePromise<ShopDeleteUserResponse> {
errors: { return __request(OpenAPI, {
422: 'Validation Error' method: "DELETE",
} url: "/shop/{shop_uuid}/user/delete",
}); path: {
} shop_uuid: data.shopUuid,
},
/** errors: {
* User logout 422: "Validation Error",
* @returns unknown Successful Response },
* @throws ApiError });
*/ }
public static shopLogout(): CancelablePromise<ShopLogoutResponse> {
return __request(OpenAPI, { /**
method: 'DELETE', * User logout
url: '/shop/{shop_uuid}/user/logout' * @returns unknown Successful Response
}); * @throws ApiError
} */
public static shopLogout(): CancelablePromise<ShopLogoutResponse> {
/** return __request(OpenAPI, {
* Register new user method: "DELETE",
* @param data The data for the request. url: "/shop/{shop_uuid}/user/logout",
* @param data.shopUuid });
* @param data.requestBody }
* @returns unknown Successful Response
* @throws ApiError /**
*/ * Register new user
public static shopRegister(data: ShopRegisterData): CancelablePromise<ShopRegisterResponse> { * @param data The data for the request.
return __request(OpenAPI, { * @param data.shopUuid
method: 'POST', * @param data.requestBody
url: '/shop/{shop_uuid}/user/register', * @returns unknown Successful Response
path: { * @throws ApiError
shop_uuid: data.shopUuid */
}, public static shopRegister(
body: data.requestBody, data: ShopRegisterData,
mediaType: 'application/json', ): CancelablePromise<ShopRegisterResponse> {
errors: { return __request(OpenAPI, {
422: 'Validation Error' method: "POST",
} url: "/shop/{shop_uuid}/user/register",
}); path: {
} shop_uuid: data.shopUuid,
},
/** body: data.requestBody,
* Update user details mediaType: "application/json",
* @param data The data for the request. errors: {
* @param data.requestBody 422: "Validation Error",
* @returns unknown Successful Response },
* @throws ApiError });
*/ }
public static shopUpdateUser(data: ShopUpdateUserData): CancelablePromise<ShopUpdateUserResponse> {
return __request(OpenAPI, { /**
method: 'PUT', * Update user details
url: '/shop/{shop_uuid}/user/update', * @param data The data for the request.
body: data.requestBody, * @param data.requestBody
mediaType: 'application/json', * @returns unknown Successful Response
errors: { * @throws ApiError
422: 'Validation Error' */
} public static shopUpdateUser(
}); data: ShopUpdateUserData,
} ): CancelablePromise<ShopUpdateUserResponse> {
return __request(OpenAPI, {
method: "PUT",
url: "/shop/{shop_uuid}/user/update",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
} }
export class UtilsService { export class UtilsService {
/** /**
* Health Check * Health Check
* Ping the API whether it's alive or not * Ping the API whether it's alive or not
* @returns boolean Successful Response * @returns boolean Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static utilsHealthCheck(): CancelablePromise<UtilsHealthCheckResponse> { public static utilsHealthCheck(): CancelablePromise<UtilsHealthCheckResponse> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'GET', method: "GET",
url: '/utils/health-check/' url: "/utils/health-check/",
}); });
} }
/** /**
* Test Db * Test Db
* Ping database using select 1 to see if connection works * Ping database using select 1 to see if connection works
* @returns boolean Successful Response * @returns boolean Successful Response
* @throws ApiError * @throws ApiError
*/ */
public static utilsTestDb(): CancelablePromise<UtilsTestDbResponse> { public static utilsTestDb(): CancelablePromise<UtilsTestDbResponse> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'GET', method: "GET",
url: '/utils/test-db/' url: "/utils/test-db/",
}); });
} }
}
}

View File

@ -1,142 +1,142 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export type Body_Dashboard_login_access_token = { export type Body_Dashboard_login_access_token = {
grant_type?: (string | null); grant_type?: string | null;
username: string; username: string;
password: string; password: string;
scope?: string; scope?: string;
client_id?: (string | null); client_id?: string | null;
client_secret?: (string | null); client_secret?: string | null;
}; };
export type Body_Shop_login_access_token = { export type Body_Shop_login_access_token = {
grant_type?: (string | null); grant_type?: string | null;
username: string; username: string;
password: string; password: string;
scope?: string; scope?: string;
client_id?: (string | null); client_id?: string | null;
client_secret?: (string | null); client_secret?: string | null;
}; };
export type HTTPValidationError = { export type HTTPValidationError = {
detail?: Array<ValidationError>; detail?: Array<ValidationError>;
}; };
export type ShopAddress = { export type ShopAddress = {
street: string; street: string;
city: string; city: string;
state: string; state: string;
postal_code: string; postal_code: string;
country: string; country: string;
}; };
export type ShopCreate = { export type ShopCreate = {
name: string; name: string;
description: string; description: string;
currency: string; currency: string;
contact_email: string; contact_email: string;
contact_phone_number: string; contact_phone_number: string;
address: ShopAddress; address: ShopAddress;
}; };
export type Token = { export type Token = {
access_token: string; access_token: string;
token_type?: string; token_type?: string;
}; };
export type UserPublic = { export type UserPublic = {
uuid: string; uuid: string;
username: string; username: string;
email: string; email: string;
first_name: (string | null); first_name: string | null;
last_name: (string | null); last_name: string | null;
phone_number: string; phone_number: string;
}; };
export type UserRegister = { export type UserRegister = {
username: string; username: string;
email: string; email: string;
phone_number: string; phone_number: string;
/** /**
* Password must conform to this regex: * Password must conform to this regex:
* ``` * ```
* ^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$ * ^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$
* ``` * ```
*/ */
password: string; password: string;
}; };
export type UserUpdate = { export type UserUpdate = {
email: (string | null); email: string | null;
phone_number: (string | null); phone_number: string | null;
username: (string | null); username: string | null;
first_name?: (string | null); first_name?: string | null;
last_name?: (string | null); last_name?: string | null;
}; };
export type ValidationError = { export type ValidationError = {
loc: Array<(string | number)>; loc: Array<string | number>;
msg: string; msg: string;
type: string; type: string;
}; };
export type UserGetUserResponse = (UserPublic); export type UserGetUserResponse = UserPublic;
export type UserUpdateUserData = { export type UserUpdateUserData = {
requestBody: UserUpdate; requestBody: UserUpdate;
}; };
export type UserUpdateUserResponse = (boolean); export type UserUpdateUserResponse = boolean;
export type UserRegisterData = { export type UserRegisterData = {
requestBody: UserRegister; requestBody: UserRegister;
}; };
export type UserRegisterResponse = (boolean); export type UserRegisterResponse = boolean;
export type UserDeleteUserResponse = (boolean); export type UserDeleteUserResponse = boolean;
export type DashboardLoginAccessTokenData = { export type DashboardLoginAccessTokenData = {
formData: Body_Dashboard_login_access_token; formData: Body_Dashboard_login_access_token;
}; };
export type DashboardLoginAccessTokenResponse = (Token); export type DashboardLoginAccessTokenResponse = Token;
export type DashboardRegisterNewShopData = { export type DashboardRegisterNewShopData = {
requestBody: ShopCreate; requestBody: ShopCreate;
}; };
export type DashboardRegisterNewShopResponse = (boolean); export type DashboardRegisterNewShopResponse = boolean;
export type ShopLoginAccessTokenData = { export type ShopLoginAccessTokenData = {
formData: Body_Shop_login_access_token; formData: Body_Shop_login_access_token;
}; };
export type ShopLoginAccessTokenResponse = (Token); export type ShopLoginAccessTokenResponse = Token;
export type ShopDeleteUserData = { export type ShopDeleteUserData = {
shopUuid: unknown; shopUuid: unknown;
}; };
export type ShopDeleteUserResponse = (unknown); export type ShopDeleteUserResponse = unknown;
export type ShopLogoutResponse = (unknown); export type ShopLogoutResponse = unknown;
export type ShopRegisterData = { export type ShopRegisterData = {
requestBody: UserRegister; requestBody: UserRegister;
shopUuid: unknown; shopUuid: unknown;
}; };
export type ShopRegisterResponse = (unknown); export type ShopRegisterResponse = unknown;
export type ShopUpdateUserData = { export type ShopUpdateUserData = {
requestBody: { requestBody: {
[key: string]: unknown; [key: string]: unknown;
}; };
}; };
export type ShopUpdateUserResponse = (unknown); export type ShopUpdateUserResponse = unknown;
export type UtilsHealthCheckResponse = (boolean); export type UtilsHealthCheckResponse = boolean;
export type UtilsTestDbResponse = (boolean); export type UtilsTestDbResponse = boolean;

View File

@ -4,7 +4,7 @@ import {
IconArrowRightDashed, IconArrowRightDashed,
IconDeviceLaptop, IconDeviceLaptop,
IconMoon, IconMoon,
IconSun IconSun,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { useSearch } from "@/context/search-context"; import { useSearch } from "@/context/search-context";
import { useTheme } from "@/context/theme-context"; import { useTheme } from "@/context/theme-context";
@ -15,7 +15,7 @@ import {
CommandInput, CommandInput,
CommandItem, CommandItem,
CommandList, CommandList,
CommandSeparator CommandSeparator,
} from "@/components/ui/command"; } from "@/components/ui/command";
import { sidebarData } from "./layout/data/sidebar-data"; import { sidebarData } from "./layout/data/sidebar-data";
import { ScrollArea } from "./ui/scroll-area"; import { ScrollArea } from "./ui/scroll-area";
@ -30,7 +30,7 @@ export function CommandMenu() {
setOpen(false); setOpen(false);
command(); command();
}, },
[setOpen] [setOpen],
); );
return ( return (
@ -49,7 +49,8 @@ export function CommandMenu() {
value={navItem.title} value={navItem.title}
onSelect={() => { onSelect={() => {
runCommand(() => navigate({ to: navItem.url })); runCommand(() => navigate({ to: navItem.url }));
}}> }}
>
<div className="mr-2 flex h-4 w-4 items-center justify-center"> <div className="mr-2 flex h-4 w-4 items-center justify-center">
<IconArrowRightDashed className="size-2 text-muted-foreground/80" /> <IconArrowRightDashed className="size-2 text-muted-foreground/80" />
</div> </div>
@ -63,7 +64,8 @@ export function CommandMenu() {
value={subItem.title} value={subItem.title}
onSelect={() => { onSelect={() => {
runCommand(() => navigate({ to: subItem.url })); runCommand(() => navigate({ to: subItem.url }));
}}> }}
>
<div className="mr-2 flex h-4 w-4 items-center justify-center"> <div className="mr-2 flex h-4 w-4 items-center justify-center">
<IconArrowRightDashed className="size-2 text-muted-foreground/80" /> <IconArrowRightDashed className="size-2 text-muted-foreground/80" />
</div> </div>

View File

@ -6,7 +6,7 @@ import {
AlertDialogDescription, AlertDialogDescription,
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle AlertDialogTitle,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -56,7 +56,8 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
<Button <Button
variant={destructive ? "destructive" : "default"} variant={destructive ? "destructive" : "default"}
onClick={handleConfirm} onClick={handleConfirm}
disabled={disabled || isLoading}> disabled={disabled || isLoading}
>
{confirmText ?? "Continue"} {confirmText ?? "Continue"}
</Button> </Button>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -153,5 +153,5 @@ const currencies = [
"YER", "YER",
"ZAR", "ZAR",
"ZMW", "ZMW",
"ZWG" "ZWG",
]; ];

View File

@ -2,11 +2,12 @@ import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarHeader, SidebarHeader,
SidebarRail SidebarRail,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { NavGroup } from "@/components/layout/nav-group"; import { NavGroup } from "@/components/layout/nav-group";
import { sidebarData } from "./data/sidebar-data"; import { sidebarData } from "./data/sidebar-data";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { IconCoin } from "@tabler/icons-react";
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return ( return (
@ -14,10 +15,11 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<SidebarHeader> <SidebarHeader>
<h1 <h1
className={cn( className={cn(
"header-fixed peer/header flex h-16 w-[inherit] items-center gap-3 rounded-md bg-background p-4 text-xl font-bold sm:gap-4" "header-fixed peer/header flex h-16 w-[inherit] items-center gap-3 rounded-md bg-background p-4 text-xl font-bold sm:gap-4",
)} )}
{...props}> {...props}
SwagShop >
<IconCoin />
</h1> </h1>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
@ -25,9 +27,6 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<NavGroup key={props.title} {...props} /> <NavGroup key={props.title} {...props} />
))} ))}
</SidebarContent> </SidebarContent>
{/* <SidebarFooter>
<NavUser user={sidebarData.user} />
</SidebarFooter> */}
<SidebarRail /> <SidebarRail />
</Sidebar> </Sidebar>
); );

View File

@ -1,6 +1,7 @@
import { import {
IconBrowserCheck, IconBrowserCheck,
IconBuildingStore, IconBuildingStore,
IconClipboardCheckFilled,
IconCoin, IconCoin,
IconForklift, IconForklift,
IconHelp, IconHelp,
@ -8,14 +9,12 @@ import {
IconNotification, IconNotification,
IconPackage, IconPackage,
IconPalette, IconPalette,
IconPercentage,
IconSettings, IconSettings,
IconTag, IconTag,
IconTool, IconTool,
IconUserCog, IconUserCog,
IconUsers IconUsers,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { AudioWaveform, Command, GalleryVerticalEnd } from "lucide-react";
import { type SidebarData } from "../types"; import { type SidebarData } from "../types";
export const sidebarData: SidebarData = { export const sidebarData: SidebarData = {
@ -26,45 +25,45 @@ export const sidebarData: SidebarData = {
{ {
title: "Dashboard", title: "Dashboard",
url: "/dashboard", url: "/dashboard",
icon: IconLayoutDashboard icon: IconLayoutDashboard,
}, },
{ {
title: "Shop", title: "Shop",
url: "/dashboard/shop", url: "/dashboard/shop",
icon: IconBuildingStore icon: IconBuildingStore,
}, },
{ {
title: "Products", title: "Products",
url: "/dashboard/products", url: "/dashboard/products",
icon: IconPackage icon: IconPackage,
}, },
{ {
title: "Inventory", title: "Inventory",
url: "/dashboard/tasks", url: "/dashboard/tasks",
icon: IconForklift icon: IconForklift,
}, },
{ {
title: "Sales", title: "Sales",
icon: IconCoin, icon: IconCoin,
items: [ items: [
{ {
title: "Discounts", title: "Recent sales",
url: "/dashboard/sales/discounts", url: "/dashboard/sales/recent-sales",
icon: IconPercentage icon: IconClipboardCheckFilled,
}, },
{ {
title: "Coupons", title: "Coupons",
url: "/dashboard/sales/coupons", url: "/dashboard/sales/coupons",
icon: IconTag icon: IconTag,
} },
] ],
}, },
{ {
title: "Customers", title: "Customers",
url: "/dashboard/users", url: "/dashboard/users",
icon: IconUsers icon: IconUsers,
} },
] ],
}, },
{ {
title: "Other", title: "Other",
@ -76,36 +75,36 @@ export const sidebarData: SidebarData = {
{ {
title: "Profile", title: "Profile",
url: "/dashboard/settings", url: "/dashboard/settings",
icon: IconUserCog icon: IconUserCog,
}, },
{ {
title: "Account", title: "Account",
url: "/dashboard/settings/account", url: "/dashboard/settings/account",
icon: IconTool icon: IconTool,
}, },
{ {
title: "Appearance", title: "Appearance",
url: "/dashboard/settings/appearance", url: "/dashboard/settings/appearance",
icon: IconPalette icon: IconPalette,
}, },
{ {
title: "Notifications", title: "Notifications",
url: "/dashboard/settings/notifications", url: "/dashboard/settings/notifications",
icon: IconNotification icon: IconNotification,
}, },
{ {
title: "Display", title: "Display",
url: "/dashboard/settings/display", url: "/dashboard/settings/display",
icon: IconBrowserCheck icon: IconBrowserCheck,
} },
] ],
}, },
{ {
title: "Help Center", title: "Help Center",
url: "/help-center", url: "/help-center",
icon: IconHelp icon: IconHelp,
} },
] ],
} },
] ],
}; };

View File

@ -34,9 +34,10 @@ export const Header = ({
"flex h-16 items-center gap-3 bg-background p-4 sm:gap-4", "flex h-16 items-center gap-3 bg-background p-4 sm:gap-4",
fixed && "header-fixed peer/header fixed z-50 w-[inherit] rounded-md", fixed && "header-fixed peer/header fixed z-50 w-[inherit] rounded-md",
offset > 10 && fixed ? "shadow" : "shadow-none", offset > 10 && fixed ? "shadow" : "shadow-none",
className className,
)} )}
{...props}> {...props}
>
<SidebarTrigger variant="outline" className="scale-125 sm:scale-100" /> <SidebarTrigger variant="outline" className="scale-125 sm:scale-100" />
<Separator orientation="vertical" className="h-6" /> <Separator orientation="vertical" className="h-6" />
{children} {children}

View File

@ -12,7 +12,7 @@ export const Main = ({ fixed, ...props }: MainProps) => {
className={cn( className={cn(
"peer-[.header-fixed]/header:mt-16", "peer-[.header-fixed]/header:mt-16",
"px-4 py-6", "px-4 py-6",
fixed && "fixed-main flex flex-grow flex-col overflow-hidden" fixed && "fixed-main flex flex-grow flex-col overflow-hidden",
)} )}
{...props} {...props}
/> />

View File

@ -4,7 +4,7 @@ import { ChevronRight } from "lucide-react";
import { import {
Collapsible, Collapsible,
CollapsibleContent, CollapsibleContent,
CollapsibleTrigger CollapsibleTrigger,
} from "@/components/ui/collapsible"; } from "@/components/ui/collapsible";
import { import {
SidebarGroup, SidebarGroup,
@ -15,7 +15,7 @@ import {
SidebarMenuSub, SidebarMenuSub,
SidebarMenuSubButton, SidebarMenuSubButton,
SidebarMenuSubItem, SidebarMenuSubItem,
useSidebar useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { Badge } from "../ui/badge"; import { Badge } from "../ui/badge";
import { import {
@ -24,7 +24,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import { NavCollapsible, NavItem, NavLink, type NavGroup } from "./types"; import { NavCollapsible, NavItem, NavLink, type NavGroup } from "./types";
@ -64,7 +64,8 @@ const SidebarMenuLink = ({ item, href }: { item: NavLink; href: string }) => {
<SidebarMenuButton <SidebarMenuButton
asChild asChild
isActive={checkIsActive(href, item)} isActive={checkIsActive(href, item)}
tooltip={item.title}> tooltip={item.title}
>
<Link to={item.url} onClick={() => setOpenMobile(false)}> <Link to={item.url} onClick={() => setOpenMobile(false)}>
{item.icon && <item.icon />} {item.icon && <item.icon />}
<span>{item.title}</span> <span>{item.title}</span>
@ -77,7 +78,7 @@ const SidebarMenuLink = ({ item, href }: { item: NavLink; href: string }) => {
const SidebarMenuCollapsible = ({ const SidebarMenuCollapsible = ({
item, item,
href href,
}: { }: {
item: NavCollapsible; item: NavCollapsible;
href: string; href: string;
@ -87,7 +88,8 @@ const SidebarMenuCollapsible = ({
<Collapsible <Collapsible
asChild asChild
defaultOpen={checkIsActive(href, item, true)} defaultOpen={checkIsActive(href, item, true)}
className="group/collapsible"> className="group/collapsible"
>
<SidebarMenuItem> <SidebarMenuItem>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}> <SidebarMenuButton tooltip={item.title}>
@ -103,7 +105,8 @@ const SidebarMenuCollapsible = ({
<SidebarMenuSubItem key={subItem.title}> <SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton <SidebarMenuSubButton
asChild asChild
isActive={checkIsActive(href, subItem)}> isActive={checkIsActive(href, subItem)}
>
<Link to={subItem.url} onClick={() => setOpenMobile(false)}> <Link to={subItem.url} onClick={() => setOpenMobile(false)}>
{subItem.icon && <subItem.icon />} {subItem.icon && <subItem.icon />}
<span>{subItem.title}</span> <span>{subItem.title}</span>
@ -121,7 +124,7 @@ const SidebarMenuCollapsible = ({
const SidebarMenuCollapsedDropdown = ({ const SidebarMenuCollapsedDropdown = ({
item, item,
href href,
}: { }: {
item: NavCollapsible; item: NavCollapsible;
href: string; href: string;
@ -132,7 +135,8 @@ const SidebarMenuCollapsedDropdown = ({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<SidebarMenuButton <SidebarMenuButton
tooltip={item.title} tooltip={item.title}
isActive={checkIsActive(href, item)}> isActive={checkIsActive(href, item)}
>
{item.icon && <item.icon />} {item.icon && <item.icon />}
<span>{item.title}</span> <span>{item.title}</span>
{item.badge && <NavBadge>{item.badge}</NavBadge>} {item.badge && <NavBadge>{item.badge}</NavBadge>}
@ -148,7 +152,8 @@ const SidebarMenuCollapsedDropdown = ({
<DropdownMenuItem key={`${sub.title}-${sub.url}`} asChild> <DropdownMenuItem key={`${sub.title}-${sub.url}`} asChild>
<Link <Link
to={sub.url} to={sub.url}
className={`${checkIsActive(href, sub) ? "bg-secondary" : ""}`}> className={`${checkIsActive(href, sub) ? "bg-secondary" : ""}`}
>
{sub.icon && <sub.icon />} {sub.icon && <sub.icon />}
<span className="max-w-52 text-wrap">{sub.title}</span> <span className="max-w-52 text-wrap">{sub.title}</span>
{sub.badge && ( {sub.badge && (

View File

@ -5,7 +5,7 @@ import {
ChevronsUpDown, ChevronsUpDown,
CreditCard, CreditCard,
LogOut, LogOut,
Sparkles Sparkles,
} from "lucide-react"; } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { import {
@ -15,17 +15,17 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { import {
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
useSidebar useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
export function NavUser({ export function NavUser({
user user,
}: { }: {
user: { user: {
name: string; name: string;
@ -42,7 +42,8 @@ export function NavUser({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<SidebarMenuButton <SidebarMenuButton
size="lg" size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"> className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg"> <Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} /> <AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">SN</AvatarFallback> <AvatarFallback className="rounded-lg">SN</AvatarFallback>
@ -58,7 +59,8 @@ export function NavUser({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"} side={isMobile ? "bottom" : "right"}
align="end" align="end"
sideOffset={4}> sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal"> <DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg"> <Avatar className="h-8 w-8 rounded-lg">

View File

@ -7,17 +7,17 @@ import {
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { import {
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
useSidebar useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
export function TeamSwitcher({ export function TeamSwitcher({
teams teams,
}: { }: {
teams: { teams: {
name: string; name: string;
@ -35,7 +35,8 @@ export function TeamSwitcher({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<SidebarMenuButton <SidebarMenuButton
size="lg" size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"> className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"> <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<activeTeam.logo className="size-4" /> <activeTeam.logo className="size-4" />
</div> </div>
@ -52,7 +53,8 @@ export function TeamSwitcher({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start" align="start"
side={isMobile ? "bottom" : "right"} side={isMobile ? "bottom" : "right"}
sideOffset={4}> sideOffset={4}
>
<DropdownMenuLabel className="text-xs text-muted-foreground"> <DropdownMenuLabel className="text-xs text-muted-foreground">
Teams Teams
</DropdownMenuLabel> </DropdownMenuLabel>
@ -60,7 +62,8 @@ export function TeamSwitcher({
<DropdownMenuItem <DropdownMenuItem
key={team.name} key={team.name}
onClick={() => setActiveTeam(team)} onClick={() => setActiveTeam(team)}
className="gap-2 p-2"> className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-sm border"> <div className="flex size-6 items-center justify-center rounded-sm border">
<team.logo className="size-4 shrink-0" /> <team.logo className="size-4 shrink-0" />
</div> </div>

View File

@ -6,7 +6,7 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
interface TopNavProps extends React.HTMLAttributes<HTMLElement> { interface TopNavProps extends React.HTMLAttributes<HTMLElement> {
@ -34,7 +34,8 @@ export function TopNav({ className, links, ...props }: TopNavProps) {
<Link <Link
to={href} to={href}
className={!isActive ? "text-muted-foreground" : ""} className={!isActive ? "text-muted-foreground" : ""}
disabled={disabled}> disabled={disabled}
>
{title} {title}
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
@ -46,15 +47,17 @@ export function TopNav({ className, links, ...props }: TopNavProps) {
<nav <nav
className={cn( className={cn(
"hidden items-center space-x-4 md:flex lg:space-x-6", "hidden items-center space-x-4 md:flex lg:space-x-6",
className className,
)} )}
{...props}> {...props}
>
{links.map(({ title, href, isActive, disabled }) => ( {links.map(({ title, href, isActive, disabled }) => (
<Link <Link
key={`${title}-${href}`} key={`${title}-${href}`}
to={href} to={href}
disabled={disabled} disabled={disabled}
className={`text-sm font-medium transition-colors hover:text-primary ${isActive ? "" : "text-muted-foreground"}`}> className={`text-sm font-medium transition-colors hover:text-primary ${isActive ? "" : "text-muted-foreground"}`}
>
{title} {title}
</Link> </Link>
))} ))}

View File

@ -3,13 +3,13 @@ import { cn } from "@/lib/utils";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
interface Props { interface Props {
@ -21,7 +21,7 @@ interface Props {
export default function LongText({ export default function LongText({
children, children,
className = "", className = "",
contentClassName = "" contentClassName = "",
}: Props) { }: Props) {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [isOverflown, setIsOverflown] = useState(false); const [isOverflown, setIsOverflown] = useState(false);

View File

@ -2,7 +2,7 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Menu } from "lucide-react"; import { Menu } from "lucide-react";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
@ -29,7 +29,6 @@ const MainNavbar = () => {
<div className="flex items-center"> <div className="flex items-center">
<DynamicLoginButton /> <DynamicLoginButton />
<div className="mr-2 flex items-center gap-2 md:hidden"> <div className="mr-2 flex items-center gap-2 md:hidden">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline" size="icon"> <Button variant="outline" size="icon">

View File

@ -5,7 +5,7 @@ import { ChevronRight, type LucideIcon } from "lucide-react";
import { import {
Collapsible, Collapsible,
CollapsibleContent, CollapsibleContent,
CollapsibleTrigger CollapsibleTrigger,
} from "@/components/ui/collapsible"; } from "@/components/ui/collapsible";
import { import {
SidebarGroup, SidebarGroup,
@ -15,11 +15,11 @@ import {
SidebarMenuItem, SidebarMenuItem,
SidebarMenuSub, SidebarMenuSub,
SidebarMenuSubButton, SidebarMenuSubButton,
SidebarMenuSubItem SidebarMenuSubItem,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
export function NavMain({ export function NavMain({
items items,
}: { }: {
items: { items: {
title: string; title: string;
@ -41,7 +41,8 @@ export function NavMain({
key={item.title} key={item.title}
asChild asChild
defaultOpen={item.isActive} defaultOpen={item.isActive}
className="group/collapsible"> className="group/collapsible"
>
<SidebarMenuItem> <SidebarMenuItem>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={item.title}> <SidebarMenuButton tooltip={item.title}>

View File

@ -5,7 +5,7 @@ import {
Forward, Forward,
MoreHorizontal, MoreHorizontal,
Trash2, Trash2,
type LucideIcon type LucideIcon,
} from "lucide-react"; } from "lucide-react";
import { import {
@ -13,7 +13,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { import {
SidebarGroup, SidebarGroup,
@ -22,11 +22,11 @@ import {
SidebarMenuAction, SidebarMenuAction,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
useSidebar useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
export function NavProjects({ export function NavProjects({
projects projects,
}: { }: {
projects: { projects: {
name: string; name: string;
@ -58,7 +58,8 @@ export function NavProjects({
<DropdownMenuContent <DropdownMenuContent
className="w-48 rounded-lg" className="w-48 rounded-lg"
side={isMobile ? "bottom" : "right"} side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}> align={isMobile ? "end" : "start"}
>
<DropdownMenuItem> <DropdownMenuItem>
<Folder className="text-muted-foreground" /> <Folder className="text-muted-foreground" />
<span>View Project</span> <span>View Project</span>

View File

@ -6,7 +6,7 @@ import {
ChevronsUpDown, ChevronsUpDown,
CreditCard, CreditCard,
LogOut, LogOut,
Sparkles Sparkles,
} from "lucide-react"; } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@ -17,17 +17,17 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { import {
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
useSidebar useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
export function NavUser({ export function NavUser({
user user,
}: { }: {
user: { user: {
name: string; name: string;
@ -44,7 +44,8 @@ export function NavUser({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<SidebarMenuButton <SidebarMenuButton
size="lg" size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"> className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg"> <Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} /> <AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback> <AvatarFallback className="rounded-lg">CN</AvatarFallback>
@ -60,7 +61,8 @@ export function NavUser({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"} side={isMobile ? "bottom" : "right"}
align="end" align="end"
sideOffset={4}> sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal"> <DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg"> <Avatar className="h-8 w-8 rounded-lg">

View File

@ -26,12 +26,13 @@ const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
variant="ghost" variant="ghost"
disabled={disabled} disabled={disabled}
className="absolute right-1 top-1/2 h-6 w-6 -translate-y-1/2 rounded-md text-muted-foreground" className="absolute right-1 top-1/2 h-6 w-6 -translate-y-1/2 rounded-md text-muted-foreground"
onClick={() => setShowPassword((prev) => !prev)}> onClick={() => setShowPassword((prev) => !prev)}
>
{showPassword ? <IconEye size={18} /> : <IconEyeOff size={18} />} {showPassword ? <IconEye size={18} /> : <IconEyeOff size={18} />}
</Button> </Button>
</div> </div>
); );
} },
); );
PasswordInput.displayName = "PasswordInput"; PasswordInput.displayName = "PasswordInput";

View File

@ -106,7 +106,7 @@ const PinInput = ({ className, children, ref, ...props }: PinInputProps) => {
placeholder, placeholder,
type, type,
length, length,
readOnly readOnly,
}); });
/* call onChange func if pinValue changes */ /* call onChange func if pinValue changes */
@ -171,7 +171,7 @@ const PinInput = ({ className, children, ref, ...props }: PinInputProps) => {
} else { } else {
refMap?.delete(pinIndex); refMap?.delete(pinIndex);
} }
} },
}); });
} }
skipRef.current = skipRef.current + 1; skipRef.current = skipRef.current + 1;
@ -220,7 +220,7 @@ const PinInputField = <T extends React.ElementType = "input">({
const isInsidePinInput = React.useContext(PinInputContext); const isInsidePinInput = React.useContext(PinInputContext);
if (!isInsidePinInput) { if (!isInsidePinInput) {
throw new Error( throw new Error(
`PinInputField must be used within ${PinInput.displayName}.` `PinInputField must be used within ${PinInput.displayName}.`,
); );
} }
@ -254,7 +254,7 @@ const usePinInput = ({
placeholder, placeholder,
type, type,
length, length,
readOnly readOnly,
}: UsePinInputProps) => { }: UsePinInputProps) => {
const pinInputs = React.useMemo( const pinInputs = React.useMemo(
() => () =>
@ -263,9 +263,9 @@ const usePinInput = ({
? defaultValue.charAt(index) ? defaultValue.charAt(index)
: value : value
? value.charAt(index) ? value.charAt(index)
: "" : "",
), ),
[defaultValue, length, value] [defaultValue, length, value],
); );
const [pins, setPins] = React.useState(pinInputs); const [pins, setPins] = React.useState(pinInputs);
@ -305,7 +305,7 @@ const usePinInput = ({
function handleFocus( function handleFocus(
event: React.FocusEvent<HTMLInputElement>, event: React.FocusEvent<HTMLInputElement>,
index: number index: number,
) { ) {
event.target.select(); event.target.select();
focusInput(index); focusInput(index);
@ -332,7 +332,7 @@ const usePinInput = ({
} else { } else {
return p; return p;
} }
}) }),
); );
} }
@ -384,7 +384,7 @@ const usePinInput = ({
function handleKeyDown( function handleKeyDown(
event: React.KeyboardEvent<HTMLInputElement>, event: React.KeyboardEvent<HTMLInputElement>,
index: number index: number,
) { ) {
const { ctrlKey, key, shiftKey, metaKey } = event; const { ctrlKey, key, shiftKey, metaKey } = event;
@ -429,7 +429,7 @@ const usePinInput = ({
handleBlur, handleBlur,
handleChange, handleChange,
handlePaste, handlePaste,
handleKeyDown handleKeyDown,
}; };
}; };

View File

@ -9,7 +9,7 @@ import {
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import useAuth from "@/hooks/useAuth"; import useAuth from "@/hooks/useAuth";

View File

@ -16,9 +16,10 @@ export function Search({ className = "", placeholder = "Search" }: Props) {
variant="outline" variant="outline"
className={cn( className={cn(
"relative h-8 w-full flex-1 justify-start rounded-md bg-muted/25 text-sm font-normal text-muted-foreground shadow-none hover:bg-muted/50 sm:pr-12 md:w-40 md:flex-none lg:w-56 xl:w-64", "relative h-8 w-full flex-1 justify-start rounded-md bg-muted/25 text-sm font-normal text-muted-foreground shadow-none hover:bg-muted/50 sm:pr-12 md:w-40 md:flex-none lg:w-56 xl:w-64",
className className,
)} )}
onClick={() => setOpen(true)}> onClick={() => setOpen(true)}
>
<IconSearch <IconSearch
aria-hidden="true" aria-hidden="true"
className="absolute left-1.5 top-1/2 -translate-y-1/2" className="absolute left-1.5 top-1/2 -translate-y-1/2"

View File

@ -6,7 +6,7 @@ import {
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
interface SelectDropdownProps { interface SelectDropdownProps {
@ -28,7 +28,7 @@ export function SelectDropdown({
placeholder, placeholder,
disabled, disabled,
className = "", className = "",
isControlled = false isControlled = false,
}: SelectDropdownProps) { }: SelectDropdownProps) {
const defaultState = isControlled const defaultState = isControlled
? { value: defaultValue, onValueChange } ? { value: defaultValue, onValueChange }

View File

@ -2,7 +2,8 @@ const SkipToMain = () => {
return ( return (
<a <a
className={`fixed left-44 z-[999] -translate-y-52 whitespace-nowrap bg-primary px-4 py-2 text-sm font-medium text-primary-foreground opacity-95 shadow transition hover:bg-primary/90 focus:translate-y-3 focus:transform focus-visible:ring-1 focus-visible:ring-ring`} className={`fixed left-44 z-[999] -translate-y-52 whitespace-nowrap bg-primary px-4 py-2 text-sm font-medium text-primary-foreground opacity-95 shadow transition hover:bg-primary/90 focus:translate-y-3 focus:transform focus-visible:ring-1 focus-visible:ring-ring`}
href="#content"> href="#content"
>
Skip to Main Skip to Main
</a> </a>
); );

View File

@ -10,17 +10,17 @@ import {
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { import {
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
useSidebar useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
export function TeamSwitcher({ export function TeamSwitcher({
teams teams,
}: { }: {
teams: { teams: {
name: string; name: string;
@ -38,7 +38,8 @@ export function TeamSwitcher({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<SidebarMenuButton <SidebarMenuButton
size="lg" size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"> className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"> <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<activeTeam.logo className="size-4" /> <activeTeam.logo className="size-4" />
</div> </div>
@ -55,7 +56,8 @@ export function TeamSwitcher({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start" align="start"
side={isMobile ? "bottom" : "right"} side={isMobile ? "bottom" : "right"}
sideOffset={4}> sideOffset={4}
>
<DropdownMenuLabel className="text-xs text-muted-foreground"> <DropdownMenuLabel className="text-xs text-muted-foreground">
Teams Teams
</DropdownMenuLabel> </DropdownMenuLabel>
@ -63,7 +65,8 @@ export function TeamSwitcher({
<DropdownMenuItem <DropdownMenuItem
key={team.name} key={team.name}
onClick={() => setActiveTeam(team)} onClick={() => setActiveTeam(team)}
className="gap-2 p-2"> className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-sm border"> <div className="flex size-6 items-center justify-center rounded-sm border">
<team.logo className="size-4 shrink-0" /> <team.logo className="size-4 shrink-0" />
</div> </div>

View File

@ -7,7 +7,7 @@ import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
export function ThemeSwitch() { export function ThemeSwitch() {

View File

@ -1,56 +1,56 @@
import * as React from "react" import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion" import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react" import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef< const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>, React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AccordionPrimitive.Item <AccordionPrimitive.Item
ref={ref} ref={ref}
className={cn("border-b", className)} className={cn("border-b", className)}
{...props} {...props}
/> />
)) ));
AccordionItem.displayName = "AccordionItem" AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef< const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>, React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
)) ));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef< const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>, React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content <AccordionPrimitive.Content
ref={ref} ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props} {...props}
> >
<div className={cn("pb-4 pt-0", className)}>{children}</div> <div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content> </AccordionPrimitive.Content>
)) ));
AccordionContent.displayName = AccordionPrimitive.Content.displayName AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@ -16,7 +16,7 @@ const AlertDialogOverlay = React.forwardRef<
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
@ -34,7 +34,7 @@ const AlertDialogContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className,
)} )}
{...props} {...props}
/> />
@ -49,7 +49,7 @@ const AlertDialogHeader = ({
<div <div
className={cn( className={cn(
"flex flex-col space-y-2 text-center sm:text-left", "flex flex-col space-y-2 text-center sm:text-left",
className className,
)} )}
{...props} {...props}
/> />
@ -63,7 +63,7 @@ const AlertDialogFooter = ({
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
@ -116,7 +116,7 @@ const AlertDialogCancel = React.forwardRef<
className={cn( className={cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0", "mt-2 sm:mt-0",
className className,
)} )}
{...props} {...props}
/> />
@ -134,5 +134,5 @@ export {
AlertDialogTitle, AlertDialogTitle,
AlertDialogDescription, AlertDialogDescription,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel AlertDialogCancel,
}; };

View File

@ -9,13 +9,13 @@ const alertVariants = cva(
variant: { variant: {
default: "bg-background text-foreground", default: "bg-background text-foreground",
destructive: destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive" "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
} },
}, },
defaultVariants: { defaultVariants: {
variant: "default" variant: "default",
} },
} },
); );
const Alert = React.forwardRef< const Alert = React.forwardRef<

View File

@ -10,7 +10,7 @@ const Avatar = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className className,
)} )}
{...props} {...props}
/> />
@ -37,7 +37,7 @@ const AvatarFallback = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted", "flex h-full w-full items-center justify-center rounded-full bg-muted",
className className,
)} )}
{...props} {...props}
/> />

View File

@ -13,13 +13,13 @@ const badgeVariants = cva(
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground" outline: "text-foreground",
} },
}, },
defaultVariants: { defaultVariants: {
variant: "default" variant: "default",
} },
} },
); );
export interface BadgeProps export interface BadgeProps

View File

@ -20,7 +20,7 @@ const BreadcrumbList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5", "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className className,
)} )}
{...props} {...props}
/> />
@ -81,7 +81,8 @@ const BreadcrumbSeparator = ({
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn("[&>svg]:h-3.5 [&>svg]:w-3.5", className)} className={cn("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
{...props}> {...props}
>
{children ?? <ChevronRight />} {children ?? <ChevronRight />}
</li> </li>
); );
@ -95,7 +96,8 @@ const BreadcrumbEllipsis = ({
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)} className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}> {...props}
>
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span> <span className="sr-only">More</span>
</span> </span>
@ -109,5 +111,5 @@ export {
BreadcrumbLink, BreadcrumbLink,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
BreadcrumbEllipsis BreadcrumbEllipsis,
}; };

View File

@ -17,20 +17,20 @@ const buttonVariants = cva(
secondary: secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80", "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline" link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-10 px-4 py-2", default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3", sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8", lg: "h-11 rounded-md px-8",
icon: "h-10 w-10" icon: "h-10 w-10",
} },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
size: "default" size: "default",
} },
} },
); );
export interface ButtonProps export interface ButtonProps
@ -49,7 +49,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{...props} {...props}
/> />
); );
} },
); );
Button.displayName = "Button"; Button.displayName = "Button";

View File

@ -25,7 +25,7 @@ function Calendar({
nav: "space-x-1 flex items-center", nav: "space-x-1 flex items-center",
nav_button: cn( nav_button: cn(
buttonVariants({ variant: "outline" }), buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100" "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
), ),
nav_button_previous: "absolute left-1", nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1", nav_button_next: "absolute right-1",
@ -37,7 +37,7 @@ function Calendar({
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn( day: cn(
buttonVariants({ variant: "ghost" }), buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100" "h-9 w-9 p-0 font-normal aria-selected:opacity-100",
), ),
day_range_end: "day-range-end", day_range_end: "day-range-end",
day_selected: day_selected:
@ -49,7 +49,7 @@ function Calendar({
day_range_middle: day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground", "aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible", day_hidden: "invisible",
...classNames ...classNames,
}} }}
components={{ components={{
IconLeft: ({ className, ...props }) => ( IconLeft: ({ className, ...props }) => (
@ -57,7 +57,7 @@ function Calendar({
), ),
IconRight: ({ className, ...props }) => ( IconRight: ({ className, ...props }) => (
<ChevronRight className={cn("h-4 w-4", className)} {...props} /> <ChevronRight className={cn("h-4 w-4", className)} {...props} />
) ),
}} }}
{...props} {...props}
/> />

View File

@ -9,7 +9,7 @@ const Card = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"rounded-xl border bg-card text-card-foreground shadow", "rounded-xl border bg-card text-card-foreground shadow",
className className,
)} )}
{...props} {...props}
/> />
@ -78,5 +78,5 @@ export {
CardFooter, CardFooter,
CardTitle, CardTitle,
CardDescription, CardDescription,
CardContent CardContent,
}; };

View File

@ -11,11 +11,13 @@ const Checkbox = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className className,
)} )}
{...props}> {...props}
>
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}> className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>

View File

@ -8,7 +8,7 @@ import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogTitle DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
const Command = React.forwardRef< const Command = React.forwardRef<
@ -19,7 +19,7 @@ const Command = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className className,
)} )}
{...props} {...props}
/> />
@ -54,7 +54,7 @@ const CommandInput = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
{...props} {...props}
/> />
@ -97,7 +97,7 @@ const CommandGroup = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
@ -125,7 +125,7 @@ const CommandItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className className,
)} )}
{...props} {...props}
/> />
@ -141,7 +141,7 @@ const CommandShortcut = ({
<span <span
className={cn( className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground", "ml-auto text-xs tracking-widest text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
@ -158,5 +158,5 @@ export {
CommandGroup, CommandGroup,
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator CommandSeparator,
}; };

View File

@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
/> />
@ -38,9 +38,10 @@ const DialogContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className,
)} )}
{...props}> {...props}
>
{children} {children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@ -58,7 +59,7 @@ const DialogHeader = ({
<div <div
className={cn( className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left", "flex flex-col space-y-1.5 text-center sm:text-left",
className className,
)} )}
{...props} {...props}
/> />
@ -72,7 +73,7 @@ const DialogFooter = ({
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
@ -87,7 +88,7 @@ const DialogTitle = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"text-lg font-semibold leading-none tracking-tight", "text-lg font-semibold leading-none tracking-tight",
className className,
)} )}
{...props} {...props}
/> />
@ -116,5 +117,5 @@ export {
DialogHeader, DialogHeader,
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription DialogDescription,
}; };

View File

@ -26,9 +26,10 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn( className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props}> {...props}
>
{children} {children}
<ChevronRight className="ml-auto" /> <ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
@ -44,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
@ -63,7 +64,7 @@ const DropdownMenuContent = React.forwardRef<
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />
@ -82,7 +83,7 @@ const DropdownMenuItem = React.forwardRef<
className={cn( className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
@ -97,10 +98,11 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
checked={checked} checked={checked}
{...props}> {...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
@ -120,9 +122,10 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props}> {...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" /> <Circle className="h-2 w-2 fill-current" />
@ -144,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", "px-2 py-1.5 text-sm font-semibold",
inset && "pl-8", inset && "pl-8",
className className,
)} )}
{...props} {...props}
/> />
@ -191,5 +194,5 @@ export {
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup DropdownMenuRadioGroup,
}; };

View File

@ -5,7 +5,7 @@ import {
FieldPath, FieldPath,
FieldValues, FieldValues,
FormProvider, FormProvider,
useFormContext useFormContext,
} from "react-hook-form"; } from "react-hook-form";
import * as LabelPrimitive from "@radix-ui/react-label"; import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot";
@ -16,18 +16,18 @@ const Form = FormProvider;
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = { > = {
name: TName; name: TName;
}; };
const FormFieldContext = React.createContext<FormFieldContextValue>( const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue {} as FormFieldContextValue,
); );
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => {
@ -57,7 +57,7 @@ const useFormField = () => {
formItemId: `${id}-form-item`, formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`, formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`, formMessageId: `${id}-form-item-message`,
...fieldState ...fieldState,
}; };
}; };
@ -66,7 +66,7 @@ type FormItemContextValue = {
}; };
const FormItemContext = React.createContext<FormItemContextValue>( const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue {} as FormItemContextValue,
); );
const FormItem = React.forwardRef< const FormItem = React.forwardRef<
@ -156,7 +156,8 @@ const FormMessage = React.forwardRef<
ref={ref} ref={ref}
id={formMessageId} id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)} className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}> {...props}
>
{body} {body}
</p> </p>
); );
@ -171,5 +172,5 @@ export {
FormControl, FormControl,
FormDescription, FormDescription,
FormMessage, FormMessage,
FormField FormField,
}; };

View File

@ -8,13 +8,13 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
type={type} type={type}
className={cn( className={cn(
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
); );
} },
); );
Input.displayName = "Input"; Input.displayName = "Input";

View File

@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
); );
const Label = React.forwardRef< const Label = React.forwardRef<

View File

@ -10,13 +10,13 @@ import {
CommandGroup, CommandGroup,
CommandInput, CommandInput,
CommandItem, CommandItem,
CommandList CommandList,
} from "@/components/ui/command"; } from "@/components/ui/command";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -53,7 +53,7 @@ const PhoneInput: React.ForwardRefExoticComponent<PhoneInputProps> =
{...props} {...props}
/> />
); );
} },
); );
PhoneInput.displayName = "PhoneInput"; PhoneInput.displayName = "PhoneInput";
@ -82,7 +82,7 @@ const CountrySelect = ({
disabled, disabled,
value: selectedCountry, value: selectedCountry,
options: countryList, options: countryList,
onChange onChange,
}: CountrySelectProps) => { }: CountrySelectProps) => {
return ( return (
<Popover> <Popover>
@ -91,7 +91,8 @@ const CountrySelect = ({
type="button" type="button"
variant="outline" variant="outline"
className="flex gap-1 rounded-e-none rounded-s-lg border-r-0 px-3 focus:z-10" className="flex gap-1 rounded-e-none rounded-s-lg border-r-0 px-3 focus:z-10"
disabled={disabled}> disabled={disabled}
>
<FlagComponent <FlagComponent
country={selectedCountry} country={selectedCountry}
countryName={selectedCountry} countryName={selectedCountry}
@ -99,7 +100,7 @@ const CountrySelect = ({
<ChevronsUpDown <ChevronsUpDown
className={cn( className={cn(
"-mr-2 size-4 opacity-50", "-mr-2 size-4 opacity-50",
disabled ? "hidden" : "opacity-100" disabled ? "hidden" : "opacity-100",
)} )}
/> />
</Button> </Button>
@ -120,7 +121,7 @@ const CountrySelect = ({
selectedCountry={selectedCountry} selectedCountry={selectedCountry}
onChange={onChange} onChange={onChange}
/> />
) : null ) : null,
)} )}
</CommandGroup> </CommandGroup>
</ScrollArea> </ScrollArea>
@ -140,7 +141,7 @@ const CountrySelectOption = ({
country, country,
countryName, countryName,
selectedCountry, selectedCountry,
onChange onChange,
}: CountrySelectOptionProps) => { }: CountrySelectOptionProps) => {
return ( return (
<CommandItem className="gap-2" onSelect={() => onChange(country)}> <CommandItem className="gap-2" onSelect={() => onChange(country)}>

View File

@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />

View File

@ -26,9 +26,10 @@ const RadioGroupItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className className,
)} )}
{...props}> {...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center"> <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-3.5 w-3.5 fill-primary" /> <Circle className="h-3.5 w-3.5 fill-primary" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>

View File

@ -14,12 +14,14 @@ const ScrollArea = React.forwardRef<
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
ref={ref} ref={ref}
className={cn("relative overflow-hidden", className)} className={cn("relative overflow-hidden", className)}
{...props}> {...props}
>
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
className={cn( className={cn(
"h-full w-full rounded-[inherit]", "h-full w-full rounded-[inherit]",
orientation === "horizontal" && "!overflow-x-auto" orientation === "horizontal" && "!overflow-x-auto",
)}> )}
>
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar orientation={orientation} /> <ScrollBar orientation={orientation} />
@ -41,9 +43,10 @@ const ScrollBar = React.forwardRef<
"h-full w-2.5 border-l border-l-transparent p-[1px]", "h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" && orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]", "h-2.5 flex-col border-t border-t-transparent p-[1px]",
className className,
)} )}
{...props}> {...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
)); ));

View File

@ -17,9 +17,10 @@ const SelectTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className,
)} )}
{...props}> {...props}
>
{children} {children}
<SelectPrimitive.Icon asChild> <SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
@ -36,9 +37,10 @@ const SelectScrollUpButton = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className className,
)} )}
{...props}> {...props}
>
<ChevronUp className="h-4 w-4" /> <ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
)); ));
@ -52,9 +54,10 @@ const SelectScrollDownButton = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className className,
)} )}
{...props}> {...props}
>
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)); ));
@ -72,17 +75,19 @@ const SelectContent = React.forwardRef<
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className,
)} )}
position={position} position={position}
{...props}> {...props}
>
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}> )}
>
{children} {children}
</SelectPrimitive.Viewport> </SelectPrimitive.Viewport>
<SelectScrollDownButton /> <SelectScrollDownButton />
@ -111,9 +116,10 @@ const SelectItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className,
)} )}
{...props}> {...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator> <SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" /> <Check className="h-4 w-4" />
@ -146,5 +152,5 @@ export {
SelectItem, SelectItem,
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton SelectScrollDownButton,
}; };

View File

@ -8,7 +8,7 @@ const Separator = React.forwardRef<
>( >(
( (
{ className, orientation = "horizontal", decorative = true, ...props }, { className, orientation = "horizontal", decorative = true, ...props },
ref ref,
) => ( ) => (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
ref={ref} ref={ref}
@ -17,11 +17,11 @@ const Separator = React.forwardRef<
className={cn( className={cn(
"shrink-0 bg-border", "shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className className,
)} )}
{...props} {...props}
/> />
) ),
); );
Separator.displayName = SeparatorPrimitive.Root.displayName; Separator.displayName = SeparatorPrimitive.Root.displayName;

View File

@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className,
)} )}
{...props} {...props}
ref={ref} ref={ref}
@ -39,13 +39,13 @@ const sheetVariants = cva(
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right: right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm" "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
} },
}, },
defaultVariants: { defaultVariants: {
side: "right" side: "right",
} },
} },
); );
interface SheetContentProps interface SheetContentProps
@ -61,7 +61,8 @@ const SheetContent = React.forwardRef<
<SheetPrimitive.Content <SheetPrimitive.Content
ref={ref} ref={ref}
className={cn(sheetVariants({ side }), className)} className={cn(sheetVariants({ side }), className)}
{...props}> {...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
@ -79,7 +80,7 @@ const SheetHeader = ({
<div <div
className={cn( className={cn(
"flex flex-col space-y-2 text-center sm:text-left", "flex flex-col space-y-2 text-center sm:text-left",
className className,
)} )}
{...props} {...props}
/> />
@ -93,7 +94,7 @@ const SheetFooter = ({
<div <div
className={cn( className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className className,
)} )}
{...props} {...props}
/> />
@ -134,5 +135,5 @@ export {
SheetHeader, SheetHeader,
SheetFooter, SheetFooter,
SheetTitle, SheetTitle,
SheetDescription SheetDescription,
}; };

View File

@ -12,14 +12,14 @@ import {
Sheet, Sheet,
SheetContent, SheetContent,
SheetDescription, SheetDescription,
SheetTitle SheetTitle,
} from "@/components/ui/sheet"; } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
const SIDEBAR_COOKIE_NAME = "sidebar:state"; const SIDEBAR_COOKIE_NAME = "sidebar:state";
@ -68,7 +68,7 @@ const SidebarProvider = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false); const [openMobile, setOpenMobile] = React.useState(false);
@ -89,7 +89,7 @@ const SidebarProvider = React.forwardRef<
// This sets the cookie to keep the sidebar state. // This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
}, },
[setOpenProp, open] [setOpenProp, open],
); );
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
@ -127,9 +127,17 @@ const SidebarProvider = React.forwardRef<
isMobile, isMobile,
openMobile, openMobile,
setOpenMobile, setOpenMobile,
toggleSidebar toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] [
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
],
); );
return ( return (
@ -140,21 +148,22 @@ const SidebarProvider = React.forwardRef<
{ {
"--sidebar-width": SIDEBAR_WIDTH, "--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON, "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style ...style,
} as React.CSSProperties } as React.CSSProperties
} }
className={cn( className={cn(
"group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex min-h-svh w-full", "group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex min-h-svh w-full",
className className,
)} )}
ref={ref} ref={ref}
{...props}> {...props}
>
{children} {children}
</div> </div>
</TooltipProvider> </TooltipProvider>
</SidebarContext.Provider> </SidebarContext.Provider>
); );
} },
); );
SidebarProvider.displayName = "SidebarProvider"; SidebarProvider.displayName = "SidebarProvider";
@ -175,7 +184,7 @@ const Sidebar = React.forwardRef<
children, children,
...props ...props
}, },
ref ref,
) => { ) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
@ -184,10 +193,11 @@ const Sidebar = React.forwardRef<
<div <div
className={cn( className={cn(
"bg-sidebar text-sidebar-foreground flex h-full w-[--sidebar-width] flex-col", "bg-sidebar text-sidebar-foreground flex h-full w-[--sidebar-width] flex-col",
className className,
)} )}
ref={ref} ref={ref}
{...props}> {...props}
>
{children} {children}
</div> </div>
); );
@ -205,10 +215,11 @@ const Sidebar = React.forwardRef<
className="bg-sidebar text-sidebar-foreground w-[--sidebar-width] p-0 [&>button]:hidden" className="bg-sidebar text-sidebar-foreground w-[--sidebar-width] p-0 [&>button]:hidden"
style={ style={
{ {
"--sidebar-width": SIDEBAR_WIDTH_MOBILE "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties } as React.CSSProperties
} }
side={side}> side={side}
>
<VisuallyHidden asChild> <VisuallyHidden asChild>
<SheetDescription /> <SheetDescription />
</VisuallyHidden> </VisuallyHidden>
@ -225,7 +236,8 @@ const Sidebar = React.forwardRef<
data-state={state} data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""} data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant} data-variant={variant}
data-side={side}> data-side={side}
>
{/* This is what handles the sidebar gap on desktop */} {/* This is what handles the sidebar gap on desktop */}
<div <div
className={cn( className={cn(
@ -234,7 +246,7 @@ const Sidebar = React.forwardRef<
"group-data-[side=right]:rotate-180", "group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]" : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]",
)} )}
/> />
<div <div
@ -247,18 +259,20 @@ const Sidebar = React.forwardRef<
variant === "floating" || variant === "inset" variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l", : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className className,
)} )}
{...props}> {...props}
>
<div <div
data-sidebar="sidebar" data-sidebar="sidebar"
className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow"> className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow"
>
{children} {children}
</div> </div>
</div> </div>
</div> </div>
); );
} },
); );
Sidebar.displayName = "Sidebar"; Sidebar.displayName = "Sidebar";
@ -279,7 +293,8 @@ const SidebarTrigger = React.forwardRef<
onClick?.(event); onClick?.(event);
toggleSidebar(); toggleSidebar();
}} }}
{...props}> {...props}
>
<PanelLeft /> <PanelLeft />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
@ -308,7 +323,7 @@ const SidebarRail = React.forwardRef<
"group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full", "group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className className,
)} )}
{...props} {...props}
/> />
@ -326,7 +341,7 @@ const SidebarInset = React.forwardRef<
className={cn( className={cn(
"relative flex min-h-svh flex-1 flex-col bg-background", "relative flex min-h-svh flex-1 flex-col bg-background",
"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className className,
)} )}
{...props} {...props}
/> />
@ -344,7 +359,7 @@ const SidebarInput = React.forwardRef<
data-sidebar="input" data-sidebar="input"
className={cn( className={cn(
"focus-visible:ring-sidebar-ring h-8 w-full bg-background shadow-none focus-visible:ring-2", "focus-visible:ring-sidebar-ring h-8 w-full bg-background shadow-none focus-visible:ring-2",
className className,
)} )}
{...props} {...props}
/> />
@ -407,7 +422,7 @@ const SidebarContent = React.forwardRef<
data-sidebar="content" data-sidebar="content"
className={cn( className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className className,
)} )}
{...props} {...props}
/> />
@ -443,7 +458,7 @@ const SidebarGroupLabel = React.forwardRef<
className={cn( className={cn(
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-none transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-none transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className className,
)} )}
{...props} {...props}
/> />
@ -466,7 +481,7 @@ const SidebarGroupAction = React.forwardRef<
// Increases the hit area of the button on mobile. // Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden", "after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
@ -520,19 +535,19 @@ const sidebarMenuButtonVariants = cva(
variant: { variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline: outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]" "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
}, },
size: { size: {
default: "h-8 text-sm", default: "h-8 text-sm",
sm: "h-7 text-xs", sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0" lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
} },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
size: "default" size: "default",
} },
} },
); );
const SidebarMenuButton = React.forwardRef< const SidebarMenuButton = React.forwardRef<
@ -553,7 +568,7 @@ const SidebarMenuButton = React.forwardRef<
className, className,
...props ...props
}, },
ref ref,
) => { ) => {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar(); const { isMobile, state } = useSidebar();
@ -575,7 +590,7 @@ const SidebarMenuButton = React.forwardRef<
if (typeof tooltip === "string") { if (typeof tooltip === "string") {
tooltip = { tooltip = {
children: tooltip children: tooltip,
}; };
} }
@ -590,7 +605,7 @@ const SidebarMenuButton = React.forwardRef<
/> />
</Tooltip> </Tooltip>
); );
} },
); );
SidebarMenuButton.displayName = "SidebarMenuButton"; SidebarMenuButton.displayName = "SidebarMenuButton";
@ -617,7 +632,7 @@ const SidebarMenuAction = React.forwardRef<
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
showOnHover && showOnHover &&
"peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className className,
)} )}
{...props} {...props}
/> />
@ -639,7 +654,7 @@ const SidebarMenuBadge = React.forwardRef<
"peer-data-[size=default]/menu-button:top-1.5", "peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5", "peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
@ -662,7 +677,8 @@ const SidebarMenuSkeleton = React.forwardRef<
ref={ref} ref={ref}
data-sidebar="menu-skeleton" data-sidebar="menu-skeleton"
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
{...props}> {...props}
>
{showIcon && ( {showIcon && (
<Skeleton <Skeleton
className="size-4 rounded-md" className="size-4 rounded-md"
@ -674,7 +690,7 @@ const SidebarMenuSkeleton = React.forwardRef<
data-sidebar="menu-skeleton-text" data-sidebar="menu-skeleton-text"
style={ style={
{ {
"--skeleton-width": width "--skeleton-width": width,
} as React.CSSProperties } as React.CSSProperties
} }
/> />
@ -693,7 +709,7 @@ const SidebarMenuSub = React.forwardRef<
className={cn( className={cn(
"border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5", "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
@ -728,7 +744,7 @@ const SidebarMenuSubButton = React.forwardRef<
size === "sm" && "text-xs", size === "sm" && "text-xs",
size === "md" && "text-sm", size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden", "group-data-[collapsible=icon]:hidden",
className className,
)} )}
{...props} {...props}
/> />
@ -760,5 +776,5 @@ export {
SidebarRail, SidebarRail,
SidebarSeparator, SidebarSeparator,
SidebarTrigger, SidebarTrigger,
useSidebar useSidebar,
}; };

View File

@ -9,13 +9,14 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Root <SwitchPrimitives.Root
className={cn( className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className className,
)} )}
{...props} {...props}
ref={ref}> ref={ref}
>
<SwitchPrimitives.Thumb <SwitchPrimitives.Thumb
className={cn( className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0" "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
)} )}
/> />
</SwitchPrimitives.Root> </SwitchPrimitives.Root>

View File

@ -43,7 +43,7 @@ const TableFooter = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className className,
)} )}
{...props} {...props}
/> />
@ -58,7 +58,7 @@ const TableRow = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className className,
)} )}
{...props} {...props}
/> />
@ -73,7 +73,7 @@ const TableHead = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className,
)} )}
{...props} {...props}
/> />
@ -88,7 +88,7 @@ const TableCell = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className className,
)} )}
{...props} {...props}
/> />
@ -115,5 +115,5 @@ export {
TableHead, TableHead,
TableRow, TableRow,
TableCell, TableCell,
TableCaption TableCaption,
}; };

View File

@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className className,
)} )}
{...props} {...props}
/> />
@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className className,
)} )}
{...props} {...props}
/> />
@ -42,7 +42,7 @@ const TabsContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className className,
)} )}
{...props} {...props}
/> />

View File

@ -9,7 +9,7 @@ const Textarea = React.forwardRef<
<textarea <textarea
className={cn( className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className className,
)} )}
ref={ref} ref={ref}
{...props} {...props}

View File

@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className className,
)} )}
{...props} {...props}
/> />
@ -28,13 +28,13 @@ const toastVariants = cva(
variant: { variant: {
default: "border bg-background text-foreground", default: "border bg-background text-foreground",
destructive: destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground" "destructive group border-destructive bg-destructive text-destructive-foreground",
} },
}, },
defaultVariants: { defaultVariants: {
variant: "default" variant: "default",
} },
} },
); );
const Toast = React.forwardRef< const Toast = React.forwardRef<
@ -60,7 +60,7 @@ const ToastAction = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className className,
)} )}
{...props} {...props}
/> />
@ -75,10 +75,11 @@ const ToastClose = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", "absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className className,
)} )}
toast-close="" toast-close=""
{...props}> {...props}
>
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</ToastPrimitives.Close> </ToastPrimitives.Close>
)); ));
@ -121,5 +122,5 @@ export {
ToastTitle, ToastTitle,
ToastDescription, ToastDescription,
ToastClose, ToastClose,
ToastAction ToastAction,
}; };

View File

@ -5,7 +5,7 @@ import {
ToastDescription, ToastDescription,
ToastProvider, ToastProvider,
ToastTitle, ToastTitle,
ToastViewport ToastViewport,
} from "@/components/ui/toast"; } from "@/components/ui/toast";
export function Toaster() { export function Toaster() {

View File

@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className,
)} )}
{...props} {...props}
/> />

View File

@ -1,2 +1,2 @@
export const AppName = "SwagShop" export const AppName = "SwagShop";
export const AdminAppName = "SwagShop Admin" export const AdminAppName = "SwagShop Admin";

View File

@ -11,7 +11,7 @@ interface FontContextType {
const FontContext = createContext<FontContextType | undefined>(undefined); const FontContext = createContext<FontContextType | undefined>(undefined);
export const FontProvider: React.FC<{ children: React.ReactNode }> = ({ export const FontProvider: React.FC<{ children: React.ReactNode }> = ({
children children,
}) => { }) => {
const [font, _setFont] = useState<Font>(() => { const [font, _setFont] = useState<Font>(() => {
const savedFont = localStorage.getItem("font"); const savedFont = localStorage.getItem("font");

View File

@ -15,7 +15,7 @@ type ThemeProviderState = {
const initialState: ThemeProviderState = { const initialState: ThemeProviderState = {
theme: "system", theme: "system",
setTheme: () => null setTheme: () => null,
}; };
const ThemeProviderContext = createContext<ThemeProviderState>(initialState); const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
@ -27,7 +27,7 @@ export function ThemeProvider({
...props ...props
}: ThemeProviderProps) { }: ThemeProviderProps) {
const [theme, _setTheme] = useState<Theme>( const [theme, _setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
); );
useEffect(() => { useEffect(() => {
@ -61,7 +61,7 @@ export function ThemeProvider({
const value = { const value = {
theme, theme,
setTheme setTheme,
}; };
return ( return (

View File

@ -22,14 +22,14 @@ const useAuth = () => {
const { data: user } = useQuery<UserPublic | null, Error>({ const { data: user } = useQuery<UserPublic | null, Error>({
queryKey: ["currentUser"], queryKey: ["currentUser"],
queryFn: authAPI.getCurrentUser, queryFn: authAPI.getCurrentUser,
enabled: loggedIn enabled: loggedIn,
}); });
const signUpMutation = useMutation({ const signUpMutation = useMutation({
mutationFn: authAPI.registerUser, mutationFn: authAPI.registerUser,
onSuccess: () => navigate({ to: "/sign-in" }), onSuccess: () => navigate({ to: "/sign-in" }),
onError: (err: ApiError) => handleServerError(err), onError: (err: ApiError) => handleServerError(err),
onSettled: () => queryClient.invalidateQueries({ queryKey: ["users"] }) onSettled: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
}); });
const login = async (data: ShopLoginAccessTokenData) => { const login = async (data: ShopLoginAccessTokenData) => {
@ -42,7 +42,7 @@ const useAuth = () => {
const loginMutation = useMutation({ const loginMutation = useMutation({
mutationFn: login, mutationFn: login,
onSuccess: () => navigate({ to: "/" }), onSuccess: () => navigate({ to: "/" }),
onError: (err: ApiError) => handleServerError(err) onError: (err: ApiError) => handleServerError(err),
}); });
const logout = () => { const logout = () => {
@ -58,7 +58,7 @@ const useAuth = () => {
toast({ title: "Account updated successfully" }); toast({ title: "Account updated successfully" });
queryClient.invalidateQueries({ queryKey: ["currentUser"] }); queryClient.invalidateQueries({ queryKey: ["currentUser"] });
}, },
onError: (err: ApiError) => handleServerError(err) onError: (err: ApiError) => handleServerError(err),
}); });
useEffect(() => { useEffect(() => {
@ -97,7 +97,7 @@ const useAuth = () => {
logout, logout,
user, user,
error, error,
resetError: () => setError(null) resetError: () => setError(null),
}; };
}; };

View File

@ -0,0 +1,39 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Coupon } from "@/api/mock/models";
import { couponAPI } from "@/api/api";
export function useCoupon(couponId?: number) {
const queryClient = useQueryClient();
const coupon = useQuery<Coupon | null>({
queryKey: ["coupon", couponId],
queryFn: () => couponAPI.getCouponById(couponId!),
enabled: !!couponId,
});
const createCoupon = useMutation({
mutationFn: (data: Omit<Coupon, "id">) => couponAPI.createCoupon(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["coupons"] });
},
});
const updateCoupon = useMutation({
mutationFn: (data: Partial<Omit<Coupon, "id">>) =>
couponAPI.updateCoupon(couponId!, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["coupons"] });
queryClient.invalidateQueries({ queryKey: ["coupon", couponId] });
},
});
const deleteCoupon = useMutation({
mutationFn: (id: number) => couponAPI.deleteCoupon(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: ["coupons"] });
queryClient.removeQueries({ queryKey: ["coupon", id] });
},
});
return { coupon, createCoupon, updateCoupon, deleteCoupon };
}

View File

@ -0,0 +1,10 @@
import { useQuery } from "@tanstack/react-query";
import { couponAPI } from "@/api/api";
import { Coupon } from "@/api/mock/models";
export function useCoupons() {
return useQuery<Coupon[]>({
queryKey: ["coupons"],
queryFn: couponAPI.getAllCoupons,
});
}

View File

@ -7,7 +7,7 @@ import { useState } from "react";
* @example const [open, setOpen] = useDialogState<"approve" | "reject">() * @example const [open, setOpen] = useDialogState<"approve" | "reject">()
*/ */
export default function useDialogState<T extends string | boolean>( export default function useDialogState<T extends string | boolean>(
initialState: T | null = null initialState: T | null = null,
) { ) {
const [open, _setOpen] = useState<T | null>(initialState); const [open, _setOpen] = useState<T | null>(initialState);

View File

@ -4,7 +4,7 @@ const MOBILE_BREAKPOINT = 768;
export function useIsMobile() { export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>( const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined undefined,
); );
React.useEffect(() => { React.useEffect(() => {

View File

@ -8,22 +8,23 @@ export function useProduct(productId?: number) {
const product = useQuery<ProductWithDetails | null>({ const product = useQuery<ProductWithDetails | null>({
queryKey: ["product", productId], queryKey: ["product", productId],
queryFn: () => productsAPI.getProductById(productId!), queryFn: () => productsAPI.getProductById(productId!),
enabled: !!productId enabled: !!productId,
}); });
const createProduct = useMutation({ const createProduct = useMutation({
mutationFn: (data: ProductCreate) => productsAPI.createProduct(data), mutationFn: (data: ProductCreate) => productsAPI.createProduct(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }) onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
}); });
const updateProduct = useMutation({ const updateProduct = useMutation({
mutationFn: (data: Partial<ProductCreate>) => productsAPI.updateProduct(productId!, data), mutationFn: (data: Partial<ProductCreate>) =>
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }) productsAPI.updateProduct(productId!, data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
}); });
const deleteProduct = useMutation({ const deleteProduct = useMutation({
mutationFn: () => productsAPI.deleteProduct(productId!), mutationFn: () => productsAPI.deleteProduct(productId!),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }) onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
}); });
return { product, createProduct, updateProduct, deleteProduct }; return { product, createProduct, updateProduct, deleteProduct };

View File

@ -1,4 +1,3 @@
// src/hooks/useProducts.ts
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { productsAPI } from "@/api/api"; import { productsAPI } from "@/api/api";
import { ProductWithDetails } from "@/api/mock/models"; import { ProductWithDetails } from "@/api/mock/models";
@ -6,7 +5,7 @@ import { ProductWithDetails } from "@/api/mock/models";
export function useProducts() { export function useProducts() {
const query = useQuery<ProductWithDetails[]>({ const query = useQuery<ProductWithDetails[]>({
queryKey: ["products"], queryKey: ["products"],
queryFn: productsAPI.getProductsForShop queryFn: productsAPI.getProductsForShop,
}); });
return query; return query;

View File

@ -0,0 +1,14 @@
import { useQuery } from "@tanstack/react-query";
import { PurchaseWithDetails } from "@/api/mock/models";
import { purchaseAPI } from "@/api/api";
export function usePurchase(purchaseId?: number) {
return useQuery<PurchaseWithDetails | null>({
queryKey: ["purchase", purchaseId],
queryFn: () => {
if (purchaseId === undefined) return Promise.resolve(null);
return purchaseAPI.getPurchaseWithDetails(purchaseId);
},
enabled: purchaseId !== undefined,
});
}

View File

@ -0,0 +1,10 @@
import { useQuery } from "@tanstack/react-query";
import { Purchase } from "@/api/mock/models";
import { purchaseAPI } from "@/api/api";
export function usePurchases() {
return useQuery<Purchase[]>({
queryKey: ["purchases"],
queryFn: purchaseAPI.getAllPurchases,
});
}

View File

@ -8,15 +8,15 @@ export function useShop() {
const { const {
data: shop, data: shop,
isLoading, isLoading,
isError isError,
} = useQuery<Shop | null>({ } = useQuery<Shop | null>({
queryKey: ["shop"], queryKey: ["shop"],
queryFn: shopAPI.getShop queryFn: shopAPI.getShop,
}); });
const updateShopMutation = useMutation({ const updateShopMutation = useMutation({
mutationFn: shopAPI.updateShop, mutationFn: shopAPI.updateShop,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["shop"] }) onSuccess: () => queryClient.invalidateQueries({ queryKey: ["shop"] }),
}); });
return { return {
@ -24,6 +24,6 @@ export function useShop() {
isLoading, isLoading,
isError, isError,
updateShop: updateShopMutation.mutate, updateShop: updateShopMutation.mutate,
updateStatus: updateShopMutation.status updateStatus: updateShopMutation.status,
}; };
} }

View File

@ -17,7 +17,7 @@ const actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST" REMOVE_TOAST: "REMOVE_TOAST",
} as const; } as const;
let count = 0; let count = 0;
@ -62,7 +62,7 @@ const addToRemoveQueue = (toastId: string) => {
toastTimeouts.delete(toastId); toastTimeouts.delete(toastId);
dispatch({ dispatch({
type: "REMOVE_TOAST", type: "REMOVE_TOAST",
toastId: toastId toastId: toastId,
}); });
}, TOAST_REMOVE_DELAY); }, TOAST_REMOVE_DELAY);
@ -74,15 +74,15 @@ const reducer = (state: State, action: Action): State => {
case "ADD_TOAST": case "ADD_TOAST":
return { return {
...state, ...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}; };
case "UPDATE_TOAST": case "UPDATE_TOAST":
return { return {
...state, ...state,
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t t.id === action.toast.id ? { ...t, ...action.toast } : t,
) ),
}; };
case "DISMISS_TOAST": { case "DISMISS_TOAST": {
@ -104,22 +104,22 @@ const reducer = (state: State, action: Action): State => {
t.id === toastId || toastId === undefined t.id === toastId || toastId === undefined
? { ? {
...t, ...t,
open: false open: false,
} }
: t : t,
) ),
}; };
} }
case "REMOVE_TOAST": case "REMOVE_TOAST":
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
toasts: [] toasts: [],
}; };
} }
return { return {
...state, ...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId) toasts: state.toasts.filter((t) => t.id !== action.toastId),
}; };
} }
}; };
@ -143,7 +143,7 @@ function toast({ ...props }: Toast) {
const update = (props: ToasterToast) => const update = (props: ToasterToast) =>
dispatch({ dispatch({
type: "UPDATE_TOAST", type: "UPDATE_TOAST",
toast: { ...props, id } toast: { ...props, id },
}); });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
@ -155,14 +155,14 @@ function toast({ ...props }: Toast) {
open: true, open: true,
onOpenChange: (open) => { onOpenChange: (open) => {
if (!open) dismiss(); if (!open) dismiss();
} },
} },
}); });
return { return {
id: id, id: id,
dismiss, dismiss,
update update,
}; };
} }
@ -182,7 +182,7 @@ function useToast() {
return { return {
...state, ...state,
toast, toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }) dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}; };
} }

View File

@ -0,0 +1,38 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { MockUser } from "@/api/mock/models";
import { usersAPI } from "@/api/api";
export function useUser(userId?: number) {
const queryClient = useQueryClient();
const user = useQuery<MockUser | null>({
queryKey: ["user", userId],
queryFn: () => usersAPI.getUserById(userId!),
enabled: !!userId,
});
const createUser = useMutation({
mutationFn: (data: Omit<MockUser, "id" | "created_at" | "updated_at">) =>
usersAPI.createUser(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
});
const updateUser = useMutation({
mutationFn: (data: Partial<Omit<MockUser, "id" | "created_at">>) =>
usersAPI.updateUser(userId!, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
queryClient.invalidateQueries({ queryKey: ["user", userId] });
},
});
const deleteUser = useMutation({
mutationFn: () => usersAPI.deleteUser(userId!),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
queryClient.removeQueries({ queryKey: ["user", userId] });
},
});
return { user, createUser, updateUser, deleteUser };
}

View File

@ -0,0 +1,10 @@
import { useQuery } from "@tanstack/react-query";
import { usersAPI } from "@/api/api";
import { MockUser } from "@/api/mock/models";
export function useUsers() {
return useQuery<MockUser[]>({
queryKey: ["users"],
queryFn: usersAPI.getAllUsers,
});
}

View File

@ -3,152 +3,150 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 222.2 84% 4.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%; --primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%; --primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%; --secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%; --muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%; --accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%; --border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%; --ring: 222.2 84% 4.9%;
--chart-1: 12 76% 61%; --chart-1: 12 76% 61%;
--chart-2: 173 58% 39%; --chart-2: 173 58% 39%;
--chart-3: 197 37% 24%; --chart-3: 197 37% 24%;
--chart-4: 43 74% 66%; --chart-4: 43 74% 66%;
--chart-5: 27 87% 67%; --chart-5: 27 87% 67%;
--radius: 0.5rem; --radius: 0.5rem;
--sidebar-background: 0 0% 98%; --sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%; --sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%; --sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%; --sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%; --sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%; --sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%; --sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%; --sidebar-ring: 217.2 91.2% 59.8%;
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%; --popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%; --popover-foreground: 210 40% 98%;
--primary: 210 40% 98%; --primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%; --primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%; --secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%; --secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%; --muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%; --muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%; --accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%; --accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%; --border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%; --ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%; --chart-1: 220 70% 50%;
--chart-2: 160 60% 45%; --chart-2: 160 60% 45%;
--chart-3: 30 80% 55%; --chart-3: 30 80% 55%;
--chart-4: 280 65% 60%; --chart-4: 280 65% 60%;
--chart-5: 340 75% 55%; --chart-5: 340 75% 55%;
--sidebar-background: var(--background); --sidebar-background: var(--background);
--sidebar-foreground: var(--foreground); --sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary); --sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground); --sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--accent); --sidebar-accent: var(--accent);
--sidebar-accent-foreground: var(--accent-foreground); --sidebar-accent-foreground: var(--accent-foreground);
--sidebar-border: var(--border); --sidebar-border: var(--border);
--sidebar-ring: var(--ring); --sidebar-ring: var(--ring);
} }
/* styles.css */ /* styles.css */
.CollapsibleContent { .CollapsibleContent {
overflow: hidden; overflow: hidden;
} }
.CollapsibleContent[data-state='open'] { .CollapsibleContent[data-state="open"] {
animation: slideDown 300ms ease-out; animation: slideDown 300ms ease-out;
} }
.CollapsibleContent[data-state='closed'] { .CollapsibleContent[data-state="closed"] {
animation: slideUp 300ms ease-out; animation: slideUp 300ms ease-out;
} }
@keyframes slideDown { @keyframes slideDown {
from { from {
height: 0; height: 0;
} }
to { to {
height: var(--radix-collapsible-content-height); height: var(--radix-collapsible-content-height);
} }
} }
@keyframes slideUp { @keyframes slideUp {
from { from {
height: var(--radix-collapsible-content-height); height: var(--radix-collapsible-content-height);
} }
to { to {
height: 0; height: 0;
} }
} }
/* Prevent focus zoom on mobile devices */ /* Prevent focus zoom on mobile devices */
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
input, input,
select, select,
textarea { textarea {
font-size: 16px !important; font-size: 16px !important;
} }
} }
} }
@layer utilities { @layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar { .no-scrollbar::-webkit-scrollbar {
display: none; display: none;
} }
/* Hide scrollbar for IE, Edge and Firefox */ /* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar { .no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */
} }
.faded-bottom { .faded-bottom {
@apply after:pointer-events-none after:absolute after:bottom-0 after:left-0 after:hidden after:h-32 after:w-full after:bg-[linear-gradient(180deg,_transparent_10%,_hsl(var(--background))_70%)] after:md:block; @apply after:pointer-events-none after:absolute after:bottom-0 after:left-0 after:hidden after:h-32 after:w-full after:bg-[linear-gradient(180deg,_transparent_10%,_hsl(var(--background))_70%)] after:md:block;
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: hsl(var(--border)) transparent; scrollbar-color: hsl(var(--border)) transparent;
} }
html { html {
@apply overflow-x-hidden; @apply overflow-x-hidden;
} }
body { body {
@apply min-h-svh w-full bg-background text-foreground; @apply min-h-svh w-full bg-background text-foreground;
} }
} }
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }

View File

@ -12,13 +12,13 @@ export const registerUser = async (userData: {
`${API_BASE_URL}/user/register`, `${API_BASE_URL}/user/register`,
{ {
...userData, ...userData,
shop_id: 0 shop_id: 0,
}, },
{ {
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json",
} },
} },
); );
return response.data; return response.data;
}; };

View File

@ -4,7 +4,7 @@ import { AxiosError } from "axios";
import { import {
QueryCache, QueryCache,
QueryClient, QueryClient,
QueryClientProvider QueryClientProvider,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router"; import { RouterProvider, createRouter } from "@tanstack/react-router";
import { useAuthStore } from "@/stores/authStore"; import { useAuthStore } from "@/stores/authStore";
@ -38,7 +38,7 @@ const queryClient = new QueryClient({
); );
}, },
refetchOnWindowFocus: import.meta.env.PROD, refetchOnWindowFocus: import.meta.env.PROD,
staleTime: 10 * 1000 // 10s staleTime: 10 * 1000, // 10s
}, },
mutations: { mutations: {
onError: (error) => { onError: (error) => {
@ -48,12 +48,12 @@ const queryClient = new QueryClient({
if (error.response?.status === 304) { if (error.response?.status === 304) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Content not modified!" title: "Content not modified!",
}); });
} }
} }
} },
} },
}, },
queryCache: new QueryCache({ queryCache: new QueryCache({
onError: (error) => { onError: (error) => {
@ -61,7 +61,7 @@ const queryClient = new QueryClient({
if (error.response?.status === 401) { if (error.response?.status === 401) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Session expired!" title: "Session expired!",
}); });
useAuthStore.getState().auth.reset(); useAuthStore.getState().auth.reset();
const redirect = `${router.history.location.href}`; const redirect = `${router.history.location.href}`;
@ -70,7 +70,7 @@ const queryClient = new QueryClient({
if (error.response?.status === 500) { if (error.response?.status === 500) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Internal Server Error!" title: "Internal Server Error!",
}); });
router.navigate({ to: "/500" }); router.navigate({ to: "/500" });
} }
@ -78,8 +78,8 @@ const queryClient = new QueryClient({
// router.navigate("/forbidden", { replace: true }); // router.navigate("/forbidden", { replace: true });
} }
} }
} },
}) }),
}); });
// Create a new router instance // Create a new router instance
@ -87,7 +87,7 @@ const router = createRouter({
routeTree, routeTree,
context: { queryClient }, context: { queryClient },
defaultPreload: "intent", defaultPreload: "intent",
defaultPreloadStaleTime: 0 defaultPreloadStaleTime: 0,
}); });
// Register the router instance for type safety // Register the router instance for type safety
@ -110,6 +110,6 @@ if (!rootElement.innerHTML) {
</FontProvider> </FontProvider>
</ThemeProvider> </ThemeProvider>
</QueryClientProvider> </QueryClientProvider>
</StrictMode> </StrictMode>,
); );
} }

View File

@ -1,4 +1,4 @@
import {AdminAppName} from "@/config/manifest" import { AdminAppName } from "@/config/manifest";
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
@ -17,7 +17,8 @@ export default function AuthLayout({ children }: Props) {
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
className="mr-2 h-6 w-6"> className="mr-2 h-6 w-6"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" /> <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg> </svg>
<h1 className="text-xl font-medium">{AdminAppName}</h1> <h1 className="text-xl font-medium">{AdminAppName}</h1>

View File

@ -10,7 +10,7 @@ import {
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -20,7 +20,7 @@ const formSchema = z.object({
email: z email: z
.string() .string()
.min(1, { message: "Please enter your email" }) .min(1, { message: "Please enter your email" })
.email({ message: "Invalid email address" }) .email({ message: "Invalid email address" }),
}); });
export function ForgotForm({ className, ...props }: ForgotFormProps) { export function ForgotForm({ className, ...props }: ForgotFormProps) {
@ -28,7 +28,7 @@ export function ForgotForm({ className, ...props }: ForgotFormProps) {
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { email: "" } defaultValues: { email: "" },
}); });
function onSubmit(data: z.infer<typeof formSchema>) { function onSubmit(data: z.infer<typeof formSchema>) {

Some files were not shown because too many files have changed in this diff Show More