Compare commits
3 Commits
384d7e0287
...
brnamin
| Author | SHA1 | Date | |
|---|---|---|---|
| d957fa4cd3 | |||
| f986e2387f | |||
| 1d2f7c0732 |
@@ -0,0 +1,4 @@
|
|||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
frontend/.vite/
|
||||||
|
frontend/*.tsbuildinfo
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"ExpandedNodes": [
|
||||||
|
"",
|
||||||
|
"\\frontend",
|
||||||
|
"\\ShopAPI",
|
||||||
|
"\\ShopAPI\\ShopAPI"
|
||||||
|
],
|
||||||
|
"SelectedNode": "\\frontend\\.env.example",
|
||||||
|
"PreviewInSolutionExplorer": false
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"Version": 1,
|
||||||
|
"WorkspaceRootPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\",
|
||||||
|
"Documents": [
|
||||||
|
{
|
||||||
|
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\frontend\\.env.example||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||||
|
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:frontend\\.env.example||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DocumentGroupContainers": [
|
||||||
|
{
|
||||||
|
"Orientation": 0,
|
||||||
|
"VerticalTabListWidth": 256,
|
||||||
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": 4,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{26341fe2-71dd-46fd-bb1b-2e51a92a0d64}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{40ea2e6b-2121-4bb8-a43e-c83c04b51041}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{b1e99781-ab81-11d0-b683-00aa00a3ee26}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Document",
|
||||||
|
"DocumentIndex": 0,
|
||||||
|
"Title": ".env.example",
|
||||||
|
"DocumentMoniker": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\frontend\\.env.example",
|
||||||
|
"RelativeDocumentMoniker": "frontend\\.env.example",
|
||||||
|
"ToolTip": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\frontend\\.env.example",
|
||||||
|
"RelativeToolTip": "frontend\\.env.example",
|
||||||
|
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAA==",
|
||||||
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||||
|
"WhenOpened": "2026-03-17T13:32:24.987Z",
|
||||||
|
"EditorCaption": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,16 +8,27 @@
|
|||||||
".NETCoreApp,Version=v8.0": {
|
".NETCoreApp,Version=v8.0": {
|
||||||
"ShopAPI/1.0.0": {
|
"ShopAPI/1.0.0": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"BCrypt.Net-Next": "4.1.0",
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.25",
|
||||||
"Microsoft.EntityFrameworkCore": "9.0.0",
|
"Microsoft.EntityFrameworkCore": "9.0.0",
|
||||||
"Microsoft.EntityFrameworkCore.Tools": "9.0.0",
|
"Microsoft.EntityFrameworkCore.Tools": "9.0.0",
|
||||||
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
||||||
"Pomelo.EntityFrameworkCore.MySql": "9.0.0",
|
"Pomelo.EntityFrameworkCore.MySql": "9.0.0",
|
||||||
"Swashbuckle.AspNetCore": "6.6.2"
|
"Swashbuckle.AspNetCore": "6.6.2",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "8.16.0"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"ShopAPI.dll": {}
|
"ShopAPI.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BCrypt.Net-Next/4.1.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard2.1/BCrypt.Net-Next.dll": {
|
||||||
|
"assemblyVersion": "4.1.0.0",
|
||||||
|
"fileVersion": "4.1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Humanizer.Core/2.14.1": {
|
"Humanizer.Core/2.14.1": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/net6.0/Humanizer.dll": {
|
"lib/net6.0/Humanizer.dll": {
|
||||||
@@ -26,6 +37,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.25": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
|
||||||
|
"assemblyVersion": "8.0.25.0",
|
||||||
|
"fileVersion": "8.0.2526.11225"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
||||||
@@ -501,6 +523,72 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/8.16.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Abstractions.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Abstractions": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Logging.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Logging": "8.16.0",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Protocols.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols": "7.1.2",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
|
||||||
|
"Microsoft.IdentityModel.Logging": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Tokens.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.14": {
|
"Microsoft.OpenApi/1.6.14": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
||||||
@@ -660,6 +748,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens": "8.16.0",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.IdentityModel.Tokens.Jwt.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"System.IO.Pipelines/9.0.0": {
|
"System.IO.Pipelines/9.0.0": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/net8.0/System.IO.Pipelines.dll": {
|
"lib/net8.0/System.IO.Pipelines.dll": {
|
||||||
@@ -711,6 +811,13 @@
|
|||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
|
"BCrypt.Net-Next/4.1.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-5YT3DKllmtkyW68PjURu/V1TOe4MKiByKwsRNVcfYE1S5KuFTeozdmKzyNzolKiQF391OXCaQtINvYT3j1ERzQ==",
|
||||||
|
"path": "bcrypt.net-next/4.1.0",
|
||||||
|
"hashPath": "bcrypt.net-next.4.1.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"Humanizer.Core/2.14.1": {
|
"Humanizer.Core/2.14.1": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -718,6 +825,13 @@
|
|||||||
"path": "humanizer.core/2.14.1",
|
"path": "humanizer.core/2.14.1",
|
||||||
"hashPath": "humanizer.core.2.14.1.nupkg.sha512"
|
"hashPath": "humanizer.core.2.14.1.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.25": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-nb6jCyxh5eP9bsXkHmGcDxUiVIl5wJSombl3LN2L+sjGEVXzcMKbdRe0fp8LQtuBM2hKXcXFxMAYdnohdYJF8Q==",
|
||||||
|
"path": "microsoft.aspnetcore.authentication.jwtbearer/8.0.25",
|
||||||
|
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.8.0.25.nupkg.sha512"
|
||||||
|
},
|
||||||
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -900,6 +1014,48 @@
|
|||||||
"path": "microsoft.extensions.primitives/9.0.0",
|
"path": "microsoft.extensions.primitives/9.0.0",
|
||||||
"hashPath": "microsoft.extensions.primitives.9.0.0.nupkg.sha512"
|
"hashPath": "microsoft.extensions.primitives.9.0.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-gSxKLWRZzBpIsEoeUPkxfywNCCvRvl7hkq146XHPk5vOQc9izSf1I+uL1vh4y2U19QPxd9Z8K/8AdWyxYz2lSg==",
|
||||||
|
"path": "microsoft.identitymodel.abstractions/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.abstractions.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-prBU72cIP4V8E9fhN+o/YdskTsLeIcnKPbhZf0X6mD7fdxoZqnS/NdEkSr+9Zp+2q7OZBOMfNBKGbTbhXODO4w==",
|
||||||
|
"path": "microsoft.identitymodel.jsonwebtokens/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.jsonwebtokens.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-MTzXmETkNQPACR7/XCXM1OGM6oU9RkyibqeJRtO9Ndew2LnGjMf9Atqj2VSf4XC27X0FQycUAlzxxEgQMWn2xQ==",
|
||||||
|
"path": "microsoft.identitymodel.logging/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.logging.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-SydLwMRFx6EHPWJ+N6+MVaoArN1Htt92b935O3RUWPY1yUF63zEjvd3lBu79eWdZUwedP8TN2I5V9T3nackvIQ==",
|
||||||
|
"path": "microsoft.identitymodel.protocols/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-6lHQoLXhnMQ42mGrfDkzbIOR3rzKM1W1tgTeMPLgLCqwwGw0d96xFi/UiX/fYsu7d6cD5MJiL3+4HuI8VU+sVQ==",
|
||||||
|
"path": "microsoft.identitymodel.protocols.openidconnect/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.openidconnect.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-rtViGJcGsN7WcfUNErwNeQgjuU5cJNl6FDQsfi9TncwO+Epzn0FTfBsg3YuFW1Q0Ch/KPxaVdjLw3/+5Z5ceFQ==",
|
||||||
|
"path": "microsoft.identitymodel.tokens/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.tokens.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.14": {
|
"Microsoft.OpenApi/1.6.14": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -1026,6 +1182,13 @@
|
|||||||
"path": "system.diagnostics.diagnosticsource/9.0.0",
|
"path": "system.diagnostics.diagnosticsource/9.0.0",
|
||||||
"hashPath": "system.diagnostics.diagnosticsource.9.0.0.nupkg.sha512"
|
"hashPath": "system.diagnostics.diagnosticsource.9.0.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-rrs2u7DRMXQG2yh0oVyF/vLwosfRv20Ld2iEpYcKwQWXHjfV+gFXNQsQ9p008kR9Ou4pxBs68Q6/9zC8Gi1wjg==",
|
||||||
|
"path": "system.identitymodel.tokens.jwt/8.16.0",
|
||||||
|
"hashPath": "system.identitymodel.tokens.jwt.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"System.IO.Pipelines/9.0.0": {
|
"System.IO.Pipelines/9.0.0": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,9 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// Dieser Code wurde von einem Tool generiert.
|
// This code was generated by a tool.
|
||||||
// Laufzeitversion:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// der Code erneut generiert wird.
|
// the code is regenerated.
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("ShopAPI")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("ShopAPI")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+f986e2387f42193e95687820c213e43880d84136")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("ShopAPI")]
|
[assembly: System.Reflection.AssemblyProductAttribute("ShopAPI")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("ShopAPI")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("ShopAPI")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
be52108e965d99112d5251b6f56629d05a000587350c277aca31d5ba1f416964
|
41098eb382a4f44343bd406c25389fc17c93ba3401bf7b32e24159269779bdd2
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ build_property._SupportedPlatformList = Linux,macOS,Windows
|
|||||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
build_property.RootNamespace = ShopAPI
|
build_property.RootNamespace = ShopAPI
|
||||||
build_property.RootNamespace = ShopAPI
|
build_property.RootNamespace = ShopAPI
|
||||||
build_property.ProjectDir = C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\
|
build_property.ProjectDir = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\
|
||||||
build_property.EnableComHosting =
|
build_property.EnableComHosting =
|
||||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||||
build_property.RazorLangVersion = 8.0
|
build_property.RazorLangVersion = 8.0
|
||||||
build_property.SupportLocalizedComponentNames =
|
build_property.SupportLocalizedComponentNames =
|
||||||
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
||||||
build_property.MSBuildProjectDirectory = C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI
|
build_property.MSBuildProjectDirectory = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI
|
||||||
build_property._RazorSourceGeneratorDebug =
|
build_property._RazorSourceGeneratorDebug =
|
||||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||||
build_property.EnableCodeStyleSeverity =
|
build_property.EnableCodeStyleSeverity =
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// Dieser Code wurde von einem Tool generiert.
|
// This code was generated by a tool.
|
||||||
// Laufzeitversion:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// der Code erneut generiert wird.
|
// the code is regenerated.
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
af378ad7e60f8f4f8f66d7100fe853bfd8622963c6af9a14946e4724352cf619
|
af9804d850122ac4e0d9fd362b41f21ce02a7b47bea57d87ec82549fa510342f
|
||||||
|
|||||||
@@ -135,3 +135,148 @@ C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net
|
|||||||
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.pdb
|
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.pdb
|
||||||
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.genruntimeconfig.cache
|
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.genruntimeconfig.cache
|
||||||
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ref\ShopAPI.dll
|
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ref\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\appsettings.Development.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\appsettings.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.staticwebassets.endpoints.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.exe
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.deps.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.runtimeconfig.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.pdb
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\BCrypt.Net-Next.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Humanizer.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Bcl.AsyncInterfaces.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Build.Locator.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.CSharp.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.Workspaces.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.Design.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.Relational.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Caching.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Caching.Memory.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Configuration.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.DependencyInjection.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.DependencyModel.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Logging.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Logging.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Options.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Primitives.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.JsonWebTokens.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Logging.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Protocols.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Tokens.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.OpenApi.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Mono.TextTemplating.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\MySqlConnector.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Pomelo.EntityFrameworkCore.MySql.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Swashbuckle.AspNetCore.Swagger.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Swashbuckle.AspNetCore.SwaggerGen.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Swashbuckle.AspNetCore.SwaggerUI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.CodeDom.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.AttributedModel.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.Convention.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.Hosting.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.Runtime.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.TypedParts.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Diagnostics.DiagnosticSource.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.IdentityModel.Tokens.Jwt.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.IO.Pipelines.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Text.Encodings.Web.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Text.Json.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\runtimes\browser\lib\net8.0\System.Text.Encodings.Web.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.csproj.AssemblyReference.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rpswa.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.GeneratedMSBuildEditorConfig.editorconfig
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.AssemblyInfoInputs.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.AssemblyInfo.cs
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.csproj.CoreCompileInputs.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.MvcApplicationPartsAssemblyInfo.cs
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.MvcApplicationPartsAssemblyInfo.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rjimswa.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rjsmrazor.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rjsmcshtml.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\scopedcss\bundle\ShopAPI.styles.css
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.build.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.build.json.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.development.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.build.endpoints.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.csproj.Up2Date
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\refint\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.pdb
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.genruntimeconfig.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ref\ShopAPI.dll
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
b94a5596d89b54f7504b98ef7116ca86018e94a94b8eedb238b7c9fddaf1c409
|
6b3390096d439ba0d6e4de6a407fa36021954eae649a7b730c75b378bef2731e
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"2SiV7x3vjV8ZFi0sgmWSSTGHCwHAp08jCG9AL/ERQ/Q=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mSjvBL27zG1kSzjpaqc9iI0QRpH2\u002BnmIxzsYeoyiZ8k=","5\u002BRipzB3qq6oLG1hzpoNvnzpM95KVvzJdVIXKGR5Nps=","k4J\u002BX/9OfyQ\u002Bvg6DNSgPmdorYBHAlUCx2XqrIUvSzvM=","P9s2CLfWnGJ3BuLhrowTd7paEsc\u002BFPTVz6iCnwAyWDY=","cZjb5kG3xkbxY4SEseWX7ClUw/XN02dd37xJoHYSocs=","k2ZK2MXYMG89By4JV3rT0m6IHy183usaiKwTwNY8md4=","7INOJdHgqe/nqyYYieTHW8/FPUSM7kVKuqZEiBTlsOE="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"jjFS3ypc+SFFIJoabqxBcUwQp4+BH7vO9y9aDuWO8Ig=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["oM\u002B92zWtRWs1ZZHQG5F\u002BZt\u002BmUwKFCmEHXl6yhJyDB9E=","m4ORQ/F\u002BR\u002BZG7hKB0ZiSTgsXFT54fbrxurDLZfuPLtU=","/26wWziwS0KH/O896xGXBSLufvhay7wSjV2zdTxws18=","WyWGi43zm6PntQI/ucnnU3lxt5XudSMI/Un84LZNCNI=","pzhO73lOLr0aoNbQo3AXbJ7qsA2VDRY9Il7vtsfu6/g=","\u002BmYXsDWUwEv8MDDi5d\u002BdcyyfamgV9HnHNS18DvTVT8g=","Tm7CcfRRz8vmlt8l9mYtBKdJqqgOF4FJyGXnX/H8m3Q="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"g/T3BUSjVMK8fsoOmXvbmTfW2MVL2VJoCyrZtQ0or0o=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mSjvBL27zG1kSzjpaqc9iI0QRpH2\u002BnmIxzsYeoyiZ8k=","5\u002BRipzB3qq6oLG1hzpoNvnzpM95KVvzJdVIXKGR5Nps=","k4J\u002BX/9OfyQ\u002Bvg6DNSgPmdorYBHAlUCx2XqrIUvSzvM=","P9s2CLfWnGJ3BuLhrowTd7paEsc\u002BFPTVz6iCnwAyWDY=","cZjb5kG3xkbxY4SEseWX7ClUw/XN02dd37xJoHYSocs=","k2ZK2MXYMG89By4JV3rT0m6IHy183usaiKwTwNY8md4=","7INOJdHgqe/nqyYYieTHW8/FPUSM7kVKuqZEiBTlsOE="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"tbgf6NkZHDHq5Bwz13FNLNq2Biw7YSKbQETYkFTRirw=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["oM\u002B92zWtRWs1ZZHQG5F\u002BZt\u002BmUwKFCmEHXl6yhJyDB9E=","m4ORQ/F\u002BR\u002BZG7hKB0ZiSTgsXFT54fbrxurDLZfuPLtU=","/26wWziwS0KH/O896xGXBSLufvhay7wSjV2zdTxws18=","WyWGi43zm6PntQI/ucnnU3lxt5XudSMI/Un84LZNCNI=","pzhO73lOLr0aoNbQo3AXbJ7qsA2VDRY9Il7vtsfu6/g=","\u002BmYXsDWUwEv8MDDi5d\u002BdcyyfamgV9HnHNS18DvTVT8g=","Tm7CcfRRz8vmlt8l9mYtBKdJqqgOF4FJyGXnX/H8m3Q="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"Jr8OaCL0xTfG0mf5QmD9ofeLsJvV3IBK9E7sPFxRjbU=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mSjvBL27zG1kSzjpaqc9iI0QRpH2\u002BnmIxzsYeoyiZ8k=","5\u002BRipzB3qq6oLG1hzpoNvnzpM95KVvzJdVIXKGR5Nps="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"XJKffwBiP1Gt5f0UmbU2Us8/L7oX34bUNWnspz6E28M=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["oM\u002B92zWtRWs1ZZHQG5F\u002BZt\u002BmUwKFCmEHXl6yhJyDB9E=","m4ORQ/F\u002BR\u002BZG7hKB0ZiSTgsXFT54fbrxurDLZfuPLtU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// Dieser Code wurde von einem Tool generiert.
|
||||||
|
// Laufzeitversion:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
||||||
|
// der Code erneut generiert wird.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: Microsoft.Extensions.Configuration.UserSecrets.UserSecretsIdAttribute("33e8cb0c-ba59-4213-842e-10895258ce75")]
|
||||||
|
[assembly: System.Reflection.AssemblyCompanyAttribute("ShopAPI")]
|
||||||
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
||||||
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+91ee7a14bf0bcb73d7d98089c76ebea2f2fe7f63")]
|
||||||
|
[assembly: System.Reflection.AssemblyProductAttribute("ShopAPI")]
|
||||||
|
[assembly: System.Reflection.AssemblyTitleAttribute("ShopAPI")]
|
||||||
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|
||||||
|
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1744c178d2610e44f397698f39e437e9c2a0dfa624dd70d434483ad467a6d847
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
is_global = true
|
||||||
|
build_property.TargetFramework = net8.0
|
||||||
|
build_property.TargetFramework = net8.0
|
||||||
|
build_property.TargetPlatformMinVersion =
|
||||||
|
build_property.TargetPlatformMinVersion =
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb = true
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb = true
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property.RootNamespace = ShopAPI
|
||||||
|
build_property.RootNamespace = ShopAPI
|
||||||
|
build_property.ProjectDir = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\
|
||||||
|
build_property.EnableComHosting =
|
||||||
|
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||||
|
build_property.RazorLangVersion = 8.0
|
||||||
|
build_property.SupportLocalizedComponentNames =
|
||||||
|
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
||||||
|
build_property.MSBuildProjectDirectory = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI
|
||||||
|
build_property._RazorSourceGeneratorDebug =
|
||||||
|
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||||
|
build_property.EnableCodeStyleSeverity =
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// <auto-generated/>
|
||||||
|
global using global::Microsoft.AspNetCore.Builder;
|
||||||
|
global using global::Microsoft.AspNetCore.Hosting;
|
||||||
|
global using global::Microsoft.AspNetCore.Http;
|
||||||
|
global using global::Microsoft.AspNetCore.Routing;
|
||||||
|
global using global::Microsoft.Extensions.Configuration;
|
||||||
|
global using global::Microsoft.Extensions.DependencyInjection;
|
||||||
|
global using global::Microsoft.Extensions.Hosting;
|
||||||
|
global using global::Microsoft.Extensions.Logging;
|
||||||
|
global using global::System;
|
||||||
|
global using global::System.Collections.Generic;
|
||||||
|
global using global::System.IO;
|
||||||
|
global using global::System.Linq;
|
||||||
|
global using global::System.Net.Http;
|
||||||
|
global using global::System.Net.Http.Json;
|
||||||
|
global using global::System.Threading;
|
||||||
|
global using global::System.Threading.Tasks;
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"format": 1,
|
"format": 1,
|
||||||
"restore": {
|
"restore": {
|
||||||
"C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {}
|
"C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {}
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {
|
"C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectUniqueName": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"projectName": "ShopAPI",
|
"projectName": "ShopAPI",
|
||||||
"projectPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
||||||
"outputPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
"outputPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
||||||
"projectStyle": "PackageReference",
|
"projectStyle": "PackageReference",
|
||||||
"fallbackFolders": [
|
"fallbackFolders": [
|
||||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
@@ -26,7 +26,9 @@
|
|||||||
],
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
"https://api.nuget.org/v3/index.json": {}
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {},
|
||||||
|
"https://packagesource1": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\bib\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\bib\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.2</NuGetToolVersion>
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
<SourceRoot Include="C:\Users\bib\.nuget\packages\" />
|
<SourceRoot Include="C:\Users\bib\.nuget\packages\" />
|
||||||
|
|||||||
@@ -3843,11 +3843,11 @@
|
|||||||
"project": {
|
"project": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectUniqueName": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"projectName": "ShopAPI",
|
"projectName": "ShopAPI",
|
||||||
"projectPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
||||||
"outputPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
"outputPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
||||||
"projectStyle": "PackageReference",
|
"projectStyle": "PackageReference",
|
||||||
"fallbackFolders": [
|
"fallbackFolders": [
|
||||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
@@ -3862,7 +3862,9 @@
|
|||||||
],
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
"https://api.nuget.org/v3/index.json": {}
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {},
|
||||||
|
"https://packagesource1": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"dgSpecHash": "spizpp0kT/Q=",
|
"dgSpecHash": "ZLHuMCNFygY=",
|
||||||
"success": true,
|
"success": true,
|
||||||
"projectFilePath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectFilePath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"expectedPackageFiles": [
|
"expectedPackageFiles": [
|
||||||
"C:\\Users\\bib\\.nuget\\packages\\bcrypt.net-next\\4.1.0\\bcrypt.net-next.4.1.0.nupkg.sha512",
|
"C:\\Users\\bib\\.nuget\\packages\\bcrypt.net-next\\4.1.0\\bcrypt.net-next.4.1.0.nupkg.sha512",
|
||||||
"C:\\Users\\bib\\.nuget\\packages\\humanizer.core\\2.14.1\\humanizer.core.2.14.1.nupkg.sha512",
|
"C:\\Users\\bib\\.nuget\\packages\\humanizer.core\\2.14.1\\humanizer.core.2.14.1.nupkg.sha512",
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_API_BASE_URL=http://localhost:5276
|
||||||
|
VITE_USE_MOCK_API=true
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Fluxon Frontend Integration Checklist
|
||||||
|
|
||||||
|
Use this with the backend teammate before switching any feature from mock mode to real API.
|
||||||
|
|
||||||
|
## Product endpoints
|
||||||
|
- Route: `GET /api/product`
|
||||||
|
- Route: `GET /api/product/{id}`
|
||||||
|
- Response fields: `id`, `name`, `description`, `price`, `stock`, `categoryId`
|
||||||
|
- Nice to have: nested `category`
|
||||||
|
|
||||||
|
## Category endpoint
|
||||||
|
- Route: `GET /api/category`
|
||||||
|
- Response fields: `id`, `name`
|
||||||
|
|
||||||
|
## Auth endpoints
|
||||||
|
- Route: `POST /api/auth/login`
|
||||||
|
- Route: `POST /api/auth/register`
|
||||||
|
- Request body:
|
||||||
|
- Login: `email`, `password`
|
||||||
|
- Register: `name`, `email`, `password`
|
||||||
|
- Response: token plus customer identity fields
|
||||||
|
|
||||||
|
## Order endpoint
|
||||||
|
- Route: `GET /api/order`
|
||||||
|
- Route: `POST /api/order`
|
||||||
|
- Request body should accept:
|
||||||
|
- customer/shipping data
|
||||||
|
- `items[]` with `productId`, `quantity`, `unitPrice`
|
||||||
|
- Response should include:
|
||||||
|
- order id
|
||||||
|
- created date
|
||||||
|
- total
|
||||||
|
- status
|
||||||
|
- payment status
|
||||||
|
|
||||||
|
## Frontend env switch
|
||||||
|
- Mock mode default: `VITE_USE_MOCK_API=true`
|
||||||
|
- Real API mode: set `VITE_USE_MOCK_API=false`
|
||||||
|
- API base URL: `VITE_API_BASE_URL=http://localhost:5276`
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Fluxon Frontend
|
||||||
|
|
||||||
|
React + Vite storefront for the Fluxon project.
|
||||||
|
|
||||||
|
## What is included
|
||||||
|
- Home page, product listing, product details, cart, login, register, checkout, confirmation, and account pages
|
||||||
|
- Cart and auth state stored in `localStorage`
|
||||||
|
- Hybrid API layer that can use real backend endpoints or fall back to mock data
|
||||||
|
- Responsive visual design meant for demos and presentations
|
||||||
|
|
||||||
|
## Run locally
|
||||||
|
1. Install Node.js if it is not already available on your machine.
|
||||||
|
2. Open `C:\Users\bib\Documents\LEA2\WebShop_Fluxon\frontend`
|
||||||
|
3. Copy `.env.example` to `.env`
|
||||||
|
4. Run `npm install`
|
||||||
|
5. Run `npm run dev`
|
||||||
|
|
||||||
|
## API mode
|
||||||
|
- Default is mock mode: `VITE_USE_MOCK_API=true`
|
||||||
|
- To use the backend, set `VITE_USE_MOCK_API=false`
|
||||||
|
- Backend base URL is controlled by `VITE_API_BASE_URL`
|
||||||
|
|
||||||
|
## Important files
|
||||||
|
- `src/services/api.ts`: real API calls plus fallback behavior
|
||||||
|
- `src/services/mockApi.ts`: mock implementations used for demos
|
||||||
|
- `src/state/AuthContext.tsx`: login/register session state
|
||||||
|
- `src/state/CartContext.tsx`: cart state and totals
|
||||||
|
- `INTEGRATION_CHECKLIST.md`: quick contract checklist for backend coordination
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Fluxon Shop</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+1771
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "fluxon-frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.30.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.12",
|
||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^5.4.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
|
import { Layout } from "./ui/Layout";
|
||||||
|
import { HomePage } from "./pages/HomePage";
|
||||||
|
import { ProductsPage } from "./pages/ProductsPage";
|
||||||
|
import { ProductDetailsPage } from "./pages/ProductDetailsPage";
|
||||||
|
import { CartPage } from "./pages/CartPage";
|
||||||
|
import { LoginPage } from "./pages/LoginPage";
|
||||||
|
import { RegisterPage } from "./pages/RegisterPage";
|
||||||
|
import { CheckoutPage } from "./pages/CheckoutPage";
|
||||||
|
import { OrderConfirmationPage } from "./pages/OrderConfirmationPage";
|
||||||
|
import { AccountPage } from "./pages/AccountPage";
|
||||||
|
import { NotFoundPage } from "./pages/NotFoundPage";
|
||||||
|
import { RequireAuth } from "./ui/RequireAuth";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<HomePage />} />
|
||||||
|
<Route path="/products" element={<ProductsPage />} />
|
||||||
|
<Route path="/products/:productId" element={<ProductDetailsPage />} />
|
||||||
|
<Route path="/cart" element={<CartPage />} />
|
||||||
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="/register" element={<RegisterPage />} />
|
||||||
|
<Route
|
||||||
|
path="/checkout"
|
||||||
|
element={
|
||||||
|
<RequireAuth>
|
||||||
|
<CheckoutPage />
|
||||||
|
</RequireAuth>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/account"
|
||||||
|
element={
|
||||||
|
<RequireAuth>
|
||||||
|
<AccountPage />
|
||||||
|
</RequireAuth>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/order-confirmation/:orderId"
|
||||||
|
element={
|
||||||
|
<RequireAuth>
|
||||||
|
<OrderConfirmationPage />
|
||||||
|
</RequireAuth>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/home" element={<Navigate to="/" replace />} />
|
||||||
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
|
</Routes>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const env = import.meta.env;
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
apiBaseUrl: env.VITE_API_BASE_URL ?? "http://localhost:5276",
|
||||||
|
useMockApi: env.VITE_USE_MOCK_API !== "false"
|
||||||
|
};
|
||||||
@@ -0,0 +1,650 @@
|
|||||||
|
import type { AuthUser, Category, Order, Product } from "../types";
|
||||||
|
|
||||||
|
const photo = (seed: string) => `https://picsum.photos/seed/${encodeURIComponent(seed)}/900/700`;
|
||||||
|
|
||||||
|
export const mockCategories: Category[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Audio",
|
||||||
|
description: "Headphones, speakers, and all-day listening essentials.",
|
||||||
|
image: photo("fluxon-audio-category")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Smart Devices",
|
||||||
|
description: "Wearables and connected products for modern routines.",
|
||||||
|
image: photo("fluxon-smart-devices-category")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Accessories",
|
||||||
|
description: "Chargers, stands, cables, and practical daily add-ons.",
|
||||||
|
image: photo("fluxon-accessories-category")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Home Tech",
|
||||||
|
description: "Useful tech for comfort, lighting, cleaning, and air quality.",
|
||||||
|
image: photo("fluxon-home-tech-category")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Personal Care",
|
||||||
|
description: "Smart grooming and self-care tools with a premium feel.",
|
||||||
|
image: photo("fluxon-personal-care-category")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
type ProductSeed = Omit<Product, "id" | "categoryId" | "category"> & { categoryName: Category["name"] };
|
||||||
|
|
||||||
|
const productSeeds: ProductSeed[] = [
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "FluxBuds Air",
|
||||||
|
description: "True wireless earbuds with balanced sound and compact charging case.",
|
||||||
|
price: 69,
|
||||||
|
oldPrice: 89,
|
||||||
|
stock: 32,
|
||||||
|
badge: "New",
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 214,
|
||||||
|
featured: true,
|
||||||
|
newArrival: true,
|
||||||
|
image: photo("fluxon-audio-1")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "NovaSound ANC",
|
||||||
|
description: "Over-ear headphones with active noise cancellation and 40-hour playback.",
|
||||||
|
price: 149,
|
||||||
|
oldPrice: 189,
|
||||||
|
stock: 18,
|
||||||
|
badge: "Best Seller",
|
||||||
|
rating: 4.9,
|
||||||
|
reviewCount: 843,
|
||||||
|
featured: true,
|
||||||
|
bestSeller: true,
|
||||||
|
image: photo("fluxon-audio-2")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "PulseBeat Mini",
|
||||||
|
description: "Pocket speaker with rich bass tuning and splash-resistant finish.",
|
||||||
|
price: 54,
|
||||||
|
stock: 25,
|
||||||
|
badge: "Sale",
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 133,
|
||||||
|
image: photo("fluxon-audio-3")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "WaveLoop Pro",
|
||||||
|
description: "Neckband earphones designed for calls, commuting, and long battery life.",
|
||||||
|
price: 39,
|
||||||
|
oldPrice: 49,
|
||||||
|
stock: 26,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 91,
|
||||||
|
image: photo("fluxon-audio-4")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "StudioArc Wired",
|
||||||
|
description: "Wired headphones with clean detail and lightweight studio-inspired design.",
|
||||||
|
price: 59,
|
||||||
|
stock: 21,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 74,
|
||||||
|
image: photo("fluxon-audio-5")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "GameTone X",
|
||||||
|
description: "Gaming headset with soft cushions, boom mic, and immersive stereo sound.",
|
||||||
|
price: 79,
|
||||||
|
oldPrice: 99,
|
||||||
|
stock: 17,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 205,
|
||||||
|
image: photo("fluxon-audio-6")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "RoomBar Slim",
|
||||||
|
description: "Minimal soundbar built for compact desks, bedrooms, and small TVs.",
|
||||||
|
price: 119,
|
||||||
|
stock: 10,
|
||||||
|
badge: "Hot",
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 168,
|
||||||
|
image: photo("fluxon-audio-7")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "EchoDot Pocket",
|
||||||
|
description: "Mini speaker with soft-touch finish and quick-connect wireless pairing.",
|
||||||
|
price: 35,
|
||||||
|
stock: 40,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 52,
|
||||||
|
image: photo("fluxon-audio-8")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "QuietPods Max",
|
||||||
|
description: "Premium earbuds with deep ANC, transparency mode, and fast USB-C charging.",
|
||||||
|
price: 109,
|
||||||
|
oldPrice: 129,
|
||||||
|
stock: 15,
|
||||||
|
bestSeller: true,
|
||||||
|
rating: 4.9,
|
||||||
|
reviewCount: 479,
|
||||||
|
image: photo("fluxon-audio-9")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Audio",
|
||||||
|
name: "Reference One",
|
||||||
|
description: "Closed-back headphones for focused work, editing, and crisp detail.",
|
||||||
|
price: 129,
|
||||||
|
stock: 8,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 118,
|
||||||
|
image: photo("fluxon-audio-10")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "Flux Watch S1",
|
||||||
|
description: "Smartwatch with AMOLED display, call support, and daily health tracking.",
|
||||||
|
price: 139,
|
||||||
|
oldPrice: 169,
|
||||||
|
stock: 22,
|
||||||
|
badge: "Best Seller",
|
||||||
|
rating: 4.9,
|
||||||
|
reviewCount: 692,
|
||||||
|
featured: true,
|
||||||
|
bestSeller: true,
|
||||||
|
image: photo("fluxon-smart-1")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "PulseBand Fit",
|
||||||
|
description: "Slim fitness band with sleep tracking, step count, and workout reminders.",
|
||||||
|
price: 49,
|
||||||
|
stock: 38,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 175,
|
||||||
|
image: photo("fluxon-smart-2")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "Halo Ring Lite",
|
||||||
|
description: "Smart ring concept for sleep tracking and low-profile daily wear.",
|
||||||
|
price: 99,
|
||||||
|
oldPrice: 119,
|
||||||
|
stock: 11,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 42,
|
||||||
|
image: photo("fluxon-smart-3")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "DeskHub Go",
|
||||||
|
description: "Compact smart desk hub with clock, USB ports, and workspace shortcuts.",
|
||||||
|
price: 89,
|
||||||
|
stock: 14,
|
||||||
|
rating: 4.4,
|
||||||
|
reviewCount: 36,
|
||||||
|
image: photo("fluxon-smart-4")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "WakeCube",
|
||||||
|
description: "Smart alarm clock with ambient light and gentle morning routines.",
|
||||||
|
price: 59,
|
||||||
|
stock: 29,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 103,
|
||||||
|
image: photo("fluxon-smart-5")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "TrackTag Duo",
|
||||||
|
description: "Bluetooth tracker set for keys, backpacks, and travel essentials.",
|
||||||
|
price: 29,
|
||||||
|
oldPrice: 39,
|
||||||
|
stock: 47,
|
||||||
|
bestSeller: true,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 318,
|
||||||
|
image: photo("fluxon-smart-6")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "Vista Mini",
|
||||||
|
description: "Portable smart display with clean interface for weather, timers, and media.",
|
||||||
|
price: 119,
|
||||||
|
stock: 9,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 65,
|
||||||
|
image: photo("fluxon-smart-7")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "HealthSync Band",
|
||||||
|
description: "Daily wellness band focused on recovery insights and heart tracking.",
|
||||||
|
price: 65,
|
||||||
|
stock: 19,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 98,
|
||||||
|
image: photo("fluxon-smart-8")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "Balance Scale Pro",
|
||||||
|
description: "Smart body scale with composition trends and companion-app readiness.",
|
||||||
|
price: 79,
|
||||||
|
stock: 13,
|
||||||
|
badge: "Popular",
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 151,
|
||||||
|
image: photo("fluxon-smart-9")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Smart Devices",
|
||||||
|
name: "Beam Pocket",
|
||||||
|
description: "Mini projector made for flexible movie nights and portable presentations.",
|
||||||
|
price: 199,
|
||||||
|
oldPrice: 239,
|
||||||
|
stock: 7,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 87,
|
||||||
|
featured: true,
|
||||||
|
image: photo("fluxon-smart-10")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "VoltCharge 35",
|
||||||
|
description: "Fast wall charger with compact form and dual-device charging.",
|
||||||
|
price: 29,
|
||||||
|
stock: 56,
|
||||||
|
bestSeller: true,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 521,
|
||||||
|
image: photo("fluxon-accessory-1")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "MagBank 10K",
|
||||||
|
description: "Magnetic power bank sized for travel days and fast top-ups.",
|
||||||
|
price: 69,
|
||||||
|
oldPrice: 89,
|
||||||
|
stock: 31,
|
||||||
|
badge: "Best Seller",
|
||||||
|
rating: 4.9,
|
||||||
|
reviewCount: 405,
|
||||||
|
featured: true,
|
||||||
|
bestSeller: true,
|
||||||
|
image: photo("fluxon-accessory-2")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "Braided Link C",
|
||||||
|
description: "Durable braided USB-C cable designed for desks, bags, and charging stations.",
|
||||||
|
price: 15,
|
||||||
|
stock: 82,
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 210,
|
||||||
|
image: photo("fluxon-accessory-3")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "AirDock Pad",
|
||||||
|
description: "Minimal wireless charging pad with soft LED ring and anti-slip base.",
|
||||||
|
price: 34,
|
||||||
|
stock: 28,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 88,
|
||||||
|
image: photo("fluxon-accessory-4")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "Rise Laptop Stand",
|
||||||
|
description: "Foldable aluminum stand for cleaner posture and cooler laptop airflow.",
|
||||||
|
price: 42,
|
||||||
|
stock: 23,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 144,
|
||||||
|
image: photo("fluxon-accessory-5")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "FlexGrip Holder",
|
||||||
|
description: "Adjustable phone holder for desks, kitchens, and bedside use.",
|
||||||
|
price: 19,
|
||||||
|
stock: 37,
|
||||||
|
rating: 4.4,
|
||||||
|
reviewCount: 61,
|
||||||
|
image: photo("fluxon-accessory-6")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "TravelPlug World",
|
||||||
|
description: "Travel adapter with clean design and multi-region compatibility.",
|
||||||
|
price: 39,
|
||||||
|
oldPrice: 49,
|
||||||
|
stock: 16,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 57,
|
||||||
|
image: photo("fluxon-accessory-7")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "KeySleeve Pro",
|
||||||
|
description: "Protective keyboard sleeve with felt lining and slim profile.",
|
||||||
|
price: 24,
|
||||||
|
stock: 26,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 41,
|
||||||
|
image: photo("fluxon-accessory-8")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "PortHub 6",
|
||||||
|
description: "Six-port USB hub for workstations that need flexible connectivity.",
|
||||||
|
price: 49,
|
||||||
|
stock: 21,
|
||||||
|
badge: "Hot",
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 129,
|
||||||
|
image: photo("fluxon-accessory-9")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Accessories",
|
||||||
|
name: "Shield Case Clear",
|
||||||
|
description: "Transparent protective phone case with reinforced corners.",
|
||||||
|
price: 18,
|
||||||
|
stock: 44,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.3,
|
||||||
|
reviewCount: 39,
|
||||||
|
image: photo("fluxon-accessory-10")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "PureFlow Air",
|
||||||
|
description: "Compact air purifier for bedrooms, workspaces, and quiet evenings.",
|
||||||
|
price: 189,
|
||||||
|
oldPrice: 229,
|
||||||
|
stock: 12,
|
||||||
|
featured: true,
|
||||||
|
badge: "Best Seller",
|
||||||
|
bestSeller: true,
|
||||||
|
rating: 4.9,
|
||||||
|
reviewCount: 366,
|
||||||
|
image: photo("fluxon-home-1")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "Luma Desk Beam",
|
||||||
|
description: "LED desk lamp with adjustable warmth and a clean architectural silhouette.",
|
||||||
|
price: 64,
|
||||||
|
stock: 34,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 202,
|
||||||
|
image: photo("fluxon-home-2")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "SweepBot Mini",
|
||||||
|
description: "Robot vacuum built for daily dust pickup in compact living spaces.",
|
||||||
|
price: 249,
|
||||||
|
oldPrice: 299,
|
||||||
|
stock: 8,
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 157,
|
||||||
|
image: photo("fluxon-home-3")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "MistCore",
|
||||||
|
description: "Quiet humidifier with soft light and modern bedside presence.",
|
||||||
|
price: 55,
|
||||||
|
stock: 20,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 84,
|
||||||
|
image: photo("fluxon-home-4")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "Aroma Glow",
|
||||||
|
description: "Aroma diffuser combining gentle vapor flow with ambient mood lighting.",
|
||||||
|
price: 39,
|
||||||
|
stock: 24,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 72,
|
||||||
|
image: photo("fluxon-home-5")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "CoolFold Fan",
|
||||||
|
description: "Portable fan with foldable body and desk-friendly footprint.",
|
||||||
|
price: 45,
|
||||||
|
stock: 30,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 93,
|
||||||
|
image: photo("fluxon-home-6")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "GlowBulb Kit",
|
||||||
|
description: "Smart bulb starter kit for mood shifts, desk scenes, and warm evenings.",
|
||||||
|
price: 79,
|
||||||
|
stock: 18,
|
||||||
|
badge: "Popular",
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 147,
|
||||||
|
image: photo("fluxon-home-7")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "WarmSpace Mini",
|
||||||
|
description: "Compact heater designed for focused work corners and cool mornings.",
|
||||||
|
price: 99,
|
||||||
|
oldPrice: 119,
|
||||||
|
stock: 11,
|
||||||
|
rating: 4.4,
|
||||||
|
reviewCount: 48,
|
||||||
|
image: photo("fluxon-home-8")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "ClearTap Filter",
|
||||||
|
description: "Countertop water filter unit with simple control and clean lines.",
|
||||||
|
price: 129,
|
||||||
|
stock: 9,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 67,
|
||||||
|
image: photo("fluxon-home-9")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Home Tech",
|
||||||
|
name: "DustGo Handheld",
|
||||||
|
description: "Handheld mini vacuum for shelves, keyboards, and quick spot cleaning.",
|
||||||
|
price: 59,
|
||||||
|
stock: 27,
|
||||||
|
newArrival: true,
|
||||||
|
featured: true,
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 112,
|
||||||
|
image: photo("fluxon-home-10")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "TrimEdge Pro",
|
||||||
|
description: "Cordless beard trimmer with precise guide combs and sharp stainless blades.",
|
||||||
|
price: 69,
|
||||||
|
oldPrice: 89,
|
||||||
|
stock: 22,
|
||||||
|
badge: "Best Seller",
|
||||||
|
featured: true,
|
||||||
|
bestSeller: true,
|
||||||
|
rating: 4.9,
|
||||||
|
reviewCount: 553,
|
||||||
|
image: photo("fluxon-care-1")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "SilkDry Air",
|
||||||
|
description: "Hair dryer with sleek build, fast airflow, and low-frizz styling mode.",
|
||||||
|
price: 99,
|
||||||
|
stock: 19,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 188,
|
||||||
|
image: photo("fluxon-care-2")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "PureFace Brush",
|
||||||
|
description: "Facial cleansing brush for a softer skincare routine and clean countertop look.",
|
||||||
|
price: 34,
|
||||||
|
stock: 31,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 77,
|
||||||
|
image: photo("fluxon-care-3")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "BodyLine Groomer",
|
||||||
|
description: "Water-resistant body groomer built for comfortable weekly upkeep.",
|
||||||
|
price: 49,
|
||||||
|
stock: 25,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 90,
|
||||||
|
image: photo("fluxon-care-4")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "ReliefPulse Mini",
|
||||||
|
description: "Compact massage gun for post-workout recovery and daily neck tension relief.",
|
||||||
|
price: 119,
|
||||||
|
oldPrice: 149,
|
||||||
|
stock: 13,
|
||||||
|
bestSeller: true,
|
||||||
|
rating: 4.8,
|
||||||
|
reviewCount: 209,
|
||||||
|
image: photo("fluxon-care-5")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "SmoothTouch Epilator",
|
||||||
|
description: "Epilator with travel cap, dual speeds, and ergonomic body shape.",
|
||||||
|
price: 79,
|
||||||
|
stock: 17,
|
||||||
|
newArrival: true,
|
||||||
|
rating: 4.5,
|
||||||
|
reviewCount: 58,
|
||||||
|
image: photo("fluxon-care-6")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "BeardCare Kit",
|
||||||
|
description: "Premium beard kit with trimmer tools and simple daily maintenance feel.",
|
||||||
|
price: 59,
|
||||||
|
stock: 28,
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 101,
|
||||||
|
image: photo("fluxon-care-7")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "SleekIron One",
|
||||||
|
description: "Hair straightener with smooth plates and salon-inspired matte finish.",
|
||||||
|
price: 69,
|
||||||
|
stock: 15,
|
||||||
|
rating: 4.6,
|
||||||
|
reviewCount: 69,
|
||||||
|
image: photo("fluxon-care-8")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "SonicBrush Daily",
|
||||||
|
description: "Sonic toothbrush with travel case and two brushing intensity modes.",
|
||||||
|
price: 45,
|
||||||
|
stock: 33,
|
||||||
|
badge: "Popular",
|
||||||
|
rating: 4.7,
|
||||||
|
reviewCount: 142,
|
||||||
|
image: photo("fluxon-care-9")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "Personal Care",
|
||||||
|
name: "NailStudio Go",
|
||||||
|
description: "Portable manicure device built for neat, at-home finishing touches.",
|
||||||
|
price: 29,
|
||||||
|
stock: 30,
|
||||||
|
newArrival: true,
|
||||||
|
featured: true,
|
||||||
|
rating: 4.4,
|
||||||
|
reviewCount: 33,
|
||||||
|
image: photo("fluxon-care-10")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mockProducts: Product[] = productSeeds.map((seed, index) => {
|
||||||
|
const category = mockCategories.find((item) => item.name === seed.categoryName)!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: index + 1,
|
||||||
|
categoryId: category.id,
|
||||||
|
category,
|
||||||
|
...seed
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mockDemoUser: AuthUser = {
|
||||||
|
name: "Demo Customer",
|
||||||
|
email: "demo@fluxon.shop",
|
||||||
|
token: "mock-jwt-token"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockOrders: Order[] = [
|
||||||
|
{
|
||||||
|
id: "FX-2026-1001",
|
||||||
|
createdAt: "2026-03-16T10:30:00.000Z",
|
||||||
|
status: "Confirmed",
|
||||||
|
total: 248.9,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
productId: 2,
|
||||||
|
productName: "NovaSound ANC",
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 149
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productId: 21,
|
||||||
|
productName: "MagBank 10K",
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 69
|
||||||
|
},
|
||||||
|
{
|
||||||
|
productId: 43,
|
||||||
|
productName: "ReliefPulse Mini",
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 119
|
||||||
|
}
|
||||||
|
],
|
||||||
|
payment: {
|
||||||
|
method: "PayPal",
|
||||||
|
amount: 337,
|
||||||
|
status: "Paid"
|
||||||
|
},
|
||||||
|
customerEmail: "demo@fluxon.shop",
|
||||||
|
shippingAddress: "18 Vision Street, Berlin"
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { api } from "../services/api";
|
||||||
|
import type { Category, Product } from "../types";
|
||||||
|
|
||||||
|
export function useCatalog() {
|
||||||
|
const [products, setProducts] = useState<Product[]>([]);
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
const [nextProducts, nextCategories] = await Promise.all([api.getProducts(), api.getCategories()]);
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProducts(nextProducts);
|
||||||
|
setCategories(nextCategories);
|
||||||
|
} catch (loadError) {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(loadError instanceof Error ? loadError.message : "Unable to load catalog");
|
||||||
|
} finally {
|
||||||
|
if (active) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void load();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { products, categories, loading, error };
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import App from "./App";
|
||||||
|
import { AuthProvider } from "./state/AuthContext";
|
||||||
|
import { CartProvider } from "./state/CartContext";
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<AuthProvider>
|
||||||
|
<CartProvider>
|
||||||
|
<App />
|
||||||
|
</CartProvider>
|
||||||
|
</AuthProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { api } from "../services/api";
|
||||||
|
import { useAuth } from "../state/AuthContext";
|
||||||
|
import type { Order } from "../types";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
|
||||||
|
export function AccountPage() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const [orders, setOrders] = useState<Order[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
async function loadOrders() {
|
||||||
|
try {
|
||||||
|
const nextOrders = await api.getOrders();
|
||||||
|
if (active) {
|
||||||
|
setOrders(nextOrders);
|
||||||
|
}
|
||||||
|
} catch (loadError) {
|
||||||
|
if (active) {
|
||||||
|
setError(loadError instanceof Error ? loadError.message : "Unable to load orders");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (active) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadOrders();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <StatusView title="Loading account" message="Fetching customer profile and recent orders." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <StatusView title="Unable to load account" message={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-stack">
|
||||||
|
<section className="panel compact">
|
||||||
|
<span className="eyebrow">Account</span>
|
||||||
|
<h1>{user?.name}</h1>
|
||||||
|
<p>{user?.email}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="panel">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">Orders</span>
|
||||||
|
<h2>Recent order history</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{orders.length === 0 ? (
|
||||||
|
<StatusView title="No orders yet" message="Orders created during checkout will appear here." />
|
||||||
|
) : (
|
||||||
|
<div className="order-list">
|
||||||
|
{orders.map((order) => (
|
||||||
|
<article key={order.id} className="order-card">
|
||||||
|
<div>
|
||||||
|
<strong>{order.id}</strong>
|
||||||
|
<p>{new Date(order.createdAt).toLocaleDateString()}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Status</span>
|
||||||
|
<strong>{order.status}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Payment</span>
|
||||||
|
<strong>{order.payment.status}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Total</span>
|
||||||
|
<strong>EUR {order.total.toFixed(2)}</strong>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useCart } from "../state/CartContext";
|
||||||
|
import { QuantityControl } from "../ui/QuantityControl";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
import { visualStyle } from "../ui/visuals";
|
||||||
|
|
||||||
|
export function CartPage() {
|
||||||
|
const { items, removeItem, updateQuantity, subtotal } = useCart();
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return (
|
||||||
|
<StatusView
|
||||||
|
title="Your cart is empty"
|
||||||
|
message="Add a few products to continue the shopping flow."
|
||||||
|
action={
|
||||||
|
<Link to="/products" className="cta-button">
|
||||||
|
Browse products
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="checkout-layout">
|
||||||
|
<section className="panel">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">Cart</span>
|
||||||
|
<h1>Review selected items</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="cart-list">
|
||||||
|
{items.map((item) => (
|
||||||
|
<article key={item.product.id} className="cart-row">
|
||||||
|
<div className="cart-visual" style={visualStyle(item.product.image)} />
|
||||||
|
<div className="cart-copy">
|
||||||
|
<h3>{item.product.name}</h3>
|
||||||
|
<p>{item.product.description}</p>
|
||||||
|
</div>
|
||||||
|
<QuantityControl
|
||||||
|
value={item.quantity}
|
||||||
|
max={item.product.stock}
|
||||||
|
onChange={(quantity) => updateQuantity(item.product.id, quantity)}
|
||||||
|
/>
|
||||||
|
<strong>EUR {(item.unitPrice * item.quantity).toFixed(2)}</strong>
|
||||||
|
<button className="text-button" onClick={() => removeItem(item.product.id)}>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside className="panel order-summary">
|
||||||
|
<span className="eyebrow">Summary</span>
|
||||||
|
<h2>Checkout snapshot</h2>
|
||||||
|
<div className="summary-row">
|
||||||
|
<span>Subtotal</span>
|
||||||
|
<strong>EUR {subtotal.toFixed(2)}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="summary-row">
|
||||||
|
<span>Shipping</span>
|
||||||
|
<strong>Free</strong>
|
||||||
|
</div>
|
||||||
|
<div className="summary-row total">
|
||||||
|
<span>Total</span>
|
||||||
|
<strong>EUR {subtotal.toFixed(2)}</strong>
|
||||||
|
</div>
|
||||||
|
<Link to="/checkout" className="cta-button">
|
||||||
|
Continue to checkout
|
||||||
|
</Link>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { api } from "../services/api";
|
||||||
|
import { useAuth } from "../state/AuthContext";
|
||||||
|
import { useCart } from "../state/CartContext";
|
||||||
|
import type { CheckoutForm } from "../types";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
|
||||||
|
export function CheckoutPage() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { items, subtotal, clearCart } = useCart();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [form, setForm] = useState<CheckoutForm>({
|
||||||
|
fullName: user?.name ?? "",
|
||||||
|
email: user?.email ?? "",
|
||||||
|
address: "",
|
||||||
|
city: "",
|
||||||
|
postalCode: "",
|
||||||
|
paymentMethod: "CreditCard"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return <StatusView title="Nothing to checkout" message="Your cart is empty. Add products before opening checkout." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(event: FormEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
setSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const order = await api.createOrder(form, items);
|
||||||
|
clearCart();
|
||||||
|
navigate(`/order-confirmation/${order.id}`, { state: { order } });
|
||||||
|
} catch (submitError) {
|
||||||
|
setError(submitError instanceof Error ? submitError.message : "Unable to create order");
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="checkout-layout">
|
||||||
|
<form className="panel" onSubmit={handleSubmit}>
|
||||||
|
<span className="eyebrow">Checkout</span>
|
||||||
|
<h1>Collect shipping and payment details</h1>
|
||||||
|
<div className="form-grid">
|
||||||
|
<label>
|
||||||
|
Full name
|
||||||
|
<input
|
||||||
|
value={form.fullName}
|
||||||
|
onChange={(event) => setForm((current) => ({ ...current, fullName: event.target.value }))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={form.email}
|
||||||
|
onChange={(event) => setForm((current) => ({ ...current, email: event.target.value }))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Address
|
||||||
|
<input
|
||||||
|
value={form.address}
|
||||||
|
onChange={(event) => setForm((current) => ({ ...current, address: event.target.value }))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
City
|
||||||
|
<input
|
||||||
|
value={form.city}
|
||||||
|
onChange={(event) => setForm((current) => ({ ...current, city: event.target.value }))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Postal code
|
||||||
|
<input
|
||||||
|
value={form.postalCode}
|
||||||
|
onChange={(event) => setForm((current) => ({ ...current, postalCode: event.target.value }))}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Payment method
|
||||||
|
<select
|
||||||
|
value={form.paymentMethod}
|
||||||
|
onChange={(event) =>
|
||||||
|
setForm((current) => ({
|
||||||
|
...current,
|
||||||
|
paymentMethod: event.target.value as CheckoutForm["paymentMethod"]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="CreditCard">Credit card</option>
|
||||||
|
<option value="PayPal">PayPal</option>
|
||||||
|
<option value="CashOnDelivery">Cash on delivery</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{error ? <p className="error-text">{error}</p> : null}
|
||||||
|
<button type="submit" disabled={submitting}>
|
||||||
|
{submitting ? "Placing order..." : "Place order"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<aside className="panel order-summary">
|
||||||
|
<span className="eyebrow">Order summary</span>
|
||||||
|
<h2>Ready for API handoff</h2>
|
||||||
|
{items.map((item) => (
|
||||||
|
<div className="summary-row" key={item.product.id}>
|
||||||
|
<span>
|
||||||
|
{item.product.name} x {item.quantity}
|
||||||
|
</span>
|
||||||
|
<strong>EUR {(item.quantity * item.unitPrice).toFixed(2)}</strong>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="summary-row total">
|
||||||
|
<span>Total</span>
|
||||||
|
<strong>EUR {subtotal.toFixed(2)}</strong>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useCatalog } from "../hooks/useCatalog";
|
||||||
|
import { ProductCard } from "../ui/ProductCard";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
import { useCart } from "../state/CartContext";
|
||||||
|
|
||||||
|
export function HomePage() {
|
||||||
|
const { products, categories, loading, error } = useCatalog();
|
||||||
|
const { addItem } = useCart();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <StatusView title="Loading storefront" message="Preparing products, categories, and hero content." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <StatusView title="Catalog unavailable" message={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const featuredProducts = products.filter((product) => product.featured).slice(0, 4);
|
||||||
|
const bestSellers = products.filter((product) => product.bestSeller).slice(0, 4);
|
||||||
|
const newArrivals = products.filter((product) => product.newArrival).slice(0, 4);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-stack">
|
||||||
|
<section className="hero-panel">
|
||||||
|
<div className="hero-copy">
|
||||||
|
<span className="eyebrow">Next-Gen Everyday Tech</span>
|
||||||
|
<h1>Smart products built for modern daily life.</h1>
|
||||||
|
<p>
|
||||||
|
Discover a curated storefront across Audio, Smart Devices, Accessories, Home Tech, and Personal Care with
|
||||||
|
a presentation-ready shopping flow.
|
||||||
|
</p>
|
||||||
|
<div className="inline-actions">
|
||||||
|
<Link to="/products" className="cta-button">
|
||||||
|
Shop Now
|
||||||
|
</Link>
|
||||||
|
<Link to="/products" className="ghost-button">
|
||||||
|
Explore Categories
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="hero-card-grid">
|
||||||
|
<div className="hero-stat">
|
||||||
|
<strong>{products.length}</strong>
|
||||||
|
<span>Store-ready products</span>
|
||||||
|
</div>
|
||||||
|
<div className="hero-stat">
|
||||||
|
<strong>{categories.length}</strong>
|
||||||
|
<span>Core categories</span>
|
||||||
|
</div>
|
||||||
|
<div className="hero-stat accent">
|
||||||
|
<strong>Deep Navy + Orange</strong>
|
||||||
|
<span>Clean tech-commerce direction</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="panel">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">Categories</span>
|
||||||
|
<h2>Shop the five storefront pillars of Fluxon</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="category-grid">
|
||||||
|
{categories.map((category) => (
|
||||||
|
<article key={category.id} className="category-card">
|
||||||
|
<h3>{category.name}</h3>
|
||||||
|
<p>{category.description}</p>
|
||||||
|
<Link to={`/products`} className="category-link">
|
||||||
|
Explore
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="panel">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">Featured</span>
|
||||||
|
<h2>Featured picks for the homepage hero flow</h2>
|
||||||
|
</div>
|
||||||
|
<Link to="/products" className="ghost-button">
|
||||||
|
View All
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="product-grid">
|
||||||
|
{featuredProducts.map((product) => (
|
||||||
|
<ProductCard key={product.id} product={product} onAdd={addItem} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="promo-band">
|
||||||
|
<article className="promo-card">
|
||||||
|
<span className="eyebrow">Promo</span>
|
||||||
|
<h3>Up to 30% off selected Audio and Accessories</h3>
|
||||||
|
<p>Use this section for discounts, launches, or your strongest weekly campaign.</p>
|
||||||
|
<Link to="/products" className="ghost-button">
|
||||||
|
See Offers
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
<article className="promo-card promo-card-dark">
|
||||||
|
<span className="eyebrow">New Drop</span>
|
||||||
|
<h3>Fresh Smart Devices and Personal Care arrivals</h3>
|
||||||
|
<p>Keep the homepage feeling alive with fast-moving campaign blocks like this one.</p>
|
||||||
|
<Link to="/products" className="cta-button">
|
||||||
|
Browse New Arrivals
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="panel">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">Best Sellers</span>
|
||||||
|
<h2>Products that add social proof to your storefront</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="product-grid">
|
||||||
|
{bestSellers.map((product) => (
|
||||||
|
<ProductCard key={product.id} product={product} onAdd={addItem} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="panel">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">New Arrivals</span>
|
||||||
|
<h2>Fresh additions to keep the shop feeling current</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="product-grid">
|
||||||
|
{newArrivals.map((product) => (
|
||||||
|
<ProductCard key={product.id} product={product} onAdd={addItem} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="trust-grid">
|
||||||
|
<article className="trust-card">
|
||||||
|
<h3>Secure Checkout</h3>
|
||||||
|
<p>Use this block to reassure users that payments and account actions are protected.</p>
|
||||||
|
</article>
|
||||||
|
<article className="trust-card">
|
||||||
|
<h3>Fast Delivery</h3>
|
||||||
|
<p>Highlight delivery expectations and shipping confidence early in the homepage flow.</p>
|
||||||
|
</article>
|
||||||
|
<article className="trust-card">
|
||||||
|
<h3>Curated Products</h3>
|
||||||
|
<p>Show that Fluxon is not random inventory, but a selected modern tech catalog.</p>
|
||||||
|
</article>
|
||||||
|
<article className="trust-card">
|
||||||
|
<h3>Helpful Support</h3>
|
||||||
|
<p>Good support messaging makes a student project feel more like a real commerce brand.</p>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { useAuth } from "../state/AuthContext";
|
||||||
|
|
||||||
|
export function LoginPage() {
|
||||||
|
const [email, setEmail] = useState("demo@fluxon.shop");
|
||||||
|
const [password, setPassword] = useState("demo123");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const { login } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
async function handleSubmit(event: FormEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
setSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await login({ email, password });
|
||||||
|
navigate((location.state as { from?: string } | null)?.from ?? "/account");
|
||||||
|
} catch (loginError) {
|
||||||
|
setError(loginError instanceof Error ? loginError.message : "Unable to login");
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="auth-shell">
|
||||||
|
<form className="panel auth-card" onSubmit={handleSubmit}>
|
||||||
|
<span className="eyebrow">Login</span>
|
||||||
|
<h1>Sign in to continue checkout</h1>
|
||||||
|
<p>Use the demo credentials or any email/password while mock auth is enabled.</p>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input type="email" value={email} onChange={(event) => setEmail(event.target.value)} required />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} required />
|
||||||
|
</label>
|
||||||
|
{error ? <p className="error-text">{error}</p> : null}
|
||||||
|
<button type="submit" disabled={submitting}>
|
||||||
|
{submitting ? "Signing in..." : "Login"}
|
||||||
|
</button>
|
||||||
|
<p className="muted">
|
||||||
|
No account yet? <Link to="/register">Create one</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
|
||||||
|
export function NotFoundPage() {
|
||||||
|
return (
|
||||||
|
<StatusView
|
||||||
|
title="Page not found"
|
||||||
|
message="This route is not part of the current storefront flow."
|
||||||
|
action={
|
||||||
|
<Link to="/" className="cta-button">
|
||||||
|
Return home
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Link, useLocation, useParams } from "react-router-dom";
|
||||||
|
import type { Order } from "../types";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
|
||||||
|
export function OrderConfirmationPage() {
|
||||||
|
const { orderId } = useParams();
|
||||||
|
const location = useLocation();
|
||||||
|
const order = (location.state as { order?: Order } | null)?.order;
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
return (
|
||||||
|
<StatusView
|
||||||
|
title="Order recorded"
|
||||||
|
message={`Your order ${orderId ?? ""} was created, but the confirmation data is not available in memory anymore.`}
|
||||||
|
action={
|
||||||
|
<Link to="/account" className="cta-button">
|
||||||
|
View account
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="panel confirmation-card">
|
||||||
|
<span className="eyebrow">Order confirmed</span>
|
||||||
|
<h1>{order.id}</h1>
|
||||||
|
<p>Your order has been submitted successfully and the frontend flow is ready for demo use.</p>
|
||||||
|
<div className="summary-row">
|
||||||
|
<span>Status</span>
|
||||||
|
<strong>{order.status}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="summary-row">
|
||||||
|
<span>Payment</span>
|
||||||
|
<strong>
|
||||||
|
{order.payment.method} / {order.payment.status}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div className="summary-row total">
|
||||||
|
<span>Total</span>
|
||||||
|
<strong>EUR {order.total.toFixed(2)}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="inline-actions">
|
||||||
|
<Link to="/products" className="ghost-button">
|
||||||
|
Continue shopping
|
||||||
|
</Link>
|
||||||
|
<Link to="/account" className="cta-button">
|
||||||
|
View account
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link, useParams } from "react-router-dom";
|
||||||
|
import { api } from "../services/api";
|
||||||
|
import type { Product } from "../types";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
import { QuantityControl } from "../ui/QuantityControl";
|
||||||
|
import { useCart } from "../state/CartContext";
|
||||||
|
import { visualStyle } from "../ui/visuals";
|
||||||
|
|
||||||
|
export function ProductDetailsPage() {
|
||||||
|
const { productId } = useParams();
|
||||||
|
const { addItem } = useCart();
|
||||||
|
const [product, setProduct] = useState<Product | null>(null);
|
||||||
|
const [quantity, setQuantity] = useState(1);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
async function loadProduct() {
|
||||||
|
if (!productId) {
|
||||||
|
setError("Missing product id");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nextProduct = await api.getProductById(Number(productId));
|
||||||
|
if (active) {
|
||||||
|
setProduct(nextProduct);
|
||||||
|
}
|
||||||
|
} catch (loadError) {
|
||||||
|
if (active) {
|
||||||
|
setError(loadError instanceof Error ? loadError.message : "Unable to load product");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (active) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadProduct();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, [productId]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <StatusView title="Loading product" message="Fetching product details and stock information." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || !product) {
|
||||||
|
return (
|
||||||
|
<StatusView
|
||||||
|
title="Product unavailable"
|
||||||
|
message={error ?? "The requested product could not be found."}
|
||||||
|
action={
|
||||||
|
<Link to="/products" className="ghost-button">
|
||||||
|
Back to shop
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="product-detail-layout">
|
||||||
|
<div className="detail-visual" style={visualStyle(product.image)} />
|
||||||
|
<div className="detail-copy panel">
|
||||||
|
<span className="eyebrow">{product.category?.name ?? "Product details"}</span>
|
||||||
|
<h1>{product.name}</h1>
|
||||||
|
<p className="lead">{product.description}</p>
|
||||||
|
<div className="rating-row">
|
||||||
|
<span>{product.rating ? `${product.rating.toFixed(1)} / 5` : "New item"}</span>
|
||||||
|
<span>{product.reviewCount ? `${product.reviewCount} verified reviews` : "Fresh listing"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-meta">
|
||||||
|
<div>
|
||||||
|
<span>Price</span>
|
||||||
|
<div className="price-stack">
|
||||||
|
<strong>EUR {product.price.toFixed(2)}</strong>
|
||||||
|
{product.oldPrice ? <span>EUR {product.oldPrice.toFixed(2)}</span> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Availability</span>
|
||||||
|
<strong>{product.stock > 0 ? `${product.stock} ready to ship` : "Sold out"}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<QuantityControl value={quantity} max={product.stock} onChange={setQuantity} />
|
||||||
|
<div className="inline-actions">
|
||||||
|
<button onClick={() => addItem(product, quantity)} disabled={product.stock === 0}>
|
||||||
|
Add {quantity} to cart
|
||||||
|
</button>
|
||||||
|
<Link to="/cart" className="ghost-button">
|
||||||
|
Go to cart
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { useCatalog } from "../hooks/useCatalog";
|
||||||
|
import { ProductCard } from "../ui/ProductCard";
|
||||||
|
import { CategoryPill } from "../ui/CategoryPill";
|
||||||
|
import { StatusView } from "../ui/StatusView";
|
||||||
|
import { useCart } from "../state/CartContext";
|
||||||
|
|
||||||
|
export function ProductsPage() {
|
||||||
|
const { products, categories, loading, error } = useCatalog();
|
||||||
|
const { addItem } = useCart();
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const [categoryId, setCategoryId] = useState<number | "all">("all");
|
||||||
|
const [sort, setSort] = useState("featured");
|
||||||
|
|
||||||
|
const filteredProducts = useMemo(() => {
|
||||||
|
let next = [...products];
|
||||||
|
|
||||||
|
if (categoryId !== "all") {
|
||||||
|
next = next.filter((product) => product.categoryId === categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.trim()) {
|
||||||
|
const lowered = query.toLowerCase();
|
||||||
|
next = next.filter(
|
||||||
|
(product) =>
|
||||||
|
product.name.toLowerCase().includes(lowered) || product.description.toLowerCase().includes(lowered)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (sort) {
|
||||||
|
case "new":
|
||||||
|
next.sort((a, b) => Number(b.newArrival) - Number(a.newArrival));
|
||||||
|
break;
|
||||||
|
case "best":
|
||||||
|
next.sort((a, b) => Number(b.bestSeller) - Number(a.bestSeller));
|
||||||
|
break;
|
||||||
|
case "price-asc":
|
||||||
|
next.sort((a, b) => a.price - b.price);
|
||||||
|
break;
|
||||||
|
case "price-desc":
|
||||||
|
next.sort((a, b) => b.price - a.price);
|
||||||
|
break;
|
||||||
|
case "stock":
|
||||||
|
next.sort((a, b) => b.stock - a.stock);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
next.sort((a, b) => Number(b.featured) - Number(a.featured));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
}, [categoryId, products, query, sort]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <StatusView title="Loading products" message="Fetching product cards and category filters." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <StatusView title="Unable to show products" message={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-stack">
|
||||||
|
<section className="panel compact">
|
||||||
|
<div className="section-heading">
|
||||||
|
<div>
|
||||||
|
<span className="eyebrow">Shop</span>
|
||||||
|
<h1>Browse, filter, and sort products</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="toolbar">
|
||||||
|
<input
|
||||||
|
value={query}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
className="search-input"
|
||||||
|
placeholder="Search products"
|
||||||
|
/>
|
||||||
|
<select value={sort} onChange={(event) => setSort(event.target.value)} className="select-input">
|
||||||
|
<option value="featured">Featured first</option>
|
||||||
|
<option value="best">Best sellers</option>
|
||||||
|
<option value="new">New arrivals</option>
|
||||||
|
<option value="price-asc">Price: low to high</option>
|
||||||
|
<option value="price-desc">Price: high to low</option>
|
||||||
|
<option value="stock">Most in stock</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pill-row">
|
||||||
|
<CategoryPill category={{ id: 0, name: "All" }} active={categoryId === "all"} onClick={() => setCategoryId("all")} />
|
||||||
|
{categories.map((category) => (
|
||||||
|
<CategoryPill
|
||||||
|
key={category.id}
|
||||||
|
category={category}
|
||||||
|
active={category.id === categoryId}
|
||||||
|
onClick={() => setCategoryId(category.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{filteredProducts.length === 0 ? (
|
||||||
|
<StatusView title="No matching products" message="Try a different search term or category filter." />
|
||||||
|
) : (
|
||||||
|
<section className="product-grid">
|
||||||
|
{filteredProducts.map((product) => (
|
||||||
|
<ProductCard key={product.id} product={product} onAdd={addItem} />
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { FormEvent, useState } from "react";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import { useAuth } from "../state/AuthContext";
|
||||||
|
|
||||||
|
export function RegisterPage() {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const { register } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
async function handleSubmit(event: FormEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
setSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await register({ name, email, password });
|
||||||
|
navigate("/account");
|
||||||
|
} catch (registerError) {
|
||||||
|
setError(registerError instanceof Error ? registerError.message : "Unable to register");
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="auth-shell">
|
||||||
|
<form className="panel auth-card" onSubmit={handleSubmit}>
|
||||||
|
<span className="eyebrow">Register</span>
|
||||||
|
<h1>Create a customer account</h1>
|
||||||
|
<p>This screen already matches the backend registration DTO and can run on mock or real auth.</p>
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input type="text" value={name} onChange={(event) => setName(event.target.value)} required />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<input type="email" value={email} onChange={(event) => setEmail(event.target.value)} required />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} required minLength={6} />
|
||||||
|
</label>
|
||||||
|
{error ? <p className="error-text">{error}</p> : null}
|
||||||
|
<button type="submit" disabled={submitting}>
|
||||||
|
{submitting ? "Creating account..." : "Register"}
|
||||||
|
</button>
|
||||||
|
<p className="muted">
|
||||||
|
Already registered? <Link to="/login">Login</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { config } from "../config";
|
||||||
|
import { mockApi } from "./mockApi";
|
||||||
|
import type {
|
||||||
|
AuthUser,
|
||||||
|
Category,
|
||||||
|
CheckoutForm,
|
||||||
|
LoginInput,
|
||||||
|
Order,
|
||||||
|
Product,
|
||||||
|
RegisterInput
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
|
const response = await fetch(`${config.apiBaseUrl}${path}`, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...(init?.headers ?? {})
|
||||||
|
},
|
||||||
|
...init
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Request failed with status ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await response.json()) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withFallback<T>(primary: () => Promise<T>, fallback: () => Promise<T>): Promise<T> {
|
||||||
|
if (config.useMockApi) {
|
||||||
|
return fallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await primary();
|
||||||
|
} catch {
|
||||||
|
return fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const api = {
|
||||||
|
getProducts(): Promise<Product[]> {
|
||||||
|
return withFallback(
|
||||||
|
() => request<Product[]>("/api/product"),
|
||||||
|
() => mockApi.getProducts()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getProductById(productId: number): Promise<Product> {
|
||||||
|
return withFallback(
|
||||||
|
() => request<Product>(`/api/product/${productId}`),
|
||||||
|
() => mockApi.getProductById(productId)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getCategories(): Promise<Category[]> {
|
||||||
|
return withFallback(
|
||||||
|
() => request<Category[]>("/api/category"),
|
||||||
|
() => mockApi.getCategories()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
login(input: LoginInput): Promise<AuthUser> {
|
||||||
|
return withFallback(
|
||||||
|
() =>
|
||||||
|
request<AuthUser>("/api/auth/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(input)
|
||||||
|
}),
|
||||||
|
() => mockApi.login(input)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
register(input: RegisterInput): Promise<AuthUser> {
|
||||||
|
return withFallback(
|
||||||
|
() =>
|
||||||
|
request<AuthUser>("/api/auth/register", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(input)
|
||||||
|
}),
|
||||||
|
() => mockApi.register(input)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
getOrders(): Promise<Order[]> {
|
||||||
|
return withFallback(
|
||||||
|
() => request<Order[]>("/api/order"),
|
||||||
|
() => mockApi.getOrders()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
createOrder(input: CheckoutForm, cart: { product: Product; quantity: number; unitPrice: number }[]): Promise<Order> {
|
||||||
|
return withFallback(
|
||||||
|
() =>
|
||||||
|
request<Order>("/api/order", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
...input,
|
||||||
|
items: cart.map((item) => ({
|
||||||
|
productId: item.product.id,
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitPrice: item.unitPrice
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
() => mockApi.createOrder(input, cart)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { mockCategories, mockDemoUser, mockOrders, mockProducts } from "../data/mockData";
|
||||||
|
import type {
|
||||||
|
AuthUser,
|
||||||
|
CheckoutForm,
|
||||||
|
LoginInput,
|
||||||
|
Order,
|
||||||
|
Product,
|
||||||
|
RegisterInput
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
|
const wait = (ms = 300) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
let orderStore = [...mockOrders];
|
||||||
|
|
||||||
|
export const mockApi = {
|
||||||
|
async getProducts(): Promise<Product[]> {
|
||||||
|
await wait();
|
||||||
|
return mockProducts.map((product) => ({
|
||||||
|
...product,
|
||||||
|
category: mockCategories.find((category) => category.id === product.categoryId)
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProductById(productId: number): Promise<Product> {
|
||||||
|
await wait();
|
||||||
|
const product = mockProducts.find((item) => item.id === productId);
|
||||||
|
if (!product) {
|
||||||
|
throw new Error("Product not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
category: mockCategories.find((category) => category.id === product.categoryId)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCategories() {
|
||||||
|
await wait();
|
||||||
|
return mockCategories;
|
||||||
|
},
|
||||||
|
|
||||||
|
async login(input: LoginInput): Promise<AuthUser> {
|
||||||
|
await wait();
|
||||||
|
if (!input.email || !input.password) {
|
||||||
|
throw new Error("Email and password are required");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...mockDemoUser,
|
||||||
|
email: input.email
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async register(input: RegisterInput): Promise<AuthUser> {
|
||||||
|
await wait();
|
||||||
|
if (!input.name || !input.email || !input.password) {
|
||||||
|
throw new Error("All fields are required");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: input.name,
|
||||||
|
email: input.email,
|
||||||
|
token: "mock-registered-token"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async getOrders(): Promise<Order[]> {
|
||||||
|
await wait();
|
||||||
|
return orderStore;
|
||||||
|
},
|
||||||
|
|
||||||
|
async createOrder(input: CheckoutForm, cart: { product: Product; quantity: number; unitPrice: number }[]): Promise<Order> {
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
const total = cart.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0);
|
||||||
|
const order: Order = {
|
||||||
|
id: `FX-2026-${1000 + orderStore.length + 1}`,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
status: "Confirmed",
|
||||||
|
total,
|
||||||
|
items: cart.map((item) => ({
|
||||||
|
productId: item.product.id,
|
||||||
|
productName: item.product.name,
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitPrice: item.unitPrice
|
||||||
|
})),
|
||||||
|
payment: {
|
||||||
|
method: input.paymentMethod,
|
||||||
|
amount: total,
|
||||||
|
status: "Paid"
|
||||||
|
},
|
||||||
|
customerEmail: input.email,
|
||||||
|
shippingAddress: `${input.address}, ${input.city}, ${input.postalCode}`
|
||||||
|
};
|
||||||
|
|
||||||
|
orderStore = [order, ...orderStore];
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { api } from "../services/api";
|
||||||
|
import type { AuthUser, LoginInput, RegisterInput } from "../types";
|
||||||
|
|
||||||
|
type AuthContextValue = {
|
||||||
|
user: AuthUser | null;
|
||||||
|
login: (input: LoginInput) => Promise<void>;
|
||||||
|
register: (input: RegisterInput) => Promise<void>;
|
||||||
|
logout: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
||||||
|
const storageKey = "fluxon.auth";
|
||||||
|
|
||||||
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [user, setUser] = useState<AuthUser | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const saved = window.localStorage.getItem(storageKey);
|
||||||
|
if (saved) {
|
||||||
|
setUser(JSON.parse(saved) as AuthUser);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function login(input: LoginInput) {
|
||||||
|
const nextUser = await api.login(input);
|
||||||
|
setUser(nextUser);
|
||||||
|
window.localStorage.setItem(storageKey, JSON.stringify(nextUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register(input: RegisterInput) {
|
||||||
|
const nextUser = await api.register(input);
|
||||||
|
setUser(nextUser);
|
||||||
|
window.localStorage.setItem(storageKey, JSON.stringify(nextUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
setUser(null);
|
||||||
|
window.localStorage.removeItem(storageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AuthContext.Provider value={{ user, login, register, logout }}>{children}</AuthContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useAuth must be used inside AuthProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
import type { CartItem, Product } from "../types";
|
||||||
|
|
||||||
|
type CartContextValue = {
|
||||||
|
items: CartItem[];
|
||||||
|
addItem: (product: Product, quantity?: number) => void;
|
||||||
|
removeItem: (productId: number) => void;
|
||||||
|
updateQuantity: (productId: number, quantity: number) => void;
|
||||||
|
clearCart: () => void;
|
||||||
|
itemCount: number;
|
||||||
|
subtotal: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CartContext = createContext<CartContextValue | undefined>(undefined);
|
||||||
|
const storageKey = "fluxon.cart";
|
||||||
|
|
||||||
|
export function CartProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [items, setItems] = useState<CartItem[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const saved = window.localStorage.getItem(storageKey);
|
||||||
|
if (saved) {
|
||||||
|
setItems(JSON.parse(saved) as CartItem[]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.localStorage.setItem(storageKey, JSON.stringify(items));
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
function addItem(product: Product, quantity = 1) {
|
||||||
|
setItems((current) => {
|
||||||
|
const existing = current.find((item) => item.product.id === product.id);
|
||||||
|
if (existing) {
|
||||||
|
return current.map((item) =>
|
||||||
|
item.product.id === product.id
|
||||||
|
? { ...item, quantity: Math.min(item.quantity + quantity, product.stock) }
|
||||||
|
: item
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...current, { product, quantity: Math.min(quantity, product.stock), unitPrice: product.price }];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeItem(productId: number) {
|
||||||
|
setItems((current) => current.filter((item) => item.product.id !== productId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQuantity(productId: number, quantity: number) {
|
||||||
|
setItems((current) =>
|
||||||
|
current.map((item) =>
|
||||||
|
item.product.id === productId
|
||||||
|
? { ...item, quantity: Math.max(1, Math.min(quantity, item.product.stock)) }
|
||||||
|
: item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCart() {
|
||||||
|
setItems([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
items,
|
||||||
|
addItem,
|
||||||
|
removeItem,
|
||||||
|
updateQuantity,
|
||||||
|
clearCart,
|
||||||
|
itemCount: items.reduce((sum, item) => sum + item.quantity, 0),
|
||||||
|
subtotal: items.reduce((sum, item) => sum + item.unitPrice * item.quantity, 0)
|
||||||
|
}),
|
||||||
|
[items]
|
||||||
|
);
|
||||||
|
|
||||||
|
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCart() {
|
||||||
|
const context = useContext(CartContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useCart must be used inside CartProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,682 @@
|
|||||||
|
:root {
|
||||||
|
font-family: "Trebuchet MS", "Segoe UI", sans-serif;
|
||||||
|
color: #132238;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(255, 186, 117, 0.45), transparent 28%),
|
||||||
|
radial-gradient(circle at top right, rgba(81, 169, 187, 0.28), transparent 24%),
|
||||||
|
linear-gradient(180deg, #fef6ee 0%, #f7fbfc 52%, #eef4f7 100%);
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
color-scheme: light;
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
--surface: rgba(255, 255, 255, 0.82);
|
||||||
|
--surface-strong: rgba(255, 255, 255, 0.94);
|
||||||
|
--border: rgba(19, 34, 56, 0.1);
|
||||||
|
--accent: #ff7a45;
|
||||||
|
--accent-deep: #db5b25;
|
||||||
|
--ink-soft: #5b6f84;
|
||||||
|
--shadow: 0 16px 50px rgba(35, 57, 93, 0.12);
|
||||||
|
--radius-lg: 28px;
|
||||||
|
--radius-md: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.cta-button,
|
||||||
|
.ghost-button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.9rem 1.3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
.cta-button:hover,
|
||||||
|
.ghost-button:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.cta-button {
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-deep) 100%);
|
||||||
|
box-shadow: 0 12px 30px rgba(219, 91, 37, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghost-button,
|
||||||
|
.nav-action {
|
||||||
|
background: rgba(255, 255, 255, 0.65);
|
||||||
|
color: #132238;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-action {
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.95rem 1rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(19, 34, 56, 0.12);
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.45rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #20364f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
width: min(1180px, calc(100vw - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px 0 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header,
|
||||||
|
.site-footer,
|
||||||
|
.panel,
|
||||||
|
.hero-panel,
|
||||||
|
.product-card,
|
||||||
|
.category-card,
|
||||||
|
.order-card,
|
||||||
|
.status-view {
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.68);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.85rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark small {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-badge {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 14px;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #132238 0%, #2a6f97 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a {
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a.active,
|
||||||
|
.main-nav a:hover {
|
||||||
|
color: #132238;
|
||||||
|
background: rgba(255, 255, 255, 0.74);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-link span {
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #132238;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel,
|
||||||
|
.panel,
|
||||||
|
.status-view {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--surface);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.4fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-copy h1,
|
||||||
|
.panel h1,
|
||||||
|
.panel h2,
|
||||||
|
.status-view h2 {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-copy h1 {
|
||||||
|
font-size: clamp(2.3rem, 4vw, 4.6rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-copy p,
|
||||||
|
.panel p,
|
||||||
|
.status-view p {
|
||||||
|
color: var(--ink-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card-grid,
|
||||||
|
.category-grid,
|
||||||
|
.product-grid,
|
||||||
|
.footer-grid,
|
||||||
|
.order-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat,
|
||||||
|
.category-card,
|
||||||
|
.order-card {
|
||||||
|
padding: 1.1rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat.accent {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 122, 69, 0.18) 0%, rgba(19, 34, 56, 0.08) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.55rem;
|
||||||
|
color: var(--accent-deep);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading,
|
||||||
|
.toolbar,
|
||||||
|
.summary-row,
|
||||||
|
.product-actions,
|
||||||
|
.meta-row,
|
||||||
|
.inline-actions,
|
||||||
|
.detail-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel.compact {
|
||||||
|
padding: 22px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card h3,
|
||||||
|
.product-card h3 {
|
||||||
|
margin: 0 0 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-link {
|
||||||
|
display: inline-flex;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
color: var(--accent-deep);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-grid {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 26px;
|
||||||
|
background: var(--surface-strong);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-visual,
|
||||||
|
.detail-visual,
|
||||||
|
.cart-visual {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-visual {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
padding: 0.45rem 0.75rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(19, 34, 56, 0.88);
|
||||||
|
color: white;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-copy {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.8rem;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-stack span {
|
||||||
|
color: var(--ink-soft);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip,
|
||||||
|
.stock,
|
||||||
|
.category-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.45rem 0.8rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip,
|
||||||
|
.category-pill {
|
||||||
|
background: rgba(255, 122, 69, 0.12);
|
||||||
|
color: var(--accent-deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock.available {
|
||||||
|
background: rgba(97, 192, 191, 0.15);
|
||||||
|
color: #17655d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock.sold-out {
|
||||||
|
background: rgba(170, 31, 57, 0.12);
|
||||||
|
color: #8b1e33;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1 1 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-input {
|
||||||
|
width: min(240px, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-pill {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-pill.active {
|
||||||
|
background: #132238;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-detail-layout,
|
||||||
|
.checkout-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.1fr 0.9fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-visual {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
min-height: 520px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lead {
|
||||||
|
font-size: 1.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-control {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.35rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(19, 34, 56, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-control button {
|
||||||
|
min-width: 42px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-list,
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px 1.4fr auto auto auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 1rem 0;
|
||||||
|
border-bottom: 1px solid rgba(19, 34, 56, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-visual {
|
||||||
|
min-height: 88px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-copy h3,
|
||||||
|
.confirmation-card h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary {
|
||||||
|
align-self: start;
|
||||||
|
position: sticky;
|
||||||
|
top: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-row.total {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
border-top: 1px solid rgba(19, 34, 56, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-shell {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
min-height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card {
|
||||||
|
width: min(520px, 100%);
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #aa1f39;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirmation-card,
|
||||||
|
.status-view {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-view {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-button {
|
||||||
|
background: transparent;
|
||||||
|
color: #8b1e33;
|
||||||
|
box-shadow: none;
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 34px;
|
||||||
|
padding: 24px 28px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-band,
|
||||||
|
.trust-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-band {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-card,
|
||||||
|
.trust-card {
|
||||||
|
padding: 28px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-card {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 122, 69, 0.12) 0%, rgba(255, 255, 255, 0.9) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-card-dark {
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #132238 0%, #20364f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-card-dark p,
|
||||||
|
.promo-card-dark .eyebrow,
|
||||||
|
.promo-card-dark h3 {
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trust-grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trust-card {
|
||||||
|
background: rgba(255, 255, 255, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trust-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
grid-template-columns: repeat(3, auto);
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.hero-panel,
|
||||||
|
.product-detail-layout,
|
||||||
|
.checkout-layout,
|
||||||
|
.promo-band,
|
||||||
|
.trust-grid,
|
||||||
|
.site-footer {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header {
|
||||||
|
border-radius: 28px;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav.open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.app-shell {
|
||||||
|
width: min(100vw - 20px, 1180px);
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel,
|
||||||
|
.panel,
|
||||||
|
.status-view {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-card-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading,
|
||||||
|
.toolbar,
|
||||||
|
.product-actions,
|
||||||
|
.rating-row,
|
||||||
|
.meta-row,
|
||||||
|
.inline-actions,
|
||||||
|
.detail-meta,
|
||||||
|
.site-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
export type Category = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
image?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Product = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
price: number;
|
||||||
|
oldPrice?: number;
|
||||||
|
stock: number;
|
||||||
|
categoryId: number;
|
||||||
|
category?: Category;
|
||||||
|
image: string;
|
||||||
|
badge?: string;
|
||||||
|
rating?: number;
|
||||||
|
reviewCount?: number;
|
||||||
|
featured?: boolean;
|
||||||
|
bestSeller?: boolean;
|
||||||
|
newArrival?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CartItem = {
|
||||||
|
product: Product;
|
||||||
|
quantity: number;
|
||||||
|
unitPrice: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginInput = {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RegisterInput = {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AuthUser = {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CheckoutForm = {
|
||||||
|
fullName: string;
|
||||||
|
email: string;
|
||||||
|
address: string;
|
||||||
|
city: string;
|
||||||
|
postalCode: string;
|
||||||
|
paymentMethod: "CreditCard" | "PayPal" | "CashOnDelivery";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderItem = {
|
||||||
|
productId: number;
|
||||||
|
productName: string;
|
||||||
|
quantity: number;
|
||||||
|
unitPrice: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Payment = {
|
||||||
|
method: string;
|
||||||
|
amount: number;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Order = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
status: string;
|
||||||
|
total: number;
|
||||||
|
items: OrderItem[];
|
||||||
|
payment: Payment;
|
||||||
|
customerEmail: string;
|
||||||
|
shippingAddress: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Category } from "../types";
|
||||||
|
|
||||||
|
type CategoryPillProps = {
|
||||||
|
category: Category | { id: number; name: string };
|
||||||
|
active: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CategoryPill({ category, active, onClick }: CategoryPillProps) {
|
||||||
|
return (
|
||||||
|
<button className={`category-pill ${active ? "active" : ""}`} onClick={onClick}>
|
||||||
|
{category.name}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Link, NavLink } from "react-router-dom";
|
||||||
|
import { useCart } from "../state/CartContext";
|
||||||
|
import { useAuth } from "../state/AuthContext";
|
||||||
|
|
||||||
|
export function Layout({ children }: { children: ReactNode }) {
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const { itemCount } = useCart();
|
||||||
|
const { user, logout } = useAuth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="app-shell">
|
||||||
|
<header className="site-header">
|
||||||
|
<Link to="/" className="brand-mark">
|
||||||
|
<span className="brand-badge">F</span>
|
||||||
|
<span>
|
||||||
|
Fluxon
|
||||||
|
<small>Visual commerce demo</small>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<button type="button" className="menu-toggle" onClick={() => setMenuOpen((value) => !value)} aria-label="Toggle menu">
|
||||||
|
Menu
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<nav className={`main-nav ${menuOpen ? "open" : ""}`}>
|
||||||
|
<NavLink to="/" onClick={() => setMenuOpen(false)}>
|
||||||
|
Home
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/products" onClick={() => setMenuOpen(false)}>
|
||||||
|
Shop
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/account" onClick={() => setMenuOpen(false)}>
|
||||||
|
Account
|
||||||
|
</NavLink>
|
||||||
|
{user ? (
|
||||||
|
<button type="button" className="nav-action" onClick={logout}>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<NavLink to="/login" onClick={() => setMenuOpen(false)}>
|
||||||
|
Login
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
<NavLink to="/cart" className="cart-link" onClick={() => setMenuOpen(false)}>
|
||||||
|
Cart <span>{itemCount}</span>
|
||||||
|
</NavLink>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>{children}</main>
|
||||||
|
|
||||||
|
<footer className="site-footer">
|
||||||
|
<div>
|
||||||
|
<strong>Fluxon</strong>
|
||||||
|
<p>Frontend demo built to stay presentable even while the API is still evolving.</p>
|
||||||
|
</div>
|
||||||
|
<div className="footer-grid">
|
||||||
|
<span>Hybrid API mode</span>
|
||||||
|
<span>Responsive storefront</span>
|
||||||
|
<span>Checkout-ready flow</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import type { Product } from "../types";
|
||||||
|
import { visualStyle } from "./visuals";
|
||||||
|
|
||||||
|
type ProductCardProps = {
|
||||||
|
product: Product;
|
||||||
|
onAdd: (product: Product) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProductCard({ product, onAdd }: ProductCardProps) {
|
||||||
|
return (
|
||||||
|
<article className="product-card">
|
||||||
|
<div className="product-visual" style={visualStyle(product.image)}>
|
||||||
|
{product.badge ? <span className="product-badge">{product.badge}</span> : null}
|
||||||
|
</div>
|
||||||
|
<div className="product-copy">
|
||||||
|
<div className="meta-row">
|
||||||
|
<span className="chip">{product.category?.name ?? "Category"}</span>
|
||||||
|
<span className={product.stock > 0 ? "stock available" : "stock sold-out"}>
|
||||||
|
{product.stock > 0 ? `${product.stock} in stock` : "Sold out"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3>{product.name}</h3>
|
||||||
|
<p>{product.description}</p>
|
||||||
|
<div className="rating-row">
|
||||||
|
<span>{product.rating ? `${product.rating.toFixed(1)} / 5` : "New item"}</span>
|
||||||
|
<span>{product.reviewCount ? `${product.reviewCount} reviews` : "No reviews yet"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="product-actions">
|
||||||
|
<div className="price-stack">
|
||||||
|
<strong>EUR {product.price.toFixed(2)}</strong>
|
||||||
|
{product.oldPrice ? <span>EUR {product.oldPrice.toFixed(2)}</span> : null}
|
||||||
|
</div>
|
||||||
|
<div className="inline-actions">
|
||||||
|
<Link to={`/products/${product.id}`} className="ghost-button">
|
||||||
|
Details
|
||||||
|
</Link>
|
||||||
|
<button onClick={() => onAdd(product)} disabled={product.stock === 0}>
|
||||||
|
Add to cart
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
type QuantityControlProps = {
|
||||||
|
value: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function QuantityControl({ value, min = 1, max = 99, onChange }: QuantityControlProps) {
|
||||||
|
const nextDown = Math.max(min, value - 1);
|
||||||
|
const nextUp = Math.min(max, value + 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="quantity-control">
|
||||||
|
<button onClick={() => onChange(nextDown)} disabled={value <= min}>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<span>{value}</span>
|
||||||
|
<button onClick={() => onChange(nextUp)} disabled={value >= max}>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { Navigate, useLocation } from "react-router-dom";
|
||||||
|
import { useAuth } from "../state/AuthContext";
|
||||||
|
|
||||||
|
export function RequireAuth({ children }: { children: ReactNode }) {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
export function StatusView({
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
action
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
action?: ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="status-view">
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<p>{message}</p>
|
||||||
|
{action}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export function visualStyle(image: string) {
|
||||||
|
if (image.startsWith("http")) {
|
||||||
|
return {
|
||||||
|
backgroundImage: `linear-gradient(180deg, rgba(19, 34, 56, 0.08), rgba(19, 34, 56, 0.2)), url("${image}")`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundRepeat: "no-repeat"
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { background: image } as const;
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5173
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user