diff --git a/frontend/biome.json.old b/frontend/biome.json.old
new file mode 100644
index 0000000..2eb0751
--- /dev/null
+++ b/frontend/biome.json.old
@@ -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"
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 5fc70f6..855e693 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,17 +1,17 @@
{
"name": "frontend",
"private": true,
- "version": "0.0.0",
+ "version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
- "format:check": "prettier --check .",
- "format": "prettier --write .",
"knip": "knip",
- "generate-client": "openapi-ts"
+ "generate-client": "openapi-ts",
+ "format:write": "biome format ./src/ --write",
+ "format:check": "biome check ./src/"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
@@ -62,6 +62,7 @@
"zustand": "^5.0.3"
},
"devDependencies": {
+ "@biomejs/biome": "^1.9.4",
"@eslint/js": "^9.16.0",
"@faker-js/faker": "^9.3.0",
"@hey-api/client-axios": "^0.6.2",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 54ba0f2..089e609 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -147,6 +147,9 @@ importers:
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))
devDependencies:
+ '@biomejs/biome':
+ specifier: ^1.9.4
+ version: 1.9.4
'@eslint/js':
specifier: ^9.16.0
version: 9.24.0
@@ -365,6 +368,59 @@ packages:
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
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':
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
@@ -3379,6 +3435,41 @@ snapshots:
'@babel/helper-string-parser': 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': {}
'@esbuild/aix-ppc64@0.25.2':
diff --git a/frontend/src/api/api-definition.ts b/frontend/src/api/api-definition.ts
index cb04c44..4a9c65f 100644
--- a/frontend/src/api/api-definition.ts
+++ b/frontend/src/api/api-definition.ts
@@ -2,7 +2,7 @@ import {
UserPublic,
UserRegister,
UserUpdate,
- ShopLoginAccessTokenData
+ ShopLoginAccessTokenData,
} from "@/client";
import { Shop } from "./mock/models";
diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts
index a1b8f7b..ff1e89f 100644
--- a/frontend/src/api/api.ts
+++ b/frontend/src/api/api.ts
@@ -3,10 +3,19 @@ import { MockAuthAPI } from "./mock/auth-mock-api";
import { AuthAPI, ShopAPI } from "./api-definition";
import { MockShopAPI } from "./mock/shop-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 =
import.meta.env.VITE_USE_MOCK_API === "true" ? MockAuthAPI : RealAuthAPI;
export const shopAPI: ShopAPI = MockShopAPI;
-export const productsAPI = MockProductAPI;
\ No newline at end of file
+export const productsAPI = MockProductAPI;
+
+export const usersAPI = MockUserAPI;
+
+export const couponAPI = MockCouponAPI;
+
+export const purchaseAPI = MockPurchaseAPI;
diff --git a/frontend/src/api/mock/auth-mock-api.ts b/frontend/src/api/mock/auth-mock-api.ts
index 7eaef29..1211b6d 100644
--- a/frontend/src/api/mock/auth-mock-api.ts
+++ b/frontend/src/api/mock/auth-mock-api.ts
@@ -5,7 +5,6 @@ import { extractSubFromJWT, generateFakeJWT } from "@/utils/jwt";
import { mockDB } from "./db";
import { MockUser, Shop } from "./models";
-
const db = mockDB;
export const MockAuthAPI: AuthAPI = {
@@ -23,7 +22,7 @@ export const MockAuthAPI: AuthAPI = {
...user,
first_name: user.first_name ?? null,
last_name: user.last_name ?? null,
- profile_picture: user.profile_picture ?? null
+ profile_picture: user.profile_picture ?? null,
};
return publicUser;
},
@@ -37,8 +36,8 @@ export const MockAuthAPI: AuthAPI = {
const newUser: MockUser = {
id: userId,
uuid: userUUID,
- user_role: "customer",
- status: "customer",
+ user_role: "owner",
+ status: "owner",
shop_id: null,
username: data.username,
email: data.email,
@@ -49,7 +48,7 @@ export const MockAuthAPI: AuthAPI = {
profile_picture: undefined,
created_at: now,
updated_at: now,
- last_login: now
+ last_login: now,
};
const newShop: Shop = {
@@ -70,16 +69,23 @@ export const MockAuthAPI: AuthAPI = {
city: "",
state: null,
postal_code: "",
- country: ""
- }
+ country: "",
+ },
};
- await mockDB.transaction('rw', mockDB.users, mockDB.shops, 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);
- });
+ await mockDB.transaction(
+ "rw",
+ mockDB.users,
+ mockDB.shops,
+ 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) {
@@ -115,7 +121,7 @@ export const MockAuthAPI: AuthAPI = {
phone_number: data.phone_number ?? "",
username: data.username ?? "",
first_name: data.first_name ?? undefined,
- last_name: data.last_name ?? undefined
+ last_name: data.last_name ?? undefined,
});
- }
+ },
};
diff --git a/frontend/src/api/mock/coupon-mock-api.ts b/frontend/src/api/mock/coupon-mock-api.ts
new file mode 100644
index 0000000..7fdb931
--- /dev/null
+++ b/frontend/src/api/mock/coupon-mock-api.ts
@@ -0,0 +1,45 @@
+import { mockDB } from "./db";
+import { Coupon } from "./models";
+
+export const MockCouponAPI = {
+ async getAllCoupons(): Promise {
+ return mockDB.coupons.toArray();
+ },
+
+ async getCouponById(id: number): Promise {
+ return (await mockDB.coupons.get(id)) ?? null;
+ },
+
+ async createCoupon(couponData: Omit): Promise {
+ const id = Date.now();
+
+ const newCoupon: Coupon = {
+ ...couponData,
+ id,
+ };
+
+ await mockDB.coupons.add(newCoupon);
+ return newCoupon;
+ },
+
+ async updateCoupon(
+ id: number,
+ data: Partial>,
+ ): Promise {
+ 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 {
+ await mockDB.coupons.delete(id);
+ return true;
+ },
+};
diff --git a/frontend/src/api/mock/db.ts b/frontend/src/api/mock/db.ts
index 78c50fd..2d83939 100644
--- a/frontend/src/api/mock/db.ts
+++ b/frontend/src/api/mock/db.ts
@@ -1,5 +1,6 @@
import Dexie, { Table } from "dexie";
import {
+ Coupon,
MockUser,
MockUserPreferences,
MockUserStatistics,
@@ -8,7 +9,9 @@ import {
ProductCategoryJunction,
ProductImage,
ProductVariant,
- Shop
+ Purchase,
+ PurchaseEntry,
+ Shop,
} from "./models";
class MockDB extends Dexie {
@@ -21,6 +24,9 @@ class MockDB extends Dexie {
product_images!: Table;
product_categories!: Table;
product_category_junctions!: Table;
+ coupons!: Table;
+ purchases!: Table;
+ purchase_entries!: Table;
constructor() {
super("MockDB");
@@ -33,7 +39,10 @@ class MockDB extends Dexie {
product_variants: "++id,product_id",
product_images: "++id,product_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",
});
}
}
diff --git a/frontend/src/api/mock/models.ts b/frontend/src/api/mock/models.ts
index 5540342..4351033 100644
--- a/frontend/src/api/mock/models.ts
+++ b/frontend/src/api/mock/models.ts
@@ -81,10 +81,23 @@ export interface ProductImage {
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 {
id: number;
product_id: number;
- index: number; // used for ordering
+ index: number;
name: string;
price: number;
comment?: string;
@@ -92,16 +105,36 @@ export interface ProductVariant {
updated_at: string;
}
-export interface ProductCreate {
+export interface Coupon {
+ id: number;
name: string;
- description: string;
- price: number;
- stock_quantity: number;
- image_data?: string | undefined;
+ text: string;
+ valid_due: string;
+ discount_amount: number;
}
-export interface ProductWithDetails extends Product {
- images: ProductImage[];
- variants: ProductVariant[];
- categories: ProductCategory[];
+export interface Purchase {
+ id: number;
+ user_id: number;
+ 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;
}
diff --git a/frontend/src/api/mock/products-mock-api.ts b/frontend/src/api/mock/products-mock-api.ts
index bf8e7de..0de9dd8 100644
--- a/frontend/src/api/mock/products-mock-api.ts
+++ b/frontend/src/api/mock/products-mock-api.ts
@@ -28,20 +28,20 @@ export const MockProductAPI = {
.toArray();
const rawCategories = await Promise.all(
categoryLinks.map((link) =>
- mockDB.product_categories.get(link.category_id)
- )
+ mockDB.product_categories.get(link.category_id),
+ ),
);
const categories = rawCategories.filter(
- (c): c is ProductCategory => c !== undefined
+ (c): c is ProductCategory => c !== undefined,
);
return {
...product,
images,
variants,
- categories
+ categories,
};
- })
+ }),
);
return detailedProducts;
@@ -66,18 +66,18 @@ export const MockProductAPI = {
const rawCategories = await Promise.all(
categoryLinks.map((link) =>
- mockDB.product_categories.get(link.category_id)
- )
+ mockDB.product_categories.get(link.category_id),
+ ),
);
const categories = rawCategories.filter(
- (c): c is ProductCategory => c !== undefined
+ (c): c is ProductCategory => c !== undefined,
);
return {
...product,
images,
variants,
- categories
+ categories,
};
},
@@ -93,7 +93,7 @@ export const MockProductAPI = {
...productData,
shop_id: user.id,
created_at: now,
- updated_at: now
+ updated_at: now,
});
if ("image_data" in productData && productData.image_data) {
@@ -103,7 +103,7 @@ export const MockProductAPI = {
product_id: productId,
image_id: image_id,
image_url: productData.image_data,
- alt_text: `${productData.name} image`
+ alt_text: `${productData.name} image`,
});
}
@@ -117,7 +117,7 @@ export const MockProductAPI = {
const updatedProduct = {
...product,
...data,
- updated_at: new Date().toISOString()
+ updated_at: new Date().toISOString(),
};
await mockDB.products.put(updatedProduct);
@@ -130,7 +130,7 @@ export const MockProductAPI = {
await mockDB.product_images.put({
...existingImage,
image_url: data.image_data,
- alt_text: `${data.name ?? product.name} image`
+ alt_text: `${data.name ?? product.name} image`,
});
} else {
const image_id = Date.now();
@@ -139,7 +139,7 @@ export const MockProductAPI = {
product_id: productId,
image_id: image_id,
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();
await mockDB.products.delete(productId);
return true;
- }
+ },
};
diff --git a/frontend/src/api/mock/purchase-mock-api.ts b/frontend/src/api/mock/purchase-mock-api.ts
new file mode 100644
index 0000000..604e2a5
--- /dev/null
+++ b/frontend/src/api/mock/purchase-mock-api.ts
@@ -0,0 +1,85 @@
+import { mockDB } from "./db";
+import {
+ Purchase,
+ PurchaseWithDetails,
+ PurchaseEntry,
+ PurchaseEntryWithProduct,
+} from "./models";
+
+export const MockPurchaseAPI = {
+ async getAllPurchases(): Promise {
+ return mockDB.purchases.toArray();
+ },
+
+ async getPurchaseById(purchaseId: number): Promise {
+ return (await mockDB.purchases.get(purchaseId)) ?? null;
+ },
+
+ async getPurchaseWithDetails(
+ purchaseId: number,
+ ): Promise {
+ 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 {
+ return mockDB.purchases.where("user_id").equals(userId).toArray();
+ },
+
+ async createPurchase(
+ purchase: Omit,
+ entries: Omit[],
+ ): Promise {
+ 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 {
+ await mockDB.purchase_entries
+ .where("purchase_id")
+ .equals(purchaseId)
+ .delete();
+ await mockDB.purchases.delete(purchaseId);
+ return true;
+ },
+};
diff --git a/frontend/src/api/mock/shop-mock-api.ts b/frontend/src/api/mock/shop-mock-api.ts
index e6ff7fe..99667cb 100644
--- a/frontend/src/api/mock/shop-mock-api.ts
+++ b/frontend/src/api/mock/shop-mock-api.ts
@@ -35,7 +35,7 @@ export const MockShopAPI: ShopAPI = {
console.log("Saving data ->", data);
await db.shops.update(user.id, {
...data,
- updated_at: new Date().toISOString()
+ updated_at: new Date().toISOString(),
});
- }
+ },
};
diff --git a/frontend/src/api/mock/user-mock-api.ts b/frontend/src/api/mock/user-mock-api.ts
new file mode 100644
index 0000000..0b70275
--- /dev/null
+++ b/frontend/src/api/mock/user-mock-api.ts
@@ -0,0 +1,60 @@
+import { mockDB } from "./db";
+import { MockUser } from "./models";
+import { getCurrentUserDirect } from "./utils/currentUser";
+
+export const MockUserAPI = {
+ async getAllUsers(): Promise {
+ return mockDB.users.toArray();
+ },
+
+ async getUserById(userId: number): Promise {
+ return (await mockDB.users.get(userId)) ?? null;
+ },
+
+ async createUser(
+ userData: Omit,
+ ): Promise {
+ 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>,
+ ): Promise {
+ 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 {
+ 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 {
+ const user = await getCurrentUserDirect();
+ if (!user?.id) return null;
+ return (await mockDB.users.get(user.id)) ?? null;
+ },
+};
diff --git a/frontend/src/api/mock/utils/currentUser.ts b/frontend/src/api/mock/utils/currentUser.ts
index 1baa0bc..0939b0d 100644
--- a/frontend/src/api/mock/utils/currentUser.ts
+++ b/frontend/src/api/mock/utils/currentUser.ts
@@ -10,4 +10,4 @@ export async function getCurrentUserDirect() {
const user = await mockDB.users.where("uuid").equals(uuid).first();
return user ?? null;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/api/real/auth-real-api.ts b/frontend/src/api/real/auth-real-api.ts
index 443506e..86591c5 100644
--- a/frontend/src/api/real/auth-real-api.ts
+++ b/frontend/src/api/real/auth-real-api.ts
@@ -10,10 +10,10 @@ export const RealAuthAPI: AuthAPI = {
},
async loginUser(data) {
return LoginService.dashboardLoginAccessToken({
- formData: data.formData
+ formData: data.formData,
});
},
async updateUser(data) {
await UserService.userUpdateUser({ requestBody: data });
- }
+ },
};
diff --git a/frontend/src/client/core/ApiError.ts b/frontend/src/client/core/ApiError.ts
index 36675d2..b5a0214 100644
--- a/frontend/src/client/core/ApiError.ts
+++ b/frontend/src/client/core/ApiError.ts
@@ -1,5 +1,5 @@
-import type { ApiRequestOptions } from './ApiRequestOptions';
-import type { ApiResult } from './ApiResult';
+import type { ApiRequestOptions } from "./ApiRequestOptions";
+import type { ApiResult } from "./ApiResult";
export class ApiError extends Error {
public readonly url: string;
@@ -8,14 +8,18 @@ export class ApiError extends Error {
public readonly body: unknown;
public readonly request: ApiRequestOptions;
- constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
+ constructor(
+ request: ApiRequestOptions,
+ response: ApiResult,
+ message: string,
+ ) {
super(message);
- this.name = 'ApiError';
+ this.name = "ApiError";
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/client/core/ApiRequestOptions.ts b/frontend/src/client/core/ApiRequestOptions.ts
index 939a0aa..89951fd 100644
--- a/frontend/src/client/core/ApiRequestOptions.ts
+++ b/frontend/src/client/core/ApiRequestOptions.ts
@@ -6,16 +6,16 @@ export type ApiRequestOptions = {
readonly headers?: Record;
readonly mediaType?: string;
readonly method:
- | 'DELETE'
- | 'GET'
- | 'HEAD'
- | 'OPTIONS'
- | 'PATCH'
- | 'POST'
- | 'PUT';
+ | "DELETE"
+ | "GET"
+ | "HEAD"
+ | "OPTIONS"
+ | "PATCH"
+ | "POST"
+ | "PUT";
readonly path?: Record;
readonly query?: Record;
readonly responseHeader?: string;
readonly responseTransformer?: (data: unknown) => Promise;
readonly url: string;
-};
\ No newline at end of file
+};
diff --git a/frontend/src/client/core/ApiResult.ts b/frontend/src/client/core/ApiResult.ts
index 4c58e39..84b9f9d 100644
--- a/frontend/src/client/core/ApiResult.ts
+++ b/frontend/src/client/core/ApiResult.ts
@@ -4,4 +4,4 @@ export type ApiResult = {
readonly status: number;
readonly statusText: string;
readonly url: string;
-};
\ No newline at end of file
+};
diff --git a/frontend/src/client/core/CancelablePromise.ts b/frontend/src/client/core/CancelablePromise.ts
index ccc082e..4d9f819 100644
--- a/frontend/src/client/core/CancelablePromise.ts
+++ b/frontend/src/client/core/CancelablePromise.ts
@@ -1,7 +1,7 @@
export class CancelError extends Error {
constructor(message: string) {
super(message);
- this.name = 'CancelError';
+ this.name = "CancelError";
}
public get isCancelled(): boolean {
@@ -30,8 +30,8 @@ export class CancelablePromise implements Promise {
executor: (
resolve: (value: T | PromiseLike) => void,
reject: (reason?: unknown) => void,
- onCancel: OnCancel
- ) => void
+ onCancel: OnCancel,
+ ) => void,
) {
this._isResolved = false;
this._isRejected = false;
@@ -64,15 +64,15 @@ export class CancelablePromise implements Promise {
this.cancelHandlers.push(cancelHandler);
};
- Object.defineProperty(onCancel, 'isResolved', {
+ Object.defineProperty(onCancel, "isResolved", {
get: (): boolean => this._isResolved,
});
- Object.defineProperty(onCancel, 'isRejected', {
+ Object.defineProperty(onCancel, "isRejected", {
get: (): boolean => this._isRejected,
});
- Object.defineProperty(onCancel, 'isCancelled', {
+ Object.defineProperty(onCancel, "isCancelled", {
get: (): boolean => this._isCancelled,
});
@@ -86,13 +86,13 @@ export class CancelablePromise implements Promise {
public then(
onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null,
- onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null
+ onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null,
): Promise {
return this.promise.then(onFulfilled, onRejected);
}
public catch(
- onRejected?: ((reason: unknown) => TResult | PromiseLike) | null
+ onRejected?: ((reason: unknown) => TResult | PromiseLike) | null,
): Promise {
return this.promise.catch(onRejected);
}
@@ -112,15 +112,15 @@ export class CancelablePromise implements Promise {
cancelHandler();
}
} catch (error) {
- console.warn('Cancellation threw an error', error);
+ console.warn("Cancellation threw an error", error);
return;
}
}
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 {
return this._isCancelled;
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/client/core/OpenAPI.ts b/frontend/src/client/core/OpenAPI.ts
index 8e48f3c..2505a43 100644
--- a/frontend/src/client/core/OpenAPI.ts
+++ b/frontend/src/client/core/OpenAPI.ts
@@ -1,32 +1,32 @@
-import type { AxiosRequestConfig, AxiosResponse } from 'axios';
-import type { ApiRequestOptions } from './ApiRequestOptions';
+import type { AxiosRequestConfig, AxiosResponse } from "axios";
+import type { ApiRequestOptions } from "./ApiRequestOptions";
type Headers = Record;
type Middleware = (value: T) => T | Promise;
type Resolver = (options: ApiRequestOptions) => Promise;
export class Interceptors {
- _fns: Middleware[];
+ _fns: Middleware[];
- constructor() {
- this._fns = [];
- }
+ constructor() {
+ this._fns = [];
+ }
- eject(fn: Middleware): void {
- const index = this._fns.indexOf(fn);
- if (index !== -1) {
- this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
- }
- }
+ eject(fn: Middleware): void {
+ const index = this._fns.indexOf(fn);
+ if (index !== -1) {
+ this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
+ }
+ }
- use(fn: Middleware): void {
- this._fns = [...this._fns, fn];
- }
+ use(fn: Middleware): void {
+ this._fns = [...this._fns, fn];
+ }
}
export type OpenAPIConfig = {
BASE: string;
- CREDENTIALS: 'include' | 'omit' | 'same-origin';
+ CREDENTIALS: "include" | "omit" | "same-origin";
ENCODE_PATH?: ((path: string) => string) | undefined;
HEADERS?: Headers | Resolver | undefined;
PASSWORD?: string | Resolver | undefined;
@@ -41,17 +41,17 @@ export type OpenAPIConfig = {
};
export const OpenAPI: OpenAPIConfig = {
- BASE: '',
- CREDENTIALS: 'include',
+ BASE: "",
+ CREDENTIALS: "include",
ENCODE_PATH: undefined,
HEADERS: undefined,
PASSWORD: undefined,
TOKEN: undefined,
USERNAME: undefined,
- VERSION: '0.0.1',
+ VERSION: "0.0.1",
WITH_CREDENTIALS: false,
interceptors: {
request: new Interceptors(),
response: new Interceptors(),
},
-};
\ No newline at end of file
+};
diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts
index ecc2e39..625bf24 100644
--- a/frontend/src/client/core/request.ts
+++ b/frontend/src/client/core/request.ts
@@ -1,19 +1,24 @@
-import axios from 'axios';
-import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
+import axios from "axios";
+import type {
+ AxiosError,
+ AxiosRequestConfig,
+ AxiosResponse,
+ AxiosInstance,
+} from "axios";
-import { ApiError } from './ApiError';
-import type { ApiRequestOptions } from './ApiRequestOptions';
-import type { ApiResult } from './ApiResult';
-import { CancelablePromise } from './CancelablePromise';
-import type { OnCancel } from './CancelablePromise';
-import type { OpenAPIConfig } from './OpenAPI';
+import { ApiError } from "./ApiError";
+import type { ApiRequestOptions } from "./ApiRequestOptions";
+import type { ApiResult } from "./ApiResult";
+import { CancelablePromise } from "./CancelablePromise";
+import type { OnCancel } from "./CancelablePromise";
+import type { OpenAPIConfig } from "./OpenAPI";
export const isString = (value: unknown): value is string => {
- return typeof value === 'string';
+ return typeof value === "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 => {
@@ -33,7 +38,7 @@ export const base64 = (str: string): string => {
return btoa(str);
} catch (err) {
// @ts-ignore
- return Buffer.from(str).toString('base64');
+ return Buffer.from(str).toString("base64");
}
};
@@ -52,8 +57,8 @@ export const getQueryString = (params: Record): string => {
if (value instanceof Date) {
append(key, value.toISOString());
} else if (Array.isArray(value)) {
- value.forEach(v => encodePair(key, v));
- } else if (typeof value === 'object') {
+ value.forEach((v) => encodePair(key, v));
+ } else if (typeof value === "object") {
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
} else {
append(key, value);
@@ -62,14 +67,14 @@ export const getQueryString = (params: Record): string => {
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 encoder = config.ENCODE_PATH || encodeURI;
const path = options.url
- .replace('{api-version}', config.VERSION)
+ .replace("{api-version}", config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(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;
};
-export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
+export const getFormData = (
+ options: ApiRequestOptions,
+): FormData | undefined => {
if (options.formData) {
const formData = new FormData();
@@ -97,7 +104,7 @@ export const getFormData = (options: ApiRequestOptions): FormData | undefined =>
.filter(([, value]) => value !== undefined && value !== null)
.forEach(([key, value]) => {
if (Array.isArray(value)) {
- value.forEach(v => process(key, v));
+ value.forEach((v) => process(key, v));
} else {
process(key, value);
}
@@ -110,14 +117,20 @@ export const getFormData = (options: ApiRequestOptions): FormData | undefined =>
type Resolver = (options: ApiRequestOptions) => Promise;
-export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => {
- if (typeof resolver === 'function') {
+export const resolve = async (
+ options: ApiRequestOptions,
+ resolver?: T | Resolver,
+): Promise => {
+ if (typeof resolver === "function") {
return (resolver as Resolver)(options);
}
return resolver;
};
-export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise> => {
+export const getHeaders = async (
+ config: OpenAPIConfig,
+ options: ApiRequestOptions,
+): Promise> => {
const [token, username, password, additionalHeaders] = await Promise.all([
// @ts-ignore
resolve(options, config.TOKEN),
@@ -130,38 +143,41 @@ export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOp
]);
const headers = Object.entries({
- Accept: 'application/json',
+ Accept: "application/json",
...additionalHeaders,
...options.headers,
})
- .filter(([, value]) => value !== undefined && value !== null)
- .reduce((headers, [key, value]) => ({
- ...headers,
- [key]: String(value),
- }), {} as Record);
+ .filter(([, value]) => value !== undefined && value !== null)
+ .reduce(
+ (headers, [key, value]) => ({
+ ...headers,
+ [key]: String(value),
+ }),
+ {} as Record,
+ );
if (isStringWithValue(token)) {
- headers['Authorization'] = `Bearer ${token}`;
+ headers["Authorization"] = `Bearer ${token}`;
}
if (isStringWithValue(username) && isStringWithValue(password)) {
const credentials = base64(`${username}:${password}`);
- headers['Authorization'] = `Basic ${credentials}`;
+ headers["Authorization"] = `Basic ${credentials}`;
}
if (options.body !== undefined) {
if (options.mediaType) {
- headers['Content-Type'] = options.mediaType;
+ headers["Content-Type"] = options.mediaType;
} 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)) {
- headers['Content-Type'] = 'text/plain';
+ headers["Content-Type"] = "text/plain";
} else if (!isFormData(options.body)) {
- headers['Content-Type'] = 'application/json';
+ headers["Content-Type"] = "application/json";
}
} else if (options.formData !== undefined) {
if (options.mediaType) {
- headers['Content-Type'] = options.mediaType;
+ headers["Content-Type"] = options.mediaType;
}
}
@@ -183,7 +199,7 @@ export const sendRequest = async (
formData: FormData | undefined,
headers: Record,
onCancel: OnCancel,
- axiosClient: AxiosInstance
+ axiosClient: AxiosInstance,
): Promise> => {
const controller = new AbortController();
@@ -213,7 +229,10 @@ export const sendRequest = async (
}
};
-export const getResponseHeader = (response: AxiosResponse, responseHeader?: string): string | undefined => {
+export const getResponseHeader = (
+ response: AxiosResponse,
+ responseHeader?: string,
+): string | undefined => {
if (responseHeader) {
const content = response.headers[responseHeader];
if (isString(content)) {
@@ -230,50 +249,53 @@ export const getResponseBody = (response: AxiosResponse): unknown => {
return undefined;
};
-export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
+export const catchErrorCodes = (
+ options: ApiRequestOptions,
+ result: ApiResult,
+): void => {
const errors: Record = {
- 400: 'Bad Request',
- 401: 'Unauthorized',
- 402: 'Payment Required',
- 403: 'Forbidden',
- 404: 'Not Found',
- 405: 'Method Not Allowed',
- 406: 'Not Acceptable',
- 407: 'Proxy Authentication Required',
- 408: 'Request Timeout',
- 409: 'Conflict',
- 410: 'Gone',
- 411: 'Length Required',
- 412: 'Precondition Failed',
- 413: 'Payload Too Large',
- 414: 'URI Too Long',
- 415: 'Unsupported Media Type',
- 416: 'Range Not Satisfiable',
- 417: 'Expectation Failed',
- 418: 'Im a teapot',
- 421: 'Misdirected Request',
- 422: 'Unprocessable Content',
- 423: 'Locked',
- 424: 'Failed Dependency',
- 425: 'Too Early',
- 426: 'Upgrade Required',
- 428: 'Precondition Required',
- 429: 'Too Many Requests',
- 431: 'Request Header Fields Too Large',
- 451: 'Unavailable For Legal Reasons',
- 500: 'Internal Server Error',
- 501: 'Not Implemented',
- 502: 'Bad Gateway',
- 503: 'Service Unavailable',
- 504: 'Gateway Timeout',
- 505: 'HTTP Version Not Supported',
- 506: 'Variant Also Negotiates',
- 507: 'Insufficient Storage',
- 508: 'Loop Detected',
- 510: 'Not Extended',
- 511: 'Network Authentication Required',
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Timeout",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Payload Too Large",
+ 414: "URI Too Long",
+ 415: "Unsupported Media Type",
+ 416: "Range Not Satisfiable",
+ 417: "Expectation Failed",
+ 418: "Im a teapot",
+ 421: "Misdirected Request",
+ 422: "Unprocessable Content",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 425: "Too Early",
+ 426: "Upgrade Required",
+ 428: "Precondition Required",
+ 429: "Too Many Requests",
+ 431: "Request Header Fields Too Large",
+ 451: "Unavailable For Legal Reasons",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Timeout",
+ 505: "HTTP Version Not Supported",
+ 506: "Variant Also Negotiates",
+ 507: "Insufficient Storage",
+ 508: "Loop Detected",
+ 510: "Not Extended",
+ 511: "Network Authentication Required",
...options.errors,
- }
+ };
const error = errors[result.status];
if (error) {
@@ -281,8 +303,8 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult):
}
if (!result.ok) {
- const errorStatus = result.status ?? 'unknown';
- const errorStatusText = result.statusText ?? 'unknown';
+ const errorStatus = result.status ?? "unknown";
+ const errorStatusText = result.statusText ?? "unknown";
const errorBody = (() => {
try {
return JSON.stringify(result.body, null, 2);
@@ -291,8 +313,10 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult):
}
})();
- throw new ApiError(options, result,
- `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
+ throw new ApiError(
+ options,
+ result,
+ `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`,
);
}
};
@@ -305,7 +329,11 @@ export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult):
* @returns CancelablePromise
* @throws ApiError
*/
-export const request = (config: OpenAPIConfig, options: ApiRequestOptions, axiosClient: AxiosInstance = axios): CancelablePromise => {
+export const request = (
+ config: OpenAPIConfig,
+ options: ApiRequestOptions,
+ axiosClient: AxiosInstance = axios,
+): CancelablePromise => {
return new CancelablePromise(async (resolve, reject, onCancel) => {
try {
const url = getUrl(config, options);
@@ -314,18 +342,30 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions,
const headers = await getHeaders(config, options);
if (!onCancel.isCancelled) {
- let response = await sendRequest(config, options, url, body, formData, headers, onCancel, axiosClient);
+ let response = await sendRequest(
+ config,
+ options,
+ url,
+ body,
+ formData,
+ headers,
+ onCancel,
+ axiosClient,
+ );
for (const fn of config.interceptors.response._fns) {
response = await fn(response);
}
const responseBody = getResponseBody(response);
- const responseHeader = getResponseHeader(response, options.responseHeader);
+ const responseHeader = getResponseHeader(
+ response,
+ options.responseHeader,
+ );
let transformedBody = responseBody;
if (options.responseTransformer && isSuccess(response.status)) {
- transformedBody = await options.responseTransformer(responseBody)
+ transformedBody = await options.responseTransformer(responseBody);
}
const result: ApiResult = {
@@ -344,4 +384,4 @@ export const request = (config: OpenAPIConfig, options: ApiRequestOptions,
reject(error);
}
});
-};
\ No newline at end of file
+};
diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts
index 50a1dd7..0739a12 100644
--- a/frontend/src/client/index.ts
+++ b/frontend/src/client/index.ts
@@ -1,6 +1,6 @@
// This file is auto-generated by @hey-api/openapi-ts
-export { ApiError } from './core/ApiError';
-export { CancelablePromise, CancelError } from './core/CancelablePromise';
-export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
-export * from './sdk.gen';
-export * from './types.gen';
\ No newline at end of file
+export { ApiError } from "./core/ApiError";
+export { CancelablePromise, CancelError } from "./core/CancelablePromise";
+export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI";
+export * from "./sdk.gen";
+export * from "./types.gen";
diff --git a/frontend/src/client/sdk.gen.ts b/frontend/src/client/sdk.gen.ts
index 464264e..3df1758 100644
--- a/frontend/src/client/sdk.gen.ts
+++ b/frontend/src/client/sdk.gen.ts
@@ -1,438 +1,485 @@
// This file is auto-generated by @hey-api/openapi-ts
-import type { CancelablePromise } from './core/CancelablePromise';
-import { OpenAPI } from './core/OpenAPI';
-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 { CancelablePromise } from "./core/CancelablePromise";
+import { OpenAPI } from "./core/OpenAPI";
+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";
export class DashboardService {
- /**
- * Get information about currently logged in user
- * @returns UserPublic Successful Response
- * @throws ApiError
- */
- public static userGetUser(): CancelablePromise {
- return __request(OpenAPI, {
- method: 'GET',
- url: '/user'
- });
- }
-
- /**
- * Update user details
- * @param data The data for the request.
- * @param data.requestBody
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static userUpdateUser(data: UserUpdateUserData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'PUT',
- url: '/user',
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Register new user
- * @param data The data for the request.
- * @param data.requestBody
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static userRegister(data: UserRegisterData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/user',
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Delete user
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static userDeleteUser(): CancelablePromise {
- 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
- * a predefined expiration period.
- *
- * - **username**: User's email
- * - **password**: User's password
- *
- * **Note:** This login is restricted to dashboard access only and cannot be
- * used for tenant accounts access to shops
- * @param data The data for the request.
- * @param data.formData
- * @returns Token Successful Response
- * @throws ApiError
- */
- public static dashboardLoginAccessToken(data: DashboardLoginAccessTokenData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/login/access-token',
- formData: data.formData,
- mediaType: 'application/x-www-form-urlencoded',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Register New Shop
- * @param data The data for the request.
- * @param data.requestBody
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static dashboardRegisterNewShop(data: DashboardRegisterNewShopData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/shop',
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
+ /**
+ * Get information about currently logged in user
+ * @returns UserPublic Successful Response
+ * @throws ApiError
+ */
+ public static userGetUser(): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "GET",
+ url: "/user",
+ });
+ }
+
+ /**
+ * Update user details
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static userUpdateUser(
+ data: UserUpdateUserData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "PUT",
+ url: "/user",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Register new user
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static userRegister(
+ data: UserRegisterData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/user",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Delete user
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static userDeleteUser(): CancelablePromise {
+ 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
+ * a predefined expiration period.
+ *
+ * - **username**: User's email
+ * - **password**: User's password
+ *
+ * **Note:** This login is restricted to dashboard access only and cannot be
+ * used for tenant accounts access to shops
+ * @param data The data for the request.
+ * @param data.formData
+ * @returns Token Successful Response
+ * @throws ApiError
+ */
+ public static dashboardLoginAccessToken(
+ data: DashboardLoginAccessTokenData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/login/access-token",
+ formData: data.formData,
+ mediaType: "application/x-www-form-urlencoded",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Register New Shop
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static dashboardRegisterNewShop(
+ data: DashboardRegisterNewShopData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/shop",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
}
export class LoginService {
- /**
- * 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
- * a predefined expiration period.
- *
- * - **username**: User's email
- * - **password**: User's password
- *
- * **Note:** This login is restricted to dashboard access only and cannot be
- * used for tenant accounts access to shops
- * @param data The data for the request.
- * @param data.formData
- * @returns Token Successful Response
- * @throws ApiError
- */
- public static dashboardLoginAccessToken(data: DashboardLoginAccessTokenData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/login/access-token',
- formData: data.formData,
- 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.
- * @param data.formData
- * @returns Token Successful Response
- * @throws ApiError
- */
- public static shopLoginAccessToken(data: ShopLoginAccessTokenData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/shop/{shop_uuid}/login/access-token',
- formData: data.formData,
- mediaType: 'application/x-www-form-urlencoded',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
+ /**
+ * 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
+ * a predefined expiration period.
+ *
+ * - **username**: User's email
+ * - **password**: User's password
+ *
+ * **Note:** This login is restricted to dashboard access only and cannot be
+ * used for tenant accounts access to shops
+ * @param data The data for the request.
+ * @param data.formData
+ * @returns Token Successful Response
+ * @throws ApiError
+ */
+ public static dashboardLoginAccessToken(
+ data: DashboardLoginAccessTokenData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/login/access-token",
+ formData: data.formData,
+ 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.
+ * @param data.formData
+ * @returns Token Successful Response
+ * @throws ApiError
+ */
+ public static shopLoginAccessToken(
+ data: ShopLoginAccessTokenData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/shop/{shop_uuid}/login/access-token",
+ formData: data.formData,
+ mediaType: "application/x-www-form-urlencoded",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
}
export class ShopService {
- /**
- * Login Access Token
- * OAuth2 compatible token login, get an access token for future requests
- * @param data The data for the request.
- * @param data.formData
- * @returns Token Successful Response
- * @throws ApiError
- */
- public static shopLoginAccessToken(data: ShopLoginAccessTokenData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/shop/{shop_uuid}/login/access-token',
- formData: data.formData,
- mediaType: 'application/x-www-form-urlencoded',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Delete user
- * @param data The data for the request.
- * @param data.shopUuid
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public static shopDeleteUser(data: ShopDeleteUserData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'DELETE',
- url: '/shop/{shop_uuid}/user/delete',
- path: {
- shop_uuid: data.shopUuid
- },
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * User logout
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public static shopLogout(): CancelablePromise {
- 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
- * @throws ApiError
- */
- public static shopRegister(data: ShopRegisterData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/shop/{shop_uuid}/user/register',
- path: {
- shop_uuid: data.shopUuid
- },
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Update user details
- * @param data The data for the request.
- * @param data.requestBody
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public static shopUpdateUser(data: ShopUpdateUserData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'PUT',
- url: '/shop/{shop_uuid}/user/update',
- body: data.requestBody,
- mediaType: 'application/json',
- 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.
+ * @param data.formData
+ * @returns Token Successful Response
+ * @throws ApiError
+ */
+ public static shopLoginAccessToken(
+ data: ShopLoginAccessTokenData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/shop/{shop_uuid}/login/access-token",
+ formData: data.formData,
+ mediaType: "application/x-www-form-urlencoded",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Delete user
+ * @param data The data for the request.
+ * @param data.shopUuid
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+ public static shopDeleteUser(
+ data: ShopDeleteUserData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "DELETE",
+ url: "/shop/{shop_uuid}/user/delete",
+ path: {
+ shop_uuid: data.shopUuid,
+ },
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * User logout
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+ public static shopLogout(): CancelablePromise {
+ 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
+ * @throws ApiError
+ */
+ public static shopRegister(
+ data: ShopRegisterData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/shop/{shop_uuid}/user/register",
+ path: {
+ shop_uuid: data.shopUuid,
+ },
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Update user details
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+ public static shopUpdateUser(
+ data: ShopUpdateUserData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "PUT",
+ url: "/shop/{shop_uuid}/user/update",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
}
export class UserService {
- /**
- * Get information about currently logged in user
- * @returns UserPublic Successful Response
- * @throws ApiError
- */
- public static userGetUser(): CancelablePromise {
- return __request(OpenAPI, {
- method: 'GET',
- url: '/user'
- });
- }
-
- /**
- * Update user details
- * @param data The data for the request.
- * @param data.requestBody
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static userUpdateUser(data: UserUpdateUserData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'PUT',
- url: '/user',
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Register new user
- * @param data The data for the request.
- * @param data.requestBody
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static userRegister(data: UserRegisterData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/user',
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Delete user
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static userDeleteUser(): CancelablePromise {
- 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
- */
- public static shopDeleteUser(data: ShopDeleteUserData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'DELETE',
- url: '/shop/{shop_uuid}/user/delete',
- path: {
- shop_uuid: data.shopUuid
- },
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * User logout
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public static shopLogout(): CancelablePromise {
- 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
- * @throws ApiError
- */
- public static shopRegister(data: ShopRegisterData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'POST',
- url: '/shop/{shop_uuid}/user/register',
- path: {
- shop_uuid: data.shopUuid
- },
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
- /**
- * Update user details
- * @param data The data for the request.
- * @param data.requestBody
- * @returns unknown Successful Response
- * @throws ApiError
- */
- public static shopUpdateUser(data: ShopUpdateUserData): CancelablePromise {
- return __request(OpenAPI, {
- method: 'PUT',
- url: '/shop/{shop_uuid}/user/update',
- body: data.requestBody,
- mediaType: 'application/json',
- errors: {
- 422: 'Validation Error'
- }
- });
- }
-
+ /**
+ * Get information about currently logged in user
+ * @returns UserPublic Successful Response
+ * @throws ApiError
+ */
+ public static userGetUser(): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "GET",
+ url: "/user",
+ });
+ }
+
+ /**
+ * Update user details
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static userUpdateUser(
+ data: UserUpdateUserData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "PUT",
+ url: "/user",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Register new user
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static userRegister(
+ data: UserRegisterData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/user",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Delete user
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static userDeleteUser(): CancelablePromise {
+ 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
+ */
+ public static shopDeleteUser(
+ data: ShopDeleteUserData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "DELETE",
+ url: "/shop/{shop_uuid}/user/delete",
+ path: {
+ shop_uuid: data.shopUuid,
+ },
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * User logout
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+ public static shopLogout(): CancelablePromise {
+ 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
+ * @throws ApiError
+ */
+ public static shopRegister(
+ data: ShopRegisterData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "POST",
+ url: "/shop/{shop_uuid}/user/register",
+ path: {
+ shop_uuid: data.shopUuid,
+ },
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
+
+ /**
+ * Update user details
+ * @param data The data for the request.
+ * @param data.requestBody
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+ public static shopUpdateUser(
+ data: ShopUpdateUserData,
+ ): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "PUT",
+ url: "/shop/{shop_uuid}/user/update",
+ body: data.requestBody,
+ mediaType: "application/json",
+ errors: {
+ 422: "Validation Error",
+ },
+ });
+ }
}
export class UtilsService {
- /**
- * Health Check
- * Ping the API whether it's alive or not
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static utilsHealthCheck(): CancelablePromise {
- return __request(OpenAPI, {
- method: 'GET',
- url: '/utils/health-check/'
- });
- }
-
- /**
- * Test Db
- * Ping database using select 1 to see if connection works
- * @returns boolean Successful Response
- * @throws ApiError
- */
- public static utilsTestDb(): CancelablePromise {
- return __request(OpenAPI, {
- method: 'GET',
- url: '/utils/test-db/'
- });
- }
-
-}
\ No newline at end of file
+ /**
+ * Health Check
+ * Ping the API whether it's alive or not
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static utilsHealthCheck(): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "GET",
+ url: "/utils/health-check/",
+ });
+ }
+
+ /**
+ * Test Db
+ * Ping database using select 1 to see if connection works
+ * @returns boolean Successful Response
+ * @throws ApiError
+ */
+ public static utilsTestDb(): CancelablePromise {
+ return __request(OpenAPI, {
+ method: "GET",
+ url: "/utils/test-db/",
+ });
+ }
+}
diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts
index 44979a4..b0c68c6 100644
--- a/frontend/src/client/types.gen.ts
+++ b/frontend/src/client/types.gen.ts
@@ -1,142 +1,142 @@
// This file is auto-generated by @hey-api/openapi-ts
export type Body_Dashboard_login_access_token = {
- grant_type?: (string | null);
- username: string;
- password: string;
- scope?: string;
- client_id?: (string | null);
- client_secret?: (string | null);
+ grant_type?: string | null;
+ username: string;
+ password: string;
+ scope?: string;
+ client_id?: string | null;
+ client_secret?: string | null;
};
export type Body_Shop_login_access_token = {
- grant_type?: (string | null);
- username: string;
- password: string;
- scope?: string;
- client_id?: (string | null);
- client_secret?: (string | null);
+ grant_type?: string | null;
+ username: string;
+ password: string;
+ scope?: string;
+ client_id?: string | null;
+ client_secret?: string | null;
};
export type HTTPValidationError = {
- detail?: Array;
+ detail?: Array;
};
export type ShopAddress = {
- street: string;
- city: string;
- state: string;
- postal_code: string;
- country: string;
+ street: string;
+ city: string;
+ state: string;
+ postal_code: string;
+ country: string;
};
export type ShopCreate = {
- name: string;
- description: string;
- currency: string;
- contact_email: string;
- contact_phone_number: string;
- address: ShopAddress;
+ name: string;
+ description: string;
+ currency: string;
+ contact_email: string;
+ contact_phone_number: string;
+ address: ShopAddress;
};
export type Token = {
- access_token: string;
- token_type?: string;
+ access_token: string;
+ token_type?: string;
};
export type UserPublic = {
- uuid: string;
- username: string;
- email: string;
- first_name: (string | null);
- last_name: (string | null);
- phone_number: string;
+ uuid: string;
+ username: string;
+ email: string;
+ first_name: string | null;
+ last_name: string | null;
+ phone_number: string;
};
export type UserRegister = {
- username: string;
- email: string;
- phone_number: string;
- /**
- * Password must conform to this regex:
- * ```
- * ^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$
- * ```
- */
- password: string;
+ username: string;
+ email: string;
+ phone_number: string;
+ /**
+ * Password must conform to this regex:
+ * ```
+ * ^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,128}$
+ * ```
+ */
+ password: string;
};
export type UserUpdate = {
- email: (string | null);
- phone_number: (string | null);
- username: (string | null);
- first_name?: (string | null);
- last_name?: (string | null);
+ email: string | null;
+ phone_number: string | null;
+ username: string | null;
+ first_name?: string | null;
+ last_name?: string | null;
};
export type ValidationError = {
- loc: Array<(string | number)>;
- msg: string;
- type: string;
+ loc: Array;
+ msg: string;
+ type: string;
};
-export type UserGetUserResponse = (UserPublic);
+export type UserGetUserResponse = UserPublic;
export type UserUpdateUserData = {
- requestBody: UserUpdate;
+ requestBody: UserUpdate;
};
-export type UserUpdateUserResponse = (boolean);
+export type UserUpdateUserResponse = boolean;
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 = {
- formData: Body_Dashboard_login_access_token;
+ formData: Body_Dashboard_login_access_token;
};
-export type DashboardLoginAccessTokenResponse = (Token);
+export type DashboardLoginAccessTokenResponse = Token;
export type DashboardRegisterNewShopData = {
- requestBody: ShopCreate;
+ requestBody: ShopCreate;
};
-export type DashboardRegisterNewShopResponse = (boolean);
+export type DashboardRegisterNewShopResponse = boolean;
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 = {
- shopUuid: unknown;
+ shopUuid: unknown;
};
-export type ShopDeleteUserResponse = (unknown);
+export type ShopDeleteUserResponse = unknown;
-export type ShopLogoutResponse = (unknown);
+export type ShopLogoutResponse = unknown;
export type ShopRegisterData = {
- requestBody: UserRegister;
- shopUuid: unknown;
+ requestBody: UserRegister;
+ shopUuid: unknown;
};
-export type ShopRegisterResponse = (unknown);
+export type ShopRegisterResponse = unknown;
export type ShopUpdateUserData = {
- requestBody: {
- [key: string]: unknown;
- };
+ requestBody: {
+ [key: string]: unknown;
+ };
};
-export type ShopUpdateUserResponse = (unknown);
+export type ShopUpdateUserResponse = unknown;
-export type UtilsHealthCheckResponse = (boolean);
+export type UtilsHealthCheckResponse = boolean;
-export type UtilsTestDbResponse = (boolean);
\ No newline at end of file
+export type UtilsTestDbResponse = boolean;
diff --git a/frontend/src/components/command-menu.tsx b/frontend/src/components/command-menu.tsx
index fb0c476..65153dc 100644
--- a/frontend/src/components/command-menu.tsx
+++ b/frontend/src/components/command-menu.tsx
@@ -4,7 +4,7 @@ import {
IconArrowRightDashed,
IconDeviceLaptop,
IconMoon,
- IconSun
+ IconSun,
} from "@tabler/icons-react";
import { useSearch } from "@/context/search-context";
import { useTheme } from "@/context/theme-context";
@@ -15,7 +15,7 @@ import {
CommandInput,
CommandItem,
CommandList,
- CommandSeparator
+ CommandSeparator,
} from "@/components/ui/command";
import { sidebarData } from "./layout/data/sidebar-data";
import { ScrollArea } from "./ui/scroll-area";
@@ -30,7 +30,7 @@ export function CommandMenu() {
setOpen(false);
command();
},
- [setOpen]
+ [setOpen],
);
return (
@@ -49,7 +49,8 @@ export function CommandMenu() {
value={navItem.title}
onSelect={() => {
runCommand(() => navigate({ to: navItem.url }));
- }}>
+ }}
+ >
@@ -63,7 +64,8 @@ export function CommandMenu() {
value={subItem.title}
onSelect={() => {
runCommand(() => navigate({ to: subItem.url }));
- }}>
+ }}
+ >
diff --git a/frontend/src/components/confirm-dialog.tsx b/frontend/src/components/confirm-dialog.tsx
index e72fd12..da531d9 100644
--- a/frontend/src/components/confirm-dialog.tsx
+++ b/frontend/src/components/confirm-dialog.tsx
@@ -6,7 +6,7 @@ import {
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
- AlertDialogTitle
+ AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
@@ -56,7 +56,8 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
+ disabled={disabled || isLoading}
+ >
{confirmText ?? "Continue"}
diff --git a/frontend/src/components/currency-input.tsx b/frontend/src/components/currency-input.tsx
index 30fb3e9..a30e12e 100644
--- a/frontend/src/components/currency-input.tsx
+++ b/frontend/src/components/currency-input.tsx
@@ -153,5 +153,5 @@ const currencies = [
"YER",
"ZAR",
"ZMW",
- "ZWG"
+ "ZWG",
];
diff --git a/frontend/src/components/layout/app-sidebar.tsx b/frontend/src/components/layout/app-sidebar.tsx
index 6187e77..114b83d 100644
--- a/frontend/src/components/layout/app-sidebar.tsx
+++ b/frontend/src/components/layout/app-sidebar.tsx
@@ -2,11 +2,12 @@ import {
Sidebar,
SidebarContent,
SidebarHeader,
- SidebarRail
+ SidebarRail,
} from "@/components/ui/sidebar";
import { NavGroup } from "@/components/layout/nav-group";
import { sidebarData } from "./data/sidebar-data";
import { cn } from "@/lib/utils";
+import { IconCoin } from "@tabler/icons-react";
export function AppSidebar({ ...props }: React.ComponentProps) {
return (
@@ -14,10 +15,11 @@ export function AppSidebar({ ...props }: React.ComponentProps) {
- SwagShop
+ {...props}
+ >
+
@@ -25,9 +27,6 @@ export function AppSidebar({ ...props }: React.ComponentProps) {
))}
- {/*
-
- */}
);
diff --git a/frontend/src/components/layout/data/sidebar-data.ts b/frontend/src/components/layout/data/sidebar-data.ts
index cf906db..6c5097e 100644
--- a/frontend/src/components/layout/data/sidebar-data.ts
+++ b/frontend/src/components/layout/data/sidebar-data.ts
@@ -1,6 +1,7 @@
import {
IconBrowserCheck,
IconBuildingStore,
+ IconClipboardCheckFilled,
IconCoin,
IconForklift,
IconHelp,
@@ -8,14 +9,12 @@ import {
IconNotification,
IconPackage,
IconPalette,
- IconPercentage,
IconSettings,
IconTag,
IconTool,
IconUserCog,
- IconUsers
+ IconUsers,
} from "@tabler/icons-react";
-import { AudioWaveform, Command, GalleryVerticalEnd } from "lucide-react";
import { type SidebarData } from "../types";
export const sidebarData: SidebarData = {
@@ -26,45 +25,45 @@ export const sidebarData: SidebarData = {
{
title: "Dashboard",
url: "/dashboard",
- icon: IconLayoutDashboard
+ icon: IconLayoutDashboard,
},
{
title: "Shop",
url: "/dashboard/shop",
- icon: IconBuildingStore
+ icon: IconBuildingStore,
},
{
title: "Products",
url: "/dashboard/products",
- icon: IconPackage
+ icon: IconPackage,
},
{
title: "Inventory",
url: "/dashboard/tasks",
- icon: IconForklift
+ icon: IconForklift,
},
{
title: "Sales",
icon: IconCoin,
items: [
{
- title: "Discounts",
- url: "/dashboard/sales/discounts",
- icon: IconPercentage
+ title: "Recent sales",
+ url: "/dashboard/sales/recent-sales",
+ icon: IconClipboardCheckFilled,
},
{
title: "Coupons",
url: "/dashboard/sales/coupons",
- icon: IconTag
- }
- ]
+ icon: IconTag,
+ },
+ ],
},
{
title: "Customers",
url: "/dashboard/users",
- icon: IconUsers
- }
- ]
+ icon: IconUsers,
+ },
+ ],
},
{
title: "Other",
@@ -76,36 +75,36 @@ export const sidebarData: SidebarData = {
{
title: "Profile",
url: "/dashboard/settings",
- icon: IconUserCog
+ icon: IconUserCog,
},
{
title: "Account",
url: "/dashboard/settings/account",
- icon: IconTool
+ icon: IconTool,
},
{
title: "Appearance",
url: "/dashboard/settings/appearance",
- icon: IconPalette
+ icon: IconPalette,
},
{
title: "Notifications",
url: "/dashboard/settings/notifications",
- icon: IconNotification
+ icon: IconNotification,
},
{
title: "Display",
url: "/dashboard/settings/display",
- icon: IconBrowserCheck
- }
- ]
+ icon: IconBrowserCheck,
+ },
+ ],
},
{
title: "Help Center",
url: "/help-center",
- icon: IconHelp
- }
- ]
- }
- ]
+ icon: IconHelp,
+ },
+ ],
+ },
+ ],
};
diff --git a/frontend/src/components/layout/header.tsx b/frontend/src/components/layout/header.tsx
index 12a84b2..64d13aa 100644
--- a/frontend/src/components/layout/header.tsx
+++ b/frontend/src/components/layout/header.tsx
@@ -34,9 +34,10 @@ export const Header = ({
"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",
offset > 10 && fixed ? "shadow" : "shadow-none",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
{children}
diff --git a/frontend/src/components/layout/main.tsx b/frontend/src/components/layout/main.tsx
index cd7ed34..ec99856 100644
--- a/frontend/src/components/layout/main.tsx
+++ b/frontend/src/components/layout/main.tsx
@@ -12,7 +12,7 @@ export const Main = ({ fixed, ...props }: MainProps) => {
className={cn(
"peer-[.header-fixed]/header:mt-16",
"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}
/>
diff --git a/frontend/src/components/layout/nav-group.tsx b/frontend/src/components/layout/nav-group.tsx
index 40fd93e..a6415bc 100644
--- a/frontend/src/components/layout/nav-group.tsx
+++ b/frontend/src/components/layout/nav-group.tsx
@@ -4,7 +4,7 @@ import { ChevronRight } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
- CollapsibleTrigger
+ CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
SidebarGroup,
@@ -15,7 +15,7 @@ import {
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
- useSidebar
+ useSidebar,
} from "@/components/ui/sidebar";
import { Badge } from "../ui/badge";
import {
@@ -24,7 +24,7 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { NavCollapsible, NavItem, NavLink, type NavGroup } from "./types";
@@ -64,7 +64,8 @@ const SidebarMenuLink = ({ item, href }: { item: NavLink; href: string }) => {
+ tooltip={item.title}
+ >
setOpenMobile(false)}>
{item.icon && }
{item.title}
@@ -77,7 +78,7 @@ const SidebarMenuLink = ({ item, href }: { item: NavLink; href: string }) => {
const SidebarMenuCollapsible = ({
item,
- href
+ href,
}: {
item: NavCollapsible;
href: string;
@@ -87,7 +88,8 @@ const SidebarMenuCollapsible = ({
+ className="group/collapsible"
+ >
@@ -103,7 +105,8 @@ const SidebarMenuCollapsible = ({
+ isActive={checkIsActive(href, subItem)}
+ >
setOpenMobile(false)}>
{subItem.icon && }
{subItem.title}
@@ -121,7 +124,7 @@ const SidebarMenuCollapsible = ({
const SidebarMenuCollapsedDropdown = ({
item,
- href
+ href,
}: {
item: NavCollapsible;
href: string;
@@ -132,7 +135,8 @@ const SidebarMenuCollapsedDropdown = ({
+ isActive={checkIsActive(href, item)}
+ >
{item.icon && }
{item.title}
{item.badge && {item.badge} }
@@ -148,7 +152,8 @@ const SidebarMenuCollapsedDropdown = ({
+ className={`${checkIsActive(href, sub) ? "bg-secondary" : ""}`}
+ >
{sub.icon && }
{sub.title}
{sub.badge && (
diff --git a/frontend/src/components/layout/nav-user.tsx b/frontend/src/components/layout/nav-user.tsx
index d340699..3545632 100644
--- a/frontend/src/components/layout/nav-user.tsx
+++ b/frontend/src/components/layout/nav-user.tsx
@@ -5,7 +5,7 @@ import {
ChevronsUpDown,
CreditCard,
LogOut,
- Sparkles
+ Sparkles,
} from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
@@ -15,17 +15,17 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
- useSidebar
+ useSidebar,
} from "@/components/ui/sidebar";
export function NavUser({
- user
+ user,
}: {
user: {
name: string;
@@ -42,7 +42,8 @@ export function NavUser({
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
+ >
SN
@@ -58,7 +59,8 @@ export function NavUser({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
- sideOffset={4}>
+ sideOffset={4}
+ >
diff --git a/frontend/src/components/layout/team-switcher.tsx b/frontend/src/components/layout/team-switcher.tsx
index 1eb944e..523e04a 100644
--- a/frontend/src/components/layout/team-switcher.tsx
+++ b/frontend/src/components/layout/team-switcher.tsx
@@ -7,17 +7,17 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
- useSidebar
+ useSidebar,
} from "@/components/ui/sidebar";
export function TeamSwitcher({
- teams
+ teams,
}: {
teams: {
name: string;
@@ -35,7 +35,8 @@ export function TeamSwitcher({
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
+ >
@@ -52,7 +53,8 @@ export function TeamSwitcher({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start"
side={isMobile ? "bottom" : "right"}
- sideOffset={4}>
+ sideOffset={4}
+ >
Teams
@@ -60,7 +62,8 @@ export function TeamSwitcher({
setActiveTeam(team)}
- className="gap-2 p-2">
+ className="gap-2 p-2"
+ >
diff --git a/frontend/src/components/layout/top-nav.tsx b/frontend/src/components/layout/top-nav.tsx
index bda0805..351d4e5 100644
--- a/frontend/src/components/layout/top-nav.tsx
+++ b/frontend/src/components/layout/top-nav.tsx
@@ -6,7 +6,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
interface TopNavProps extends React.HTMLAttributes {
@@ -34,7 +34,8 @@ export function TopNav({ className, links, ...props }: TopNavProps) {
+ disabled={disabled}
+ >
{title}
@@ -46,15 +47,17 @@ export function TopNav({ className, links, ...props }: TopNavProps) {
+ {...props}
+ >
{links.map(({ title, href, isActive, disabled }) => (
+ className={`text-sm font-medium transition-colors hover:text-primary ${isActive ? "" : "text-muted-foreground"}`}
+ >
{title}
))}
diff --git a/frontend/src/components/long-text.tsx b/frontend/src/components/long-text.tsx
index 1b675d4..74de8bb 100644
--- a/frontend/src/components/long-text.tsx
+++ b/frontend/src/components/long-text.tsx
@@ -3,13 +3,13 @@ import { cn } from "@/lib/utils";
import {
Popover,
PopoverContent,
- PopoverTrigger
+ PopoverTrigger,
} from "@/components/ui/popover";
import {
Tooltip,
TooltipContent,
TooltipProvider,
- TooltipTrigger
+ TooltipTrigger,
} from "@/components/ui/tooltip";
interface Props {
@@ -21,7 +21,7 @@ interface Props {
export default function LongText({
children,
className = "",
- contentClassName = ""
+ contentClassName = "",
}: Props) {
const ref = useRef(null);
const [isOverflown, setIsOverflown] = useState(false);
diff --git a/frontend/src/components/main-navbar.tsx b/frontend/src/components/main-navbar.tsx
index 770b625..208975e 100644
--- a/frontend/src/components/main-navbar.tsx
+++ b/frontend/src/components/main-navbar.tsx
@@ -2,7 +2,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Menu } from "lucide-react";
import { Card } from "@/components/ui/card";
@@ -29,7 +29,6 @@ const MainNavbar = () => {
-
diff --git a/frontend/src/components/nav-main.tsx b/frontend/src/components/nav-main.tsx
index c6c95c4..902fb43 100644
--- a/frontend/src/components/nav-main.tsx
+++ b/frontend/src/components/nav-main.tsx
@@ -5,7 +5,7 @@ import { ChevronRight, type LucideIcon } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
- CollapsibleTrigger
+ CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
SidebarGroup,
@@ -15,11 +15,11 @@ import {
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
- SidebarMenuSubItem
+ SidebarMenuSubItem,
} from "@/components/ui/sidebar";
export function NavMain({
- items
+ items,
}: {
items: {
title: string;
@@ -41,7 +41,8 @@ export function NavMain({
key={item.title}
asChild
defaultOpen={item.isActive}
- className="group/collapsible">
+ className="group/collapsible"
+ >
diff --git a/frontend/src/components/nav-projects.tsx b/frontend/src/components/nav-projects.tsx
index 1ce36e9..cd27490 100644
--- a/frontend/src/components/nav-projects.tsx
+++ b/frontend/src/components/nav-projects.tsx
@@ -5,7 +5,7 @@ import {
Forward,
MoreHorizontal,
Trash2,
- type LucideIcon
+ type LucideIcon,
} from "lucide-react";
import {
@@ -13,7 +13,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarGroup,
@@ -22,11 +22,11 @@ import {
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
- useSidebar
+ useSidebar,
} from "@/components/ui/sidebar";
export function NavProjects({
- projects
+ projects,
}: {
projects: {
name: string;
@@ -58,7 +58,8 @@ export function NavProjects({
+ align={isMobile ? "end" : "start"}
+ >
View Project
diff --git a/frontend/src/components/nav-user.tsx b/frontend/src/components/nav-user.tsx
index 234d7ec..c9b9b39 100644
--- a/frontend/src/components/nav-user.tsx
+++ b/frontend/src/components/nav-user.tsx
@@ -6,7 +6,7 @@ import {
ChevronsUpDown,
CreditCard,
LogOut,
- Sparkles
+ Sparkles,
} from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -17,17 +17,17 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
- useSidebar
+ useSidebar,
} from "@/components/ui/sidebar";
export function NavUser({
- user
+ user,
}: {
user: {
name: string;
@@ -44,7 +44,8 @@ export function NavUser({
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
+ >
CN
@@ -60,7 +61,8 @@ export function NavUser({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
- sideOffset={4}>
+ sideOffset={4}
+ >
diff --git a/frontend/src/components/password-input.tsx b/frontend/src/components/password-input.tsx
index 93c6fc8..97103da 100644
--- a/frontend/src/components/password-input.tsx
+++ b/frontend/src/components/password-input.tsx
@@ -26,12 +26,13 @@ const PasswordInput = React.forwardRef(
variant="ghost"
disabled={disabled}
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 ? : }
);
- }
+ },
);
PasswordInput.displayName = "PasswordInput";
diff --git a/frontend/src/components/pin-input.tsx b/frontend/src/components/pin-input.tsx
index d811d20..971fd1e 100644
--- a/frontend/src/components/pin-input.tsx
+++ b/frontend/src/components/pin-input.tsx
@@ -106,7 +106,7 @@ const PinInput = ({ className, children, ref, ...props }: PinInputProps) => {
placeholder,
type,
length,
- readOnly
+ readOnly,
});
/* call onChange func if pinValue changes */
@@ -171,7 +171,7 @@ const PinInput = ({ className, children, ref, ...props }: PinInputProps) => {
} else {
refMap?.delete(pinIndex);
}
- }
+ },
});
}
skipRef.current = skipRef.current + 1;
@@ -220,7 +220,7 @@ const PinInputField = ({
const isInsidePinInput = React.useContext(PinInputContext);
if (!isInsidePinInput) {
throw new Error(
- `PinInputField must be used within ${PinInput.displayName}.`
+ `PinInputField must be used within ${PinInput.displayName}.`,
);
}
@@ -254,7 +254,7 @@ const usePinInput = ({
placeholder,
type,
length,
- readOnly
+ readOnly,
}: UsePinInputProps) => {
const pinInputs = React.useMemo(
() =>
@@ -263,9 +263,9 @@ const usePinInput = ({
? defaultValue.charAt(index)
: value
? value.charAt(index)
- : ""
+ : "",
),
- [defaultValue, length, value]
+ [defaultValue, length, value],
);
const [pins, setPins] = React.useState(pinInputs);
@@ -305,7 +305,7 @@ const usePinInput = ({
function handleFocus(
event: React.FocusEvent,
- index: number
+ index: number,
) {
event.target.select();
focusInput(index);
@@ -332,7 +332,7 @@ const usePinInput = ({
} else {
return p;
}
- })
+ }),
);
}
@@ -384,7 +384,7 @@ const usePinInput = ({
function handleKeyDown(
event: React.KeyboardEvent,
- index: number
+ index: number,
) {
const { ctrlKey, key, shiftKey, metaKey } = event;
@@ -429,7 +429,7 @@ const usePinInput = ({
handleBlur,
handleChange,
handlePaste,
- handleKeyDown
+ handleKeyDown,
};
};
diff --git a/frontend/src/components/profile-dropdown.tsx b/frontend/src/components/profile-dropdown.tsx
index 428615a..52a8864 100644
--- a/frontend/src/components/profile-dropdown.tsx
+++ b/frontend/src/components/profile-dropdown.tsx
@@ -9,7 +9,7 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import useAuth from "@/hooks/useAuth";
diff --git a/frontend/src/components/search.tsx b/frontend/src/components/search.tsx
index 6bec1e9..898b92e 100644
--- a/frontend/src/components/search.tsx
+++ b/frontend/src/components/search.tsx
@@ -16,9 +16,10 @@ export function Search({ className = "", placeholder = "Search" }: Props) {
variant="outline"
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",
- className
+ className,
)}
- onClick={() => setOpen(true)}>
+ onClick={() => setOpen(true)}
+ >
{
return (
+ href="#content"
+ >
Skip to Main
);
diff --git a/frontend/src/components/team-switcher.tsx b/frontend/src/components/team-switcher.tsx
index 65be2f2..c2b4fe0 100644
--- a/frontend/src/components/team-switcher.tsx
+++ b/frontend/src/components/team-switcher.tsx
@@ -10,17 +10,17 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
- useSidebar
+ useSidebar,
} from "@/components/ui/sidebar";
export function TeamSwitcher({
- teams
+ teams,
}: {
teams: {
name: string;
@@ -38,7 +38,8 @@ export function TeamSwitcher({
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
+ >
@@ -55,7 +56,8 @@ export function TeamSwitcher({
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start"
side={isMobile ? "bottom" : "right"}
- sideOffset={4}>
+ sideOffset={4}
+ >
Teams
@@ -63,7 +65,8 @@ export function TeamSwitcher({
setActiveTeam(team)}
- className="gap-2 p-2">
+ className="gap-2 p-2"
+ >
diff --git a/frontend/src/components/theme-switch.tsx b/frontend/src/components/theme-switch.tsx
index 578f201..575992d 100644
--- a/frontend/src/components/theme-switch.tsx
+++ b/frontend/src/components/theme-switch.tsx
@@ -7,7 +7,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export function ThemeSwitch() {
diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx
index e6a723d..e6cae02 100644
--- a/frontend/src/components/ui/accordion.tsx
+++ b/frontend/src/components/ui/accordion.tsx
@@ -1,56 +1,56 @@
-import * as React from "react"
-import * as AccordionPrimitive from "@radix-ui/react-accordion"
-import { ChevronDown } from "lucide-react"
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion";
+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<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-))
-AccordionItem.displayName = "AccordionItem"
+
+));
+AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
- svg]:rotate-180",
- className
- )}
- {...props}
- >
- {children}
-
-
-
-))
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+ svg]:rotate-180",
+ className,
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+));
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
-
- {children}
-
-))
+
+ {children}
+
+));
-AccordionContent.displayName = AccordionPrimitive.Content.displayName
+AccordionContent.displayName = AccordionPrimitive.Content.displayName;
-export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/frontend/src/components/ui/alert-dialog.tsx b/frontend/src/components/ui/alert-dialog.tsx
index 96bbd76..1023712 100644
--- a/frontend/src/components/ui/alert-dialog.tsx
+++ b/frontend/src/components/ui/alert-dialog.tsx
@@ -16,7 +16,7 @@ const AlertDialogOverlay = React.forwardRef<
@@ -49,7 +49,7 @@ const AlertDialogHeader = ({
@@ -63,7 +63,7 @@ const AlertDialogFooter = ({
@@ -116,7 +116,7 @@ const AlertDialogCancel = React.forwardRef<
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
- className
+ className,
)}
{...props}
/>
@@ -134,5 +134,5 @@ export {
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
- AlertDialogCancel
+ AlertDialogCancel,
};
diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx
index 3c731cb..bb77ce1 100644
--- a/frontend/src/components/ui/alert.tsx
+++ b/frontend/src/components/ui/alert.tsx
@@ -9,13 +9,13 @@ const alertVariants = cva(
variant: {
default: "bg-background text-foreground",
destructive:
- "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive"
- }
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
},
defaultVariants: {
- variant: "default"
- }
- }
+ variant: "default",
+ },
+ },
);
const Alert = React.forwardRef<
diff --git a/frontend/src/components/ui/avatar.tsx b/frontend/src/components/ui/avatar.tsx
index b7f9284..5137d9e 100644
--- a/frontend/src/components/ui/avatar.tsx
+++ b/frontend/src/components/ui/avatar.tsx
@@ -10,7 +10,7 @@ const Avatar = React.forwardRef<
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
/>
@@ -37,7 +37,7 @@ const AvatarFallback = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
- className
+ className,
)}
{...props}
/>
diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx
index 9236c57..d06380e 100644
--- a/frontend/src/components/ui/badge.tsx
+++ b/frontend/src/components/ui/badge.tsx
@@ -13,13 +13,13 @@ const badgeVariants = cva(
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
- outline: "text-foreground"
- }
+ outline: "text-foreground",
+ },
},
defaultVariants: {
- variant: "default"
- }
- }
+ variant: "default",
+ },
+ },
);
export interface BadgeProps
diff --git a/frontend/src/components/ui/breadcrumb.tsx b/frontend/src/components/ui/breadcrumb.tsx
index c7a2ff3..896036d 100644
--- a/frontend/src/components/ui/breadcrumb.tsx
+++ b/frontend/src/components/ui/breadcrumb.tsx
@@ -20,7 +20,7 @@ const BreadcrumbList = React.forwardRef<
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
- className
+ className,
)}
{...props}
/>
@@ -81,7 +81,8 @@ const BreadcrumbSeparator = ({
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
- {...props}>
+ {...props}
+ >
{children ?? }
);
@@ -95,7 +96,8 @@ const BreadcrumbEllipsis = ({
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
- {...props}>
+ {...props}
+ >
More
@@ -109,5 +111,5 @@ export {
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
- BreadcrumbEllipsis
+ BreadcrumbEllipsis,
};
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
index 6daa85a..f20309b 100644
--- a/frontend/src/components/ui/button.tsx
+++ b/frontend/src/components/ui/button.tsx
@@ -17,20 +17,20 @@ const buttonVariants = cva(
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
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: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10"
- }
+ icon: "h-10 w-10",
+ },
},
defaultVariants: {
variant: "default",
- size: "default"
- }
- }
+ size: "default",
+ },
+ },
);
export interface ButtonProps
@@ -49,7 +49,7 @@ const Button = React.forwardRef(
{...props}
/>
);
- }
+ },
);
Button.displayName = "Button";
diff --git a/frontend/src/components/ui/calendar.tsx b/frontend/src/components/ui/calendar.tsx
index 931d0ea..f4314c0 100644
--- a/frontend/src/components/ui/calendar.tsx
+++ b/frontend/src/components/ui/calendar.tsx
@@ -25,7 +25,7 @@ function Calendar({
nav: "space-x-1 flex items-center",
nav_button: cn(
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_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",
day: cn(
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_selected:
@@ -49,7 +49,7 @@ function Calendar({
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
- ...classNames
+ ...classNames,
}}
components={{
IconLeft: ({ className, ...props }) => (
@@ -57,7 +57,7 @@ function Calendar({
),
IconRight: ({ className, ...props }) => (
- )
+ ),
}}
{...props}
/>
diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx
index 7312ef9..0a260aa 100644
--- a/frontend/src/components/ui/card.tsx
+++ b/frontend/src/components/ui/card.tsx
@@ -9,7 +9,7 @@ const Card = React.forwardRef<
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
- className
+ className,
)}
{...props}
/>
@@ -78,5 +78,5 @@ export {
CardFooter,
CardTitle,
CardDescription,
- CardContent
+ CardContent,
};
diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx
index 2d40935..d0b020c 100644
--- a/frontend/src/components/ui/checkbox.tsx
+++ b/frontend/src/components/ui/checkbox.tsx
@@ -11,11 +11,13 @@ const Checkbox = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
+ className={cn("flex items-center justify-center text-current")}
+ >
diff --git a/frontend/src/components/ui/command.tsx b/frontend/src/components/ui/command.tsx
index 0ae2459..8834d43 100644
--- a/frontend/src/components/ui/command.tsx
+++ b/frontend/src/components/ui/command.tsx
@@ -8,7 +8,7 @@ import {
Dialog,
DialogContent,
DialogDescription,
- DialogTitle
+ DialogTitle,
} from "@/components/ui/dialog";
const Command = React.forwardRef<
@@ -19,7 +19,7 @@ const Command = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
- className
+ className,
)}
{...props}
/>
@@ -54,7 +54,7 @@ const CommandInput = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -97,7 +97,7 @@ const CommandGroup = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -125,7 +125,7 @@ const CommandItem = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -141,7 +141,7 @@ const CommandShortcut = ({
@@ -158,5 +158,5 @@ export {
CommandGroup,
CommandItem,
CommandShortcut,
- CommandSeparator
+ CommandSeparator,
};
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
index ab7fda4..6ce3ec2 100644
--- a/frontend/src/components/ui/dialog.tsx
+++ b/frontend/src/components/ui/dialog.tsx
@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -38,9 +38,10 @@ const DialogContent = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
{children}
@@ -58,7 +59,7 @@ const DialogHeader = ({
@@ -72,7 +73,7 @@ const DialogFooter = ({
@@ -87,7 +88,7 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
- className
+ className,
)}
{...props}
/>
@@ -116,5 +117,5 @@ export {
DialogHeader,
DialogFooter,
DialogTitle,
- DialogDescription
+ DialogDescription,
};
diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx
index 9158764..7f789a5 100644
--- a/frontend/src/components/ui/dropdown-menu.tsx
+++ b/frontend/src/components/ui/dropdown-menu.tsx
@@ -26,9 +26,10 @@ const DropdownMenuSubTrigger = React.forwardRef<
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",
inset && "pl-8",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
{children}
@@ -44,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -63,7 +64,7 @@ const DropdownMenuContent = React.forwardRef<
className={cn(
"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",
- className
+ className,
)}
{...props}
/>
@@ -82,7 +83,7 @@ const DropdownMenuItem = React.forwardRef<
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",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -97,10 +98,11 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
checked={checked}
- {...props}>
+ {...props}
+ >
@@ -120,9 +122,10 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
@@ -144,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
- className
+ className,
)}
{...props}
/>
@@ -191,5 +194,5 @@ export {
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
- DropdownMenuRadioGroup
+ DropdownMenuRadioGroup,
};
diff --git a/frontend/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx
index 9c42a3b..cc7958b 100644
--- a/frontend/src/components/ui/form.tsx
+++ b/frontend/src/components/ui/form.tsx
@@ -5,7 +5,7 @@ import {
FieldPath,
FieldValues,
FormProvider,
- useFormContext
+ useFormContext,
} from "react-hook-form";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
@@ -16,18 +16,18 @@ const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TName extends FieldPath = FieldPath,
> = {
name: TName;
};
const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
+ {} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
+ TName extends FieldPath = FieldPath,
>({
...props
}: ControllerProps) => {
@@ -57,7 +57,7 @@ const useFormField = () => {
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
- ...fieldState
+ ...fieldState,
};
};
@@ -66,7 +66,7 @@ type FormItemContextValue = {
};
const FormItemContext = React.createContext(
- {} as FormItemContextValue
+ {} as FormItemContextValue,
);
const FormItem = React.forwardRef<
@@ -156,7 +156,8 @@ const FormMessage = React.forwardRef<
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
- {...props}>
+ {...props}
+ >
{body}
);
@@ -171,5 +172,5 @@ export {
FormControl,
FormDescription,
FormMessage,
- FormField
+ FormField,
};
diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx
index 57fec36..4866831 100644
--- a/frontend/src/components/ui/input.tsx
+++ b/frontend/src/components/ui/input.tsx
@@ -8,13 +8,13 @@ const Input = React.forwardRef>(
type={type}
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",
- className
+ className,
)}
ref={ref}
{...props}
/>
);
- }
+ },
);
Input.displayName = "Input";
diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx
index c075973..4ef31bd 100644
--- a/frontend/src/components/ui/label.tsx
+++ b/frontend/src/components/ui/label.tsx
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
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<
diff --git a/frontend/src/components/ui/phone-number-input.tsx b/frontend/src/components/ui/phone-number-input.tsx
index 6cf1f06..7a18104 100644
--- a/frontend/src/components/ui/phone-number-input.tsx
+++ b/frontend/src/components/ui/phone-number-input.tsx
@@ -10,13 +10,13 @@ import {
CommandGroup,
CommandInput,
CommandItem,
- CommandList
+ CommandList,
} from "@/components/ui/command";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
- PopoverTrigger
+ PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
@@ -53,7 +53,7 @@ const PhoneInput: React.ForwardRefExoticComponent =
{...props}
/>
);
- }
+ },
);
PhoneInput.displayName = "PhoneInput";
@@ -82,7 +82,7 @@ const CountrySelect = ({
disabled,
value: selectedCountry,
options: countryList,
- onChange
+ onChange,
}: CountrySelectProps) => {
return (
@@ -91,7 +91,8 @@ const CountrySelect = ({
type="button"
variant="outline"
className="flex gap-1 rounded-e-none rounded-s-lg border-r-0 px-3 focus:z-10"
- disabled={disabled}>
+ disabled={disabled}
+ >
@@ -120,7 +121,7 @@ const CountrySelect = ({
selectedCountry={selectedCountry}
onChange={onChange}
/>
- ) : null
+ ) : null,
)}
@@ -140,7 +141,7 @@ const CountrySelectOption = ({
country,
countryName,
selectedCountry,
- onChange
+ onChange,
}: CountrySelectOptionProps) => {
return (
onChange(country)}>
diff --git a/frontend/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx
index cc102a9..f3ea4ef 100644
--- a/frontend/src/components/ui/popover.tsx
+++ b/frontend/src/components/ui/popover.tsx
@@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset}
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",
- className
+ className,
)}
{...props}
/>
diff --git a/frontend/src/components/ui/radio-group.tsx b/frontend/src/components/ui/radio-group.tsx
index 9564584..0cba4b0 100644
--- a/frontend/src/components/ui/radio-group.tsx
+++ b/frontend/src/components/ui/radio-group.tsx
@@ -26,9 +26,10 @@ const RadioGroupItem = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
diff --git a/frontend/src/components/ui/scroll-area.tsx b/frontend/src/components/ui/scroll-area.tsx
index f99c10a..bf93d16 100644
--- a/frontend/src/components/ui/scroll-area.tsx
+++ b/frontend/src/components/ui/scroll-area.tsx
@@ -14,12 +14,14 @@ const ScrollArea = React.forwardRef<
+ {...props}
+ >
+ orientation === "horizontal" && "!overflow-x-auto",
+ )}
+ >
{children}
@@ -41,9 +43,10 @@ const ScrollBar = React.forwardRef<
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
));
diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx
index cf65bfb..5a8276a 100644
--- a/frontend/src/components/ui/select.tsx
+++ b/frontend/src/components/ui/select.tsx
@@ -17,9 +17,10 @@ const SelectTrigger = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
{children}
@@ -36,9 +37,10 @@ const SelectScrollUpButton = React.forwardRef<
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
));
@@ -52,9 +54,10 @@ const SelectScrollDownButton = React.forwardRef<
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
));
@@ -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",
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",
- className
+ className,
)}
position={position}
- {...props}>
+ {...props}
+ >
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
+ )}
+ >
{children}
@@ -111,9 +116,10 @@ const SelectItem = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
- {...props}>
+ {...props}
+ >
@@ -146,5 +152,5 @@ export {
SelectItem,
SelectSeparator,
SelectScrollUpButton,
- SelectScrollDownButton
+ SelectScrollDownButton,
};
diff --git a/frontend/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx
index 646938e..07b10bb 100644
--- a/frontend/src/components/ui/separator.tsx
+++ b/frontend/src/components/ui/separator.tsx
@@ -8,7 +8,7 @@ const Separator = React.forwardRef<
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
- ref
+ ref,
) => (
- )
+ ),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
diff --git a/frontend/src/components/ui/sheet.tsx b/frontend/src/components/ui/sheet.tsx
index 0e4ebcf..c35dcc0 100644
--- a/frontend/src/components/ui/sheet.tsx
+++ b/frontend/src/components/ui/sheet.tsx
@@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
+ {...props}
+ >
Close
@@ -79,7 +80,7 @@ const SheetHeader = ({
@@ -93,7 +94,7 @@ const SheetFooter = ({
@@ -134,5 +135,5 @@ export {
SheetHeader,
SheetFooter,
SheetTitle,
- SheetDescription
+ SheetDescription,
};
diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx
index 3d9793d..219d94b 100644
--- a/frontend/src/components/ui/sidebar.tsx
+++ b/frontend/src/components/ui/sidebar.tsx
@@ -12,14 +12,14 @@ import {
Sheet,
SheetContent,
SheetDescription,
- SheetTitle
+ SheetTitle,
} from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import {
Tooltip,
TooltipContent,
TooltipProvider,
- TooltipTrigger
+ TooltipTrigger,
} from "@/components/ui/tooltip";
const SIDEBAR_COOKIE_NAME = "sidebar:state";
@@ -68,7 +68,7 @@ const SidebarProvider = React.forwardRef<
children,
...props
},
- ref
+ ref,
) => {
const isMobile = useIsMobile();
const [openMobile, setOpenMobile] = React.useState(false);
@@ -89,7 +89,7 @@ const SidebarProvider = React.forwardRef<
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
- [setOpenProp, open]
+ [setOpenProp, open],
);
// Helper to toggle the sidebar.
@@ -127,9 +127,17 @@ const SidebarProvider = React.forwardRef<
isMobile,
openMobile,
setOpenMobile,
- toggleSidebar
+ toggleSidebar,
}),
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ [
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ ],
);
return (
@@ -140,21 +148,22 @@ const SidebarProvider = React.forwardRef<
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
- ...style
+ ...style,
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper has-[[data-variant=inset]]:bg-sidebar flex min-h-svh w-full",
- className
+ className,
)}
ref={ref}
- {...props}>
+ {...props}
+ >
{children}
);
- }
+ },
);
SidebarProvider.displayName = "SidebarProvider";
@@ -175,7 +184,7 @@ const Sidebar = React.forwardRef<
children,
...props
},
- ref
+ ref,
) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
@@ -184,10 +193,11 @@ const Sidebar = React.forwardRef<
+ {...props}
+ >
{children}
);
@@ -205,10 +215,11 @@ const Sidebar = React.forwardRef<
className="bg-sidebar text-sidebar-foreground w-[--sidebar-width] p-0 [&>button]:hidden"
style={
{
- "--sidebar-width": SIDEBAR_WIDTH_MOBILE
+ "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
- side={side}>
+ side={side}
+ >
@@ -225,7 +236,8 @@ const Sidebar = React.forwardRef<
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
- data-side={side}>
+ data-side={side}
+ >
{/* This is what handles the sidebar gap on desktop */}
+ {...props}
+ >
+ 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}
);
- }
+ },
);
Sidebar.displayName = "Sidebar";
@@ -279,7 +293,8 @@ const SidebarTrigger = React.forwardRef<
onClick?.(event);
toggleSidebar();
}}
- {...props}>
+ {...props}
+ >
Toggle Sidebar
@@ -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",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
- className
+ className,
)}
{...props}
/>
@@ -326,7 +341,7 @@ const SidebarInset = React.forwardRef<
className={cn(
"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",
- className
+ className,
)}
{...props}
/>
@@ -344,7 +359,7 @@ const SidebarInput = React.forwardRef<
data-sidebar="input"
className={cn(
"focus-visible:ring-sidebar-ring h-8 w-full bg-background shadow-none focus-visible:ring-2",
- className
+ className,
)}
{...props}
/>
@@ -407,7 +422,7 @@ const SidebarContent = React.forwardRef<
data-sidebar="content"
className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
- className
+ className,
)}
{...props}
/>
@@ -443,7 +458,7 @@ const SidebarGroupLabel = React.forwardRef<
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",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
- className
+ className,
)}
{...props}
/>
@@ -466,7 +481,7 @@ const SidebarGroupAction = React.forwardRef<
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden",
- className
+ className,
)}
{...props}
/>
@@ -520,19 +535,19 @@ const sidebarMenuButtonVariants = cva(
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
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: {
default: "h-8 text-sm",
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: {
variant: "default",
- size: "default"
- }
- }
+ size: "default",
+ },
+ },
);
const SidebarMenuButton = React.forwardRef<
@@ -553,7 +568,7 @@ const SidebarMenuButton = React.forwardRef<
className,
...props
},
- ref
+ ref,
) => {
const Comp = asChild ? Slot : "button";
const { isMobile, state } = useSidebar();
@@ -575,7 +590,7 @@ const SidebarMenuButton = React.forwardRef<
if (typeof tooltip === "string") {
tooltip = {
- children: tooltip
+ children: tooltip,
};
}
@@ -590,7 +605,7 @@ const SidebarMenuButton = React.forwardRef<
/>
);
- }
+ },
);
SidebarMenuButton.displayName = "SidebarMenuButton";
@@ -617,7 +632,7 @@ const SidebarMenuAction = React.forwardRef<
"group-data-[collapsible=icon]:hidden",
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",
- className
+ className,
)}
{...props}
/>
@@ -639,7 +654,7 @@ const SidebarMenuBadge = React.forwardRef<
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
- className
+ className,
)}
{...props}
/>
@@ -662,7 +677,8 @@ const SidebarMenuSkeleton = React.forwardRef<
ref={ref}
data-sidebar="menu-skeleton"
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
- {...props}>
+ {...props}
+ >
{showIcon && (
@@ -693,7 +709,7 @@ const SidebarMenuSub = React.forwardRef<
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",
"group-data-[collapsible=icon]:hidden",
- className
+ className,
)}
{...props}
/>
@@ -728,7 +744,7 @@ const SidebarMenuSubButton = React.forwardRef<
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
- className
+ className,
)}
{...props}
/>
@@ -760,5 +776,5 @@ export {
SidebarRail,
SidebarSeparator,
SidebarTrigger,
- useSidebar
+ useSidebar,
};
diff --git a/frontend/src/components/ui/switch.tsx b/frontend/src/components/ui/switch.tsx
index 755311a..68d702d 100644
--- a/frontend/src/components/ui/switch.tsx
+++ b/frontend/src/components/ui/switch.tsx
@@ -9,13 +9,14 @@ const Switch = React.forwardRef<
+ ref={ref}
+ >
diff --git a/frontend/src/components/ui/table.tsx b/frontend/src/components/ui/table.tsx
index ee1780b..f378a50 100644
--- a/frontend/src/components/ui/table.tsx
+++ b/frontend/src/components/ui/table.tsx
@@ -43,7 +43,7 @@ const TableFooter = React.forwardRef<
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
- className
+ className,
)}
{...props}
/>
@@ -58,7 +58,7 @@ const TableRow = React.forwardRef<
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
- className
+ className,
)}
{...props}
/>
@@ -73,7 +73,7 @@ const TableHead = React.forwardRef<
ref={ref}
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]",
- className
+ className,
)}
{...props}
/>
@@ -88,7 +88,7 @@ const TableCell = React.forwardRef<
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
- className
+ className,
)}
{...props}
/>
@@ -115,5 +115,5 @@ export {
TableHead,
TableRow,
TableCell,
- TableCaption
+ TableCaption,
};
diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx
index dc81d47..71cec3a 100644
--- a/frontend/src/components/ui/tabs.tsx
+++ b/frontend/src/components/ui/tabs.tsx
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
- className
+ className,
)}
{...props}
/>
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -42,7 +42,7 @@ const TabsContent = React.forwardRef<
ref={ref}
className={cn(
"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}
/>
diff --git a/frontend/src/components/ui/textarea.tsx b/frontend/src/components/ui/textarea.tsx
index b26bddd..2710be0 100644
--- a/frontend/src/components/ui/textarea.tsx
+++ b/frontend/src/components/ui/textarea.tsx
@@ -9,7 +9,7 @@ const Textarea = React.forwardRef<
@@ -28,13 +28,13 @@ const toastVariants = cva(
variant: {
default: "border bg-background text-foreground",
destructive:
- "destructive group border-destructive bg-destructive text-destructive-foreground"
- }
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
},
defaultVariants: {
- variant: "default"
- }
- }
+ variant: "default",
+ },
+ },
);
const Toast = React.forwardRef<
@@ -60,7 +60,7 @@ const ToastAction = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
{...props}
/>
@@ -75,10 +75,11 @@ const ToastClose = React.forwardRef<
ref={ref}
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",
- className
+ className,
)}
toast-close=""
- {...props}>
+ {...props}
+ >
));
@@ -121,5 +122,5 @@ export {
ToastTitle,
ToastDescription,
ToastClose,
- ToastAction
+ ToastAction,
};
diff --git a/frontend/src/components/ui/toaster.tsx b/frontend/src/components/ui/toaster.tsx
index ad001cb..858697a 100644
--- a/frontend/src/components/ui/toaster.tsx
+++ b/frontend/src/components/ui/toaster.tsx
@@ -5,7 +5,7 @@ import {
ToastDescription,
ToastProvider,
ToastTitle,
- ToastViewport
+ ToastViewport,
} from "@/components/ui/toast";
export function Toaster() {
diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx
index 79f0260..53854c9 100644
--- a/frontend/src/components/ui/tooltip.tsx
+++ b/frontend/src/components/ui/tooltip.tsx
@@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
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",
- className
+ className,
)}
{...props}
/>
diff --git a/frontend/src/config/manifest.ts b/frontend/src/config/manifest.ts
index 973f3f2..50b56f6 100644
--- a/frontend/src/config/manifest.ts
+++ b/frontend/src/config/manifest.ts
@@ -1,2 +1,2 @@
-export const AppName = "SwagShop"
-export const AdminAppName = "SwagShop Admin"
\ No newline at end of file
+export const AppName = "SwagShop";
+export const AdminAppName = "SwagShop Admin";
diff --git a/frontend/src/context/font-context.tsx b/frontend/src/context/font-context.tsx
index d6760c3..6e793f6 100644
--- a/frontend/src/context/font-context.tsx
+++ b/frontend/src/context/font-context.tsx
@@ -11,7 +11,7 @@ interface FontContextType {
const FontContext = createContext(undefined);
export const FontProvider: React.FC<{ children: React.ReactNode }> = ({
- children
+ children,
}) => {
const [font, _setFont] = useState(() => {
const savedFont = localStorage.getItem("font");
diff --git a/frontend/src/context/theme-context.tsx b/frontend/src/context/theme-context.tsx
index 43ee165..8fbf492 100644
--- a/frontend/src/context/theme-context.tsx
+++ b/frontend/src/context/theme-context.tsx
@@ -15,7 +15,7 @@ type ThemeProviderState = {
const initialState: ThemeProviderState = {
theme: "system",
- setTheme: () => null
+ setTheme: () => null,
};
const ThemeProviderContext = createContext(initialState);
@@ -27,7 +27,7 @@ export function ThemeProvider({
...props
}: ThemeProviderProps) {
const [theme, _setTheme] = useState(
- () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
+ () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
);
useEffect(() => {
@@ -61,7 +61,7 @@ export function ThemeProvider({
const value = {
theme,
- setTheme
+ setTheme,
};
return (
diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts
index f1ecc27..e840d53 100644
--- a/frontend/src/hooks/useAuth.ts
+++ b/frontend/src/hooks/useAuth.ts
@@ -22,14 +22,14 @@ const useAuth = () => {
const { data: user } = useQuery({
queryKey: ["currentUser"],
queryFn: authAPI.getCurrentUser,
- enabled: loggedIn
+ enabled: loggedIn,
});
const signUpMutation = useMutation({
mutationFn: authAPI.registerUser,
onSuccess: () => navigate({ to: "/sign-in" }),
onError: (err: ApiError) => handleServerError(err),
- onSettled: () => queryClient.invalidateQueries({ queryKey: ["users"] })
+ onSettled: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
});
const login = async (data: ShopLoginAccessTokenData) => {
@@ -42,7 +42,7 @@ const useAuth = () => {
const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => navigate({ to: "/" }),
- onError: (err: ApiError) => handleServerError(err)
+ onError: (err: ApiError) => handleServerError(err),
});
const logout = () => {
@@ -58,7 +58,7 @@ const useAuth = () => {
toast({ title: "Account updated successfully" });
queryClient.invalidateQueries({ queryKey: ["currentUser"] });
},
- onError: (err: ApiError) => handleServerError(err)
+ onError: (err: ApiError) => handleServerError(err),
});
useEffect(() => {
@@ -97,7 +97,7 @@ const useAuth = () => {
logout,
user,
error,
- resetError: () => setError(null)
+ resetError: () => setError(null),
};
};
diff --git a/frontend/src/hooks/useCoupon.ts b/frontend/src/hooks/useCoupon.ts
new file mode 100644
index 0000000..7b4e8ac
--- /dev/null
+++ b/frontend/src/hooks/useCoupon.ts
@@ -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({
+ queryKey: ["coupon", couponId],
+ queryFn: () => couponAPI.getCouponById(couponId!),
+ enabled: !!couponId,
+ });
+
+ const createCoupon = useMutation({
+ mutationFn: (data: Omit) => couponAPI.createCoupon(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["coupons"] });
+ },
+ });
+
+ const updateCoupon = useMutation({
+ mutationFn: (data: Partial>) =>
+ 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 };
+}
diff --git a/frontend/src/hooks/useCoupons.ts b/frontend/src/hooks/useCoupons.ts
new file mode 100644
index 0000000..3f45367
--- /dev/null
+++ b/frontend/src/hooks/useCoupons.ts
@@ -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({
+ queryKey: ["coupons"],
+ queryFn: couponAPI.getAllCoupons,
+ });
+}
diff --git a/frontend/src/hooks/useDialogState.tsx b/frontend/src/hooks/useDialogState.tsx
index 8a98047..41931e5 100644
--- a/frontend/src/hooks/useDialogState.tsx
+++ b/frontend/src/hooks/useDialogState.tsx
@@ -7,7 +7,7 @@ import { useState } from "react";
* @example const [open, setOpen] = useDialogState<"approve" | "reject">()
*/
export default function useDialogState(
- initialState: T | null = null
+ initialState: T | null = null,
) {
const [open, _setOpen] = useState(initialState);
diff --git a/frontend/src/hooks/useMobile.tsx b/frontend/src/hooks/useMobile.tsx
index a5c6014..0a89231 100644
--- a/frontend/src/hooks/useMobile.tsx
+++ b/frontend/src/hooks/useMobile.tsx
@@ -4,7 +4,7 @@ const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState(
- undefined
+ undefined,
);
React.useEffect(() => {
diff --git a/frontend/src/hooks/useProduct.ts b/frontend/src/hooks/useProduct.ts
index 84f0ee7..ee64215 100644
--- a/frontend/src/hooks/useProduct.ts
+++ b/frontend/src/hooks/useProduct.ts
@@ -8,22 +8,23 @@ export function useProduct(productId?: number) {
const product = useQuery({
queryKey: ["product", productId],
queryFn: () => productsAPI.getProductById(productId!),
- enabled: !!productId
+ enabled: !!productId,
});
const createProduct = useMutation({
mutationFn: (data: ProductCreate) => productsAPI.createProduct(data),
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] })
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
});
const updateProduct = useMutation({
- mutationFn: (data: Partial) => productsAPI.updateProduct(productId!, data),
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] })
+ mutationFn: (data: Partial) =>
+ productsAPI.updateProduct(productId!, data),
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
});
const deleteProduct = useMutation({
mutationFn: () => productsAPI.deleteProduct(productId!),
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] })
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["products"] }),
});
return { product, createProduct, updateProduct, deleteProduct };
diff --git a/frontend/src/hooks/useProducts.ts b/frontend/src/hooks/useProducts.ts
index fd63ee4..4345bf2 100644
--- a/frontend/src/hooks/useProducts.ts
+++ b/frontend/src/hooks/useProducts.ts
@@ -1,4 +1,3 @@
-// src/hooks/useProducts.ts
import { useQuery } from "@tanstack/react-query";
import { productsAPI } from "@/api/api";
import { ProductWithDetails } from "@/api/mock/models";
@@ -6,7 +5,7 @@ import { ProductWithDetails } from "@/api/mock/models";
export function useProducts() {
const query = useQuery({
queryKey: ["products"],
- queryFn: productsAPI.getProductsForShop
+ queryFn: productsAPI.getProductsForShop,
});
return query;
diff --git a/frontend/src/hooks/usePurchase.ts b/frontend/src/hooks/usePurchase.ts
new file mode 100644
index 0000000..08be1c8
--- /dev/null
+++ b/frontend/src/hooks/usePurchase.ts
@@ -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({
+ queryKey: ["purchase", purchaseId],
+ queryFn: () => {
+ if (purchaseId === undefined) return Promise.resolve(null);
+ return purchaseAPI.getPurchaseWithDetails(purchaseId);
+ },
+ enabled: purchaseId !== undefined,
+ });
+}
diff --git a/frontend/src/hooks/usePurchases.ts b/frontend/src/hooks/usePurchases.ts
new file mode 100644
index 0000000..fe50b77
--- /dev/null
+++ b/frontend/src/hooks/usePurchases.ts
@@ -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({
+ queryKey: ["purchases"],
+ queryFn: purchaseAPI.getAllPurchases,
+ });
+}
diff --git a/frontend/src/hooks/useShop.ts b/frontend/src/hooks/useShop.ts
index fba1cd9..e959a43 100644
--- a/frontend/src/hooks/useShop.ts
+++ b/frontend/src/hooks/useShop.ts
@@ -8,15 +8,15 @@ export function useShop() {
const {
data: shop,
isLoading,
- isError
+ isError,
} = useQuery({
queryKey: ["shop"],
- queryFn: shopAPI.getShop
+ queryFn: shopAPI.getShop,
});
const updateShopMutation = useMutation({
mutationFn: shopAPI.updateShop,
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["shop"] })
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["shop"] }),
});
return {
@@ -24,6 +24,6 @@ export function useShop() {
isLoading,
isError,
updateShop: updateShopMutation.mutate,
- updateStatus: updateShopMutation.status
+ updateStatus: updateShopMutation.status,
};
}
diff --git a/frontend/src/hooks/useToast.ts b/frontend/src/hooks/useToast.ts
index 3e340e4..2933bb7 100644
--- a/frontend/src/hooks/useToast.ts
+++ b/frontend/src/hooks/useToast.ts
@@ -17,7 +17,7 @@ const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
- REMOVE_TOAST: "REMOVE_TOAST"
+ REMOVE_TOAST: "REMOVE_TOAST",
} as const;
let count = 0;
@@ -62,7 +62,7 @@ const addToRemoveQueue = (toastId: string) => {
toastTimeouts.delete(toastId);
dispatch({
type: "REMOVE_TOAST",
- toastId: toastId
+ toastId: toastId,
});
}, TOAST_REMOVE_DELAY);
@@ -74,15 +74,15 @@ const reducer = (state: State, action: Action): State => {
case "ADD_TOAST":
return {
...state,
- toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT)
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
};
case "UPDATE_TOAST":
return {
...state,
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": {
@@ -104,22 +104,22 @@ const reducer = (state: State, action: Action): State => {
t.id === toastId || toastId === undefined
? {
...t,
- open: false
+ open: false,
}
- : t
- )
+ : t,
+ ),
};
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
- toasts: []
+ toasts: [],
};
}
return {
...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) =>
dispatch({
type: "UPDATE_TOAST",
- toast: { ...props, id }
+ toast: { ...props, id },
});
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
@@ -155,14 +155,14 @@ function toast({ ...props }: Toast) {
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
- }
- }
+ },
+ },
});
return {
id: id,
dismiss,
- update
+ update,
};
}
@@ -182,7 +182,7 @@ function useToast() {
return {
...state,
toast,
- dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId })
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
};
}
diff --git a/frontend/src/hooks/useUser.ts b/frontend/src/hooks/useUser.ts
new file mode 100644
index 0000000..045f95a
--- /dev/null
+++ b/frontend/src/hooks/useUser.ts
@@ -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({
+ queryKey: ["user", userId],
+ queryFn: () => usersAPI.getUserById(userId!),
+ enabled: !!userId,
+ });
+
+ const createUser = useMutation({
+ mutationFn: (data: Omit) =>
+ usersAPI.createUser(data),
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["users"] }),
+ });
+
+ const updateUser = useMutation({
+ mutationFn: (data: Partial>) =>
+ 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 };
+}
diff --git a/frontend/src/hooks/useUsers.ts b/frontend/src/hooks/useUsers.ts
new file mode 100644
index 0000000..0321782
--- /dev/null
+++ b/frontend/src/hooks/useUsers.ts
@@ -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({
+ queryKey: ["users"],
+ queryFn: usersAPI.getAllUsers,
+ });
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 5a61c1f..6a86e04 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -3,152 +3,150 @@
@tailwind utilities;
@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 222.2 84% 4.9%;
- --card: 0 0% 100%;
- --card-foreground: 222.2 84% 4.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 222.2 84% 4.9%;
- --primary: 222.2 47.4% 11.2%;
- --primary-foreground: 210 40% 98%;
- --secondary: 210 40% 96.1%;
- --secondary-foreground: 222.2 47.4% 11.2%;
- --muted: 210 40% 96.1%;
- --muted-foreground: 215.4 16.3% 46.9%;
- --accent: 210 40% 96.1%;
- --accent-foreground: 222.2 47.4% 11.2%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 210 40% 98%;
- --border: 214.3 31.8% 91.4%;
- --input: 214.3 31.8% 91.4%;
- --ring: 222.2 84% 4.9%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
- --radius: 0.5rem;
- --sidebar-background: 0 0% 98%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
- --sidebar-accent: 240 4.8% 95.9%;
- --sidebar-accent-foreground: 240 5.9% 10%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 217.2 91.2% 59.8%;
- }
- .dark {
- --background: 222.2 84% 4.9%;
- --foreground: 210 40% 98%;
- --card: 222.2 84% 4.9%;
- --card-foreground: 210 40% 98%;
- --popover: 222.2 84% 4.9%;
- --popover-foreground: 210 40% 98%;
- --primary: 210 40% 98%;
- --primary-foreground: 222.2 47.4% 11.2%;
- --secondary: 217.2 32.6% 17.5%;
- --secondary-foreground: 210 40% 98%;
- --muted: 217.2 32.6% 17.5%;
- --muted-foreground: 215 20.2% 65.1%;
- --accent: 217.2 32.6% 17.5%;
- --accent-foreground: 210 40% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 210 40% 98%;
- --border: 217.2 32.6% 17.5%;
- --input: 217.2 32.6% 17.5%;
- --ring: 212.7 26.8% 83.9%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%;
- --sidebar-background: var(--background);
- --sidebar-foreground: var(--foreground);
- --sidebar-primary: var(--primary);
- --sidebar-primary-foreground: var(--primary-foreground);
- --sidebar-accent: var(--accent);
- --sidebar-accent-foreground: var(--accent-foreground);
- --sidebar-border: var(--border);
- --sidebar-ring: var(--ring);
- }
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ --sidebar-background: 0 0% 98%;
+ --sidebar-foreground: 240 5.3% 26.1%;
+ --sidebar-primary: 240 5.9% 10%;
+ --sidebar-primary-foreground: 0 0% 98%;
+ --sidebar-accent: 240 4.8% 95.9%;
+ --sidebar-accent-foreground: 240 5.9% 10%;
+ --sidebar-border: 220 13% 91%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+ }
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ --sidebar-background: var(--background);
+ --sidebar-foreground: var(--foreground);
+ --sidebar-primary: var(--primary);
+ --sidebar-primary-foreground: var(--primary-foreground);
+ --sidebar-accent: var(--accent);
+ --sidebar-accent-foreground: var(--accent-foreground);
+ --sidebar-border: var(--border);
+ --sidebar-ring: var(--ring);
+ }
- /* styles.css */
- .CollapsibleContent {
- overflow: hidden;
- }
- .CollapsibleContent[data-state='open'] {
- animation: slideDown 300ms ease-out;
- }
- .CollapsibleContent[data-state='closed'] {
- animation: slideUp 300ms ease-out;
- }
+ /* styles.css */
+ .CollapsibleContent {
+ overflow: hidden;
+ }
+ .CollapsibleContent[data-state="open"] {
+ animation: slideDown 300ms ease-out;
+ }
+ .CollapsibleContent[data-state="closed"] {
+ animation: slideUp 300ms ease-out;
+ }
- @keyframes slideDown {
- from {
- height: 0;
- }
- to {
- height: var(--radix-collapsible-content-height);
- }
- }
+ @keyframes slideDown {
+ from {
+ height: 0;
+ }
+ to {
+ height: var(--radix-collapsible-content-height);
+ }
+ }
- @keyframes slideUp {
- from {
- height: var(--radix-collapsible-content-height);
- }
- to {
- height: 0;
- }
- }
+ @keyframes slideUp {
+ from {
+ height: var(--radix-collapsible-content-height);
+ }
+ to {
+ height: 0;
+ }
+ }
- /* Prevent focus zoom on mobile devices */
- @media screen and (max-width: 767px) {
- input,
- select,
- textarea {
- font-size: 16px !important;
- }
- }
+ /* Prevent focus zoom on mobile devices */
+ @media screen and (max-width: 767px) {
+ input,
+ select,
+ textarea {
+ font-size: 16px !important;
+ }
+ }
}
@layer utilities {
- /* Hide scrollbar for Chrome, Safari and Opera */
- .no-scrollbar::-webkit-scrollbar {
- display: none;
- }
- /* Hide scrollbar for IE, Edge and Firefox */
- .no-scrollbar {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
- }
+ /* Hide scrollbar for Chrome, Safari and Opera */
+ .no-scrollbar::-webkit-scrollbar {
+ display: none;
+ }
+ /* Hide scrollbar for IE, Edge and Firefox */
+ .no-scrollbar {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+ }
- .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;
- }
+ .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;
+ }
}
@layer base {
- * {
- @apply border-border;
- scrollbar-width: thin;
- scrollbar-color: hsl(var(--border)) transparent;
- }
- html {
- @apply overflow-x-hidden;
- }
- body {
- @apply min-h-svh w-full bg-background text-foreground;
- }
+ * {
+ @apply border-border;
+ scrollbar-width: thin;
+ scrollbar-color: hsl(var(--border)) transparent;
+ }
+ html {
+ @apply overflow-x-hidden;
+ }
+ body {
+ @apply min-h-svh w-full bg-background text-foreground;
+ }
}
-
-
@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 47ec047..fe05018 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -12,13 +12,13 @@ export const registerUser = async (userData: {
`${API_BASE_URL}/user/register`,
{
...userData,
- shop_id: 0
+ shop_id: 0,
},
{
headers: {
- "Content-Type": "application/json"
- }
- }
+ "Content-Type": "application/json",
+ },
+ },
);
return response.data;
};
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 9825e36..7ee9328 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -4,7 +4,7 @@ import { AxiosError } from "axios";
import {
QueryCache,
QueryClient,
- QueryClientProvider
+ QueryClientProvider,
} from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { useAuthStore } from "@/stores/authStore";
@@ -38,7 +38,7 @@ const queryClient = new QueryClient({
);
},
refetchOnWindowFocus: import.meta.env.PROD,
- staleTime: 10 * 1000 // 10s
+ staleTime: 10 * 1000, // 10s
},
mutations: {
onError: (error) => {
@@ -48,12 +48,12 @@ const queryClient = new QueryClient({
if (error.response?.status === 304) {
toast({
variant: "destructive",
- title: "Content not modified!"
+ title: "Content not modified!",
});
}
}
- }
- }
+ },
+ },
},
queryCache: new QueryCache({
onError: (error) => {
@@ -61,7 +61,7 @@ const queryClient = new QueryClient({
if (error.response?.status === 401) {
toast({
variant: "destructive",
- title: "Session expired!"
+ title: "Session expired!",
});
useAuthStore.getState().auth.reset();
const redirect = `${router.history.location.href}`;
@@ -70,7 +70,7 @@ const queryClient = new QueryClient({
if (error.response?.status === 500) {
toast({
variant: "destructive",
- title: "Internal Server Error!"
+ title: "Internal Server Error!",
});
router.navigate({ to: "/500" });
}
@@ -78,8 +78,8 @@ const queryClient = new QueryClient({
// router.navigate("/forbidden", { replace: true });
}
}
- }
- })
+ },
+ }),
});
// Create a new router instance
@@ -87,7 +87,7 @@ const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: "intent",
- defaultPreloadStaleTime: 0
+ defaultPreloadStaleTime: 0,
});
// Register the router instance for type safety
@@ -110,6 +110,6 @@ if (!rootElement.innerHTML) {
-
+ ,
);
}
diff --git a/frontend/src/pages/auth/auth-layout.tsx b/frontend/src/pages/auth/auth-layout.tsx
index 09d17f1..a187073 100644
--- a/frontend/src/pages/auth/auth-layout.tsx
+++ b/frontend/src/pages/auth/auth-layout.tsx
@@ -1,4 +1,4 @@
-import {AdminAppName} from "@/config/manifest"
+import { AdminAppName } from "@/config/manifest";
interface Props {
children: React.ReactNode;
@@ -17,7 +17,8 @@ export default function AuthLayout({ children }: Props) {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
- className="mr-2 h-6 w-6">
+ className="mr-2 h-6 w-6"
+ >
{AdminAppName}
diff --git a/frontend/src/pages/auth/forgot-password/components/forgot-password-form.tsx b/frontend/src/pages/auth/forgot-password/components/forgot-password-form.tsx
index eba84c0..cf4ab46 100644
--- a/frontend/src/pages/auth/forgot-password/components/forgot-password-form.tsx
+++ b/frontend/src/pages/auth/forgot-password/components/forgot-password-form.tsx
@@ -10,7 +10,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
@@ -20,7 +20,7 @@ const formSchema = z.object({
email: z
.string()
.min(1, { message: "Please enter your email" })
- .email({ message: "Invalid email address" })
+ .email({ message: "Invalid email address" }),
});
export function ForgotForm({ className, ...props }: ForgotFormProps) {
@@ -28,7 +28,7 @@ export function ForgotForm({ className, ...props }: ForgotFormProps) {
const form = useForm>({
resolver: zodResolver(formSchema),
- defaultValues: { email: "" }
+ defaultValues: { email: "" },
});
function onSubmit(data: z.infer) {
diff --git a/frontend/src/pages/auth/forgot-password/index.tsx b/frontend/src/pages/auth/forgot-password/index.tsx
index 073517b..af66bbc 100644
--- a/frontend/src/pages/auth/forgot-password/index.tsx
+++ b/frontend/src/pages/auth/forgot-password/index.tsx
@@ -21,7 +21,8 @@ export default function ForgotPassword() {
Don't have an account?{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Sign up
.
diff --git a/frontend/src/pages/auth/otp/components/otp-form.tsx b/frontend/src/pages/auth/otp/components/otp-form.tsx
index eacd93e..830d52c 100644
--- a/frontend/src/pages/auth/otp/components/otp-form.tsx
+++ b/frontend/src/pages/auth/otp/components/otp-form.tsx
@@ -11,7 +11,7 @@ import {
FormControl,
FormField,
FormItem,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
@@ -20,7 +20,7 @@ import { PinInput, PinInputField } from "@/components/pin-input";
type OtpFormProps = HTMLAttributes;
const formSchema = z.object({
- otp: z.string().min(1, { message: "Please enter your otp code." })
+ otp: z.string().min(1, { message: "Please enter your otp code." }),
});
export function OtpForm({ className, ...props }: OtpFormProps) {
@@ -30,7 +30,7 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
const form = useForm>({
resolver: zodResolver(formSchema),
- defaultValues: { otp: "" }
+ defaultValues: { otp: "" },
});
function onSubmit(data: z.infer) {
@@ -41,7 +41,7 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
{JSON.stringify(data, null, 2)}
- )
+ ),
});
setTimeout(() => {
@@ -65,7 +65,8 @@ export function OtpForm({ className, ...props }: OtpFormProps) {
{...field}
className="flex h-10 justify-between"
onComplete={() => setDisabledBtn(false)}
- onIncomplete={() => setDisabledBtn(true)}>
+ onIncomplete={() => setDisabledBtn(true)}
+ >
{Array.from({ length: 7 }, (_, i) => {
if (i === 3)
return ;
diff --git a/frontend/src/pages/auth/otp/index.tsx b/frontend/src/pages/auth/otp/index.tsx
index 11baa58..55d0f2f 100644
--- a/frontend/src/pages/auth/otp/index.tsx
+++ b/frontend/src/pages/auth/otp/index.tsx
@@ -21,7 +21,8 @@ export default function Otp() {
Haven't received it?{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Resend a new code.
.
diff --git a/frontend/src/pages/auth/sign-in/components/user-auth-form.tsx b/frontend/src/pages/auth/sign-in/components/user-auth-form.tsx
index 257feb2..6ae28af 100644
--- a/frontend/src/pages/auth/sign-in/components/user-auth-form.tsx
+++ b/frontend/src/pages/auth/sign-in/components/user-auth-form.tsx
@@ -11,7 +11,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "@/components/password-input";
@@ -27,11 +27,11 @@ const formSchema = z.object({
password: z
.string()
.min(1, {
- message: "Please enter your password"
+ message: "Please enter your password",
})
.min(7, {
- message: "Password must be at least 7 characters long"
- })
+ message: "Password must be at least 7 characters long",
+ }),
});
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
@@ -41,14 +41,16 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
- password: ""
- }
+ password: "",
+ },
});
async function onSubmit(data: z.infer) {
resetError();
try {
- await loginMutation.mutateAsync({formData: {username: data.email, password: data.password}});
+ await loginMutation.mutateAsync({
+ formData: { username: data.email, password: data.password },
+ });
} catch {
// Error handled in useAuth hook
}
@@ -81,7 +83,8 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
Password
+ className="text-sm font-medium text-muted-foreground hover:opacity-75"
+ >
Forgot password?
diff --git a/frontend/src/pages/auth/sign-in/index.tsx b/frontend/src/pages/auth/sign-in/index.tsx
index adf73dd..73b1b74 100644
--- a/frontend/src/pages/auth/sign-in/index.tsx
+++ b/frontend/src/pages/auth/sign-in/index.tsx
@@ -18,13 +18,15 @@ export default function SignIn() {
By clicking login, you agree to our{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Terms of Service
{" "}
and{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Privacy Policy
.
diff --git a/frontend/src/pages/auth/sign-in/sign-in-2.tsx b/frontend/src/pages/auth/sign-in/sign-in-2.tsx
index 0ab9dfa..377c758 100644
--- a/frontend/src/pages/auth/sign-in/sign-in-2.tsx
+++ b/frontend/src/pages/auth/sign-in/sign-in-2.tsx
@@ -16,7 +16,8 @@ export default function SignIn2() {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
- className="mr-2 h-6 w-6">
+ className="mr-2 h-6 w-6"
+ >
{AdminAppName}
@@ -55,13 +56,15 @@ export default function SignIn2() {
By clicking login, you agree to our{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Terms of Service
{" "}
and{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Privacy Policy
.
diff --git a/frontend/src/pages/auth/sign-up/components/sign-up-form.tsx b/frontend/src/pages/auth/sign-up/components/sign-up-form.tsx
index cd0fe68..2a087cd 100644
--- a/frontend/src/pages/auth/sign-up/components/sign-up-form.tsx
+++ b/frontend/src/pages/auth/sign-up/components/sign-up-form.tsx
@@ -10,7 +10,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "@/components/password-input";
@@ -41,27 +41,27 @@ const formSchema = z
.string()
.min(1, { message: "Please enter your password" })
.min(8, {
- message: "Password must be at least 8 characters long"
+ message: "Password must be at least 8 characters long",
})
.max(128, { message: "Password must be at most 128 characters long" })
.refine((password) => /[A-Z]/.test(password), {
- message: "Password must contain at least one uppercase letter"
+ message: "Password must contain at least one uppercase letter",
})
.refine((password) => /[a-z]/.test(password), {
- message: "Password must contain at least one lowercase letter"
+ message: "Password must contain at least one lowercase letter",
})
.refine((password) => /\d/.test(password), {
- message: "Password must contain at least one number"
+ message: "Password must contain at least one number",
})
.refine((password) => /[@$!%*?&]/.test(password), {
message:
- "Password must contain at least one special character (@, $, !, %, *, ?, &)"
+ "Password must contain at least one special character (@, $, !, %, *, ?, &)",
}),
- confirmPassword: z.string()
+ confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match.",
- path: ["confirmPassword"]
+ path: ["confirmPassword"],
});
export function SignUpForm({ className, ...props }: SignUpFormProps) {
@@ -75,15 +75,15 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) {
email: "",
phone_number: "",
password: "",
- confirmPassword: ""
- }
+ confirmPassword: "",
+ },
});
async function onSubmit(data: z.infer) {
setIsLoading(true);
signUpMutation.mutate(data);
toast({
- title: "Successfully registered"
+ title: "Successfully registered",
});
setIsLoading(false);
}
diff --git a/frontend/src/pages/auth/sign-up/index.tsx b/frontend/src/pages/auth/sign-up/index.tsx
index 6926f32..e010abd 100644
--- a/frontend/src/pages/auth/sign-up/index.tsx
+++ b/frontend/src/pages/auth/sign-up/index.tsx
@@ -16,7 +16,8 @@ export default function SignUp() {
Already have an account?{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Sign In
@@ -26,13 +27,15 @@ export default function SignUp() {
By creating an account, you agree to our{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Terms of Service
{" "}
and{" "}
+ className="underline underline-offset-4 hover:text-primary"
+ >
Privacy Policy
.
diff --git a/frontend/src/pages/chats/components/new-chat.tsx b/frontend/src/pages/chats/components/new-chat.tsx
index 0736435..80e7072 100644
--- a/frontend/src/pages/chats/components/new-chat.tsx
+++ b/frontend/src/pages/chats/components/new-chat.tsx
@@ -9,13 +9,13 @@ import {
CommandGroup,
CommandInput,
CommandItem,
- CommandList
+ CommandList,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogHeader,
- DialogTitle
+ DialogTitle,
} from "@/components/ui/dialog";
import { ChatUser } from "../data/chat-types";
@@ -56,7 +56,7 @@ export function NewChat({ users, onOpenChange, open }: Props) {
{JSON.stringify(selectedUsers, null, 2)}
- )
+ ),
});
};
@@ -79,7 +79,8 @@ export function NewChat({ users, onOpenChange, open }: Props) {
handleRemoveUser(user.id);
}
}}
- onClick={() => handleRemoveUser(user.id)}>
+ onClick={() => handleRemoveUser(user.id)}
+ >
@@ -97,7 +98,8 @@ export function NewChat({ users, onOpenChange, open }: Props) {
handleSelectUser(user)}
- className="flex items-center justify-between gap-2">
+ className="flex items-center justify-between gap-2"
+ >
+ disabled={selectedUsers.length === 0}
+ >
Chat
diff --git a/frontend/src/pages/chats/data/convo.json b/frontend/src/pages/chats/data/convo.json
index 310160f..f8fa586 100644
--- a/frontend/src/pages/chats/data/convo.json
+++ b/frontend/src/pages/chats/data/convo.json
@@ -1,309 +1,309 @@
{
- "conversations": [
- {
- "id": "conv1",
- "profile": "https://randomuser.me/api/portraits/men/32.jpg",
- "username": "alex_dev",
- "fullName": "Alex John",
- "title": "Senior Backend Dev",
- "messages": [
- {
- "sender": "You",
- "message": "See you later, Alex!",
- "timestamp": "2024-08-24T11:15:15"
- },
- {
- "sender": "Alex",
- "message": "Alright, talk to you later!",
- "timestamp": "2024-08-24T11:11:30"
- },
- {
- "sender": "You",
- "message": "For sure. Anyway, I should get back to reviewing the project.",
- "timestamp": "2024-08-23T09:26:50"
- },
- {
- "sender": "Alex",
- "message": "Yeah, let me know what you think.",
- "timestamp": "2024-08-23T09:25:15"
- },
- {
- "sender": "You",
- "message": "Oh, nice! I've been waiting for that. I'll check it out later.",
- "timestamp": "2024-08-23T09:24:30"
- },
- {
- "sender": "Alex",
- "message": "They've added a dark mode option! It looks really sleek.",
- "timestamp": "2024-08-23T09:23:10"
- },
- {
- "sender": "You",
- "message": "No, not yet. What's new?",
- "timestamp": "2024-08-23T09:22:00"
- },
- {
- "sender": "Alex",
- "message": "By the way, have you seen the new feature update?",
- "timestamp": "2024-08-23T09:21:05"
- },
- {
- "sender": "You",
- "message": "Will do! Thanks, Alex.",
- "timestamp": "2024-08-23T09:20:10"
- },
- {
- "sender": "Alex",
- "message": "Great! Let me know if you need any help.",
- "timestamp": "2024-08-23T09:19:20"
- },
- {
- "sender": "You",
- "message": "Almost done. Just need to review a few things.",
- "timestamp": "2024-08-23T09:18:45"
- },
- {
- "sender": "Alex",
- "message": "I'm good, thanks! Did you finish the project?",
- "timestamp": "2024-08-23T09:17:10"
- },
- {
- "sender": "You",
- "message": "Hey Alex, I'm doing well! How about you?",
- "timestamp": "2024-08-23T09:16:30"
- },
- {
- "sender": "Alex",
- "message": "Hey Bob, how are you doing?",
- "timestamp": "2024-08-23T09:15:00"
- }
- ]
- },
- {
- "id": "conv2",
- "profile": "https://randomuser.me/api/portraits/women/45.jpg",
- "username": "taylor.codes",
- "fullName": "Taylor Grande",
- "title": "Tech Lead",
- "messages": [
- {
- "sender": "Taylor",
- "message": "Yeah, it's really well-explained. You should give it a try.",
- "timestamp": "2024-08-23T10:35:00"
- },
- {
- "sender": "You",
- "message": "Not yet, is it good?",
- "timestamp": "2024-08-23T10:32:00"
- },
- {
- "sender": "Taylor",
- "message": "Hey, did you check out that new tutorial?",
- "timestamp": "2024-08-23T10:30:00"
- }
- ]
- },
- {
- "id": "conv3",
- "profile": "https://randomuser.me/api/portraits/men/54.jpg",
- "username": "john_stack",
- "fullName": "John Doe",
- "title": "QA",
- "messages": [
- {
- "sender": "You",
- "message": "Yep, see ya. 👋🏼",
- "timestamp": "2024-08-22T18:59:00"
- },
- {
- "sender": "John",
- "message": "Great, see you then!",
- "timestamp": "2024-08-22T18:55:00"
- },
- {
- "sender": "You",
- "message": "Yes, same time as usual. I'll send the invite shortly.",
- "timestamp": "2024-08-22T18:50:00"
- },
- {
- "sender": "John",
- "message": "Are we still on for the meeting tomorrow?",
- "timestamp": "2024-08-22T18:45:00"
- }
- ]
- },
- {
- "id": "conv4",
- "profile": "https://randomuser.me/api/portraits/women/29.jpg",
- "username": "megan_frontend",
- "fullName": "Megan Flux",
- "title": "Jr Developer",
- "messages": [
- {
- "sender": "You",
- "message": "Sure ✌🏼",
- "timestamp": "2024-08-23T11:30:00"
- },
- {
- "sender": "Megan",
- "message": "Thanks, appreciate it!",
- "timestamp": "2024-08-23T11:30:00"
- },
- {
- "sender": "You",
- "message": "Sure thing! I'll take a look in the next hour.",
- "timestamp": "2024-08-23T11:25:00"
- },
- {
- "sender": "Megan",
- "message": "Hey! Do you have time to review my PR today?",
- "timestamp": "2024-08-23T11:20:00"
- }
- ]
- },
- {
- "id": "conv5",
- "profile": "https://randomuser.me/api/portraits/men/72.jpg",
- "username": "dev_david",
- "fullName": "David Brown",
- "title": "Senior UI/UX Designer",
- "messages": [
- {
- "sender": "You",
- "message": "Great, I'll review them now!",
- "timestamp": "2024-08-23T12:00:00"
- },
- {
- "sender": "David",
- "message": "Just sent you the files. Let me know if you need any changes.",
- "timestamp": "2024-08-23T11:58:00"
- },
- {
- "sender": "David",
- "message": "I finished the design for the dashboard. Thoughts?",
- "timestamp": "2024-08-23T11:55:00"
- }
- ]
- },
- {
- "id": "conv6",
- "profile": "https://randomuser.me/api/portraits/women/68.jpg",
- "username": "julia.design",
- "fullName": "Julia Carter",
- "title": "Product Designer",
- "messages": [
- {
- "sender": "Julia",
- "message": "Same here! It's coming together nicely.",
- "timestamp": "2024-08-22T14:10:00"
- },
- {
- "sender": "You",
- "message": "I'm really excited to see the final product!",
- "timestamp": "2024-08-22T14:15:00"
- },
- {
- "sender": "You",
- "message": "How's the project looking on your end?",
- "timestamp": "2024-08-22T14:05:00"
- }
- ]
- },
- {
- "id": "conv7",
- "profile": "https://randomuser.me/api/portraits/men/24.jpg",
- "username": "brad_dev",
- "fullName": "Brad Wilson",
- "title": "CEO",
- "messages": [
- {
- "sender": "Brad",
- "message": "Got it! Thanks for the update.",
- "timestamp": "2024-08-23T15:45:00"
- },
- {
- "sender": "You",
- "message": "The release has been delayed to next week.",
- "timestamp": "2024-08-23T15:40:00"
- },
- {
- "sender": "Brad",
- "message": "Hey, any news on the release?",
- "timestamp": "2024-08-23T15:35:00"
- }
- ]
- },
- {
- "id": "conv8",
- "profile": "https://randomuser.me/api/portraits/women/34.jpg",
- "username": "katie_ui",
- "fullName": "Katie Lee",
- "title": "QA",
- "messages": [
- {
- "sender": "Katie",
- "message": "I'll join the call in a few minutes.",
- "timestamp": "2024-08-23T09:50:00"
- },
- {
- "sender": "You",
- "message": "Perfect! We'll start as soon as you're in.",
- "timestamp": "2024-08-23T09:48:00"
- },
- {
- "sender": "Katie",
- "message": "Is the meeting still on?",
- "timestamp": "2024-08-23T09:45:00"
- }
- ]
- },
- {
- "id": "conv9",
- "profile": "https://randomuser.me/api/portraits/men/67.jpg",
- "username": "matt_fullstack",
- "fullName": "Matt Green",
- "title": "Full-stack Dev",
- "messages": [
- {
- "sender": "Matt",
- "message": "Sure thing, I'll send over the updates shortly.",
- "timestamp": "2024-08-23T10:25:00"
- },
- {
- "sender": "You",
- "message": "Could you update the backend as well?",
- "timestamp": "2024-08-23T10:23:00"
- },
- {
- "sender": "Matt",
- "message": "The frontend updates are done. How does it look?",
- "timestamp": "2024-08-23T10:20:00"
- }
- ]
- },
- {
- "id": "conv10",
- "profile": "https://randomuser.me/api/portraits/women/56.jpg",
- "username": "sophie_dev",
- "fullName": "Sophie Alex",
- "title": "Jr. Frontend Dev",
- "messages": [
- {
- "sender": "You",
- "message": "Thanks! I'll review your code and get back to you.",
- "timestamp": "2024-08-23T16:10:00"
- },
- {
- "sender": "Sophie",
- "message": "Let me know if you need anything else.",
- "timestamp": "2024-08-23T16:05:00"
- },
- {
- "sender": "Sophie",
- "message": "The feature is implemented. Can you review it?",
- "timestamp": "2024-08-23T16:00:00"
- }
- ]
- }
- ]
+ "conversations": [
+ {
+ "id": "conv1",
+ "profile": "https://randomuser.me/api/portraits/men/32.jpg",
+ "username": "alex_dev",
+ "fullName": "Alex John",
+ "title": "Senior Backend Dev",
+ "messages": [
+ {
+ "sender": "You",
+ "message": "See you later, Alex!",
+ "timestamp": "2024-08-24T11:15:15"
+ },
+ {
+ "sender": "Alex",
+ "message": "Alright, talk to you later!",
+ "timestamp": "2024-08-24T11:11:30"
+ },
+ {
+ "sender": "You",
+ "message": "For sure. Anyway, I should get back to reviewing the project.",
+ "timestamp": "2024-08-23T09:26:50"
+ },
+ {
+ "sender": "Alex",
+ "message": "Yeah, let me know what you think.",
+ "timestamp": "2024-08-23T09:25:15"
+ },
+ {
+ "sender": "You",
+ "message": "Oh, nice! I've been waiting for that. I'll check it out later.",
+ "timestamp": "2024-08-23T09:24:30"
+ },
+ {
+ "sender": "Alex",
+ "message": "They've added a dark mode option! It looks really sleek.",
+ "timestamp": "2024-08-23T09:23:10"
+ },
+ {
+ "sender": "You",
+ "message": "No, not yet. What's new?",
+ "timestamp": "2024-08-23T09:22:00"
+ },
+ {
+ "sender": "Alex",
+ "message": "By the way, have you seen the new feature update?",
+ "timestamp": "2024-08-23T09:21:05"
+ },
+ {
+ "sender": "You",
+ "message": "Will do! Thanks, Alex.",
+ "timestamp": "2024-08-23T09:20:10"
+ },
+ {
+ "sender": "Alex",
+ "message": "Great! Let me know if you need any help.",
+ "timestamp": "2024-08-23T09:19:20"
+ },
+ {
+ "sender": "You",
+ "message": "Almost done. Just need to review a few things.",
+ "timestamp": "2024-08-23T09:18:45"
+ },
+ {
+ "sender": "Alex",
+ "message": "I'm good, thanks! Did you finish the project?",
+ "timestamp": "2024-08-23T09:17:10"
+ },
+ {
+ "sender": "You",
+ "message": "Hey Alex, I'm doing well! How about you?",
+ "timestamp": "2024-08-23T09:16:30"
+ },
+ {
+ "sender": "Alex",
+ "message": "Hey Bob, how are you doing?",
+ "timestamp": "2024-08-23T09:15:00"
+ }
+ ]
+ },
+ {
+ "id": "conv2",
+ "profile": "https://randomuser.me/api/portraits/women/45.jpg",
+ "username": "taylor.codes",
+ "fullName": "Taylor Grande",
+ "title": "Tech Lead",
+ "messages": [
+ {
+ "sender": "Taylor",
+ "message": "Yeah, it's really well-explained. You should give it a try.",
+ "timestamp": "2024-08-23T10:35:00"
+ },
+ {
+ "sender": "You",
+ "message": "Not yet, is it good?",
+ "timestamp": "2024-08-23T10:32:00"
+ },
+ {
+ "sender": "Taylor",
+ "message": "Hey, did you check out that new tutorial?",
+ "timestamp": "2024-08-23T10:30:00"
+ }
+ ]
+ },
+ {
+ "id": "conv3",
+ "profile": "https://randomuser.me/api/portraits/men/54.jpg",
+ "username": "john_stack",
+ "fullName": "John Doe",
+ "title": "QA",
+ "messages": [
+ {
+ "sender": "You",
+ "message": "Yep, see ya. 👋🏼",
+ "timestamp": "2024-08-22T18:59:00"
+ },
+ {
+ "sender": "John",
+ "message": "Great, see you then!",
+ "timestamp": "2024-08-22T18:55:00"
+ },
+ {
+ "sender": "You",
+ "message": "Yes, same time as usual. I'll send the invite shortly.",
+ "timestamp": "2024-08-22T18:50:00"
+ },
+ {
+ "sender": "John",
+ "message": "Are we still on for the meeting tomorrow?",
+ "timestamp": "2024-08-22T18:45:00"
+ }
+ ]
+ },
+ {
+ "id": "conv4",
+ "profile": "https://randomuser.me/api/portraits/women/29.jpg",
+ "username": "megan_frontend",
+ "fullName": "Megan Flux",
+ "title": "Jr Developer",
+ "messages": [
+ {
+ "sender": "You",
+ "message": "Sure ✌🏼",
+ "timestamp": "2024-08-23T11:30:00"
+ },
+ {
+ "sender": "Megan",
+ "message": "Thanks, appreciate it!",
+ "timestamp": "2024-08-23T11:30:00"
+ },
+ {
+ "sender": "You",
+ "message": "Sure thing! I'll take a look in the next hour.",
+ "timestamp": "2024-08-23T11:25:00"
+ },
+ {
+ "sender": "Megan",
+ "message": "Hey! Do you have time to review my PR today?",
+ "timestamp": "2024-08-23T11:20:00"
+ }
+ ]
+ },
+ {
+ "id": "conv5",
+ "profile": "https://randomuser.me/api/portraits/men/72.jpg",
+ "username": "dev_david",
+ "fullName": "David Brown",
+ "title": "Senior UI/UX Designer",
+ "messages": [
+ {
+ "sender": "You",
+ "message": "Great, I'll review them now!",
+ "timestamp": "2024-08-23T12:00:00"
+ },
+ {
+ "sender": "David",
+ "message": "Just sent you the files. Let me know if you need any changes.",
+ "timestamp": "2024-08-23T11:58:00"
+ },
+ {
+ "sender": "David",
+ "message": "I finished the design for the dashboard. Thoughts?",
+ "timestamp": "2024-08-23T11:55:00"
+ }
+ ]
+ },
+ {
+ "id": "conv6",
+ "profile": "https://randomuser.me/api/portraits/women/68.jpg",
+ "username": "julia.design",
+ "fullName": "Julia Carter",
+ "title": "Product Designer",
+ "messages": [
+ {
+ "sender": "Julia",
+ "message": "Same here! It's coming together nicely.",
+ "timestamp": "2024-08-22T14:10:00"
+ },
+ {
+ "sender": "You",
+ "message": "I'm really excited to see the final product!",
+ "timestamp": "2024-08-22T14:15:00"
+ },
+ {
+ "sender": "You",
+ "message": "How's the project looking on your end?",
+ "timestamp": "2024-08-22T14:05:00"
+ }
+ ]
+ },
+ {
+ "id": "conv7",
+ "profile": "https://randomuser.me/api/portraits/men/24.jpg",
+ "username": "brad_dev",
+ "fullName": "Brad Wilson",
+ "title": "CEO",
+ "messages": [
+ {
+ "sender": "Brad",
+ "message": "Got it! Thanks for the update.",
+ "timestamp": "2024-08-23T15:45:00"
+ },
+ {
+ "sender": "You",
+ "message": "The release has been delayed to next week.",
+ "timestamp": "2024-08-23T15:40:00"
+ },
+ {
+ "sender": "Brad",
+ "message": "Hey, any news on the release?",
+ "timestamp": "2024-08-23T15:35:00"
+ }
+ ]
+ },
+ {
+ "id": "conv8",
+ "profile": "https://randomuser.me/api/portraits/women/34.jpg",
+ "username": "katie_ui",
+ "fullName": "Katie Lee",
+ "title": "QA",
+ "messages": [
+ {
+ "sender": "Katie",
+ "message": "I'll join the call in a few minutes.",
+ "timestamp": "2024-08-23T09:50:00"
+ },
+ {
+ "sender": "You",
+ "message": "Perfect! We'll start as soon as you're in.",
+ "timestamp": "2024-08-23T09:48:00"
+ },
+ {
+ "sender": "Katie",
+ "message": "Is the meeting still on?",
+ "timestamp": "2024-08-23T09:45:00"
+ }
+ ]
+ },
+ {
+ "id": "conv9",
+ "profile": "https://randomuser.me/api/portraits/men/67.jpg",
+ "username": "matt_fullstack",
+ "fullName": "Matt Green",
+ "title": "Full-stack Dev",
+ "messages": [
+ {
+ "sender": "Matt",
+ "message": "Sure thing, I'll send over the updates shortly.",
+ "timestamp": "2024-08-23T10:25:00"
+ },
+ {
+ "sender": "You",
+ "message": "Could you update the backend as well?",
+ "timestamp": "2024-08-23T10:23:00"
+ },
+ {
+ "sender": "Matt",
+ "message": "The frontend updates are done. How does it look?",
+ "timestamp": "2024-08-23T10:20:00"
+ }
+ ]
+ },
+ {
+ "id": "conv10",
+ "profile": "https://randomuser.me/api/portraits/women/56.jpg",
+ "username": "sophie_dev",
+ "fullName": "Sophie Alex",
+ "title": "Jr. Frontend Dev",
+ "messages": [
+ {
+ "sender": "You",
+ "message": "Thanks! I'll review your code and get back to you.",
+ "timestamp": "2024-08-23T16:10:00"
+ },
+ {
+ "sender": "Sophie",
+ "message": "Let me know if you need anything else.",
+ "timestamp": "2024-08-23T16:05:00"
+ },
+ {
+ "sender": "Sophie",
+ "message": "The feature is implemented. Can you review it?",
+ "timestamp": "2024-08-23T16:00:00"
+ }
+ ]
+ }
+ ]
}
diff --git a/frontend/src/pages/chats/index.tsx b/frontend/src/pages/chats/index.tsx
index 7a3dd58..ef3639c 100644
--- a/frontend/src/pages/chats/index.tsx
+++ b/frontend/src/pages/chats/index.tsx
@@ -12,7 +12,7 @@ import {
IconPlus,
IconSearch,
IconSend,
- IconVideo
+ IconVideo,
} from "@tabler/icons-react";
import { cn } from "@/lib/utils";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -33,14 +33,14 @@ export default function Chats() {
const [search, setSearch] = useState("");
const [selectedUser, setSelectedUser] = useState(null);
const [mobileSelectedUser, setMobileSelectedUser] = useState(
- null
+ null,
);
const [createConversationDialogOpened, setCreateConversationDialog] =
useState(false);
// Filtered data based on the search query
const filteredChatList = conversations.filter(({ fullName }) =>
- fullName.toLowerCase().includes(search.trim().toLowerCase())
+ fullName.toLowerCase().includes(search.trim().toLowerCase()),
);
const currentMessage = selectedUser?.messages.reduce(
@@ -57,7 +57,7 @@ export default function Chats() {
return acc;
},
- {}
+ {},
);
const users = conversations.map(({ messages, ...user }) => user);
@@ -88,7 +88,8 @@ export default function Chats() {
size="icon"
variant="ghost"
onClick={() => setCreateConversationDialog(true)}
- className="rounded-lg">
+ className="rounded-lg"
+ >
@@ -120,12 +121,13 @@ export default function Chats() {
type="button"
className={cn(
`-mx-1 flex w-full rounded-md px-2 py-2 text-left text-sm hover:bg-secondary/75`,
- selectedUser?.id === id && "sm:bg-muted"
+ selectedUser?.id === id && "sm:bg-muted",
)}
onClick={() => {
setSelectedUser(chatUsr);
setMobileSelectedUser(chatUsr);
- }}>
+ }}
+ >
@@ -153,8 +155,9 @@ export default function Chats() {
+ mobileSelectedUser && "left-0 flex",
+ )}
+ >
{/* Top Part */}
{/* Left */}
@@ -163,7 +166,8 @@ export default function Chats() {
size="icon"
variant="ghost"
className="-ml-2 h-full sm:hidden"
- onClick={() => setMobileSelectedUser(null)}>
+ onClick={() => setMobileSelectedUser(null)}
+ >
@@ -190,19 +194,22 @@ export default function Chats() {
+ className="hidden size-8 rounded-full sm:inline-flex lg:size-10"
+ >
+ className="hidden size-8 rounded-full sm:inline-flex lg:size-10"
+ >
+ className="h-10 rounded-md sm:h-8 sm:w-4 lg:h-10 lg:w-6"
+ >
@@ -223,14 +230,16 @@ export default function Chats() {
"chat-box max-w-72 break-words px-3 py-2 shadow-lg",
msg.sender === "You"
? "self-end rounded-[16px_16px_0_16px] bg-primary/85 text-primary-foreground/75"
- : "self-start rounded-[16px_16px_16px_0] bg-secondary"
- )}>
+ : "self-start rounded-[16px_16px_16px_0] bg-secondary",
+ )}
+ >
{msg.message}{" "}
+ msg.sender === "You" && "text-right",
+ )}
+ >
{format(msg.timestamp, "h:mm a")}
@@ -248,7 +257,8 @@ export default function Chats() {
size="icon"
type="button"
variant="ghost"
- className="h-8 rounded-md">
+ className="h-8 rounded-md"
+ >
+ className="hidden h-8 rounded-md lg:inline-flex"
+ >
+ className="hidden h-8 rounded-md lg:inline-flex"
+ >
+ className="hidden sm:inline-flex"
+ >
@@ -299,8 +312,9 @@ export default function Chats() {
) : (
+ "absolute inset-0 left-full z-50 hidden w-full flex-1 flex-col justify-center rounded-md border bg-primary-foreground shadow-sm transition-all duration-200 sm:static sm:z-auto sm:flex",
+ )}
+ >
@@ -313,7 +327,8 @@ export default function Chats() {
setCreateConversationDialog(true)}>
+ onClick={() => setCreateConversationDialog(true)}
+ >
Send message
diff --git a/frontend/src/pages/dashboard/components/overview.tsx b/frontend/src/pages/dashboard/components/overview.tsx
index e91a8d7..7269fb4 100644
--- a/frontend/src/pages/dashboard/components/overview.tsx
+++ b/frontend/src/pages/dashboard/components/overview.tsx
@@ -3,52 +3,52 @@ import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts";
const data = [
{
name: "Jan",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Feb",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Mar",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Apr",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "May",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Jun",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Jul",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Aug",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Sep",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Oct",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Nov",
- total: Math.floor(Math.random() * 5000) + 1000
+ total: Math.floor(Math.random() * 5000) + 1000,
},
{
name: "Dec",
- total: Math.floor(Math.random() * 5000) + 1000
- }
+ total: Math.floor(Math.random() * 5000) + 1000,
+ },
];
export function Overview() {
diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx
index a07c695..fb58360 100644
--- a/frontend/src/pages/dashboard/index.tsx
+++ b/frontend/src/pages/dashboard/index.tsx
@@ -3,9 +3,9 @@ import {
CardContent,
CardDescription,
CardHeader,
- CardTitle
+ CardTitle,
} from "@/components/ui/card";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Tabs, TabsContent } from "@/components/ui/tabs";
import { Header } from "@/components/layout/header";
import { Main } from "@/components/layout/main";
import { ProfileDropdown } from "@/components/profile-dropdown";
@@ -34,21 +34,8 @@ export default function Dashboard() {
-
-
- Overview
-
- Analytics
-
-
- Reports
-
-
- Notifications
-
-
-
+ className="space-y-4"
+ >
@@ -64,7 +51,8 @@ export default function Dashboard() {
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
- className="h-4 w-4 text-muted-foreground">
+ className="h-4 w-4 text-muted-foreground"
+ >
@@ -88,7 +76,8 @@ export default function Dashboard() {
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
- className="h-4 w-4 text-muted-foreground">
+ className="h-4 w-4 text-muted-foreground"
+ >
@@ -112,7 +101,8 @@ export default function Dashboard() {
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
- className="h-4 w-4 text-muted-foreground">
+ className="h-4 w-4 text-muted-foreground"
+ >
@@ -137,7 +127,8 @@ export default function Dashboard() {
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
- className="h-4 w-4 text-muted-foreground">
+ className="h-4 w-4 text-muted-foreground"
+ >
@@ -175,4 +166,4 @@ export default function Dashboard() {
>
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/errors/general-error.tsx b/frontend/src/pages/errors/general-error.tsx
index e296f41..2dfd2c2 100644
--- a/frontend/src/pages/errors/general-error.tsx
+++ b/frontend/src/pages/errors/general-error.tsx
@@ -8,7 +8,7 @@ interface GeneralErrorProps extends React.HTMLAttributes {
export default function GeneralError({
className,
- minimal = false
+ minimal = false,
}: GeneralErrorProps) {
const navigate = useNavigate();
const { history } = useRouter();
diff --git a/frontend/src/pages/home/components/call-to-action.tsx b/frontend/src/pages/home/components/call-to-action.tsx
index 90b3a39..20f4159 100644
--- a/frontend/src/pages/home/components/call-to-action.tsx
+++ b/frontend/src/pages/home/components/call-to-action.tsx
@@ -1,11 +1,13 @@
import { Button } from "@/components/ui/button";
export default function CallToAction() {
- return (
-
-
Ready to Get Started?
-
Join today and start managing your shop with ease.
-
Sign Up Now
-
- );
- };
\ No newline at end of file
+ return (
+
+
Ready to Get Started?
+
+ Join today and start managing your shop with ease.
+
+
Sign Up Now
+
+ );
+}
diff --git a/frontend/src/pages/home/components/faq.tsx b/frontend/src/pages/home/components/faq.tsx
index d084d59..0d9ea5a 100644
--- a/frontend/src/pages/home/components/faq.tsx
+++ b/frontend/src/pages/home/components/faq.tsx
@@ -2,7 +2,7 @@ import {
Accordion,
AccordionContent,
AccordionItem,
- AccordionTrigger
+ AccordionTrigger,
} from "@/components/ui/accordion";
import { faqs } from "../data/faq-data";
diff --git a/frontend/src/pages/home/components/pricing.tsx b/frontend/src/pages/home/components/pricing.tsx
index dd4c497..b16d7b6 100644
--- a/frontend/src/pages/home/components/pricing.tsx
+++ b/frontend/src/pages/home/components/pricing.tsx
@@ -5,7 +5,9 @@ import { Button } from "@/components/ui/button";
export default function Pricing() {
return (
-
Pricing Plans
+
+ Pricing Plans
+
Prices are subject to change. These estimates help guide your decision.
diff --git a/frontend/src/pages/home/data/faq-data.ts b/frontend/src/pages/home/data/faq-data.ts
index 786d310..63c8a36 100644
--- a/frontend/src/pages/home/data/faq-data.ts
+++ b/frontend/src/pages/home/data/faq-data.ts
@@ -2,14 +2,14 @@ export const faqs = [
{
question: "How do I get started?",
answer:
- "Sign up for a free account and start integrating our API into your shop."
+ "Sign up for a free account and start integrating our API into your shop.",
},
{
question: "Can I upgrade or downgrade my plan?",
- answer: "Yes, you can change your plan anytime from your dashboard."
+ answer: "Yes, you can change your plan anytime from your dashboard.",
},
{
question: "Is there customer support?",
- answer: "Yes, we offer different levels of support depending on your plan."
- }
+ answer: "Yes, we offer different levels of support depending on your plan.",
+ },
];
diff --git a/frontend/src/pages/home/data/feature-data.ts b/frontend/src/pages/home/data/feature-data.ts
index 4c06595..efced04 100644
--- a/frontend/src/pages/home/data/feature-data.ts
+++ b/frontend/src/pages/home/data/feature-data.ts
@@ -2,30 +2,30 @@ export const features = [
{
title: "Inventory Control",
description:
- "Easily manage product stock, availability, and updates in real time."
+ "Easily manage product stock, availability, and updates in real time.",
},
{
title: "Flexible API",
description:
- "Build custom storefronts with a backend that adapts to your needs."
+ "Build custom storefronts with a backend that adapts to your needs.",
},
{
title: "Seamless Transactions",
- description: "Handle purchases smoothly with minimal backend complexity."
+ description: "Handle purchases smoothly with minimal backend complexity.",
},
{
title: "User Roles & Permissions",
description:
- "Predefined roles (Owner, Manager, Employee) with different access levels to manage store operations efficiently."
+ "Predefined roles (Owner, Manager, Employee) with different access levels to manage store operations efficiently.",
},
{
title: "Multi-Currency Support",
description:
- "Each shop can define its default currency to handle transactions in the appropriate financial context."
+ "Each shop can define its default currency to handle transactions in the appropriate financial context.",
},
{
title: "Future-Ready Analytics",
description:
- "Upcoming analytics and statistics to help businesses track performance and make data-driven decisions."
- }
-];
\ No newline at end of file
+ "Upcoming analytics and statistics to help businesses track performance and make data-driven decisions.",
+ },
+];
diff --git a/frontend/src/pages/home/data/pricing-data.ts b/frontend/src/pages/home/data/pricing-data.ts
index 9e8b85e..f1f753c 100644
--- a/frontend/src/pages/home/data/pricing-data.ts
+++ b/frontend/src/pages/home/data/pricing-data.ts
@@ -1,20 +1,32 @@
export const plans = [
- {
- title: "Free Plan",
- price: "$0/month",
- description: "Basic features for small shops starting out.",
- features: ["Basic Inventory Management", "Limited API Requests", "Community Support"]
- },
- {
- title: "Pro Plan",
- price: "$29/month",
- description: "For growing businesses needing more control.",
- features: ["Advanced Inventory Management", "Higher API Limits", "Email Support"]
- },
- {
- title: "Enterprise Plan",
- price: "$99/month",
- description: "For high-scale businesses requiring full access.",
- features: ["Unlimited API Access", "Priority Support", "Custom Integrations"]
- }
- ];
\ No newline at end of file
+ {
+ title: "Free Plan",
+ price: "$0/month",
+ description: "Basic features for small shops starting out.",
+ features: [
+ "Basic Inventory Management",
+ "Limited API Requests",
+ "Community Support",
+ ],
+ },
+ {
+ title: "Pro Plan",
+ price: "$29/month",
+ description: "For growing businesses needing more control.",
+ features: [
+ "Advanced Inventory Management",
+ "Higher API Limits",
+ "Email Support",
+ ],
+ },
+ {
+ title: "Enterprise Plan",
+ price: "$99/month",
+ description: "For high-scale businesses requiring full access.",
+ features: [
+ "Unlimited API Access",
+ "Priority Support",
+ "Custom Integrations",
+ ],
+ },
+];
diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx
index a5c79f6..907210f 100644
--- a/frontend/src/pages/home/index.tsx
+++ b/frontend/src/pages/home/index.tsx
@@ -10,7 +10,6 @@ export default function MainPage() {
<>
-
{/* Top screen padding */}
diff --git a/frontend/src/pages/products/components/product-dialog.tsx b/frontend/src/pages/products/components/product-dialog.tsx
index 7f9e3b0..688ddce 100644
--- a/frontend/src/pages/products/components/product-dialog.tsx
+++ b/frontend/src/pages/products/components/product-dialog.tsx
@@ -6,7 +6,7 @@ import {
DialogContent,
DialogHeader,
DialogTitle,
- DialogFooter
+ DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -37,7 +37,7 @@ export function ProductDialog({
onImageUpload,
submitLabel,
imagePreview,
- showDeleteButton = false
+ showDeleteButton = false,
}: ProductDialogProps) {
return (
@@ -76,7 +76,7 @@ export function ProductDialog({
id="price"
{...form.register("price", {
valueAsNumber: true,
- setValueAs: (v) => parseFloat(v).toFixed(2)
+ setValueAs: (v) => parseFloat(v).toFixed(2),
})}
placeholder="0.00"
/>
diff --git a/frontend/src/pages/products/components/product-form.tsx b/frontend/src/pages/products/components/product-form.tsx
index 1675a0a..53388f4 100644
--- a/frontend/src/pages/products/components/product-form.tsx
+++ b/frontend/src/pages/products/components/product-form.tsx
@@ -10,7 +10,7 @@ const schema = z.object({
description: z.string().min(1, "Description is required"),
price: z.coerce.number().min(0),
stock_quantity: z.coerce.number().int().min(0),
- image_data: z.string().optional()
+ image_data: z.string().optional(),
});
type ProductForm = z.infer;
@@ -20,7 +20,7 @@ export function ProductForm({
form,
imagePreview,
onImageUpload,
- submitLabel
+ submitLabel,
}: {
onSubmit: () => void;
form: UseFormReturn;
diff --git a/frontend/src/pages/products/components/product-list.tsx b/frontend/src/pages/products/components/product-list.tsx
index 2cfc19a..a489db5 100644
--- a/frontend/src/pages/products/components/product-list.tsx
+++ b/frontend/src/pages/products/components/product-list.tsx
@@ -4,16 +4,16 @@ import {
Card,
CardDescription,
CardHeader,
- CardTitle
+ CardTitle,
} from "@/components/ui/card";
export function ProductList({
currency,
products,
isLoading,
- onClick
+ onClick,
}: {
- currency: string,
+ currency: string;
products: ProductWithDetails[];
isLoading: boolean;
onClick: (id: number) => void;
@@ -26,7 +26,8 @@ export function ProductList({
onClick(product.id)}
- className="cursor-pointer overflow-hidden">
+ className="cursor-pointer overflow-hidden"
+ >
{product.images.length > 0 ? (
;
@@ -23,8 +25,8 @@ export function useProductForm(productData?: ProductWithDetails | null) {
description: "",
price: 0,
stock_quantity: 0,
- image_data: ""
- }
+ image_data: "",
+ },
});
useEffect(() => {
@@ -34,7 +36,7 @@ export function useProductForm(productData?: ProductWithDetails | null) {
description: productData.description,
price: productData.price,
stock_quantity: productData.stock_quantity,
- image_data: productData.images[0]?.image_url || ""
+ image_data: productData.images[0]?.image_url || "",
});
}
}, [productData, form]);
diff --git a/frontend/src/pages/products/index.tsx b/frontend/src/pages/products/index.tsx
index ba5bb2e..f53a020 100644
--- a/frontend/src/pages/products/index.tsx
+++ b/frontend/src/pages/products/index.tsx
@@ -2,7 +2,7 @@ import {
IconAdjustmentsHorizontal,
IconSortAscendingLetters,
IconSortDescendingLetters,
- IconPlus
+ IconPlus,
} from "@tabler/icons-react";
import { Header } from "@/components/layout/header";
@@ -17,7 +17,7 @@ import {
SelectTrigger,
SelectValue,
SelectContent,
- SelectItem
+ SelectItem,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
@@ -60,7 +60,7 @@ export default function Products() {
description: "",
price: 0,
stock_quantity: 0,
- image_data: ""
+ image_data: "",
});
setSelectedProductId(undefined);
setDialogOpen(true);
@@ -89,11 +89,11 @@ export default function Products() {
const sortedProducts = [...products].sort((a, b) =>
sort === "ascending"
? a.name.localeCompare(b.name)
- : b.name.localeCompare(a.name)
+ : b.name.localeCompare(a.name),
);
const filteredProducts = sortedProducts.filter((product) =>
- product.name.toLowerCase().includes(searchTerm.toLowerCase())
+ product.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
return (
@@ -125,7 +125,8 @@ export default function Products() {
/>
setSort(v as "ascending" | "descending")}>
+ onValueChange={(v) => setSort(v as "ascending" | "descending")}
+ >
@@ -163,7 +164,8 @@ export default function Products() {
handleCreateClicked()}
className="absolute bottom-0 z-10 mb-4 size-14 rounded-full p-0 shadow-lg hover:brightness-110"
- variant="default">
+ variant="default"
+ >
diff --git a/frontend/src/pages/sales/coupons/components/coupon-action-dialog.tsx b/frontend/src/pages/sales/coupons/components/coupon-action-dialog.tsx
new file mode 100644
index 0000000..c7f8994
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/coupon-action-dialog.tsx
@@ -0,0 +1,198 @@
+"use client";
+
+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { toast } from "@/hooks/useToast";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { Coupon } from "../data/schema";
+import { useCoupon } from "@/hooks/useCoupon";
+
+const formSchema = z.object({
+ name: z.string().min(1, { message: "Name is required." }),
+ text: z.string().min(1, { message: "Text is required." }),
+ valid_due: z.coerce.date({
+ errorMap: () => ({ message: "Invalid date." }),
+ }),
+ discount_amount: z
+ .number({ invalid_type_error: "Must be a number." })
+ .min(1, { message: "Discount must be at least 1%." })
+ .max(100, { message: "Discount can't exceed 100%." }),
+ isEdit: z.boolean(),
+});
+
+type CouponForm = z.infer;
+
+interface Props {
+ currentRow?: Coupon;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export function CouponsActionDialog({ currentRow, open, onOpenChange }: Props) {
+ const { createCoupon, updateCoupon } = useCoupon(currentRow?.id);
+ const isEdit = !!currentRow;
+
+ const form = useForm({
+ resolver: zodResolver(formSchema),
+ defaultValues: isEdit
+ ? {
+ ...currentRow,
+ isEdit,
+ }
+ : {
+ name: "",
+ text: "",
+ valid_due: new Date(),
+ discount_amount: 10,
+ isEdit,
+ },
+ });
+
+ const onSubmit = (values: CouponForm) => {
+ try {
+ const payload = {
+ ...values,
+ valid_due: values.valid_due.toISOString(), // convert to string
+ };
+
+ if (isEdit) {
+ updateCoupon.mutate({ ...currentRow, ...payload });
+ } else {
+ createCoupon.mutate({
+ ...payload,
+ });
+ }
+
+ toast({
+ title: isEdit ? "Coupon updated" : "Coupon created",
+ description: `${values.name} (${values.discount_amount}%)`,
+ });
+ form.reset();
+ onOpenChange(false);
+ } catch (err) {
+ toast({
+ title: "An error occurred",
+ description: (err as Error).message,
+ variant: "destructive",
+ });
+ }
+ };
+
+ return (
+ {
+ form.reset();
+ onOpenChange(state);
+ }}
+ >
+
+
+ {isEdit ? "Edit Coupon" : "Add New Coupon"}
+
+ {isEdit ? "Update the coupon below." : "Create a new coupon."}
+
+
+
+
+
+
+
+
+
+
+ Save changes
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/coupon-columns.tsx b/frontend/src/pages/sales/coupons/components/coupon-columns.tsx
new file mode 100644
index 0000000..46c85cc
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/coupon-columns.tsx
@@ -0,0 +1,121 @@
+import { ColumnDef } from "@tanstack/react-table";
+import { Checkbox } from "@/components/ui/checkbox";
+import { cn } from "@/lib/utils";
+import { Coupon } from "../data/schema";
+import { DataTableColumnHeader } from "./data-table-column-header";
+import { DataTableRowActions } from "./data-table-row-actions";
+import { Badge } from "@/components/ui/badge";
+import { format, isBefore } from "date-fns";
+import { couponStatusColors, couponTypes } from "../data/data";
+
+export const columns: ColumnDef[] = [
+ {
+ id: "select",
+ header: ({ table }) => (
+ table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-[2px]"
+ />
+ ),
+ meta: {
+ className: cn(
+ "sticky md:table-cell left-0 z-10 rounded-tl",
+ "bg-background transition-colors duration-200 group-hover/row:bg-muted group-data-[state=selected]/row:bg-muted",
+ ),
+ },
+ cell: ({ row }) => (
+ row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-[2px]"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "name",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => (
+ {row.getValue("name")}
+ ),
+ enableHiding: false,
+ },
+ {
+ accessorKey: "text",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => (
+
+ {row.getValue("text")}
+
+ ),
+ },
+ {
+ accessorKey: "Valid Due",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {
+ const { valid_due } = row.original;
+ return {format(new Date(valid_due), "PPPp")}
;
+ },
+ },
+ {
+ accessorKey: "status",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {
+ const validDue = new Date(row.original.valid_due);
+ const now = new Date();
+
+ const isExpired = isBefore(validDue, now);
+ const status = isExpired ? "expired" : "active";
+ const badgeColor = couponStatusColors.get(status);
+ const statusMeta = couponTypes.find((t) => t.value === status);
+
+ return (
+
+ {statusMeta?.icon && (
+
+ )}
+
+ {statusMeta?.label}
+
+
+ );
+ },
+ filterFn: (row, id, value) => {
+ const validDue = new Date(row.original.valid_due);
+ const now = new Date();
+ const status = isBefore(validDue, now) ? "expired" : "active";
+ return value.includes(status);
+ },
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "Discount",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {
+ const { discount_amount } = row.original;
+ return {discount_amount}% ;
+ },
+ },
+ {
+ id: "actions",
+ cell: DataTableRowActions,
+ },
+];
diff --git a/frontend/src/pages/sales/coupons/components/coupon-delete-dialog.tsx b/frontend/src/pages/sales/coupons/components/coupon-delete-dialog.tsx
new file mode 100644
index 0000000..9e092e6
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/coupon-delete-dialog.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import { toast } from "@/hooks/useToast";
+import { IconAlertTriangle } from "@tabler/icons-react";
+import { ConfirmDialog } from "@/components/confirm-dialog";
+import { Coupon } from "../data/schema";
+import { useCoupon } from "@/hooks/useCoupon";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+
+interface Props {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ currentRow: Coupon;
+}
+
+export function CouponsDeleteDialog({ open, onOpenChange, currentRow }: Props) {
+ const { deleteCoupon } = useCoupon(currentRow.id);
+
+ const handleDelete = () => {
+ deleteCoupon.mutate(currentRow.id);
+
+ onOpenChange(false);
+ toast({
+ title: "Coupon deleted",
+ description: `"${currentRow.name}" (${currentRow.discount_amount}% off) has been removed.`,
+ });
+ };
+
+ return (
+
+ {" "}
+ Delete Coupon
+
+ }
+ desc={
+
+
+ Are you sure you want to delete the coupon{" "}
+ {currentRow.name} ?
+
+ This action cannot be undone.
+
+
+
+ Warning!
+
+ Once removed, this coupon will no longer be available to
+ customers.
+
+
+
+ }
+ confirmText="Delete"
+ destructive
+ />
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/coupon-dialog.tsx b/frontend/src/pages/sales/coupons/components/coupon-dialog.tsx
new file mode 100644
index 0000000..5af8e50
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/coupon-dialog.tsx
@@ -0,0 +1,45 @@
+import { useCouponsContext } from "../context/coupon-context";
+import { CouponsActionDialog } from "./coupon-action-dialog";
+import { CouponsDeleteDialog } from "./coupon-delete-dialog";
+
+export function CouponsDialogs() {
+ const { open, setOpen, currentRow, setCurrentRow } = useCouponsContext();
+
+ return (
+ <>
+ setOpen("add")}
+ />
+
+ {currentRow && (
+ <>
+ {
+ setOpen("edit");
+ setTimeout(() => {
+ setCurrentRow(null);
+ }, 500);
+ }}
+ currentRow={currentRow}
+ />
+
+ {
+ setOpen("delete");
+ setTimeout(() => {
+ setCurrentRow(null);
+ }, 500);
+ }}
+ currentRow={currentRow}
+ />
+ >
+ )}
+ >
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/coupon-primary-buttons.tsx b/frontend/src/pages/sales/coupons/components/coupon-primary-buttons.tsx
new file mode 100644
index 0000000..aaccece
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/coupon-primary-buttons.tsx
@@ -0,0 +1,14 @@
+import { IconPlus } from "@tabler/icons-react";
+import { Button } from "@/components/ui/button";
+import { useCouponsContext } from "../context/coupon-context";
+
+export function CouponPrimaryButtons() {
+ const { setOpen } = useCouponsContext();
+ return (
+
+ setOpen("add")}>
+ Add Coupon
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/coupon-table.tsx b/frontend/src/pages/sales/coupons/components/coupon-table.tsx
new file mode 100644
index 0000000..d91820f
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/coupon-table.tsx
@@ -0,0 +1,131 @@
+import { useState } from "react";
+import {
+ ColumnDef,
+ ColumnFiltersState,
+ RowData,
+ SortingState,
+ VisibilityState,
+ flexRender,
+ getCoreRowModel,
+ getFacetedRowModel,
+ getFacetedUniqueValues,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Coupon } from "../data/schema";
+import { DataTablePagination } from "./data-table-pagination";
+import { DataTableToolbar } from "./data-table-toolbar";
+
+declare module "@tanstack/react-table" {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ interface ColumnMeta {
+ className: string;
+ }
+}
+
+interface DataTableProps {
+ columns: ColumnDef[];
+ data: Coupon[];
+}
+
+export function CouponsTable({ columns, data }: DataTableProps) {
+ const [rowSelection, setRowSelection] = useState({});
+ const [columnVisibility, setColumnVisibility] = useState({});
+ const [columnFilters, setColumnFilters] = useState([]);
+ const [sorting, setSorting] = useState([]);
+
+ const table = useReactTable({
+ data,
+ columns,
+ state: {
+ sorting,
+ columnVisibility,
+ rowSelection,
+ columnFilters,
+ },
+ enableRowSelection: true,
+ onRowSelectionChange: setRowSelection,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ onColumnVisibilityChange: setColumnVisibility,
+ getCoreRowModel: getCoreRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFacetedRowModel: getFacetedRowModel(),
+ getFacetedUniqueValues: getFacetedUniqueValues(),
+ });
+
+ return (
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/data-table-column-header.tsx b/frontend/src/pages/sales/coupons/components/data-table-column-header.tsx
new file mode 100644
index 0000000..eadd055
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/data-table-column-header.tsx
@@ -0,0 +1,74 @@
+import {
+ ArrowDownIcon,
+ ArrowUpIcon,
+ CaretSortIcon,
+ EyeNoneIcon,
+} from "@radix-ui/react-icons";
+import { Column } from "@tanstack/react-table";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+interface DataTableColumnHeaderProps
+ extends React.HTMLAttributes {
+ column: Column;
+ title: string;
+}
+
+export function DataTableColumnHeader({
+ column,
+ title,
+ className,
+}: DataTableColumnHeaderProps) {
+ if (!column.getCanSort()) {
+ return {title}
;
+ }
+
+ return (
+
+
+
+
+ {title}
+ {column.getIsSorted() === "desc" ? (
+
+ ) : column.getIsSorted() === "asc" ? (
+
+ ) : (
+
+ )}
+
+
+
+ column.toggleSorting(false)}>
+
+ Asc
+
+ column.toggleSorting(true)}>
+
+ Desc
+
+ {column.getCanHide() && (
+ <>
+
+ column.toggleVisibility(false)}>
+
+ Hide
+
+ >
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/data-table-faceted-filter.tsx b/frontend/src/pages/sales/coupons/components/data-table-faceted-filter.tsx
new file mode 100644
index 0000000..5275546
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/data-table-faceted-filter.tsx
@@ -0,0 +1,145 @@
+import * as React from "react";
+import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons";
+import { Column } from "@tanstack/react-table";
+import { cn } from "@/lib/utils";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Separator } from "@/components/ui/separator";
+
+interface DataTableFacetedFilterProps {
+ column?: Column;
+ title?: string;
+ options: {
+ label: string;
+ value: string;
+ icon?: React.ComponentType<{ className?: string }>;
+ }[];
+}
+
+export function DataTableFacetedFilter({
+ column,
+ title,
+ options,
+}: DataTableFacetedFilterProps) {
+ const facets = column?.getFacetedUniqueValues();
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
+ return (
+
+
+
+
+ {title}
+ {selectedValues?.size > 0 && (
+ <>
+
+
+ {selectedValues.size}
+
+
+ {selectedValues.size > 2 ? (
+
+ {selectedValues.size} selected
+
+ ) : (
+ options
+ .filter((option) => selectedValues.has(option.value))
+ .map((option) => (
+
+ {option.label}
+
+ ))
+ )}
+
+ >
+ )}
+
+
+
+
+
+
+ No results found.
+
+ {options.map((option) => {
+ const isSelected = selectedValues.has(option.value);
+ return (
+ {
+ if (isSelected) {
+ selectedValues.delete(option.value);
+ } else {
+ selectedValues.add(option.value);
+ }
+ const filterValues = Array.from(selectedValues);
+ column?.setFilterValue(
+ filterValues.length ? filterValues : undefined,
+ );
+ }}
+ >
+
+
+
+ {option.icon && (
+
+ )}
+ {option.label}
+ {facets?.get(option.value) && (
+
+ {facets.get(option.value)}
+
+ )}
+
+ );
+ })}
+
+ {selectedValues.size > 0 && (
+ <>
+
+
+ column?.setFilterValue(undefined)}
+ className="justify-center text-center"
+ >
+ Clear filters
+
+
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/data-table-pagination.tsx b/frontend/src/pages/sales/coupons/components/data-table-pagination.tsx
new file mode 100644
index 0000000..936f40e
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/data-table-pagination.tsx
@@ -0,0 +1,99 @@
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ DoubleArrowLeftIcon,
+ DoubleArrowRightIcon,
+} from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+interface DataTablePaginationProps {
+ table: Table;
+}
+
+export function DataTablePagination({
+ table,
+}: DataTablePaginationProps) {
+ return (
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
+ {table.getFilteredRowModel().rows.length} row(s) selected.
+
+
+
+
Rows per page
+
{
+ table.setPageSize(Number(value));
+ }}
+ >
+
+
+
+
+ {[10, 20, 30, 40, 50].map((pageSize) => (
+
+ {pageSize}
+
+ ))}
+
+
+
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "}
+ {table.getPageCount()}
+
+
+ table.setPageIndex(0)}
+ disabled={!table.getCanPreviousPage()}
+ >
+ Go to first page
+
+
+ table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ Go to previous page
+
+
+ table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ Go to next page
+
+
+ table.setPageIndex(table.getPageCount() - 1)}
+ disabled={!table.getCanNextPage()}
+ >
+ Go to last page
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/data-table-row-actions.tsx b/frontend/src/pages/sales/coupons/components/data-table-row-actions.tsx
new file mode 100644
index 0000000..bb1b079
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/data-table-row-actions.tsx
@@ -0,0 +1,61 @@
+import { DotsHorizontalIcon } from "@radix-ui/react-icons";
+import { Row } from "@tanstack/react-table";
+import { IconEdit, IconTrash } from "@tabler/icons-react";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { useCouponsContext } from "../context/coupon-context";
+import { Coupon } from "../data/schema";
+
+interface DataTableRowActionsProps {
+ row: Row;
+}
+
+export function DataTableRowActions({ row }: DataTableRowActionsProps) {
+ const { setOpen, setCurrentRow } = useCouponsContext();
+ return (
+
+
+
+
+ Open menu
+
+
+
+ {
+ setCurrentRow(row.original);
+ setOpen("edit");
+ }}
+ >
+ Edit
+
+
+
+
+
+ {
+ setCurrentRow(row.original);
+ setOpen("delete");
+ }}
+ className="!text-red-500"
+ >
+ Delete
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/data-table-toolbar.tsx b/frontend/src/pages/sales/coupons/components/data-table-toolbar.tsx
new file mode 100644
index 0000000..0477141
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/data-table-toolbar.tsx
@@ -0,0 +1,52 @@
+import { Cross2Icon } from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { couponTypes } from "../data/data";
+import { DataTableFacetedFilter } from "./data-table-faceted-filter";
+import { DataTableViewOptions } from "./data-table-view-options";
+
+interface DataTableToolbarProps {
+ table: Table;
+}
+
+export function DataTableToolbar({
+ table,
+}: DataTableToolbarProps) {
+ const isFiltered = table.getState().columnFilters.length > 0;
+
+ return (
+
+
+
+ table.getColumn("name")?.setFilterValue(event.target.value)
+ }
+ className="h-8 w-[150px] lg:w-[250px]"
+ />
+
+ {table.getColumn("valid_due") && (
+ ({ ...t }))}
+ />
+ )}
+
+ {isFiltered && (
+
table.resetColumnFilters()}
+ className="h-8 px-2 lg:px-3"
+ >
+ Reset
+
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/components/data-table-view-options.tsx b/frontend/src/pages/sales/coupons/components/data-table-view-options.tsx
new file mode 100644
index 0000000..b20f30a
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/components/data-table-view-options.tsx
@@ -0,0 +1,56 @@
+import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
+import { MixerHorizontalIcon } from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+} from "@/components/ui/dropdown-menu";
+
+interface DataTableViewOptionsProps {
+ table: Table;
+}
+
+export function DataTableViewOptions({
+ table,
+}: DataTableViewOptionsProps) {
+ return (
+
+
+
+
+ View
+
+
+
+ Toggle columns
+
+ {table
+ .getAllColumns()
+ .filter(
+ (column) =>
+ typeof column.accessorFn !== "undefined" && column.getCanHide(),
+ )
+ .map((column) => {
+ return (
+ column.toggleVisibility(!!value)}
+ >
+ {column.id}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/frontend/src/pages/sales/coupons/context/coupon-context.tsx b/frontend/src/pages/sales/coupons/context/coupon-context.tsx
new file mode 100644
index 0000000..cf106d8
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/context/coupon-context.tsx
@@ -0,0 +1,42 @@
+import React, { useState } from "react";
+import useDialogState from "@/hooks/useDialogState";
+import { Coupon } from "../data/schema";
+
+type CouponsDialogType = "add" | "edit" | "delete";
+
+interface CouponsContextType {
+ open: CouponsDialogType | null;
+ setOpen: (type: CouponsDialogType | null) => void;
+ currentRow: Coupon | null;
+ setCurrentRow: React.Dispatch>;
+}
+
+const CouponsContext = React.createContext(null);
+
+interface Props {
+ children: React.ReactNode;
+}
+
+export default function CouponsProvider({ children }: Props) {
+ const [open, setOpen] = useDialogState(null);
+ const [currentRow, setCurrentRow] = useState(null);
+
+ return (
+
+ {children}
+
+ );
+}
+
+// eslint-disable-next-line react-refresh/only-export-components
+export const useCouponsContext = () => {
+ const context = React.useContext(CouponsContext);
+
+ if (!context) {
+ throw new Error("useCouponsContext must be used within ");
+ }
+
+ return context;
+};
diff --git a/frontend/src/pages/sales/coupons/data/data.ts b/frontend/src/pages/sales/coupons/data/data.ts
new file mode 100644
index 0000000..9507565
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/data/data.ts
@@ -0,0 +1,25 @@
+import { IconClockCancel, IconClock } from "@tabler/icons-react";
+
+export const couponStatusColors = new Map<"active" | "expired", string>([
+ [
+ "active",
+ "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100 border border-green-300 dark:border-green-600",
+ ],
+ [
+ "expired",
+ "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100 border border-red-300 dark:border-red-600",
+ ],
+]);
+
+export const couponTypes = [
+ {
+ label: "Active",
+ value: "active",
+ icon: IconClock,
+ },
+ {
+ label: "Expired",
+ value: "expired",
+ icon: IconClockCancel,
+ },
+] as const;
diff --git a/frontend/src/pages/sales/coupons/data/schema.ts b/frontend/src/pages/sales/coupons/data/schema.ts
new file mode 100644
index 0000000..e2ef84f
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/data/schema.ts
@@ -0,0 +1,14 @@
+import { z } from "zod";
+
+// Coupon schema
+export const couponSchema = z.object({
+ id: z.number(),
+ name: z.string().max(200),
+ text: z.string().max(50),
+ valid_due: z.coerce.date(),
+ discount_amount: z.number().min(1).max(100),
+});
+
+export type Coupon = z.infer;
+
+export const couponListSchema = z.array(couponSchema);
diff --git a/frontend/src/pages/sales/coupons/index.tsx b/frontend/src/pages/sales/coupons/index.tsx
new file mode 100644
index 0000000..dd48508
--- /dev/null
+++ b/frontend/src/pages/sales/coupons/index.tsx
@@ -0,0 +1,69 @@
+import { Header } from "@/components/layout/header";
+import { Main } from "@/components/layout/main";
+import { ProfileDropdown } from "@/components/profile-dropdown";
+import { Search } from "@/components/search";
+import { ThemeSwitch } from "@/components/theme-switch";
+import { columns } from "./components/coupon-columns";
+import { CouponsDialogs } from "./components/coupon-dialog";
+import { CouponPrimaryButtons } from "./components/coupon-primary-buttons";
+import { CouponsTable } from "./components/coupon-table";
+import CouponsProvider from "./context/coupon-context";
+import { Coupon, couponListSchema } from "./data/schema";
+import { useCoupons } from "@/hooks/useCoupons";
+
+export default function Coupons() {
+ const { data, error, isLoading } = useCoupons();
+
+ let couponList: Coupon[] = [];
+ if (data) {
+ try {
+ couponList = couponListSchema.parse(data);
+ } catch (e) {
+ console.error("Schema validation failed:", e);
+ couponList = [];
+ }
+ }
+
+ console.log(couponList);
+
+ return (
+
+
+
+
+
+
+
Coupons
+
+ Create, edit and manage product discounts.
+
+
+
+
+
+ {isLoading && (
+ Loading coupons...
+ )}
+ {error && (
+
+ Failed to load coupons: {(error as Error).message}
+
+ )}
+
+ {!isLoading && !error && (
+
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/settings/account/account-form.tsx b/frontend/src/pages/settings/account/account-form.tsx
index 73ccaac..02d60f0 100644
--- a/frontend/src/pages/settings/account/account-form.tsx
+++ b/frontend/src/pages/settings/account/account-form.tsx
@@ -2,14 +2,14 @@ import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
-import
- {
- Form,
- FormControl, FormField,
- FormItem,
- FormLabel,
- FormMessage
- } from "@/components/ui/form";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import useAuth from "@/hooks/useAuth";
@@ -46,7 +46,7 @@ export function AccountForm() {
const { updateAccountMutation, user } = useAuth();
const form = useForm({
- resolver: zodResolver(accountFormSchema)
+ resolver: zodResolver(accountFormSchema),
});
function onSubmit(data: AccountFormValues) {
@@ -57,7 +57,7 @@ export function AccountForm() {
phone_number: data.phone_number,
username: data.username,
first_name: data.first_name,
- last_name: data.last_name
+ last_name: data.last_name,
});
}
@@ -71,7 +71,11 @@ export function AccountForm() {
Username
-
+
@@ -84,7 +88,11 @@ export function AccountForm() {
Email
-
+
@@ -97,7 +105,11 @@ export function AccountForm() {
Phone Number
-
+
@@ -120,4 +132,4 @@ export function AccountForm() {
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/settings/account/index.tsx b/frontend/src/pages/settings/account/index.tsx
index d119829..b7363d6 100644
--- a/frontend/src/pages/settings/account/index.tsx
+++ b/frontend/src/pages/settings/account/index.tsx
@@ -6,7 +6,8 @@ export default function SettingsAccount() {
+ timezone."
+ >
);
diff --git a/frontend/src/pages/settings/appearance/appearance-form.tsx b/frontend/src/pages/settings/appearance/appearance-form.tsx
index bd601c6..c8d1aa3 100644
--- a/frontend/src/pages/settings/appearance/appearance-form.tsx
+++ b/frontend/src/pages/settings/appearance/appearance-form.tsx
@@ -15,18 +15,18 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
const appearanceFormSchema = z.object({
theme: z.enum(["light", "dark"], {
- required_error: "Please select a theme."
+ required_error: "Please select a theme.",
}),
font: z.enum(fonts, {
invalid_type_error: "Select a font",
- required_error: "Please select a font."
- })
+ required_error: "Please select a font.",
+ }),
});
type AppearanceFormValues = z.infer;
@@ -38,12 +38,12 @@ export function AppearanceForm() {
// This can come from your database or API.
const defaultValues: Partial = {
theme: theme as "light" | "dark",
- font
+ font,
};
const form = useForm({
resolver: zodResolver(appearanceFormSchema),
- defaultValues
+ defaultValues,
});
function onSubmit(data: AppearanceFormValues) {
@@ -56,7 +56,7 @@ export function AppearanceForm() {
{JSON.stringify(data, null, 2)}
- )
+ ),
});
}
@@ -74,9 +74,10 @@ export function AppearanceForm() {
+ {...field}
+ >
{fonts.map((font) => (
{font}
@@ -106,7 +107,8 @@ export function AppearanceForm() {
+ className="grid max-w-md grid-cols-2 gap-8 pt-2"
+ >
diff --git a/frontend/src/pages/settings/appearance/index.tsx b/frontend/src/pages/settings/appearance/index.tsx
index efe796f..2f6f43c 100644
--- a/frontend/src/pages/settings/appearance/index.tsx
+++ b/frontend/src/pages/settings/appearance/index.tsx
@@ -6,7 +6,8 @@ export default function SettingsAppearance() {
+ and night themes."
+ >
);
diff --git a/frontend/src/pages/settings/components/content-section.tsx b/frontend/src/pages/settings/components/content-section.tsx
index aa6703b..e3d2974 100644
--- a/frontend/src/pages/settings/components/content-section.tsx
+++ b/frontend/src/pages/settings/components/content-section.tsx
@@ -10,7 +10,7 @@ interface ContentSectionProps {
export default function ContentSection({
title,
desc,
- children
+ children,
}: ContentSectionProps) {
return (
diff --git a/frontend/src/pages/settings/components/sidebar-nav.tsx b/frontend/src/pages/settings/components/sidebar-nav.tsx
index 883724b..dd9a720 100644
--- a/frontend/src/pages/settings/components/sidebar-nav.tsx
+++ b/frontend/src/pages/settings/components/sidebar-nav.tsx
@@ -9,7 +9,7 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
- SelectValue
+ SelectValue,
} from "@/components/ui/select";
interface SidebarNavProps extends React.HTMLAttributes
{
@@ -57,13 +57,15 @@ export default function SidebarNav({
+ className="hidden w-full min-w-40 bg-background px-1 py-2 md:block"
+ >
+ {...props}
+ >
{items.map((item) => (
+ "justify-start",
+ )}
+ >
{item.icon}
{item.title}
diff --git a/frontend/src/pages/settings/display/display-form.tsx b/frontend/src/pages/settings/display/display-form.tsx
index 66201a0..f43a46f 100644
--- a/frontend/src/pages/settings/display/display-form.tsx
+++ b/frontend/src/pages/settings/display/display-form.tsx
@@ -11,53 +11,53 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
const items = [
{
id: "recents",
- label: "Recents"
+ label: "Recents",
},
{
id: "home",
- label: "Home"
+ label: "Home",
},
{
id: "applications",
- label: "Applications"
+ label: "Applications",
},
{
id: "desktop",
- label: "Desktop"
+ label: "Desktop",
},
{
id: "downloads",
- label: "Downloads"
+ label: "Downloads",
},
{
id: "documents",
- label: "Documents"
- }
+ label: "Documents",
+ },
] as const;
const displayFormSchema = z.object({
items: z.array(z.string()).refine((value) => value.some((item) => item), {
- message: "You have to select at least one item."
- })
+ message: "You have to select at least one item.",
+ }),
});
type DisplayFormValues = z.infer;
// This can come from your database or API.
const defaultValues: Partial = {
- items: ["recents", "home"]
+ items: ["recents", "home"],
};
export function DisplayForm() {
const form = useForm({
resolver: zodResolver(displayFormSchema),
- defaultValues
+ defaultValues,
});
function onSubmit(data: DisplayFormValues) {
@@ -67,7 +67,7 @@ export function DisplayForm() {
{JSON.stringify(data, null, 2)}
- )
+ ),
});
}
@@ -94,7 +94,8 @@ export function DisplayForm() {
return (
+ className="flex flex-row items-start space-x-3 space-y-0"
+ >
value !== item.id
- )
+ (value) => value !== item.id,
+ ),
);
}}
/>
diff --git a/frontend/src/pages/settings/display/index.tsx b/frontend/src/pages/settings/display/index.tsx
index b189e62..44ead9d 100644
--- a/frontend/src/pages/settings/display/index.tsx
+++ b/frontend/src/pages/settings/display/index.tsx
@@ -5,7 +5,8 @@ export default function SettingsDisplay() {
return (
+ desc="Turn items on or off to control what's displayed in the app."
+ >
);
diff --git a/frontend/src/pages/settings/index.tsx b/frontend/src/pages/settings/index.tsx
index f006b30..983e3b3 100644
--- a/frontend/src/pages/settings/index.tsx
+++ b/frontend/src/pages/settings/index.tsx
@@ -4,7 +4,7 @@ import {
IconLock,
IconNotification,
IconPalette,
- IconUser
+ IconUser,
} from "@tabler/icons-react";
import { Separator } from "@/components/ui/separator";
import { Header } from "@/components/layout/header";
@@ -53,26 +53,26 @@ const sidebarNavItems = [
{
title: "Profile",
icon: ,
- href: "/dashboard/settings"
+ href: "/dashboard/settings",
},
{
title: "Security",
icon: ,
- href: "/dashboard/settings/security"
+ href: "/dashboard/settings/security",
},
{
title: "Appearance",
icon: ,
- href: "/dashboard/settings/appearance"
+ href: "/dashboard/settings/appearance",
},
{
title: "Notifications",
icon: ,
- href: "/dashboard/settings/notifications"
+ href: "/dashboard/settings/notifications",
},
{
title: "Display",
icon: ,
- href: "/dashboard/settings/display"
- }
+ href: "/dashboard/settings/display",
+ },
];
diff --git a/frontend/src/pages/settings/notifications/index.tsx b/frontend/src/pages/settings/notifications/index.tsx
index f1331c5..d5e53ab 100644
--- a/frontend/src/pages/settings/notifications/index.tsx
+++ b/frontend/src/pages/settings/notifications/index.tsx
@@ -5,7 +5,8 @@ export default function SettingsNotifications() {
return (
+ desc="Configure how you receive notifications."
+ >
);
diff --git a/frontend/src/pages/settings/notifications/notifications-form.tsx b/frontend/src/pages/settings/notifications/notifications-form.tsx
index 67383f4..a99305a 100644
--- a/frontend/src/pages/settings/notifications/notifications-form.tsx
+++ b/frontend/src/pages/settings/notifications/notifications-form.tsx
@@ -12,20 +12,20 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Switch } from "@/components/ui/switch";
const notificationsFormSchema = z.object({
type: z.enum(["all", "mentions", "none"], {
- required_error: "You need to select a notification type."
+ required_error: "You need to select a notification type.",
}),
mobile: z.boolean().default(false).optional(),
communication_emails: z.boolean().default(false).optional(),
social_emails: z.boolean().default(false).optional(),
marketing_emails: z.boolean().default(false).optional(),
- security_emails: z.boolean()
+ security_emails: z.boolean(),
});
type NotificationsFormValues = z.infer;
@@ -35,13 +35,13 @@ const defaultValues: Partial = {
communication_emails: false,
marketing_emails: false,
social_emails: true,
- security_emails: true
+ security_emails: true,
};
export function NotificationsForm() {
const form = useForm({
resolver: zodResolver(notificationsFormSchema),
- defaultValues
+ defaultValues,
});
function onSubmit(data: NotificationsFormValues) {
@@ -51,7 +51,7 @@ export function NotificationsForm() {
{JSON.stringify(data, null, 2)}
- )
+ ),
});
}
@@ -68,7 +68,8 @@ export function NotificationsForm() {
+ className="flex flex-col space-y-1"
+ >
@@ -207,7 +208,8 @@ export function NotificationsForm() {
You can manage your mobile notifications in the{" "}
+ className="underline decoration-dashed underline-offset-4 hover:decoration-solid"
+ >
mobile settings
{" "}
page.
diff --git a/frontend/src/pages/settings/profile/index.tsx b/frontend/src/pages/settings/profile/index.tsx
index 31926de..fc4a326 100644
--- a/frontend/src/pages/settings/profile/index.tsx
+++ b/frontend/src/pages/settings/profile/index.tsx
@@ -5,7 +5,8 @@ export default function SettingsProfile() {
return (
+ desc="This is how others will see you on the site."
+ >
);
diff --git a/frontend/src/pages/settings/profile/profile-form.tsx b/frontend/src/pages/settings/profile/profile-form.tsx
index e368f38..bb65c81 100644
--- a/frontend/src/pages/settings/profile/profile-form.tsx
+++ b/frontend/src/pages/settings/profile/profile-form.tsx
@@ -11,7 +11,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PhoneInput } from "@/components/ui/phone-number-input";
@@ -20,42 +20,42 @@ import useAuth from "@/hooks/useAuth";
const profileFormSchema = z.object({
username: z
.string({
- required_error: "Please enter your desired username"
+ required_error: "Please enter your desired username",
})
.min(3, {
- message: "Username must be at least 3 characters."
+ message: "Username must be at least 3 characters.",
})
.max(64, {
- message: "Username must not be longer than 64 characters."
+ message: "Username must not be longer than 64 characters.",
}),
email: z
.string()
.email({
- message: "Please enter your email address."
+ message: "Please enter your email address.",
})
.email(),
phone_number: z
.string({
- required_error: "Please input a valid phone number."
+ required_error: "Please input a valid phone number.",
})
.min(2, {
- message: "Phone number must be at least 2 characters."
+ message: "Phone number must be at least 2 characters.",
})
.max(16, {
- message: "Phone number must not be longer than 16 characters."
+ message: "Phone number must not be longer than 16 characters.",
}),
first_name: z
.string()
.max(64, {
- message: "First name must not be longer than 64 characters."
+ message: "First name must not be longer than 64 characters.",
})
.optional(),
last_name: z
.string()
.max(64, {
- message: "Last name must not be longer than 64 characters."
+ message: "Last name must not be longer than 64 characters.",
})
- .optional()
+ .optional(),
});
type ProfileFormValues = z.infer;
@@ -68,13 +68,13 @@ export default function ProfileForm() {
email: user?.email ?? "",
phone_number: user?.phone_number ?? "",
first_name: user?.first_name ?? "",
- last_name: user?.last_name ?? ""
+ last_name: user?.last_name ?? "",
};
const form = useForm({
resolver: zodResolver(profileFormSchema),
defaultValues,
- mode: "onChange"
+ mode: "onChange",
});
function onSubmit(data: ProfileFormValues) {
@@ -85,7 +85,7 @@ export default function ProfileForm() {
{JSON.stringify(data, null, 2)}
- )
+ ),
});
}
diff --git a/frontend/src/pages/settings/security/security-form.tsx b/frontend/src/pages/settings/security/security-form.tsx
index 6f35176..d2ecdcf 100644
--- a/frontend/src/pages/settings/security/security-form.tsx
+++ b/frontend/src/pages/settings/security/security-form.tsx
@@ -8,7 +8,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { PasswordInput } from "@/components/password-input";
import { toast } from "@/hooks/useToast";
@@ -16,7 +16,7 @@ import { toast } from "@/hooks/useToast";
const passwordChangeSchema = z
.object({
oldPassword: z.string().min(1, {
- message: "Please enter your existing password"
+ message: "Please enter your existing password",
}),
newPassword: z
.string()
@@ -24,25 +24,25 @@ const passwordChangeSchema = z
.min(8, { message: "Password must be at least 8 characters long" })
.max(128, { message: "Password must be at most 128 characters long" })
.refine((password) => /[A-Z]/.test(password), {
- message: "Password must contain at least one uppercase letter"
+ message: "Password must contain at least one uppercase letter",
})
.refine((password) => /[a-z]/.test(password), {
- message: "Password must contain at least one lowercase letter"
+ message: "Password must contain at least one lowercase letter",
})
.refine((password) => /\d/.test(password), {
- message: "Password must contain at least one number"
+ message: "Password must contain at least one number",
})
.refine((password) => /[@$!%*?&]/.test(password), {
message:
- "Password must contain at least one special character (@, $, !, %, *, ?, &)"
+ "Password must contain at least one special character (@, $, !, %, *, ?, &)",
}),
confirmNewPassword: z.string().min(1, {
- message: "Please confirm your new password"
- })
+ message: "Please confirm your new password",
+ }),
})
.refine((data) => data.newPassword === data.confirmNewPassword, {
message: "Passwords don't match.",
- path: ["confirmNewPassword"]
+ path: ["confirmNewPassword"],
});
type PasswordChangeFormValues = z.infer;
@@ -53,14 +53,14 @@ export default function PasswordChangeForm() {
defaultValues: {
oldPassword: "",
newPassword: "",
- confirmNewPassword: ""
- }
+ confirmNewPassword: "",
+ },
});
function onSubmit(data: PasswordChangeFormValues) {
toast({
title: "Password Changed (MOCK)",
- description: "Your password has been updated."
+ description: "Your password has been updated.",
});
}
diff --git a/frontend/src/pages/shop/components/shop-about-form.tsx b/frontend/src/pages/shop/components/shop-about-form.tsx
index f2cb670..b9bbdd0 100644
--- a/frontend/src/pages/shop/components/shop-about-form.tsx
+++ b/frontend/src/pages/shop/components/shop-about-form.tsx
@@ -8,7 +8,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { PhoneInput } from "@/components/ui/phone-number-input";
@@ -30,9 +30,9 @@ const shopAboutSchema = z.object({
city: z.string(),
state: z.string().nullable().optional(),
postal_code: z.string(),
- country: z.string()
+ country: z.string(),
}),
- status: z.enum(["active", "inactive", "suspended"])
+ status: z.enum(["active", "inactive", "suspended"]),
});
type ShopAboutFormValues = z.infer;
@@ -43,7 +43,7 @@ export function ShopAboutForm() {
const form = useForm({
resolver: zodResolver(shopAboutSchema),
defaultValues: shop ?? undefined,
- mode: "onChange"
+ mode: "onChange",
});
useEffect(() => {
@@ -52,7 +52,7 @@ export function ShopAboutForm() {
function onSubmit(data: ShopAboutFormValues) {
updateShop(data);
- toast({title: "Saved shop data"})
+ toast({ title: "Saved shop data" });
}
if (isLoading) return Loading...
;
if (!shop) return No shop found
;
@@ -61,7 +61,8 @@ export function ShopAboutForm() {
+ className="hidden w-full min-w-40 bg-background px-1 md:block"
+ >
);
- }
+ },
},
{
accessorKey: "status",
@@ -65,7 +65,7 @@ export const columns: ColumnDef[] = [
),
cell: ({ row }) => {
const status = statuses.find(
- (status) => status.value === row.getValue("status")
+ (status) => status.value === row.getValue("status"),
);
if (!status) {
@@ -83,7 +83,7 @@ export const columns: ColumnDef[] = [
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
- }
+ },
},
{
accessorKey: "priority",
@@ -92,7 +92,7 @@ export const columns: ColumnDef[] = [
),
cell: ({ row }) => {
const priority = priorities.find(
- (priority) => priority.value === row.getValue("priority")
+ (priority) => priority.value === row.getValue("priority"),
);
if (!priority) {
@@ -110,10 +110,10 @@ export const columns: ColumnDef[] = [
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id));
- }
+ },
},
{
id: "actions",
- cell: ({ row }) =>
- }
+ cell: ({ row }) => ,
+ },
];
diff --git a/frontend/src/pages/tasks/components/data-table-column-header.tsx b/frontend/src/pages/tasks/components/data-table-column-header.tsx
index 95a76d9..eadd055 100644
--- a/frontend/src/pages/tasks/components/data-table-column-header.tsx
+++ b/frontend/src/pages/tasks/components/data-table-column-header.tsx
@@ -2,7 +2,7 @@ import {
ArrowDownIcon,
ArrowUpIcon,
CaretSortIcon,
- EyeNoneIcon
+ EyeNoneIcon,
} from "@radix-ui/react-icons";
import { Column } from "@tanstack/react-table";
import { cn } from "@/lib/utils";
@@ -12,7 +12,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
interface DataTableColumnHeaderProps
@@ -24,7 +24,7 @@ interface DataTableColumnHeaderProps
export function DataTableColumnHeader({
column,
title,
- className
+ className,
}: DataTableColumnHeaderProps) {
if (!column.getCanSort()) {
return {title}
;
@@ -37,7 +37,8 @@ export function DataTableColumnHeader({
+ className="-ml-3 h-8 data-[state=open]:bg-accent"
+ >
{title}
{column.getIsSorted() === "desc" ? (
diff --git a/frontend/src/pages/tasks/components/data-table-faceted-filter.tsx b/frontend/src/pages/tasks/components/data-table-faceted-filter.tsx
index c030b37..7b6282f 100644
--- a/frontend/src/pages/tasks/components/data-table-faceted-filter.tsx
+++ b/frontend/src/pages/tasks/components/data-table-faceted-filter.tsx
@@ -11,12 +11,12 @@ import {
CommandInput,
CommandItem,
CommandList,
- CommandSeparator
+ CommandSeparator,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
- PopoverTrigger
+ PopoverTrigger,
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
@@ -33,7 +33,7 @@ interface DataTableFacetedFilterProps {
export function DataTableFacetedFilter({
column,
title,
- options
+ options,
}: DataTableFacetedFilterProps) {
const facets = column?.getFacetedUniqueValues();
const selectedValues = new Set(column?.getFilterValue() as string[]);
@@ -49,14 +49,16 @@ export function DataTableFacetedFilter({
+ className="rounded-sm px-1 font-normal lg:hidden"
+ >
{selectedValues.size}
{selectedValues.size > 2 ? (
+ className="rounded-sm px-1 font-normal"
+ >
{selectedValues.size} selected
) : (
@@ -66,7 +68,8 @@ export function DataTableFacetedFilter
({
+ className="rounded-sm px-1 font-normal"
+ >
{option.label}
))
@@ -95,16 +98,18 @@ export function DataTableFacetedFilter({
}
const filterValues = Array.from(selectedValues);
column?.setFilterValue(
- filterValues.length ? filterValues : undefined
+ filterValues.length ? filterValues : undefined,
);
- }}>
+ }}
+ >
+ : "opacity-50 [&_svg]:invisible",
+ )}
+ >
{option.icon && (
@@ -126,7 +131,8 @@ export function DataTableFacetedFilter({
column?.setFilterValue(undefined)}
- className="justify-center text-center">
+ className="justify-center text-center"
+ >
Clear filters
diff --git a/frontend/src/pages/tasks/components/data-table-pagination.tsx b/frontend/src/pages/tasks/components/data-table-pagination.tsx
index da36631..936f40e 100644
--- a/frontend/src/pages/tasks/components/data-table-pagination.tsx
+++ b/frontend/src/pages/tasks/components/data-table-pagination.tsx
@@ -2,7 +2,7 @@ import {
ChevronLeftIcon,
ChevronRightIcon,
DoubleArrowLeftIcon,
- DoubleArrowRightIcon
+ DoubleArrowRightIcon,
} from "@radix-ui/react-icons";
import { Table } from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
@@ -11,7 +11,7 @@ import {
SelectContent,
SelectItem,
SelectTrigger,
- SelectValue
+ SelectValue,
} from "@/components/ui/select";
interface DataTablePaginationProps {
@@ -19,12 +19,13 @@ interface DataTablePaginationProps {
}
export function DataTablePagination({
- table
+ table,
}: DataTablePaginationProps) {
return (
+ style={{ overflowClipMargin: 1 }}
+ >
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
@@ -36,7 +37,8 @@ export function DataTablePagination
({
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
- }}>
+ }}
+ >
@@ -58,7 +60,8 @@ export function DataTablePagination({
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
- disabled={!table.getCanPreviousPage()}>
+ disabled={!table.getCanPreviousPage()}
+ >
Go to first page
@@ -66,7 +69,8 @@ export function DataTablePagination({
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
- disabled={!table.getCanPreviousPage()}>
+ disabled={!table.getCanPreviousPage()}
+ >
Go to previous page
@@ -74,7 +78,8 @@ export function DataTablePagination({
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
- disabled={!table.getCanNextPage()}>
+ disabled={!table.getCanNextPage()}
+ >
Go to next page
@@ -82,7 +87,8 @@ export function DataTablePagination({
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
- disabled={!table.getCanNextPage()}>
+ disabled={!table.getCanNextPage()}
+ >
Go to last page
diff --git a/frontend/src/pages/tasks/components/data-table-row-actions.tsx b/frontend/src/pages/tasks/components/data-table-row-actions.tsx
index e42f556..26e23ae 100644
--- a/frontend/src/pages/tasks/components/data-table-row-actions.tsx
+++ b/frontend/src/pages/tasks/components/data-table-row-actions.tsx
@@ -13,7 +13,7 @@ import {
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
- DropdownMenuTrigger
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useTasks } from "../context/tasks-context";
import { labels } from "../data/data";
@@ -24,7 +24,7 @@ interface DataTableRowActionsProps {
}
export function DataTableRowActions({
- row
+ row,
}: DataTableRowActionsProps) {
const task = taskSchema.parse(row.original);
@@ -35,7 +35,8 @@ export function DataTableRowActions({
+ className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
+ >
Open menu
@@ -45,7 +46,8 @@ export function DataTableRowActions({
onClick={() => {
setCurrentRow(task);
setOpen("update");
- }}>
+ }}
+ >
Edit
Make a copy
@@ -68,7 +70,8 @@ export function DataTableRowActions({
onClick={() => {
setCurrentRow(task);
setOpen("delete");
- }}>
+ }}
+ >
Delete
diff --git a/frontend/src/pages/tasks/components/data-table-toolbar.tsx b/frontend/src/pages/tasks/components/data-table-toolbar.tsx
index f43fd7d..95a0993 100644
--- a/frontend/src/pages/tasks/components/data-table-toolbar.tsx
+++ b/frontend/src/pages/tasks/components/data-table-toolbar.tsx
@@ -11,7 +11,7 @@ interface DataTableToolbarProps {
}
export function DataTableToolbar({
- table
+ table,
}: DataTableToolbarProps) {
const isFiltered = table.getState().columnFilters.length > 0;
@@ -46,7 +46,8 @@ export function DataTableToolbar({
table.resetColumnFilters()}
- className="h-8 px-2 lg:px-3">
+ className="h-8 px-2 lg:px-3"
+ >
Reset
diff --git a/frontend/src/pages/tasks/components/data-table-view-options.tsx b/frontend/src/pages/tasks/components/data-table-view-options.tsx
index 3c49d14..b20f30a 100644
--- a/frontend/src/pages/tasks/components/data-table-view-options.tsx
+++ b/frontend/src/pages/tasks/components/data-table-view-options.tsx
@@ -7,7 +7,7 @@ import {
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
- DropdownMenuSeparator
+ DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
interface DataTableViewOptionsProps {
@@ -15,7 +15,7 @@ interface DataTableViewOptionsProps {
}
export function DataTableViewOptions({
- table
+ table,
}: DataTableViewOptionsProps) {
return (
@@ -23,7 +23,8 @@ export function DataTableViewOptions({
+ className="ml-auto hidden h-8 lg:flex"
+ >
View
@@ -35,7 +36,7 @@ export function DataTableViewOptions({
.getAllColumns()
.filter(
(column) =>
- typeof column.accessorFn !== "undefined" && column.getCanHide()
+ typeof column.accessorFn !== "undefined" && column.getCanHide(),
)
.map((column) => {
return (
@@ -43,7 +44,8 @@ export function DataTableViewOptions({
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
- onCheckedChange={(value) => column.toggleVisibility(!!value)}>
+ onCheckedChange={(value) => column.toggleVisibility(!!value)}
+ >
{column.id}
);
diff --git a/frontend/src/pages/tasks/components/data-table.tsx b/frontend/src/pages/tasks/components/data-table.tsx
index 41096b0..edbbe01 100644
--- a/frontend/src/pages/tasks/components/data-table.tsx
+++ b/frontend/src/pages/tasks/components/data-table.tsx
@@ -11,7 +11,7 @@ import {
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
- useReactTable
+ useReactTable,
} from "@tanstack/react-table";
import {
Table,
@@ -19,7 +19,7 @@ import {
TableCell,
TableHead,
TableHeader,
- TableRow
+ TableRow,
} from "@/components/ui/table";
import { DataTablePagination } from "../components/data-table-pagination";
import { DataTableToolbar } from "../components/data-table-toolbar";
@@ -31,13 +31,13 @@ interface DataTableProps {
export function DataTable({
columns,
- data
+ data,
}: DataTableProps) {
const [rowSelection, setRowSelection] = React.useState({});
const [columnVisibility, setColumnVisibility] =
React.useState({});
const [columnFilters, setColumnFilters] = React.useState(
- []
+ [],
);
const [sorting, setSorting] = React.useState([]);
@@ -48,7 +48,7 @@ export function DataTable({
sorting,
columnVisibility,
rowSelection,
- columnFilters
+ columnFilters,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
@@ -60,7 +60,7 @@ export function DataTable({
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
- getFacetedUniqueValues: getFacetedUniqueValues()
+ getFacetedUniqueValues: getFacetedUniqueValues(),
});
return (
@@ -78,7 +78,7 @@ export function DataTable({
? null
: flexRender(
header.column.columnDef.header,
- header.getContext()
+ header.getContext(),
)}
);
@@ -91,12 +91,13 @@ export function DataTable({
table.getRowModel().rows.map((row) => (
+ data-state={row.getIsSelected() && "selected"}
+ >
{row.getVisibleCells().map((cell) => (
{flexRender(
cell.column.columnDef.cell,
- cell.getContext()
+ cell.getContext(),
)}
))}
@@ -106,7 +107,8 @@ export function DataTable({
+ className="h-24 text-center"
+ >
No results.
diff --git a/frontend/src/pages/tasks/components/tasks-dialogs.tsx b/frontend/src/pages/tasks/components/tasks-dialogs.tsx
index ffcde1b..ee1cbe7 100644
--- a/frontend/src/pages/tasks/components/tasks-dialogs.tsx
+++ b/frontend/src/pages/tasks/components/tasks-dialogs.tsx
@@ -57,7 +57,7 @@ export function TasksDialogs() {
{JSON.stringify(currentRow, null, 2)}
- )
+ ),
});
}}
className="max-w-md"
diff --git a/frontend/src/pages/tasks/components/tasks-import-dialog.tsx b/frontend/src/pages/tasks/components/tasks-import-dialog.tsx
index c9befc9..e4da1a2 100644
--- a/frontend/src/pages/tasks/components/tasks-import-dialog.tsx
+++ b/frontend/src/pages/tasks/components/tasks-import-dialog.tsx
@@ -10,7 +10,7 @@ import {
DialogDescription,
DialogFooter,
DialogHeader,
- DialogTitle
+ DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
@@ -18,7 +18,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
@@ -26,12 +26,12 @@ const formSchema = z.object({
file: z
.instanceof(FileList)
.refine((files) => files.length > 0, {
- message: "Please upload a file"
+ message: "Please upload a file",
})
.refine(
(files) => ["text/csv"].includes(files?.[0]?.type),
- "Please upload csv format."
- )
+ "Please upload csv format.",
+ ),
});
interface Props {
@@ -42,7 +42,7 @@ interface Props {
export function TasksImportDialog({ open, onOpenChange }: Props) {
const form = useForm>({
resolver: zodResolver(formSchema),
- defaultValues: { file: undefined }
+ defaultValues: { file: undefined },
});
const fileRef = form.register("file");
@@ -54,7 +54,7 @@ export function TasksImportDialog({ open, onOpenChange }: Props) {
const fileDetails = {
name: file[0].name,
size: file[0].size,
- type: file[0].type
+ type: file[0].type,
};
toast({
title: "You have imported the following file:",
@@ -64,7 +64,7 @@ export function TasksImportDialog({ open, onOpenChange }: Props) {
{JSON.stringify(fileDetails, null, 2)}
- )
+ ),
});
}
onOpenChange(false);
@@ -76,7 +76,8 @@ export function TasksImportDialog({ open, onOpenChange }: Props) {
onOpenChange={(val) => {
onOpenChange(val);
form.reset();
- }}>
+ }}
+ >
Import Tasks
diff --git a/frontend/src/pages/tasks/components/tasks-mutate-drawer.tsx b/frontend/src/pages/tasks/components/tasks-mutate-drawer.tsx
index 574322a..ac7ddd9 100644
--- a/frontend/src/pages/tasks/components/tasks-mutate-drawer.tsx
+++ b/frontend/src/pages/tasks/components/tasks-mutate-drawer.tsx
@@ -9,7 +9,7 @@ import {
FormField,
FormItem,
FormLabel,
- FormMessage
+ FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
@@ -20,7 +20,7 @@ import {
SheetDescription,
SheetFooter,
SheetHeader,
- SheetTitle
+ SheetTitle,
} from "@/components/ui/sheet";
import { SelectDropdown } from "@/components/select-dropdown";
import { Task } from "../data/schema";
@@ -35,7 +35,7 @@ const formSchema = z.object({
title: z.string().min(1, "Title is required."),
status: z.string().min(1, "Please select a status."),
label: z.string().min(1, "Please select a label."),
- priority: z.string().min(1, "Please choose a priority.")
+ priority: z.string().min(1, "Please choose a priority."),
});
type TasksForm = z.infer;
@@ -48,8 +48,8 @@ export function TasksMutateDrawer({ open, onOpenChange, currentRow }: Props) {
title: "",
status: "",
label: "",
- priority: ""
- }
+ priority: "",
+ },
});
const onSubmit = (data: TasksForm) => {
@@ -62,7 +62,7 @@ export function TasksMutateDrawer({ open, onOpenChange, currentRow }: Props) {
{JSON.stringify(data, null, 2)}
- )
+ ),
});
};
@@ -72,7 +72,8 @@ export function TasksMutateDrawer({ open, onOpenChange, currentRow }: Props) {
onOpenChange={(v) => {
onOpenChange(v);
form.reset();
- }}>
+ }}
+ >
{isUpdate ? "Update" : "Create"} Task
@@ -87,7 +88,8 @@ export function TasksMutateDrawer({ open, onOpenChange, currentRow }: Props) {