diff --git a/README.md b/README.md
new file mode 100644
index 0000000..df7576d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+## AwaiShop
+
+A Simple E-Commerce System with C# & SignalR & FluentUI3 & AzureAD
+
+## Basic Requirements
+
+- .NET 8
+- Node.js 17+
+- Visual Studio 2022 Preview
+- Visual Studio Code
diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs b/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs
index 5084180..5595f0d 100644
--- a/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs
+++ b/SoarCraft.AwaiShop/AdminHub/Product/Delete.cs
@@ -106,13 +106,13 @@ private async Task deleteType(Type type) {
*
* @author Aloento
* @since 0.1.0
- * @version 0.2.0
+ * @version 0.3.0
*
*/
- public async Task ProductDeleteType(uint variantId, string reqType) {
+ public async Task ProductDeleteType(uint typeId) {
await this.deleteType(
await this.Db.Types
- .Where(x => x.VariantId == variantId && x.Name == reqType)
+ .Where(x => x.TypeId == typeId)
.IncludeOptimized(x => x.Combos)
.SingleAsync()
);
diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Get.cs b/SoarCraft.AwaiShop/AdminHub/Product/Get.cs
index 9dc6c86..b0f3d95 100644
--- a/SoarCraft.AwaiShop/AdminHub/Product/Get.cs
+++ b/SoarCraft.AwaiShop/AdminHub/Product/Get.cs
@@ -36,15 +36,25 @@ await this.Db.Products
*
* @author Aloento
* @since 0.1.0
- * @version 1.0.0
+ * @version 1.1.0
*
*/
- public async Task ProductGetVariants(uint prodId) =>
- await this.Db.Variants
+ public Task ProductGetVariants(uint prodId) =>
+ this.Db.Variants
.Where(x => x.ProductId == prodId)
- .Select(x => new {
- x.VariantId,
- Types = x.Types.Select(t => t.TypeId).ToArray()
- })
+ .Select(x => x.VariantId)
+ .ToArrayAsync();
+
+ /**
+ *
+ * @author Aloento
+ * @since 1.3.0
+ * @version 0.1.0
+ *
+ */
+ public Task ProductGetTypes(uint variantId) =>
+ this.Db.Types
+ .Where(x => x.VariantId == variantId)
+ .Select(x => x.TypeId)
.ToArrayAsync();
}
diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs b/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs
index d7da6db..414075b 100644
--- a/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs
+++ b/SoarCraft.AwaiShop/AdminHub/Product/Patch.cs
@@ -228,10 +228,10 @@ private async Task> archiveCombos(ICollection oldCombos) {
*
* @author Aloento
* @since 0.1.0
- * @version 1.0.0
+ * @version 1.1.0
*
*/
- public async Task ProductPatchType(uint variantId, string oldName, string newName) {
+ public async Task ProductPatchType(uint typeId, string newName) {
var valid = typeof(Type)
.GetProperty(nameof(Type.Name))!
.GetCustomAttribute()!;
@@ -240,7 +240,7 @@ public async Task ProductPatchType(uint variantId, string oldName, string
throw new HubException(valid.FormatErrorMessage("Name"));
var type = this.Db.Types
- .Where(x => x.VariantId == variantId && x.Name == oldName);
+ .Where(x => x.TypeId == typeId);
var any = await type
.SelectMany(x => x.Combos)
diff --git a/SoarCraft.AwaiShop/AdminHub/Product/Post.cs b/SoarCraft.AwaiShop/AdminHub/Product/Post.cs
index 728c02d..d35cb28 100644
--- a/SoarCraft.AwaiShop/AdminHub/Product/Post.cs
+++ b/SoarCraft.AwaiShop/AdminHub/Product/Post.cs
@@ -110,7 +110,7 @@ public async Task ProductPostPhoto(uint prodId, IAsyncEnumerable i
*
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*
*/
public async Task ProductPostVariant(uint prodId, string name) {
@@ -138,14 +138,14 @@ public async Task ProductPostVariant(uint prodId, string name) {
});
await this.Db.SaveChangesAsync();
- return temp.Entity.ProductId;
+ return temp.Entity.VariantId;
}
/**
*
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*
*/
public async Task ProductPostType(uint variantId, string name) {
@@ -173,7 +173,7 @@ public async Task ProductPostType(uint variantId, string name) {
});
await this.Db.SaveChangesAsync();
- return temp.Entity.VariantId;
+ return temp.Entity.TypeId;
}
/**
diff --git a/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs b/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs
index 2f810c0..4ca8e9b 100644
--- a/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs
+++ b/SoarCraft.AwaiShop/Helpers/LoggerExtension.cs
@@ -6,13 +6,23 @@ namespace SoarCraft.AwaiShop.Helpers;
*
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.2.0
*
*/
internal static partial class LoggerExtension {
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Debug,
+ Message = "Guest : Visit from [{ip}]"
+ )]
+ private static partial void guestVisit(ILogger logger, string? ip);
+
+ public static void GuestVisit(this ILogger logger, HubCallerContext ctx) =>
+ guestVisit(logger, ctx.GetHttpContext()?.Connection.RemoteIpAddress?.ToString());
+
+ [LoggerMessage(
+ EventId = 2001,
+ Level = LogLevel.Information,
Message = "User {name} : [{uid}] Logged from [{ip}]"
)]
private static partial void userLogin(ILogger logger, string? name, string? uid, string? ip);
diff --git a/SoarCraft.AwaiShop/Hub/ShopHub.cs b/SoarCraft.AwaiShop/Hub/ShopHub.cs
index a606538..b2e1bd2 100644
--- a/SoarCraft.AwaiShop/Hub/ShopHub.cs
+++ b/SoarCraft.AwaiShop/Hub/ShopHub.cs
@@ -18,7 +18,7 @@ internal partial class ShopHub(ShopContext db, ILogger logger) : CraftH
*
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.1.1
*
*/
public override async Task OnConnectedAsync() {
@@ -34,7 +34,8 @@ public override async Task OnConnectedAsync() {
await this.Clients.Caller.OnNewUser();
this.Context.Items.TryAdd("NewUser", true);
}
- }
+ } else
+ this.Logger.GuestVisit(this.Context);
}
/**
diff --git a/package.json b/package.json
index 9020100..706d1f4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "awaishop",
"private": true,
- "version": "1.4.0",
+ "version": "1.4.5",
"type": "module",
"author": {
"name": "Aloento",
diff --git a/src/Pages/Admin/Order/Ship.tsx b/src/Pages/Admin/Order/Ship.tsx
index 9f73f30..98325da 100644
--- a/src/Pages/Admin/Order/Ship.tsx
+++ b/src/Pages/Admin/Order/Ship.tsx
@@ -1,7 +1,7 @@
import { Button, Field, Input, Toast, ToastTitle } from "@fluentui/react-components";
import { EditRegular, SendRegular } from "@fluentui/react-icons";
import { useBoolean } from "ahooks";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { useOrder } from "~/Components/Order/useOrder";
import { Logger } from "~/Helpers/Logger";
import { useErrorToast } from "~/Helpers/useToast";
@@ -12,14 +12,18 @@ const log = new Logger("Admin", "Order", "Detail", "Shipment");
/**
* @author Aloento
* @since 0.5.0
- * @version 0.3.0
+ * @version 0.3.1
*/
export function Shipment({ OrderId }: { OrderId: number }) {
const [edit, { setTrue, setFalse }] = useBoolean();
const { dispatch, dispatchToast } = useErrorToast(log);
const { data: order, mutate } = useOrder(OrderId, true);
- const [track, setTrack] = useState(order?.TrackingNumber);
+ const [track, setTrack] = useState("");
+
+ useEffect(() => {
+ order?.TrackingNumber && setTrack(order?.TrackingNumber);
+ }, [order]);
const { run } = AdminHub.Order.Post.useShip({
manual: true,
diff --git a/src/Pages/Admin/Product/Combo/Detail.tsx b/src/Pages/Admin/Product/Combo/Detail.tsx
index e948c2c..17c00c1 100644
--- a/src/Pages/Admin/Product/Combo/Detail.tsx
+++ b/src/Pages/Admin/Product/Combo/Detail.tsx
@@ -1,23 +1,23 @@
import { Button, Combobox, DataGridCell, DataGridHeaderCell, Dialog, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Label, Option, SpinButton, TableColumnDefinition, Toast, ToastTitle, createTableColumn, makeStyles, tokens } from "@fluentui/react-components";
import { DismissRegular, EditRegular } from "@fluentui/react-icons";
-import { useBoolean, useRequest } from "ahooks";
+import { useAsyncEffect, useBoolean } from "ahooks";
import { useState } from "react";
import { DelegateDataGrid } from "~/Components/DataGrid";
import { Logger } from "~/Helpers/Logger";
import { Flex } from "~/Helpers/Styles";
import { useErrorToast } from "~/Helpers/useToast";
+import { Hub } from "~/ShopNet";
import { AdminHub } from "~/ShopNet/Admin";
import { IComboItem } from ".";
-import { IVariantItem } from "../Variant";
+import { IUpdateComboItem, IVariantItem } from "./New";
/**
* @author Aloento
* @since 0.5.0
* @version 0.1.0
*/
-interface IEditComboItem extends IVariantItem {
+interface IEditComboItem extends IUpdateComboItem {
Current: string;
- Update: (type: string) => void;
}
/**
@@ -80,6 +80,7 @@ const useStyles = makeStyles({
*/
export interface IDetailComboItem extends IComboItem {
ProdId: number;
+ /** @deprecated */
Refresh: () => void;
}
@@ -88,21 +89,48 @@ const log = new Logger("Admin", "Product", "Detail", "Combo", "Detail");
/**
* @author Aloento
* @since 0.5.0
- * @version 0.2.3
+ * @version 0.3.0
*/
export function AdminProductComboDetail({ Id, ProdId, Combo, Stock, Refresh }: IDetailComboItem) {
const [open, { toggle }] = useBoolean();
const [combo, setCombo] = useState(Combo);
const [stock, setStock] = useState(Stock);
- const { data: varis } = useRequest(() => AdminHub.Product.Get.Variants(ProdId, log), {
+ const [varis, setVaris] = useState([]);
+ const { data: varIds } = AdminHub.Product.Get.useVariants(ProdId, {
onError: log.error
});
+ useAsyncEffect(async () => {
+ if (!varIds)
+ return;
+
+ const varis: IVariantItem[] = [];
+
+ for (const i of varIds) {
+ const typeIds = await AdminHub.Product.Get.Types(i);
+ const types = [];
+
+ for (const typeId of typeIds) {
+ const type = await Hub.Product.Get.Type(typeId);
+ types.push(type);
+ }
+
+ const { Name } = await Hub.Product.Get.Variant(i);
+
+ varis.push({
+ Id: i,
+ Name: Name,
+ Types: types.map(x => x.Name)
+ });
+ }
+
+ setVaris(varis);
+ }, [varIds]);
+
const { dispatch, dispatchToast } = useErrorToast(log);
- const { run } = AdminHub.Product.Patch.useCombo({
- manual: true,
+ const { run, loading } = AdminHub.Product.Patch.useCombo({
onError(e, req) {
dispatch({
Message: "Failed Update Combo",
@@ -167,7 +195,13 @@ export function AdminProductComboDetail({ Id, ProdId, Combo, Stock, Refresh }: I
setStock(val);
}} />
-
+
diff --git a/src/Pages/Admin/Product/Combo/New.tsx b/src/Pages/Admin/Product/Combo/New.tsx
index 4cf24f9..0e0fc88 100644
--- a/src/Pages/Admin/Product/Combo/New.tsx
+++ b/src/Pages/Admin/Product/Combo/New.tsx
@@ -1,20 +1,31 @@
import { Button, Combobox, DataGridCell, DataGridHeaderCell, Dialog, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Label, Option, SpinButton, TableColumnDefinition, Toast, ToastTitle, createTableColumn, makeStyles, tokens } from "@fluentui/react-components";
import { AddRegular, DismissRegular } from "@fluentui/react-icons";
-import { useBoolean, useRequest } from "ahooks";
+import { useAsyncEffect, useBoolean } from "ahooks";
import { useState } from "react";
import { DelegateDataGrid } from "~/Components/DataGrid";
import { Logger } from "~/Helpers/Logger";
import { Flex } from "~/Helpers/Styles";
import { useErrorToast } from "~/Helpers/useToast";
+import { Hub } from "~/ShopNet";
import { AdminHub } from "~/ShopNet/Admin";
-import { IVariantItem } from "../Variant";
/**
* @author Aloento
* @since 0.5.0
* @version 0.1.0
*/
-interface INewComboItem extends IVariantItem {
+export interface IVariantItem {
+ Id: number;
+ Name: string;
+ Types: string[];
+}
+
+/**
+ * @author Aloento
+ * @since 0.5.0
+ * @version 0.1.0
+ */
+export interface IUpdateComboItem extends IVariantItem {
Update: (type: string) => void;
}
@@ -23,8 +34,8 @@ interface INewComboItem extends IVariantItem {
* @since 0.5.0
* @version 0.1.0
*/
-const columns: TableColumnDefinition[] = [
- createTableColumn({
+const columns: TableColumnDefinition[] = [
+ createTableColumn({
columnId: "Variant",
renderHeaderCell: () => {
return Variant
@@ -33,7 +44,7 @@ const columns: TableColumnDefinition[] = [
return {item.Name}
}
}),
- createTableColumn({
+ createTableColumn({
columnId: "Type",
renderHeaderCell: () => {
return Type
@@ -72,26 +83,52 @@ const log = new Logger("Admin", "Product", "Detail", "Combo", "NewCombo");
/**
* @author Aloento
* @since 0.5.0
- * @version 0.2.3
+ * @version 1.0.0
*/
export function AdminProductNewCombo({ ProdId, Refresh }: { ProdId: number; Refresh: () => void }) {
const [open, { toggle }] = useBoolean();
+
+ const [varis, setVaris] = useState([]);
const [combo, setCombo] = useState>({});
const [stock, setStock] = useState(1);
- const { data: varis } = useRequest(() => AdminHub.Product.Get.Variants(ProdId, log), {
- onSuccess(data) {
- for (const i of data)
- combo[i.Name] = "";
-
- setCombo({ ...combo });
- },
+ const { data: varIds } = AdminHub.Product.Get.useVariants(ProdId, {
onError: log.error
});
+ useAsyncEffect(async () => {
+ if (!varIds)
+ return;
+
+ const varis: IVariantItem[] = [];
+
+ for (const i of varIds) {
+ const typeIds = await AdminHub.Product.Get.Types(i);
+ const types = [];
+
+ for (const typeId of typeIds) {
+ const type = await Hub.Product.Get.Type(typeId);
+ types.push(type);
+ }
+
+ const { Name } = await Hub.Product.Get.Variant(i);
+
+ varis.push({
+ Id: i,
+ Name: Name,
+ Types: types.map(x => x.Name)
+ });
+
+ combo[Name] = "";
+ }
+
+ setVaris(varis);
+ setCombo({ ...combo });
+ }, [varIds]);
+
const { dispatch, dispatchToast } = useErrorToast(log);
- const { run } = AdminHub.Product.Post.useCombo({
+ const { run, loading } = AdminHub.Product.Post.useCombo({
onError(e, req) {
dispatch({
Message: "Failed Create Combo",
@@ -154,7 +191,13 @@ export function AdminProductNewCombo({ ProdId, Refresh }: { ProdId: number; Refr
setStock(val);
}} />
-
+
diff --git a/src/Pages/Admin/Product/Variant/Delete.tsx b/src/Pages/Admin/Product/Variant/Delete.tsx
index d9ba861..57c301b 100644
--- a/src/Pages/Admin/Product/Variant/Delete.tsx
+++ b/src/Pages/Admin/Product/Variant/Delete.tsx
@@ -9,12 +9,13 @@ const log = new Logger("Admin", "Product", "Detail", "Variant", "Delete");
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.2
+ * @version 0.2.0
+ * @todo Add the ability to refresh the variant list
*/
-export function AdminProductVariantDelete({ VariantId, Refresh }: { VariantId: number; Refresh: () => void }) {
+export function AdminProductVariantDelete({ VariantId }: { VariantId: number; }) {
const { dispatch, dispatchToast } = useErrorToast(log);
- const { run } = AdminHub.Product.Delete.useVariant({
+ const { run, loading } = AdminHub.Product.Delete.useVariant(VariantId, {
onError(e, req) {
dispatch({
Message: "Failed Delete Variant",
@@ -29,16 +30,15 @@ export function AdminProductVariantDelete({ VariantId, Refresh }: { VariantId: n
,
{ intent: "success" }
);
-
- Refresh();
}
});
return (
}
- onClick={() => run(VariantId)}
+ onClick={() => run()}
/>
)
}
diff --git a/src/Pages/Admin/Product/Variant/Edit/Delete.tsx b/src/Pages/Admin/Product/Variant/Edit/Delete.tsx
index 7cde07e..3ed6ccb 100644
--- a/src/Pages/Admin/Product/Variant/Edit/Delete.tsx
+++ b/src/Pages/Admin/Product/Variant/Edit/Delete.tsx
@@ -9,12 +9,12 @@ const log = new Logger("Admin", "Product", "Detail", "Variant", "Edit", "TypeDel
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.1
+ * @version 0.2.0
*/
-export function AdminProductTypeDelete({ VariantId, Type, Refresh }: { VariantId: number; Type: string; Refresh: () => void }) {
+export function AdminProductTypeDelete({ TypeId }: { TypeId: number; }) {
const { dispatch, dispatchToast } = useErrorToast(log);
- const { run } = AdminHub.Product.Delete.useType({
+ const { run } = AdminHub.Product.Delete.useType(TypeId, {
onError(e, req) {
dispatch({
Message: "Failed Delete Type",
@@ -29,8 +29,6 @@ export function AdminProductTypeDelete({ VariantId, Type, Refresh }: { VariantId
,
{ intent: "success" }
);
-
- Refresh();
}
});
@@ -38,7 +36,7 @@ export function AdminProductTypeDelete({ VariantId, Type, Refresh }: { VariantId
}
- onClick={() => run(VariantId, Type)}
+ onClick={() => run()}
/>
)
}
diff --git a/src/Pages/Admin/Product/Variant/Edit/Name.tsx b/src/Pages/Admin/Product/Variant/Edit/Name.tsx
index 239979e..7893524 100644
--- a/src/Pages/Admin/Product/Variant/Edit/Name.tsx
+++ b/src/Pages/Admin/Product/Variant/Edit/Name.tsx
@@ -1,9 +1,10 @@
import { Button, Input, Subtitle2, Toast, ToastTitle } from "@fluentui/react-components";
import { EditRegular, SendRegular } from "@fluentui/react-icons";
import { useBoolean } from "ahooks";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { Logger } from "~/Helpers/Logger";
import { useErrorToast } from "~/Helpers/useToast";
+import { Hub } from "~/ShopNet";
import { AdminHub } from "~/ShopNet/Admin";
const log = new Logger("Admin", "Product", "Detail", "Variant", "Edit", "Name");
@@ -11,14 +12,22 @@ const log = new Logger("Admin", "Product", "Detail", "Variant", "Edit", "Name");
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.1
+ * @version 0.2.0
*/
-export function AdminProductVariantName({ Id, Name }: { Id: number; Name: string; }) {
- const [name, setName] = useState(Name);
+export function AdminProductVariantName({ VariantId }: { VariantId: number }) {
+ const [name, setName] = useState("");
+ const { data } = Hub.Product.Get.useVariant(VariantId, {
+ onError: log.error
+ });
+
+ useEffect(() => {
+ data && setName(data.Name);
+ }, [data]);
+
const [edit, { setTrue, setFalse }] = useBoolean();
const { dispatch, dispatchToast } = useErrorToast(log);
- const { run } = AdminHub.Product.Patch.useVariantName({
+ const { run, loading } = AdminHub.Product.Patch.useVariantName(VariantId, {
onError(e, params) {
dispatch({
Message: "Failed Update Variant Name",
@@ -47,7 +56,7 @@ export function AdminProductVariantName({ Id, Name }: { Id: number; Name: string
onChange={(_, v) => setName(v.value)}
contentBefore={Name}
contentAfter={edit
- ? } onClick={() => run(Id, name)} />
+ ? } onClick={() => run(name)} disabled={loading} />
: } onClick={setTrue} />}
/>
);
diff --git a/src/Pages/Admin/Product/Variant/Edit/Type.tsx b/src/Pages/Admin/Product/Variant/Edit/Type.tsx
index 98d1fdc..6e34c71 100644
--- a/src/Pages/Admin/Product/Variant/Edit/Type.tsx
+++ b/src/Pages/Admin/Product/Variant/Edit/Type.tsx
@@ -6,6 +6,7 @@ import { useState } from "react";
import { Logger } from "~/Helpers/Logger";
import { ColFlex } from "~/Helpers/Styles";
import { useErrorToast } from "~/Helpers/useToast";
+import { Hub } from "~/ShopNet";
import { AdminHub } from "~/ShopNet/Admin";
/**
@@ -25,24 +26,31 @@ const log = new Logger("Admin", "Product", "Detail", "Variant", "Edit", "Type");
/**
* @author Aloento
* @since 0.5.0
- * @version 0.1.0
+ * @version 0.2.0
*/
interface IAdminProductType {
VariantId: number;
- Type?: string;
- Refresh: () => void;
+ TypeId?: number;
New?: true;
}
/**
* @author Aloento
* @since 0.5.0
- * @version 0.2.1
+ * @version 0.3.0
*/
-export function AdminProductType({ VariantId, Type, Refresh, New }: IAdminProductType) {
+export function AdminProductType({ VariantId, TypeId, New }: IAdminProductType) {
const style = useStyles();
const [open, { toggle }] = useBoolean();
- const [name, setName] = useState(Type || "");
+
+ const [name, setName] = useState("");
+ Hub.Product.Get.useType(TypeId!, {
+ manual: New,
+ onSuccess(data) {
+ setName(data.Name);
+ },
+ onError: log.error
+ });
const { dispatch, dispatchToast } = useErrorToast(log);
@@ -64,15 +72,14 @@ export function AdminProductType({ VariantId, Type, Refresh, New }: IAdminProduc
{ intent: "success" }
);
- Refresh();
setName("");
toggle();
}
}
- const { run: post } = AdminHub.Product.Post.useType(options);
+ const { run: post, loading: loadingN } = AdminHub.Product.Post.useType(VariantId, options);
- const { run: patch } = AdminHub.Product.Patch.useType(options);
+ const { run: patch, loading } = AdminHub.Product.Patch.useType(TypeId!, options);
return (
@@ -90,7 +97,10 @@ export function AdminProductType({ VariantId, Type, Refresh, New }: IAdminProduc
setName(e.value)} />
-