Compare commits
1 Commits
91ee7a14bf
...
brnamin
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d2f7c0732 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
frontend/.vite/
|
||||||
10
.vs/VSWorkspaceState.json
Normal file
10
.vs/VSWorkspaceState.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"ExpandedNodes": [
|
||||||
|
"",
|
||||||
|
"\\frontend",
|
||||||
|
"\\ShopAPI",
|
||||||
|
"\\ShopAPI\\ShopAPI"
|
||||||
|
],
|
||||||
|
"SelectedNode": "\\frontend\\.env.example",
|
||||||
|
"PreviewInSolutionExplorer": false
|
||||||
|
}
|
||||||
BIN
.vs/WebShop_Fluxon/CopilotIndices/17.14.1584.41681/CodeChunks.db
Normal file
BIN
.vs/WebShop_Fluxon/CopilotIndices/17.14.1584.41681/CodeChunks.db
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.vs/WebShop_Fluxon/v17/.wsuo
Normal file
BIN
.vs/WebShop_Fluxon/v17/.wsuo
Normal file
Binary file not shown.
53
.vs/WebShop_Fluxon/v17/DocumentLayout.json
Normal file
53
.vs/WebShop_Fluxon/v17/DocumentLayout.json
Normal 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
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
BIN
ShopAPI/ShopAPI/bin/Debug/net8.0/BCrypt.Net-Next.dll
Normal file
BIN
ShopAPI/ShopAPI/bin/Debug/net8.0/BCrypt.Net-Next.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,16 +8,27 @@
|
|||||||
".NETCoreApp,Version=v8.0": {
|
".NETCoreApp,Version=v8.0": {
|
||||||
"ShopAPI/1.0.0": {
|
"ShopAPI/1.0.0": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"BCrypt.Net-Next": "4.1.0",
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.25",
|
||||||
"Microsoft.EntityFrameworkCore": "9.0.0",
|
"Microsoft.EntityFrameworkCore": "9.0.0",
|
||||||
"Microsoft.EntityFrameworkCore.Tools": "9.0.0",
|
"Microsoft.EntityFrameworkCore.Tools": "9.0.0",
|
||||||
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
"Microsoft.VisualStudio.Azure.Containers.Tools.Targets": "1.22.1",
|
||||||
"Pomelo.EntityFrameworkCore.MySql": "9.0.0",
|
"Pomelo.EntityFrameworkCore.MySql": "9.0.0",
|
||||||
"Swashbuckle.AspNetCore": "6.6.2"
|
"Swashbuckle.AspNetCore": "6.6.2",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "8.16.0"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"ShopAPI.dll": {}
|
"ShopAPI.dll": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BCrypt.Net-Next/4.1.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard2.1/BCrypt.Net-Next.dll": {
|
||||||
|
"assemblyVersion": "4.1.0.0",
|
||||||
|
"fileVersion": "4.1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Humanizer.Core/2.14.1": {
|
"Humanizer.Core/2.14.1": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/net6.0/Humanizer.dll": {
|
"lib/net6.0/Humanizer.dll": {
|
||||||
@@ -26,6 +37,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.25": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.1.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
|
||||||
|
"assemblyVersion": "8.0.25.0",
|
||||||
|
"fileVersion": "8.0.2526.11225"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
||||||
@@ -501,6 +523,72 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/8.16.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Abstractions.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Abstractions": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Logging.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Logging": "8.16.0",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Protocols.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols": "7.1.2",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
|
||||||
|
"assemblyVersion": "7.1.2.0",
|
||||||
|
"fileVersion": "7.1.2.41121"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "9.0.0",
|
||||||
|
"Microsoft.IdentityModel.Logging": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.IdentityModel.Tokens.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.14": {
|
"Microsoft.OpenApi/1.6.14": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
||||||
@@ -660,6 +748,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/8.16.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens": "8.16.0",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.16.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.IdentityModel.Tokens.Jwt.dll": {
|
||||||
|
"assemblyVersion": "8.16.0.0",
|
||||||
|
"fileVersion": "8.16.0.26043"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"System.IO.Pipelines/9.0.0": {
|
"System.IO.Pipelines/9.0.0": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/net8.0/System.IO.Pipelines.dll": {
|
"lib/net8.0/System.IO.Pipelines.dll": {
|
||||||
@@ -711,6 +811,13 @@
|
|||||||
"serviceable": false,
|
"serviceable": false,
|
||||||
"sha512": ""
|
"sha512": ""
|
||||||
},
|
},
|
||||||
|
"BCrypt.Net-Next/4.1.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-5YT3DKllmtkyW68PjURu/V1TOe4MKiByKwsRNVcfYE1S5KuFTeozdmKzyNzolKiQF391OXCaQtINvYT3j1ERzQ==",
|
||||||
|
"path": "bcrypt.net-next/4.1.0",
|
||||||
|
"hashPath": "bcrypt.net-next.4.1.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"Humanizer.Core/2.14.1": {
|
"Humanizer.Core/2.14.1": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -718,6 +825,13 @@
|
|||||||
"path": "humanizer.core/2.14.1",
|
"path": "humanizer.core/2.14.1",
|
||||||
"hashPath": "humanizer.core.2.14.1.nupkg.sha512"
|
"hashPath": "humanizer.core.2.14.1.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.25": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-nb6jCyxh5eP9bsXkHmGcDxUiVIl5wJSombl3LN2L+sjGEVXzcMKbdRe0fp8LQtuBM2hKXcXFxMAYdnohdYJF8Q==",
|
||||||
|
"path": "microsoft.aspnetcore.authentication.jwtbearer/8.0.25",
|
||||||
|
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.8.0.25.nupkg.sha512"
|
||||||
|
},
|
||||||
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
"Microsoft.Bcl.AsyncInterfaces/7.0.0": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -900,6 +1014,48 @@
|
|||||||
"path": "microsoft.extensions.primitives/9.0.0",
|
"path": "microsoft.extensions.primitives/9.0.0",
|
||||||
"hashPath": "microsoft.extensions.primitives.9.0.0.nupkg.sha512"
|
"hashPath": "microsoft.extensions.primitives.9.0.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-gSxKLWRZzBpIsEoeUPkxfywNCCvRvl7hkq146XHPk5vOQc9izSf1I+uL1vh4y2U19QPxd9Z8K/8AdWyxYz2lSg==",
|
||||||
|
"path": "microsoft.identitymodel.abstractions/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.abstractions.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-prBU72cIP4V8E9fhN+o/YdskTsLeIcnKPbhZf0X6mD7fdxoZqnS/NdEkSr+9Zp+2q7OZBOMfNBKGbTbhXODO4w==",
|
||||||
|
"path": "microsoft.identitymodel.jsonwebtokens/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.jsonwebtokens.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-MTzXmETkNQPACR7/XCXM1OGM6oU9RkyibqeJRtO9Ndew2LnGjMf9Atqj2VSf4XC27X0FQycUAlzxxEgQMWn2xQ==",
|
||||||
|
"path": "microsoft.identitymodel.logging/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.logging.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-SydLwMRFx6EHPWJ+N6+MVaoArN1Htt92b935O3RUWPY1yUF63zEjvd3lBu79eWdZUwedP8TN2I5V9T3nackvIQ==",
|
||||||
|
"path": "microsoft.identitymodel.protocols/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-6lHQoLXhnMQ42mGrfDkzbIOR3rzKM1W1tgTeMPLgLCqwwGw0d96xFi/UiX/fYsu7d6cD5MJiL3+4HuI8VU+sVQ==",
|
||||||
|
"path": "microsoft.identitymodel.protocols.openidconnect/7.1.2",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.openidconnect.7.1.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-rtViGJcGsN7WcfUNErwNeQgjuU5cJNl6FDQsfi9TncwO+Epzn0FTfBsg3YuFW1Q0Ch/KPxaVdjLw3/+5Z5ceFQ==",
|
||||||
|
"path": "microsoft.identitymodel.tokens/8.16.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.tokens.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.14": {
|
"Microsoft.OpenApi/1.6.14": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -1026,6 +1182,13 @@
|
|||||||
"path": "system.diagnostics.diagnosticsource/9.0.0",
|
"path": "system.diagnostics.diagnosticsource/9.0.0",
|
||||||
"hashPath": "system.diagnostics.diagnosticsource.9.0.0.nupkg.sha512"
|
"hashPath": "system.diagnostics.diagnosticsource.9.0.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/8.16.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-rrs2u7DRMXQG2yh0oVyF/vLwosfRv20Ld2iEpYcKwQWXHjfV+gFXNQsQ9p008kR9Ou4pxBs68Q6/9zC8Gi1wjg==",
|
||||||
|
"path": "system.identitymodel.tokens.jwt/8.16.0",
|
||||||
|
"hashPath": "system.identitymodel.tokens.jwt.8.16.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"System.IO.Pipelines/9.0.0": {
|
"System.IO.Pipelines/9.0.0": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
be52108e965d99112d5251b6f56629d05a000587350c277aca31d5ba1f416964
|
68325b9d62d9605c3967e910dbbaf739f50cf6103123311d26ac71b61bb3e3af
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ build_property._SupportedPlatformList = Linux,macOS,Windows
|
|||||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
build_property.RootNamespace = ShopAPI
|
build_property.RootNamespace = ShopAPI
|
||||||
build_property.RootNamespace = ShopAPI
|
build_property.RootNamespace = ShopAPI
|
||||||
build_property.ProjectDir = C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\
|
build_property.ProjectDir = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\
|
||||||
build_property.EnableComHosting =
|
build_property.EnableComHosting =
|
||||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||||
build_property.RazorLangVersion = 8.0
|
build_property.RazorLangVersion = 8.0
|
||||||
build_property.SupportLocalizedComponentNames =
|
build_property.SupportLocalizedComponentNames =
|
||||||
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
||||||
build_property.MSBuildProjectDirectory = C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI
|
build_property.MSBuildProjectDirectory = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI
|
||||||
build_property._RazorSourceGeneratorDebug =
|
build_property._RazorSourceGeneratorDebug =
|
||||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||||
build_property.EnableCodeStyleSeverity =
|
build_property.EnableCodeStyleSeverity =
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// Dieser Code wurde von einem Tool generiert.
|
// This code was generated by a tool.
|
||||||
// Laufzeitversion:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// der Code erneut generiert wird.
|
// the code is regenerated.
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
af378ad7e60f8f4f8f66d7100fe853bfd8622963c6af9a14946e4724352cf619
|
b576f6f2fa93412c5a3d2af54787d83dab30afa4b61df58637d064e044907b77
|
||||||
|
|||||||
@@ -135,3 +135,148 @@ C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net
|
|||||||
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.pdb
|
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.pdb
|
||||||
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.genruntimeconfig.cache
|
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.genruntimeconfig.cache
|
||||||
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ref\ShopAPI.dll
|
C:\Users\bib\Desktop\Projekt-Fluxon\website_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ref\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\appsettings.Development.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\appsettings.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.staticwebassets.endpoints.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.exe
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.deps.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.runtimeconfig.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ShopAPI.pdb
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\BCrypt.Net-Next.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Humanizer.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Bcl.AsyncInterfaces.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Build.Locator.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.CSharp.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.CSharp.Workspaces.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.Workspaces.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.Design.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.EntityFrameworkCore.Relational.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Caching.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Caching.Memory.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Configuration.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.DependencyInjection.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.DependencyModel.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Logging.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Logging.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Options.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.Extensions.Primitives.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Abstractions.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.JsonWebTokens.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Logging.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Protocols.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.IdentityModel.Tokens.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Microsoft.OpenApi.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Mono.TextTemplating.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\MySqlConnector.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Pomelo.EntityFrameworkCore.MySql.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Swashbuckle.AspNetCore.Swagger.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Swashbuckle.AspNetCore.SwaggerGen.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\Swashbuckle.AspNetCore.SwaggerUI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.CodeDom.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.AttributedModel.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.Convention.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.Hosting.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.Runtime.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Composition.TypedParts.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Diagnostics.DiagnosticSource.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.IdentityModel.Tokens.Jwt.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.IO.Pipelines.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Text.Encodings.Web.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\System.Text.Json.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\cs\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\de\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\es\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\fr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\it\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ja\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ko\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pl\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\pt-BR\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\ru\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\bin\Debug\net8.0\runtimes\browser\lib\net8.0\System.Text.Encodings.Web.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.csproj.AssemblyReference.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rpswa.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.GeneratedMSBuildEditorConfig.editorconfig
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.AssemblyInfoInputs.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.AssemblyInfo.cs
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.csproj.CoreCompileInputs.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.MvcApplicationPartsAssemblyInfo.cs
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.MvcApplicationPartsAssemblyInfo.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rjimswa.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rjsmrazor.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\rjsmcshtml.dswa.cache.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\scopedcss\bundle\ShopAPI.styles.css
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.build.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.build.json.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.development.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\staticwebassets.build.endpoints.json
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.csproj.Up2Date
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\refint\ShopAPI.dll
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.pdb
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ShopAPI.genruntimeconfig.cache
|
||||||
|
C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\obj\Debug\net8.0\ref\ShopAPI.dll
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
b94a5596d89b54f7504b98ef7116ca86018e94a94b8eedb238b7c9fddaf1c409
|
6b3390096d439ba0d6e4de6a407fa36021954eae649a7b730c75b378bef2731e
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"2SiV7x3vjV8ZFi0sgmWSSTGHCwHAp08jCG9AL/ERQ/Q=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mSjvBL27zG1kSzjpaqc9iI0QRpH2\u002BnmIxzsYeoyiZ8k=","5\u002BRipzB3qq6oLG1hzpoNvnzpM95KVvzJdVIXKGR5Nps=","k4J\u002BX/9OfyQ\u002Bvg6DNSgPmdorYBHAlUCx2XqrIUvSzvM=","P9s2CLfWnGJ3BuLhrowTd7paEsc\u002BFPTVz6iCnwAyWDY=","cZjb5kG3xkbxY4SEseWX7ClUw/XN02dd37xJoHYSocs=","k2ZK2MXYMG89By4JV3rT0m6IHy183usaiKwTwNY8md4=","7INOJdHgqe/nqyYYieTHW8/FPUSM7kVKuqZEiBTlsOE="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"jjFS3ypc+SFFIJoabqxBcUwQp4+BH7vO9y9aDuWO8Ig=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["oM\u002B92zWtRWs1ZZHQG5F\u002BZt\u002BmUwKFCmEHXl6yhJyDB9E=","m4ORQ/F\u002BR\u002BZG7hKB0ZiSTgsXFT54fbrxurDLZfuPLtU=","/26wWziwS0KH/O896xGXBSLufvhay7wSjV2zdTxws18=","WyWGi43zm6PntQI/ucnnU3lxt5XudSMI/Un84LZNCNI=","pzhO73lOLr0aoNbQo3AXbJ7qsA2VDRY9Il7vtsfu6/g=","\u002BmYXsDWUwEv8MDDi5d\u002BdcyyfamgV9HnHNS18DvTVT8g=","VCMjbsGvZwkzIwBPIxYVkJGNjdVrL8NLULTnwkheTNo="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"g/T3BUSjVMK8fsoOmXvbmTfW2MVL2VJoCyrZtQ0or0o=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mSjvBL27zG1kSzjpaqc9iI0QRpH2\u002BnmIxzsYeoyiZ8k=","5\u002BRipzB3qq6oLG1hzpoNvnzpM95KVvzJdVIXKGR5Nps=","k4J\u002BX/9OfyQ\u002Bvg6DNSgPmdorYBHAlUCx2XqrIUvSzvM=","P9s2CLfWnGJ3BuLhrowTd7paEsc\u002BFPTVz6iCnwAyWDY=","cZjb5kG3xkbxY4SEseWX7ClUw/XN02dd37xJoHYSocs=","k2ZK2MXYMG89By4JV3rT0m6IHy183usaiKwTwNY8md4=","7INOJdHgqe/nqyYYieTHW8/FPUSM7kVKuqZEiBTlsOE="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"tbgf6NkZHDHq5Bwz13FNLNq2Biw7YSKbQETYkFTRirw=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["oM\u002B92zWtRWs1ZZHQG5F\u002BZt\u002BmUwKFCmEHXl6yhJyDB9E=","m4ORQ/F\u002BR\u002BZG7hKB0ZiSTgsXFT54fbrxurDLZfuPLtU=","/26wWziwS0KH/O896xGXBSLufvhay7wSjV2zdTxws18=","WyWGi43zm6PntQI/ucnnU3lxt5XudSMI/Un84LZNCNI=","pzhO73lOLr0aoNbQo3AXbJ7qsA2VDRY9Il7vtsfu6/g=","\u002BmYXsDWUwEv8MDDi5d\u002BdcyyfamgV9HnHNS18DvTVT8g=","VCMjbsGvZwkzIwBPIxYVkJGNjdVrL8NLULTnwkheTNo="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"Jr8OaCL0xTfG0mf5QmD9ofeLsJvV3IBK9E7sPFxRjbU=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["mSjvBL27zG1kSzjpaqc9iI0QRpH2\u002BnmIxzsYeoyiZ8k=","5\u002BRipzB3qq6oLG1hzpoNvnzpM95KVvzJdVIXKGR5Nps="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"XJKffwBiP1Gt5f0UmbU2Us8/L7oX34bUNWnspz6E28M=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["oM\u002B92zWtRWs1ZZHQG5F\u002BZt\u002BmUwKFCmEHXl6yhJyDB9E=","m4ORQ/F\u002BR\u002BZG7hKB0ZiSTgsXFT54fbrxurDLZfuPLtU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// <autogenerated />
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||||
24
ShopAPI/ShopAPI/obj/Release/net8.0/ShopAPI.AssemblyInfo.cs
Normal file
24
ShopAPI/ShopAPI/obj/Release/net8.0/ShopAPI.AssemblyInfo.cs
Normal 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.
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1744c178d2610e44f397698f39e437e9c2a0dfa624dd70d434483ad467a6d847
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
is_global = true
|
||||||
|
build_property.TargetFramework = net8.0
|
||||||
|
build_property.TargetFramework = net8.0
|
||||||
|
build_property.TargetPlatformMinVersion =
|
||||||
|
build_property.TargetPlatformMinVersion =
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb = true
|
||||||
|
build_property.UsingMicrosoftNETSdkWeb = true
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.ProjectTypeGuids =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.InvariantGlobalization =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.PlatformNeutralAssembly =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property.EnforceExtendedAnalyzerRules =
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||||
|
build_property.RootNamespace = ShopAPI
|
||||||
|
build_property.RootNamespace = ShopAPI
|
||||||
|
build_property.ProjectDir = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI\
|
||||||
|
build_property.EnableComHosting =
|
||||||
|
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||||
|
build_property.RazorLangVersion = 8.0
|
||||||
|
build_property.SupportLocalizedComponentNames =
|
||||||
|
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
||||||
|
build_property.MSBuildProjectDirectory = C:\Users\bib\Documents\LEA2\WebShop_Fluxon\ShopAPI\ShopAPI
|
||||||
|
build_property._RazorSourceGeneratorDebug =
|
||||||
|
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||||
|
build_property.EnableCodeStyleSeverity =
|
||||||
17
ShopAPI/ShopAPI/obj/Release/net8.0/ShopAPI.GlobalUsings.g.cs
Normal file
17
ShopAPI/ShopAPI/obj/Release/net8.0/ShopAPI.GlobalUsings.g.cs
Normal 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;
|
||||||
BIN
ShopAPI/ShopAPI/obj/Release/net8.0/ShopAPI.assets.cache
Normal file
BIN
ShopAPI/ShopAPI/obj/Release/net8.0/ShopAPI.assets.cache
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"format": 1,
|
"format": 1,
|
||||||
"restore": {
|
"restore": {
|
||||||
"C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {}
|
"C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {}
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {
|
"C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectUniqueName": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"projectName": "ShopAPI",
|
"projectName": "ShopAPI",
|
||||||
"projectPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
||||||
"outputPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
"outputPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
||||||
"projectStyle": "PackageReference",
|
"projectStyle": "PackageReference",
|
||||||
"fallbackFolders": [
|
"fallbackFolders": [
|
||||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
@@ -26,7 +26,9 @@
|
|||||||
],
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
"https://api.nuget.org/v3/index.json": {}
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {},
|
||||||
|
"https://packagesource1": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\bib\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\bib\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.2</NuGetToolVersion>
|
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||||
<SourceRoot Include="C:\Users\bib\.nuget\packages\" />
|
<SourceRoot Include="C:\Users\bib\.nuget\packages\" />
|
||||||
|
|||||||
@@ -3843,11 +3843,11 @@
|
|||||||
"project": {
|
"project": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectUniqueName": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"projectName": "ShopAPI",
|
"projectName": "ShopAPI",
|
||||||
"projectPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
"projectPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\ShopAPI.csproj",
|
||||||
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
"packagesPath": "C:\\Users\\bib\\.nuget\\packages\\",
|
||||||
"outputPath": "C:\\Users\\bib\\Desktop\\Projekt-Fluxon\\website_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
"outputPath": "C:\\Users\\bib\\Documents\\LEA2\\WebShop_Fluxon\\ShopAPI\\ShopAPI\\obj\\",
|
||||||
"projectStyle": "PackageReference",
|
"projectStyle": "PackageReference",
|
||||||
"fallbackFolders": [
|
"fallbackFolders": [
|
||||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||||
@@ -3862,7 +3862,9 @@
|
|||||||
],
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||||
"https://api.nuget.org/v3/index.json": {}
|
"C:\\Program Files\\dotnet\\library-packs": {},
|
||||||
|
"https://api.nuget.org/v3/index.json": {},
|
||||||
|
"https://packagesource1": {}
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net8.0": {
|
"net8.0": {
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
2
frontend/.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_API_BASE_URL=http://localhost:5276
|
||||||
|
VITE_USE_MOCK_API=true
|
||||||
39
frontend/INTEGRATION_CHECKLIST.md
Normal file
39
frontend/INTEGRATION_CHECKLIST.md
Normal 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
28
frontend/README.md
Normal 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
12
frontend/index.html
Normal 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
1771
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
frontend/package.json
Normal file
23
frontend/package.json
Normal 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
54
frontend/src/App.tsx
Normal 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
6
frontend/src/config.ts
Normal 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"
|
||||||
|
};
|
||||||
110
frontend/src/data/mockData.ts
Normal file
110
frontend/src/data/mockData.ts
Normal 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"
|
||||||
|
}
|
||||||
|
];
|
||||||
44
frontend/src/hooks/useCatalog.ts
Normal file
44
frontend/src/hooks/useCatalog.ts
Normal 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
19
frontend/src/main.tsx
Normal 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>
|
||||||
|
);
|
||||||
93
frontend/src/pages/AccountPage.tsx
Normal file
93
frontend/src/pages/AccountPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
frontend/src/pages/CartPage.tsx
Normal file
76
frontend/src/pages/CartPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
132
frontend/src/pages/CheckoutPage.tsx
Normal file
132
frontend/src/pages/CheckoutPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
91
frontend/src/pages/HomePage.tsx
Normal file
91
frontend/src/pages/HomePage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
frontend/src/pages/LoginPage.tsx
Normal file
53
frontend/src/pages/LoginPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
frontend/src/pages/NotFoundPage.tsx
Normal file
16
frontend/src/pages/NotFoundPage.tsx
Normal 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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
frontend/src/pages/OrderConfirmationPage.tsx
Normal file
53
frontend/src/pages/OrderConfirmationPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
97
frontend/src/pages/ProductDetailsPage.tsx
Normal file
97
frontend/src/pages/ProductDetailsPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
frontend/src/pages/ProductsPage.tsx
Normal file
105
frontend/src/pages/ProductsPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
frontend/src/pages/RegisterPage.tsx
Normal file
57
frontend/src/pages/RegisterPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
frontend/src/services/api.ts
Normal file
109
frontend/src/services/api.ts
Normal 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
99
frontend/src/services/mockApi.ts
Normal file
99
frontend/src/services/mockApi.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
53
frontend/src/state/AuthContext.tsx
Normal file
53
frontend/src/state/AuthContext.tsx
Normal 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;
|
||||||
|
}
|
||||||
88
frontend/src/state/CartContext.tsx
Normal file
88
frontend/src/state/CartContext.tsx
Normal 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
587
frontend/src/styles.css
Normal 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
72
frontend/src/types.ts
Normal 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;
|
||||||
|
};
|
||||||
15
frontend/src/ui/CategoryPill.tsx
Normal file
15
frontend/src/ui/CategoryPill.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
frontend/src/ui/Layout.tsx
Normal file
67
frontend/src/ui/Layout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
frontend/src/ui/ProductCard.tsx
Normal file
36
frontend/src/ui/ProductCard.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
frontend/src/ui/QuantityControl.tsx
Normal file
23
frontend/src/ui/QuantityControl.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
frontend/src/ui/RequireAuth.tsx
Normal file
14
frontend/src/ui/RequireAuth.tsx
Normal 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}</>;
|
||||||
|
}
|
||||||
19
frontend/src/ui/StatusView.tsx
Normal file
19
frontend/src/ui/StatusView.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
frontend/tsconfig.app.json
Normal file
21
frontend/tsconfig.app.json
Normal 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
6
frontend/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
9
frontend/vite.config.ts
Normal file
9
frontend/vite.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5173
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user