[];
}
+const log = new Logger("Product");
+
/**
* @author Aloento
* @since 0.1.0
- * @version 0.1.4
+ * @version 0.1.5
*/
export function Product() {
const style = useStyle();
const { Nav, Paths } = useRouter();
const id = parseInt(Paths.at(1)!);
- const { data } = useRequest(() => Hub.Product.Get.Basic(id), {
+ const { data } = useRequest(() => Hub.Product.Get.Basic(id, log), {
onBefore() {
isNaN(id) && Nav("/");
},
onError(e) {
Nav("/");
- console.error(e);
+ log.error(e);
}
});
diff --git a/src/Pages/index.tsx b/src/Pages/index.tsx
index 7ae2904..5163ee8 100644
--- a/src/Pages/index.tsx
+++ b/src/Pages/index.tsx
@@ -1,4 +1,5 @@
import { makeStyles, tokens } from "@fluentui/react-components";
+import { useMemo } from "react";
import { NewUser } from "~/Components/NewUser";
import { ColFlex, NavH, NavW } from "~/Helpers/Styles";
import { Admin } from "~/Pages/Admin";
@@ -40,14 +41,14 @@ const useStyle = makeStyles({
/**
* @author Aloento
* @since 0.2.2 MusiLand
- * @version 0.3.0
+ * @version 0.3.1
*/
export function Layout() {
const style = useStyle();
const { Paths } = useRouter();
const path = Paths.at(0);
- function Matcher() {
+ const match = useMemo(() => {
switch (path) {
case "Product":
return ;
@@ -68,14 +69,14 @@ export function Layout() {
default:
return 404
;
}
- }
+ }, [path]);
return <>
-
+ {match}
diff --git a/src/ShopNet/Admin/AdminNet.ts b/src/ShopNet/Admin/AdminNet.ts
index 8da5a85..175e1d0 100644
--- a/src/ShopNet/Admin/AdminNet.ts
+++ b/src/ShopNet/Admin/AdminNet.ts
@@ -7,13 +7,16 @@ import { SignalR } from "../SignalR";
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.0
+ * @version 0.1.1
*/
export abstract class AdminNet extends SignalR {
+ /** "|", "AdminNet" */
+ protected static readonly Log = ["|", "AdminNet"];
+
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.0
+ * @version 0.1.2
*/
public static readonly Hub = new HubConnectionBuilder()
.withUrl(import.meta.env.DEV ? "https://localhost/AdminHub" : "/AdminHub",
@@ -30,6 +33,7 @@ export abstract class AdminNet extends SignalR {
},
})
.withAutomaticReconnect()
+ .withStatefulReconnect()
.withHubProtocol(new MessagePackHubProtocol())
.configureLogging(import.meta.env.DEV ? LogLevel.Debug : LogLevel.Information)
.build();
diff --git a/src/ShopNet/Admin/Order/Get.ts b/src/ShopNet/Admin/Order/Get.ts
index 3825a95..6c5c579 100644
--- a/src/ShopNet/Admin/Order/Get.ts
+++ b/src/ShopNet/Admin/Order/Get.ts
@@ -1,6 +1,11 @@
import dayjs from "dayjs";
+import { ICartItem } from "~/Components/ShopCart";
+import { Logger } from "~/Helpers/Logger";
import { IAdminOrderItem } from "~/Pages/Admin/Order";
+import { IComment } from "~/Pages/History/Comment";
+import { IOrderDetail } from "~/Pages/History/Detail";
import { ProductEntity } from "~/ShopNet/Product/Entity";
+import { ProductGet } from "~/ShopNet/Product/Get";
import { AdminNet } from "../AdminNet";
import { AdminUserEntity } from "../User/Entity";
import { AdminOrderEntity } from "./Entity";
@@ -8,16 +13,20 @@ import { AdminOrderEntity } from "./Entity";
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*/
export abstract class AdminOrderGet extends AdminNet {
+ /** "Order", "Get" */
+ protected static override readonly Log = [...super.Log, "Order", "Get"];
+
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*/
- public static async List(): Promise {
+ public static async List(pLog: Logger): Promise {
this.EnsureLogin();
+ const log = pLog.With(...this.Log, "List");
const list = await this.WithTimeCache<
{
@@ -33,7 +42,7 @@ export abstract class AdminOrderGet extends AdminNet {
const order = await AdminOrderEntity.Order(meta.OrderId);
if (!order) {
- console.error(`AdminOrderGetList Mismatch: Order ${meta.OrderId} not found`);
+ log.warn(`[Mismatch] Order ${meta.OrderId} not found`);
continue;
}
@@ -43,7 +52,7 @@ export abstract class AdminOrderGet extends AdminNet {
const prod = await ProductEntity.Product(prodId);
if (!prod) {
- console.error(`AdminOrderGetList Mismatch: Product ${prodId} not found`);
+ log.warn(`[Mismatch] Product ${prodId} not found`);
continue;
}
@@ -53,7 +62,7 @@ export abstract class AdminOrderGet extends AdminNet {
const user = await AdminUserEntity.User(order.UserId);
if (!user) {
- console.error(`AdminOrderGetList Mismatch: User ${order.UserId} not found`);
+ log.error(`[Mismatch] User ${order.UserId} not found`);
continue;
}
@@ -70,4 +79,108 @@ export abstract class AdminOrderGet extends AdminNet {
return items.sort((a, b) => b.OrderDate.getTime() - a.OrderDate.getTime());
}
+
+ /**
+ * @author Aloento
+ * @since 1.0.0
+ * @version 0.1.1
+ */
+ public static async Detail(orderId: number, pLog: Logger): Promise {
+ this.EnsureLogin();
+ const log = pLog.With(...this.Log, "Detail");
+
+ const meta = await this.WithTimeCache<
+ {
+ Items: {
+ Types: number[];
+ Quantity: number;
+ }[],
+ Comments: number[];
+ }
+ >(orderId, "OrderGetDetail", dayjs().add(1, "m"), orderId);
+
+ const items: ICartItem[] = [];
+ let index = 0;
+
+ for (const combo of meta.Items) {
+ const variType: Record = {};
+ let prodId = 0;
+
+ for (const typeId of combo.Types) {
+ const type = await ProductEntity.Type(typeId);
+
+ if (!type) {
+ log.warn(`[Mismatch] Type ${typeId} not found. Order : ${orderId}`);
+ continue;
+ }
+
+ const vari = await ProductEntity.Variant(type.VariantId);
+
+ if (!vari) {
+ log.warn(`[Mismatch] Variant ${type.VariantId} not found. Type : ${typeId}, Order : ${orderId}`);
+ continue;
+ }
+
+ variType[vari.Name] = type.Name;
+ prodId = vari.ProductId;
+ }
+
+ const prod = await ProductEntity.Product(prodId);
+
+ if (!prod) {
+ log.warn(`[Mismatch] Product ${prodId} not found. Order : ${orderId}`);
+ continue;
+ }
+
+ const list = await ProductGet.PhotoList(prodId);
+ const cover = await this.FindCover(list, prodId, log);
+
+ if (!cover)
+ log.warn(`Product ${prodId} has no photo`);
+
+ items.push({
+ Id: index++,
+ ProdId: prodId,
+ Cover: cover || "",
+ Name: prod.Name,
+ Type: variType,
+ Quantity: combo.Quantity,
+ });
+ }
+
+ const comments: IComment[] = [];
+
+ for (const cmtId of meta.Comments) {
+ const cmt = await AdminOrderEntity.Comment(cmtId);
+
+ if (!cmt) {
+ log.warn(`[Mismatch] Comment ${cmtId} not found. Order : ${orderId}`);
+ continue;
+ }
+
+ let name = "Client";
+
+ if (cmt.UserId) {
+ const user = await AdminUserEntity.User(cmt.UserId);
+
+ if (user)
+ name = user.Name;
+ else
+ log.warn(`[Mismatch] User ${cmt.UserId} not found. Order : ${orderId}`);
+ }
+
+ comments.push({
+ Content: cmt.Content,
+ Time: cmt.CreateAt,
+ User: name
+ });
+ }
+
+ return {
+ ShopCart: items,
+ Comments: comments.sort((a, b) => a.Time.getTime() - b.Time.getTime())
+ };
+ }
+
+ public static Order = AdminOrderEntity.Order;
}
diff --git a/src/ShopNet/Admin/Order/Post.ts b/src/ShopNet/Admin/Order/Post.ts
index bda20cd..b4f4081 100644
--- a/src/ShopNet/Admin/Order/Post.ts
+++ b/src/ShopNet/Admin/Order/Post.ts
@@ -11,19 +11,27 @@ export abstract class AdminOrderPost extends AdminNet {
/**
* @author Aloento
* @since 0.5.0
- * @version 0.2.0
+ * @version 0.2.1
*/
public static useAppend(options: Options) {
- return useRequest((orderId, cmt) => this.Invoke("OrderPostAppend", orderId, cmt), options);
+ return useRequest(async (orderId, cmt) => {
+ const res = await this.Invoke("OrderPostAppend", orderId, cmt);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
* @author Aloento
* @since 0.5.0
- * @version 0.2.0
+ * @version 0.2.1
*/
public static useClose(options: Options) {
- return useRequest((orderId, reason) => this.Invoke("OrderPostClose", orderId, reason), options);
+ return useRequest(async (orderId, reason) => {
+ const res = await this.Invoke("OrderPostClose", orderId, reason);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
@@ -32,6 +40,23 @@ export abstract class AdminOrderPost extends AdminNet {
* @version 0.2.0
*/
public static useShip(options: Options) {
- return useRequest((orderId, track) => this.Invoke("OrderPostShip", orderId, track), options);
+ return useRequest(async (orderId, track) => {
+ const res = await this.Invoke("OrderPostShip", orderId, track);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
+ }
+
+ /**
+ * @author Aloento
+ * @since 1.0.0
+ * @version 0.1.0
+ */
+ public static useAccept(options: Options) {
+ return useRequest(async orderId => {
+ const res = await this.Invoke("OrderPostAccept", orderId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
}
diff --git a/src/ShopNet/Admin/Product/Delete.ts b/src/ShopNet/Admin/Product/Delete.ts
index 57ef5e9..39da2ed 100644
--- a/src/ShopNet/Admin/Product/Delete.ts
+++ b/src/ShopNet/Admin/Product/Delete.ts
@@ -14,7 +14,11 @@ export abstract class AdminProductDelete extends AdminNet {
* @version 0.2.0
*/
public static usePhoto(options: Options) {
- return useRequest((photoId) => this.Invoke("ProductDeletePhoto", photoId), options);
+ return useRequest(async photoId => {
+ const res = await this.Invoke("ProductDeletePhoto", photoId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
@@ -23,7 +27,11 @@ export abstract class AdminProductDelete extends AdminNet {
* @version 0.2.0
*/
public static useVariant(options: Options) {
- return useRequest((variantId) => this.Invoke("ProductDeleteVariant", variantId), options);
+ return useRequest(async variantId => {
+ const res = await this.Invoke("ProductDeleteVariant", variantId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
@@ -32,7 +40,11 @@ export abstract class AdminProductDelete extends AdminNet {
* @version 0.2.0
*/
public static useType(options: Options) {
- return useRequest((variantId, type) => this.Invoke("ProductDeleteType", variantId, type), options);
+ return useRequest(async (variantId, type) => {
+ const res = await this.Invoke("ProductDeleteType", variantId, type);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
@@ -41,7 +53,11 @@ export abstract class AdminProductDelete extends AdminNet {
* @version 0.2.0
*/
public static useCombo(options: Options) {
- return useRequest((comboId) => this.Invoke("ProductDeleteCombo", comboId), options);
+ return useRequest(async comboId => {
+ const res = await this.Invoke("ProductDeleteCombo", comboId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
@@ -50,6 +66,10 @@ export abstract class AdminProductDelete extends AdminNet {
* @version 0.2.0
*/
public static useProduct(options: Options) {
- return useRequest((prodId) => this.Invoke("ProductDeleteProduct", prodId), options);
+ return useRequest(async prodId => {
+ const res = await this.Invoke("ProductDeleteProduct", prodId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
}
diff --git a/src/ShopNet/Admin/Product/Get.ts b/src/ShopNet/Admin/Product/Get.ts
index cad1f8e..485a683 100644
--- a/src/ShopNet/Admin/Product/Get.ts
+++ b/src/ShopNet/Admin/Product/Get.ts
@@ -1,4 +1,5 @@
import dayjs from "dayjs";
+import type { Logger } from "~/Helpers/Logger";
import { IProductItem } from "~/Pages/Admin/Product";
import { IVariantItem } from "~/Pages/Admin/Product/Variant";
import { ProductEntity } from "~/ShopNet/Product/Entity";
@@ -8,15 +9,20 @@ import { AdminNet } from "../AdminNet";
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*/
export abstract class AdminProductGet extends AdminNet {
+ /** "Product", "Get" */
+ protected static override readonly Log = [...super.Log, "Product", "Get"];
+
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async List(): Promise {
+ public static async List(pLog: Logger): Promise {
+ const log = pLog.With(...this.Log, "List");
+
const list = await this.WithTimeCache<
{
ProductId: number;
@@ -32,15 +38,15 @@ export abstract class AdminProductGet extends AdminNet {
const prod = await ProductEntity.Product(meta.ProductId);
if (!prod) {
- console.error(`Product ${meta.ProductId} Not Found`);
+ log.warn(`Product ${meta.ProductId} Not Found`);
continue;
}
const photos = await ProductGet.PhotoList(meta.ProductId);
- const cover = await this.FindCover(photos, meta.ProductId);
+ const cover = await this.FindCover(photos, meta.ProductId, log);
if (!cover)
- console.warn(`Product ${meta.ProductId} has no photo`);
+ log.warn(`Product ${meta.ProductId} has no photo`);
items.push({
Id: meta.ProductId,
@@ -87,9 +93,11 @@ export abstract class AdminProductGet extends AdminNet {
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async Variants(prodId: number): Promise {
+ public static async Variants(prodId: number, pLog: Logger): Promise {
+ const log = pLog.With(...this.Log, "Variants");
+
const list = await this.WithTimeCache<
{
VariantId: number;
@@ -103,7 +111,7 @@ export abstract class AdminProductGet extends AdminNet {
const vari = await ProductEntity.Variant(meta.VariantId);
if (!vari) {
- console.error(`Variant ${meta} Not Found. Product : ${prodId}`);
+ log.warn(`Variant ${meta} Not Found. Product : ${prodId}`);
continue;
}
@@ -113,7 +121,7 @@ export abstract class AdminProductGet extends AdminNet {
const type = await ProductEntity.Type(typeId);
if (!type) {
- console.error(`Type ${typeId} Not Found. Variant : ${meta.VariantId}, Product : ${prodId}`);
+ log.warn(`Type ${typeId} Not Found. Variant : ${meta.VariantId}, Product : ${prodId}`);
continue;
}
diff --git a/src/ShopNet/Admin/Product/Patch.ts b/src/ShopNet/Admin/Product/Patch.ts
index 99abbe4..0aa2a61 100644
--- a/src/ShopNet/Admin/Product/Patch.ts
+++ b/src/ShopNet/Admin/Product/Patch.ts
@@ -1,14 +1,19 @@
+import { useConst } from "@fluentui/react-hooks";
import { useRequest } from "ahooks";
import { Options } from "ahooks/lib/useRequest/src/types";
import { Subject } from "rxjs";
+import { Logger } from "~/Helpers/Logger";
import { AdminNet } from "../AdminNet";
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*/
export abstract class AdminProductPatch extends AdminNet {
+ /** "Product", "Patch" */
+ protected static override readonly Log = [...super.Log, "Product", "Patch"];
+
/**
* @author Aloento
* @since 0.5.0
@@ -38,9 +43,11 @@ export abstract class AdminProductPatch extends AdminNet {
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.1
+ * @version 1.0.2
*/
- public static usePhoto(options: Options) {
+ public static usePhoto(pLog: Logger, options: Options) {
+ const log = useConst(() => pLog.With(...this.Log, "Photo"));
+
return useRequest(async (photoId, file) => {
if (!file.type.startsWith("image/"))
throw new TypeError("File is not an image");
@@ -52,7 +59,7 @@ export abstract class AdminProductPatch extends AdminNet {
const subject = new Subject();
const res = this.Hub.invoke("ProductPatchPhoto", photoId, subject);
- await this.HandleFileStream(file, subject);
+ await this.HandleFileStream(file, subject, log);
this.EnsureTrue(await res);
return true as const;
diff --git a/src/ShopNet/Admin/Product/Post.ts b/src/ShopNet/Admin/Product/Post.ts
index beda3c7..4b84d3b 100644
--- a/src/ShopNet/Admin/Product/Post.ts
+++ b/src/ShopNet/Admin/Product/Post.ts
@@ -1,6 +1,8 @@
+import { useConst } from "@fluentui/react-hooks";
import { useRequest } from "ahooks";
import { Options } from "ahooks/lib/useRequest/src/types";
import { Subject } from "rxjs";
+import { Logger } from "~/Helpers/Logger";
import { AdminNet } from "../AdminNet";
/**
@@ -9,13 +11,16 @@ import { AdminNet } from "../AdminNet";
* @version 0.1.0
*/
export abstract class AdminProductPost extends AdminNet {
+ /** "Product", "Post" */
+ protected static override readonly Log = [...super.Log, "Product", "Post"];
+
/**
* @author Aloento
* @since 0.5.0
* @version 0.2.0
*/
public static useCreate(options: Options) {
- return useRequest((name) => this.Invoke("ProductPostCreate", name), options);
+ return useRequest(name => this.Invoke("ProductPostCreate", name), options);
}
/**
@@ -24,15 +29,21 @@ export abstract class AdminProductPost extends AdminNet {
* @version 0.2.0
*/
public static useMovePhoto(options: Options) {
- return useRequest((photoId, up) => this.Invoke("ProductPostMovePhoto", photoId, up), options);
+ return useRequest(async (photoId, up) => {
+ const res = await this.Invoke("ProductPostMovePhoto", photoId, up);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.2
+ * @version 1.0.3
*/
- public static usePhoto(options: Options) {
+ public static usePhoto(pLog: Logger, options: Options) {
+ const log = useConst(() => pLog.With(...this.Log, "Photo"));
+
return useRequest(async (prodId, file) => {
if (!file.type.startsWith("image/"))
throw new TypeError("File is not an image");
@@ -41,10 +52,11 @@ export abstract class AdminProductPost extends AdminNet {
throw new RangeError("File is too large, max 10MB");
const subject = new Subject();
- const res = this.Invoke("ProductPostPhoto", prodId, subject);
- await this.HandleFileStream(file, subject);
+ const res = this.Invoke("ProductPostPhoto", prodId, subject);
+ await this.HandleFileStream(file, subject, log);
- return res;
+ this.EnsureTrue(await res);
+ return true as const;
}, options);
}
diff --git a/src/ShopNet/Admin/User/Delete.ts b/src/ShopNet/Admin/User/Delete.ts
index e052c07..b91002d 100644
--- a/src/ShopNet/Admin/User/Delete.ts
+++ b/src/ShopNet/Admin/User/Delete.ts
@@ -14,7 +14,11 @@ export abstract class AdminUserDelete extends AdminNet {
* @version 0.2.0
*/
public static useUser(options: Options) {
- return useRequest((userId) => this.Invoke("UserDeleteUser", userId), options);
+ return useRequest(async userId => {
+ const res = await this.Invoke("UserDeleteUser", userId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
/**
@@ -23,6 +27,10 @@ export abstract class AdminUserDelete extends AdminNet {
* @version 0.2.0
*/
public static useAdmin(options: Options) {
- return useRequest((userId) => this.Invoke("UserDeleteAdmin", userId), options);
+ return useRequest(async userId => {
+ const res = await this.Invoke("UserDeleteAdmin", userId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
}
diff --git a/src/ShopNet/Admin/User/Post.ts b/src/ShopNet/Admin/User/Post.ts
index 911b7d5..85cf0b3 100644
--- a/src/ShopNet/Admin/User/Post.ts
+++ b/src/ShopNet/Admin/User/Post.ts
@@ -14,6 +14,10 @@ export abstract class AdminUserPost extends AdminNet {
* @version 0.1.0
*/
public static useAdmin(options: Options) {
- return useRequest((userId) => this.Invoke("UserPostAdmin", userId), options);
+ return useRequest(async userId => {
+ const res = await this.Invoke("UserPostAdmin", userId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
}
}
diff --git a/src/ShopNet/ObjectStorage.ts b/src/ShopNet/ObjectStorage.ts
index 892e6c6..23a6489 100644
--- a/src/ShopNet/ObjectStorage.ts
+++ b/src/ShopNet/ObjectStorage.ts
@@ -1,11 +1,12 @@
import { IStreamResult } from "@microsoft/signalr";
+import { Logger } from "~/Helpers/Logger";
import { Shared } from "./Database";
import { ShopNet } from "./ShopNet";
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.0
+ * @version 0.1.1
*/
export class ObjectStorage extends ShopNet {
/**
@@ -24,21 +25,21 @@ export class ObjectStorage extends ShopNet {
/**
* @author Aloento
* @since 1.0.0
- * @version 0.2.0
+ * @version 0.2.2
*/
- public static GetBySlice(objId: string): Promise {
+ public static GetBySlice(objId: string, logger: Logger): Promise {
const slice: Uint8Array[] = [];
return Shared.GetOrSet(objId, () => new Promise(
(resolve, reject) => {
- ObjectStorage.Get(objId).then(x =>
+ this.Get(objId).then(x =>
x.subscribe({
error(err) {
reject(err);
},
next(value) {
slice.push(value);
- console.debug("Received Slice", objId, slice.length);
+ logger.debug("Received Slice", objId, slice.length);
},
complete() {
resolve(slice);
diff --git a/src/ShopNet/Order/Delete.ts b/src/ShopNet/Order/Delete.ts
new file mode 100644
index 0000000..dfa31f3
--- /dev/null
+++ b/src/ShopNet/Order/Delete.ts
@@ -0,0 +1,24 @@
+import { useRequest } from "ahooks";
+import { Options } from "ahooks/lib/useRequest/src/types";
+import { ShopNet } from "../ShopNet";
+
+/**
+ * @author Aloento
+ * @since 1.0.0
+ * @version 0.1.0
+ */
+export abstract class OrderDelete extends ShopNet {
+ /**
+ * @author Aloento
+ * @since 1.0.0
+ * @version 0.1.0
+ */
+ public static useDelete(options: Options) {
+ return useRequest(async orderId => {
+ this.EnsureLogin();
+ const res = await this.Invoke("OrderDeleteCancel", orderId);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
+ }
+}
diff --git a/src/ShopNet/Order/Get.ts b/src/ShopNet/Order/Get.ts
index 6da6d50..6dcb890 100644
--- a/src/ShopNet/Order/Get.ts
+++ b/src/ShopNet/Order/Get.ts
@@ -1,5 +1,6 @@
import dayjs from "dayjs";
import { ICartItem } from "~/Components/ShopCart";
+import { Logger } from "~/Helpers/Logger";
import { IOrderItem } from "~/Pages/History";
import { IComment } from "~/Pages/History/Comment";
import { IOrderDetail } from "~/Pages/History/Detail";
@@ -11,16 +12,20 @@ import { OrderEntity } from "./Entity";
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.2.0
*/
export abstract class OrderGet extends ShopNet {
+ /** "Order", "Get" */
+ protected static override readonly Log = [...super.Log, "Order", "Get"];
+
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async List(): Promise {
+ public static async List(pLog: Logger): Promise {
this.EnsureLogin();
+ const log = pLog.With(...this.Log, "List");
const list = await this.WithTimeCache<
{
@@ -36,7 +41,7 @@ export abstract class OrderGet extends ShopNet {
const order = await OrderEntity.Order(meta.OrderId);
if (!order) {
- console.error(`OrderGetList Mismatch: Order ${meta.OrderId} not found`);
+ log.warn(`[Mismatch] Order ${meta.OrderId} not found`);
continue;
}
@@ -46,7 +51,7 @@ export abstract class OrderGet extends ShopNet {
const prod = await ProductEntity.Product(prodId);
if (!prod) {
- console.error(`OrderGetList Mismatch: Product ${prodId} not found`);
+ log.warn(`[Mismatch] Product ${prodId} not found`);
continue;
}
@@ -69,10 +74,11 @@ export abstract class OrderGet extends ShopNet {
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async Detail(orderId: number): Promise {
+ public static async Detail(orderId: number, pLog: Logger): Promise {
this.EnsureLogin();
+ const log = pLog.With(...this.Log, "Detail");
const meta = await this.WithTimeCache<
{
@@ -95,14 +101,14 @@ export abstract class OrderGet extends ShopNet {
const type = await ProductEntity.Type(typeId);
if (!type) {
- console.error(`OrderGetDetail Mismatch: Type ${typeId} not found. Order : ${orderId}`);
+ log.warn(`[Mismatch] Type ${typeId} not found. Order : ${orderId}`);
continue;
}
const vari = await ProductEntity.Variant(type.VariantId);
if (!vari) {
- console.error(`OrderGetDetail Mismatch: Variant ${type.VariantId} not found. Type : ${typeId}, Order : ${orderId}`);
+ log.warn(`[Mismatch] Variant ${type.VariantId} not found. Type : ${typeId}, Order : ${orderId}`);
continue;
}
@@ -113,15 +119,15 @@ export abstract class OrderGet extends ShopNet {
const prod = await ProductEntity.Product(prodId);
if (!prod) {
- console.error(`OrderGetDetail Mismatch: Product ${prodId} not found. Order : ${orderId}`);
+ log.warn(`[Mismatch] Product ${prodId} not found. Order : ${orderId}`);
continue;
}
const list = await ProductGet.PhotoList(prodId);
- const cover = await this.FindCover(list, prodId);
+ const cover = await this.FindCover(list, prodId, log);
if (!cover)
- console.warn(`Product ${prodId} has no photo`);
+ log.warn(`Product ${prodId} has no photo`);
items.push({
Id: index++,
@@ -139,7 +145,7 @@ export abstract class OrderGet extends ShopNet {
const cmt = await OrderEntity.Comment(cmtId);
if (!cmt) {
- console.error(`OrderGetDetail Mismatch: Comment ${cmtId} not found. Order : ${orderId}`);
+ log.warn(`[Mismatch] Comment ${cmtId} not found. Order : ${orderId}`);
continue;
}
diff --git a/src/ShopNet/Order/Post.ts b/src/ShopNet/Order/Post.ts
index f7f762f..68f0223 100644
--- a/src/ShopNet/Order/Post.ts
+++ b/src/ShopNet/Order/Post.ts
@@ -36,9 +36,11 @@ export abstract class OrderPost extends ShopNet {
* @version 0.2.0
*/
public static useAppend(options: Options) {
- return useRequest((orderId, cmt) => {
+ return useRequest(async (orderId, cmt) => {
this.EnsureLogin();
- return this.Invoke("OrderPostAppend", orderId, cmt);
+ const res = await this.Invoke("OrderPostAppend", orderId, cmt);
+ this.EnsureTrue(res);
+ return res;
}, options);
}
@@ -48,9 +50,25 @@ export abstract class OrderPost extends ShopNet {
* @version 0.2.0
*/
public static useCancel(options: Options) {
- return useRequest((orderId, reason) => {
+ return useRequest(async (orderId, reason) => {
this.EnsureLogin();
- return this.Invoke("OrderPostCancel", orderId, reason);
+ const res = await this.Invoke("OrderPostCancel", orderId, reason);
+ this.EnsureTrue(res);
+ return res;
+ }, options);
+ }
+
+ /**
+ * @author Aloento
+ * @since 1.0.0
+ * @version 0.1.0
+ */
+ public static useReceived(options: Options) {
+ return useRequest(async orderId => {
+ this.EnsureLogin();
+ const res = await this.Invoke("OrderPostReceived", orderId);
+ this.EnsureTrue(res);
+ return res;
}, options);
}
}
diff --git a/src/ShopNet/Order/index.ts b/src/ShopNet/Order/index.ts
index 83def11..0f2d477 100644
--- a/src/ShopNet/Order/index.ts
+++ b/src/ShopNet/Order/index.ts
@@ -1,3 +1,4 @@
+import { OrderDelete } from "./Delete";
import { OrderGet } from "./Get";
import { OrderPost } from "./Post";
@@ -8,5 +9,6 @@ import { OrderPost } from "./Post";
*/
export const Order = {
Get: OrderGet,
- Post: OrderPost
+ Post: OrderPost,
+ Delete: OrderDelete,
}
diff --git a/src/ShopNet/Product/Get.ts b/src/ShopNet/Product/Get.ts
index 39653c9..f23ae95 100644
--- a/src/ShopNet/Product/Get.ts
+++ b/src/ShopNet/Product/Get.ts
@@ -1,4 +1,5 @@
import dayjs from "dayjs";
+import type { Logger } from "~/Helpers/Logger";
import { IComboItem } from "~/Pages/Admin/Product/Combo";
import { IPhotoItem } from "~/Pages/Admin/Product/Photo";
import { IProductInfo } from "~/Pages/Gallery";
@@ -9,21 +10,26 @@ import { ProductEntity } from "./Entity";
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.2.0
*/
export abstract class ProductGet extends ShopNet {
+ /** "Product", "Get" */
+ protected static override readonly Log = [...super.Log, "Product", "Get"];
+
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async Basic(prodId: number): Promise {
+ public static async Basic(prodId: number, pLog: Logger): Promise {
+ const log = pLog.With(...this.Log, "Basic");
+
const res = await ProductEntity.Product(prodId);
if (!res)
throw new Error(`Product ${prodId} Not Found`);
const list = await this.PhotoList(prodId);
- const cover = await this.FindCover(list, prodId);
+ const cover = await this.FindCover(list, prodId, log);
if (cover)
return {
@@ -31,7 +37,7 @@ export abstract class ProductGet extends ShopNet {
Cover: cover
};
- console.warn(`Product ${prodId} has no photo`);
+ log.warn(`Product ${prodId} has no photo`);
return {
Name: res.Name,
Cover: "",
@@ -50,9 +56,11 @@ export abstract class ProductGet extends ShopNet {
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async Combo(prodId: number): Promise {
+ public static async Combo(prodId: number, pLog: Logger): Promise {
+ const log = pLog.With(...this.Log, "Combo");
+
const list = await this.ComboList(prodId);
const items: IComboItem[] = [];
@@ -63,14 +71,14 @@ export abstract class ProductGet extends ShopNet {
const type = await ProductEntity.Type(typeId);
if (!type) {
- console.error(`ComboList Mismatch: Type ${typeId} not found. Combo ${combo.ComboId} : Product ${prodId}`);
+ log.error(`[Mismatch] Type ${typeId} not found. Combo ${combo.ComboId} : Product ${prodId}`);
continue;
}
const vari = await ProductEntity.Variant(type.VariantId);
if (!vari) {
- console.error(`ComboList Mismatch: Variant ${type.VariantId} not found. Combo ${combo.ComboId} : Type ${typeId} : Product ${prodId}`);
+ log.error(`[Mismatch] Variant ${type.VariantId} not found. Combo ${combo.ComboId} : Type ${typeId} : Product ${prodId}`);
continue;
}
@@ -90,9 +98,11 @@ export abstract class ProductGet extends ShopNet {
/**
* @author Aloento
* @since 0.5.0
- * @version 1.0.0
+ * @version 1.0.1
*/
- public static async Carousel(prodId: number): Promise {
+ public static async Carousel(prodId: number, pLog: Logger): Promise {
+ const log = pLog.With(...this.Log, "Carousel");
+
const list = await this.PhotoList(prodId);
const photos: IPhotoItem[] = [];
@@ -107,7 +117,7 @@ export abstract class ProductGet extends ShopNet {
Caption: p.Caption,
});
else
- console.warn(`Photo ${id} not found in Product ${prodId}`);
+ log.warn(`Photo ${id} not found in Product ${prodId}`);
}
return photos.sort((a, b) => a.Id - b.Id);
diff --git a/src/ShopNet/ShopNet.ts b/src/ShopNet/ShopNet.ts
index efe1f26..504385e 100644
--- a/src/ShopNet/ShopNet.ts
+++ b/src/ShopNet/ShopNet.ts
@@ -7,13 +7,16 @@ import { SignalR } from "./SignalR";
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.1
+ * @version 0.1.2
*/
export abstract class ShopNet extends SignalR {
+ /** "|", "ShopNet" */
+ protected static readonly Log = ["|", "ShopNet"];
+
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.0
+ * @version 0.1.2
*/
public static readonly Hub = new HubConnectionBuilder()
.withUrl(import.meta.env.DEV ? "https://localhost/Hub" : "/Hub",
@@ -30,6 +33,7 @@ export abstract class ShopNet extends SignalR {
},
})
.withAutomaticReconnect()
+ .withStatefulReconnect()
.withHubProtocol(new MessagePackHubProtocol())
.configureLogging(import.meta.env.DEV ? LogLevel.Debug : LogLevel.Information)
.build();
diff --git a/src/ShopNet/SignalR.ts b/src/ShopNet/SignalR.ts
index 7bd10c5..a7295cc 100644
--- a/src/ShopNet/SignalR.ts
+++ b/src/ShopNet/SignalR.ts
@@ -2,8 +2,9 @@ import { HubConnectionState } from "@microsoft/signalr";
import dayjs, { Dayjs } from "dayjs";
import { Subject } from "rxjs";
import { NotLoginError, NotTrueError } from "~/Helpers/Exceptions";
+import type { Logger } from "~/Helpers/Logger";
import type { AdminNet } from "./Admin/AdminNet";
-import { Common, IConcurrency, Shared } from "./Database";
+import { Common, Shared, type IConcurrency } from "./Database";
import { ShopNet } from "./ShopNet";
/**
@@ -11,12 +12,12 @@ import { ShopNet } from "./ShopNet";
* @since 1.0.0
* @version 0.1.0
*/
-type Nets = typeof ShopNet | typeof AdminNet;
+type INet = typeof ShopNet | typeof AdminNet;
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.0
+ * @version 1.0.0
*/
export abstract class SignalR {
/**
@@ -24,13 +25,13 @@ export abstract class SignalR {
* @since 1.0.0
* @version 0.1.1
*/
- protected static EnsureConnected(this: Nets): Promise {
+ protected static async EnsureConnected(this: INet): Promise {
if (this.Hub.state === HubConnectionState.Connected)
return Promise.resolve();
if (this.Hub.state === HubConnectionState.Disconnected
|| this.Hub.state === HubConnectionState.Disconnecting)
- return this.Hub.start();
+ await this.Hub.start();
return new Promise(resolve => {
const interval = setInterval(() => {
@@ -47,7 +48,7 @@ export abstract class SignalR {
* @since 1.0.0
* @version 0.1.0
*/
- protected static async Invoke(this: Nets, methodName: string, ...args: any[]): Promise {
+ protected static async Invoke(this: INet, methodName: string, ...args: any[]): Promise {
await this.EnsureConnected();
return this.Hub.invoke(methodName, ...args);
}
@@ -57,7 +58,7 @@ export abstract class SignalR {
* @since 1.0.0
* @version 0.1.1
*/
- protected static EnsureLogin() {
+ protected static EnsureLogin(this: INet) {
if (!Common.LocalUser || Common.LocalUser.expired)
throw new NotLoginError();
}
@@ -67,7 +68,7 @@ export abstract class SignalR {
* @since 1.0.0
* @version 0.1.0
*/
- protected static EnsureTrue(res: boolean | null | undefined): asserts res is true {
+ protected static EnsureTrue(this: INet, res: boolean | null | undefined): asserts res is true {
if (!res)
throw new NotTrueError();
}
@@ -78,7 +79,7 @@ export abstract class SignalR {
* @version 0.2.1
*/
protected static async WithVersionCache(
- this: Nets, key: string | number, methodName: string, admin?: boolean
+ this: INet, key: string | number, methodName: string, admin?: boolean
): Promise {
const index = `${methodName}_${admin ? `Admin_${key}` : key}`;
const find = await Shared.Get(index);
@@ -114,7 +115,7 @@ export abstract class SignalR {
* @version 0.1.1
*/
protected static async WithTimeCache(
- this: Nets, key: string | number, methodName: string, exp: Dayjs, ...args: any[]
+ this: INet, key: string | number, methodName: string, exp: Dayjs, ...args: any[]
): Promise {
const res = await Shared.GetOrSet(
`${methodName}_${key}`,
@@ -131,14 +132,13 @@ export abstract class SignalR {
/**
* @author Aloento
* @since 1.0.0
- * @version 0.2.0
+ * @version 0.2.2
*/
- protected static async FindCover(photos: number[], prodId?: number): Promise {
+ protected static async FindCover(this: INet, photos: number[], prodId: number, logger: Logger): Promise {
const list = [];
for (const photoId of photos) {
- const { ProductEntity } = await import("./Product/Entity")
- const photo = await ProductEntity.Photo(photoId);
+ const photo = await (await import("./Product/Entity")).ProductEntity.Photo(photoId);
if (photo) {
list.push(photo);
@@ -146,11 +146,11 @@ export abstract class SignalR {
if (photo.Cover)
return photo.ObjectId;
} else
- console.warn(`Photo ${photoId} not found in Product ${prodId}`);
+ logger?.warn(`Photo ${photoId} not found in Product ${prodId}`);
}
if (list.length > 0) {
- console.warn(`Product ${prodId} has no cover photo, using first photo instead`);
+ logger?.warn(`Product ${prodId} has no cover photo, using first photo instead`);
return list.sort((a, b) => a.Order - b.Order)[0].ObjectId;
}
}
@@ -158,9 +158,9 @@ export abstract class SignalR {
/**
* @author Aloento
* @since 1.0.0
- * @version 0.1.0
+ * @version 0.1.2
*/
- protected static async HandleFileStream(file: File, subject: Subject) {
+ protected static async HandleFileStream(this: INet, file: File, subject: Subject, pLog: Logger) {
const chunkSize = 30 * 1024;
const chunks = Math.ceil(file.size / chunkSize);
let index = 0;
@@ -171,14 +171,14 @@ export abstract class SignalR {
const chunk = file.slice(start, end);
const reader = new FileReader();
- const buffer = await new Promise((resolve, reject) => {
- reader.onload = () => resolve(new Uint8Array(reader.result as ArrayBuffer));
- reader.onerror = () => reject(reader.error);
+ const buffer = await new Promise((res, rej) => {
+ reader.onload = () => res(new Uint8Array(reader.result as ArrayBuffer));
+ reader.onerror = () => rej(reader.error);
reader.readAsArrayBuffer(chunk);
});
subject.next(buffer);
- console.debug(`Sent chunk ${index + 1}/${chunks}`);
+ pLog?.debug(`Sent chunk ${index + 1}/${chunks}`);
index++;
}
diff --git a/src/ShopNet/Table.ts b/src/ShopNet/Table.ts
index eb55c05..05240fa 100644
--- a/src/ShopNet/Table.ts
+++ b/src/ShopNet/Table.ts
@@ -65,11 +65,11 @@ export class Table {
/**
* @author Aloento
* @since 0.1.0 MusiLand
- * @version 0.2.0
+ * @version 0.2.1
*/
public async Set(id: string, val: T, exp?: Dayjs | null): Promise {
if (!val)
- throw `val is null or undefined`;
+ throw TypeError("Value cannot be null");
if (exp === null) {
await this.Sto.put({
@@ -82,7 +82,7 @@ export class Table {
const time = (exp || dayjs().add(1, "week")).unix();
if (exp && time < dayjs().unix())
- throw "The expiration time cannot be less than the current time";
+ throw RangeError(`Expire time [${time}] cannot be less than now`);
await this.Sto.put({
Id: id, Exp: time,
@@ -95,11 +95,11 @@ export class Table {
/**
* @author Aloento
* @since 0.3.0 MusiLand
- * @version 0.2.0
+ * @version 0.2.1
*/
public Trim() {
- return this.Sto.filter(x => {
- return typeof x.Exp === "number" && x.Exp < dayjs().unix();
- }).delete();
+ return this.Sto
+ .filter(x => typeof x.Exp === "number" && x.Exp < dayjs().unix())
+ .delete();
}
}
diff --git a/src/ShopNet/User/Get.tsx b/src/ShopNet/User/Get.tsx
index 04eba2e..5662705 100644
--- a/src/ShopNet/User/Get.tsx
+++ b/src/ShopNet/User/Get.tsx
@@ -1,7 +1,9 @@
import { Toast, ToastTitle } from "@fluentui/react-components";
+import { useConst } from "@fluentui/react-hooks";
import { useRequest } from "ahooks";
import type { Options } from "ahooks/lib/useRequest/src/types";
import { NotLoginError } from "~/Helpers/Exceptions";
+import type { Logger } from "~/Helpers/Logger";
import { useErrorToast } from "~/Helpers/useToast";
import { IConcurrency } from "../Database";
import { ShopNet } from "../ShopNet";
@@ -22,10 +24,11 @@ export abstract class UserGet extends ShopNet {
/**
* @author Aloento
* @since 1.0.0
- * @version 0.2.0
+ * @version 0.2.1
*/
- public static useMe(options?: Options, suppress: boolean = true) {
- const { dispatch, dispatchToast } = useErrorToast();
+ public static useMe(pLog: Logger, options?: Options, suppress: boolean = true) {
+ const log = useConst(() => pLog.With("|", "Hub", "User", "Get", "Me"));
+ const { dispatch, dispatchToast } = useErrorToast(log);
return useRequest(() => {
this.EnsureLogin();
@@ -35,7 +38,7 @@ export abstract class UserGet extends ShopNet {
onError: (e, req) => {
if (e instanceof NotLoginError) {
if (suppress)
- console.debug(e);
+ log.debug(e);
else
dispatchToast(
diff --git a/src/ShopNet/User/Post.ts b/src/ShopNet/User/Post.ts
index bb70ecb..cd5da34 100644
--- a/src/ShopNet/User/Post.ts
+++ b/src/ShopNet/User/Post.ts
@@ -15,9 +15,11 @@ export abstract class UserPost extends ShopNet {
* @version 0.2.0
*/
public static useUpdate(options: Options]>) {
- return useRequest(req => {
+ return useRequest(async req => {
this.EnsureLogin();
- return this.Invoke("UserPostUpdate", req);
+ const res = await this.Invoke("UserPostUpdate", req);
+ this.EnsureTrue(res);
+ return res;
}, options);
}
}
diff --git a/vite.config.ts b/vite.config.ts
index 72084e9..a38a46a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,10 +1,12 @@
-import react from "@vitejs/plugin-react-swc"
-import path from "path"
-import { defineConfig } from "vite"
+import react from "@vitejs/plugin-react-swc";
+import path from "path";
+import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [
+ react(),
+ ],
resolve: {
alias: {
"~": path.resolve(__dirname, "src"),
@@ -15,6 +17,6 @@ export default defineConfig({
reportCompressedSize: false,
modulePreload: {
polyfill: false,
- }
+ },
},
-})
+});