Compare commits

1 Commits

Author SHA1 Message Date
1d2f7c0732 Add Fluxon frontend storefront 2026-03-17 15:04:55 +01:00
89 changed files with 4580 additions and 29 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
frontend/node_modules/
frontend/dist/
frontend/.vite/

10
.vs/VSWorkspaceState.json Normal file
View File

@@ -0,0 +1,10 @@
{
"ExpandedNodes": [
"",
"\\frontend",
"\\ShopAPI",
"\\ShopAPI\\ShopAPI"
],
"SelectedNode": "\\frontend\\.env.example",
"PreviewInSolutionExplorer": false
}

Binary file not shown.

View File

@@ -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": ""
}
]
}
]
}
]
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -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,

View File

@@ -15,7 +15,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+91ee7a14bf0bcb73d7d98089c76ebea2f2fe7f63")]
[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")]

View File

@@ -1 +1 @@
be52108e965d99112d5251b6f56629d05a000587350c277aca31d5ba1f416964 68325b9d62d9605c3967e910dbbaf739f50cf6103123311d26ac71b61bb3e3af

View File

@@ -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 =

View File

@@ -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>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -1 +1 @@
af378ad7e60f8f4f8f66d7100fe853bfd8622963c6af9a14946e4724352cf619 b576f6f2fa93412c5a3d2af54787d83dab30afa4b61df58637d064e044907b77

View File

@@ -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

View File

@@ -1 +1 @@
b94a5596d89b54f7504b98ef7116ca86018e94a94b8eedb238b7c9fddaf1c409 6b3390096d439ba0d6e4de6a407fa36021954eae649a7b730c75b378bef2731e

View File

@@ -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=","VCMjbsGvZwkzIwBPIxYVkJGNjdVrL8NLULTnwkheTNo="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -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=","VCMjbsGvZwkzIwBPIxYVkJGNjdVrL8NLULTnwkheTNo="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -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":{}}

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -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.

View File

@@ -0,0 +1 @@
1744c178d2610e44f397698f39e437e9c2a0dfa624dd70d434483ad467a6d847

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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": {
@@ -104,7 +106,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.312/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.311/PortableRuntimeIdentifierGraph.json"
} }
} }
} }

View File

@@ -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\" />

View File

@@ -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": {
@@ -3940,7 +3942,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.312/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.311/PortableRuntimeIdentifierGraph.json"
} }
} }
} }

View File

@@ -1,8 +1,8 @@
{ {
"version": 2, "version": 2,
"dgSpecHash": "spizpp0kT/Q=", "dgSpecHash": "NZxEHR2RpcI=",
"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",

2
frontend/.env.example Normal file
View File

@@ -0,0 +1,2 @@
VITE_API_BASE_URL=http://localhost:5276
VITE_USE_MOCK_API=true

View File

@@ -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`

28
frontend/README.md Normal file
View File

@@ -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

12
frontend/index.html Normal file
View File

@@ -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>

1771
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
frontend/package.json Normal file
View File

@@ -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"
}
}

54
frontend/src/App.tsx Normal file
View File

@@ -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>
);
}

6
frontend/src/config.ts Normal file
View File

@@ -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"
};

View File

@@ -0,0 +1,110 @@
import type { AuthUser, Category, Order, Product } from "../types";
export const mockCategories: Category[] = [
{ id: 1, name: "Tech Essentials" },
{ id: 2, name: "Studio Setup" },
{ id: 3, name: "Smart Living" },
{ id: 4, name: "Travel Picks" }
];
export const mockProducts: Product[] = [
{
id: 1,
name: "Flux One Headphones",
description:
"Wireless over-ear headphones tuned for deep focus sessions, clear calls, and long battery life.",
price: 179.9,
stock: 14,
categoryId: 1,
image: "linear-gradient(135deg, #ff8a5b 0%, #ffd166 100%)",
featured: true
},
{
id: 2,
name: "Nova Desk Lamp",
description:
"A sculptural LED lamp with warm-to-cool temperature control for late-night work and clean desk aesthetics.",
price: 69,
stock: 22,
categoryId: 2,
image: "linear-gradient(135deg, #0f4c81 0%, #9bd1e5 100%)",
featured: true
},
{
id: 3,
name: "Orbit Speaker Mini",
description:
"Portable speaker with punchy sound, matte finish, and enough battery to last through weekend trips.",
price: 95.5,
stock: 8,
categoryId: 4,
image: "linear-gradient(135deg, #23395d 0%, #b6c9f0 100%)",
featured: true
},
{
id: 4,
name: "Aero Bottle",
description:
"Insulated stainless steel bottle designed for city commutes, travel, and everyday carry.",
price: 32,
stock: 40,
categoryId: 4,
image: "linear-gradient(135deg, #2a6f97 0%, #61c0bf 100%)"
},
{
id: 5,
name: "Canvas Keyboard",
description:
"Compact mechanical keyboard with low-profile switches and a clean, minimalist layout.",
price: 124,
stock: 16,
categoryId: 1,
image: "linear-gradient(135deg, #5f0f40 0%, #fb8b24 100%)"
},
{
id: 6,
name: "Pulse Air Purifier",
description:
"Smart purifier with quiet mode, room-quality indicator, and app-ready control concept.",
price: 210,
stock: 11,
categoryId: 3,
image: "linear-gradient(135deg, #355070 0%, #b56576 100%)"
}
];
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: 1,
productName: "Flux One Headphones",
quantity: 1,
unitPrice: 179.9
},
{
productId: 4,
productName: "Aero Bottle",
quantity: 2,
unitPrice: 32
}
],
payment: {
method: "PayPal",
amount: 248.9,
status: "Paid"
},
customerEmail: "demo@fluxon.shop",
shippingAddress: "18 Vision Street, Berlin"
}
];

View File

@@ -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 };
}

19
frontend/src/main.tsx Normal file
View File

@@ -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>
);

View File

@@ -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>
);
}

View File

@@ -0,0 +1,76 @@
import { Link } from "react-router-dom";
import { useCart } from "../state/CartContext";
import { QuantityControl } from "../ui/QuantityControl";
import { StatusView } from "../ui/StatusView";
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={{ background: 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>
);
}

View File

@@ -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>
);
}

View File

@@ -0,0 +1,91 @@
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, 3);
return (
<div className="page-stack">
<section className="hero-panel">
<div className="hero-copy">
<span className="eyebrow">Fluxon storefront</span>
<h1>Build a demo-ready shop now, then swap in real APIs as the backend catches up.</h1>
<p>
This frontend is structured for hybrid delivery: polished enough for presentation, flexible enough for
incomplete endpoints.
</p>
<div className="inline-actions">
<Link to="/products" className="cta-button">
Explore Products
</Link>
<Link to="/register" className="ghost-button">
Create Demo Account
</Link>
</div>
</div>
<div className="hero-card-grid">
<div className="hero-stat">
<strong>{products.length}</strong>
<span>Curated products</span>
</div>
<div className="hero-stat">
<strong>{categories.length}</strong>
<span>Shop categories</span>
</div>
<div className="hero-stat accent">
<strong>Mock + API</strong>
<span>Safe for evolving backend</span>
</div>
</div>
</section>
<section className="panel">
<div className="section-heading">
<div>
<span className="eyebrow">Categories</span>
<h2>Shape the browsing experience around the backend data model</h2>
</div>
</div>
<div className="category-grid">
{categories.map((category) => (
<article key={category.id} className="category-card">
<h3>{category.name}</h3>
<p>Use this category as a filter, navigation cue, and featured-content block.</p>
</article>
))}
</div>
</section>
<section className="panel">
<div className="section-heading">
<div>
<span className="eyebrow">Featured</span>
<h2>Presentation-friendly products for your MVP demo 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>
</div>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
}
/>
);
}

View File

@@ -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>
);
}

View File

@@ -0,0 +1,97 @@
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";
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={{ background: 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="detail-meta">
<div>
<span>Price</span>
<strong>EUR {product.price.toFixed(2)}</strong>
</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>
);
}

View File

@@ -0,0 +1,105 @@
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 "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="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>
);
}

View File

@@ -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>
);
}

View File

@@ -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)
);
}
};

View File

@@ -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;
}
};

View File

@@ -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;
}

View File

@@ -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;
}

587
frontend/src/styles.css Normal file
View File

@@ -0,0 +1,587 @@
: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;
}
.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-copy {
display: grid;
gap: 0.8rem;
padding: 20px;
}
.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);
}
.footer-grid {
grid-template-columns: repeat(3, auto);
align-content: center;
}
@media (max-width: 900px) {
.hero-panel,
.product-detail-layout,
.checkout-layout,
.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,
.meta-row,
.inline-actions,
.detail-meta,
.site-footer {
flex-direction: column;
align-items: flex-start;
}
.footer-grid {
grid-template-columns: 1fr;
}
}

72
frontend/src/types.ts Normal file
View File

@@ -0,0 +1,72 @@
export type Category = {
id: number;
name: string;
};
export type Product = {
id: number;
name: string;
description: string;
price: number;
stock: number;
categoryId: number;
category?: Category;
image: string;
featured?: 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;
};

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -0,0 +1,36 @@
import { Link } from "react-router-dom";
import type { Product } from "../types";
type ProductCardProps = {
product: Product;
onAdd: (product: Product) => void;
};
export function ProductCard({ product, onAdd }: ProductCardProps) {
return (
<article className="product-card">
<div className="product-visual" style={{ background: product.image }} />
<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="product-actions">
<strong>EUR {product.price.toFixed(2)}</strong>
<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>
);
}

View File

@@ -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>
);
}

View File

@@ -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}</>;
}

View File

@@ -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>
);
}

View File

@@ -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"]
}

6
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,6 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" }
]
}

9
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 5173
}
});