├── .gitattributes
├── README.md
├── backend
├── .vs
│ ├── ProjectEvaluation
│ │ ├── backend.metadata.v5.2
│ │ └── backend.projects.v5.2
│ ├── VSWorkspaceState.json
│ ├── backend
│ │ ├── DesignTimeBuild
│ │ │ └── .dtbcache.v2
│ │ ├── FileContentIndex
│ │ │ ├── 5abb3df0-7788-474f-b8d8-8294849ba414.vsidx
│ │ │ ├── 709da6fa-206d-4d71-bc1d-7ee88ee45dcc.vsidx
│ │ │ ├── a88236e6-3776-449b-806f-6afe13117d63.vsidx
│ │ │ ├── b74b1431-114f-4bb5-97dc-505a4873c342.vsidx
│ │ │ └── read.lock
│ │ ├── config
│ │ │ └── applicationhost.config
│ │ └── v17
│ │ │ ├── .futdcache.v2
│ │ │ ├── .suo
│ │ │ └── .wsuo
│ ├── server
│ │ ├── FileContentIndex
│ │ │ ├── 352ded9e-6022-48da-8df6-fdea0968bd98.vsidx
│ │ │ ├── 715e4efa-1d4a-4cfe-a07e-9c587a42fe43.vsidx
│ │ │ ├── 95302e9c-f47a-43be-9670-f389dc036be4.vsidx
│ │ │ ├── bd2029c9-8f84-4008-b573-efcc80815d56.vsidx
│ │ │ └── read.lock
│ │ └── v17
│ │ │ └── .wsuo
│ └── slnx.sqlite
├── Controllers
│ ├── OrderController.cs
│ ├── OrderItemController.cs
│ ├── ProductController.cs
│ ├── ProductSizeController.cs
│ └── UserController.cs
├── JwtService.cs
├── Models
│ ├── Order.cs
│ ├── OrderItem.cs
│ ├── Product.cs
│ ├── ProductSize.cs
│ ├── User.cs
│ └── UserLoginRequest.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Repositories
│ ├── IListRepository.cs
│ ├── IRepository.cs
│ ├── OrderItemRepository.cs
│ ├── OrderRepository.cs
│ ├── ProductRepository.cs
│ ├── ProductSizeRepository.cs
│ └── UserRepository.cs
├── Startup.cs
├── TypeConverter.cs
├── appsettings.Development.json
├── appsettings.json
├── backend.csproj
├── backend.csproj.user
├── backend.sln
├── bin
│ └── Debug
│ │ └── net6.0
│ │ ├── Microsoft.AspNetCore.Authentication.JwtBearer.dll
│ │ ├── Microsoft.AspNetCore.JsonPatch.dll
│ │ ├── Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll
│ │ ├── Microsoft.IdentityModel.Abstractions.dll
│ │ ├── Microsoft.IdentityModel.JsonWebTokens.dll
│ │ ├── Microsoft.IdentityModel.Logging.dll
│ │ ├── Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
│ │ ├── Microsoft.IdentityModel.Protocols.dll
│ │ ├── Microsoft.IdentityModel.Tokens.dll
│ │ ├── Microsoft.OpenApi.dll
│ │ ├── Newtonsoft.Json.Bson.dll
│ │ ├── Newtonsoft.Json.dll
│ │ ├── Serilog.dll
│ │ ├── Swashbuckle.AspNetCore.Swagger.dll
│ │ ├── Swashbuckle.AspNetCore.SwaggerGen.dll
│ │ ├── Swashbuckle.AspNetCore.SwaggerUI.dll
│ │ ├── System.Data.SqlClient.dll
│ │ ├── System.IdentityModel.Tokens.Jwt.dll
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.json
│ │ ├── backend.deps.json
│ │ ├── backend.dll
│ │ ├── backend.exe
│ │ ├── backend.pdb
│ │ ├── backend.runtimeconfig.json
│ │ └── runtimes
│ │ ├── unix
│ │ └── lib
│ │ │ └── netcoreapp2.1
│ │ │ └── System.Data.SqlClient.dll
│ │ ├── win-arm64
│ │ └── native
│ │ │ └── sni.dll
│ │ ├── win-x64
│ │ └── native
│ │ │ └── sni.dll
│ │ ├── win-x86
│ │ └── native
│ │ │ └── sni.dll
│ │ └── win
│ │ └── lib
│ │ └── netcoreapp2.1
│ │ └── System.Data.SqlClient.dll
└── obj
│ ├── Debug
│ └── net6.0
│ │ ├── .NETCoreApp,Version=v6.0.AssemblyAttributes.cs
│ │ ├── apphost.exe
│ │ ├── backend.AssemblyInfo.cs
│ │ ├── backend.AssemblyInfoInputs.cache
│ │ ├── backend.GeneratedMSBuildEditorConfig.editorconfig
│ │ ├── backend.GlobalUsings.g.cs
│ │ ├── backend.MvcApplicationPartsAssemblyInfo.cache
│ │ ├── backend.MvcApplicationPartsAssemblyInfo.cs
│ │ ├── backend.assets.cache
│ │ ├── backend.csproj.AssemblyReference.cache
│ │ ├── backend.csproj.BuildWithSkipAnalyzers
│ │ ├── backend.csproj.CopyComplete
│ │ ├── backend.csproj.CoreCompileInputs.cache
│ │ ├── backend.csproj.FileListAbsolute.txt
│ │ ├── backend.dll
│ │ ├── backend.genruntimeconfig.cache
│ │ ├── backend.pdb
│ │ ├── ref
│ │ └── backend.dll
│ │ ├── refint
│ │ └── backend.dll
│ │ ├── staticwebassets.build.json
│ │ └── staticwebassets
│ │ ├── msbuild.build.backend.props
│ │ ├── msbuild.buildMultiTargeting.backend.props
│ │ └── msbuild.buildTransitive.backend.props
│ ├── Release
│ └── net6.0
│ │ ├── .NETCoreApp,Version=v6.0.AssemblyAttributes.cs
│ │ ├── backend.AssemblyInfo.cs
│ │ ├── backend.AssemblyInfoInputs.cache
│ │ ├── backend.GeneratedMSBuildEditorConfig.editorconfig
│ │ ├── backend.GlobalUsings.g.cs
│ │ ├── backend.assets.cache
│ │ └── backend.csproj.AssemblyReference.cache
│ ├── backend.csproj.nuget.dgspec.json
│ ├── backend.csproj.nuget.g.props
│ ├── backend.csproj.nuget.g.targets
│ ├── project.assets.json
│ └── project.nuget.cache
└── frontend
├── .gitignore
├── README.md
├── dist
├── bundle.js
└── node_modules_web-vitals_dist_web-vitals_js.bundle.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── assets
├── fonts
│ ├── Montserrat-Italic-VariableFont_wght.ttf
│ └── Montserrat-VariableFont_wght.ttf
├── icons
│ ├── facebook.svg
│ ├── hero.png
│ ├── hero1.png
│ ├── hero2.png
│ ├── icons.js
│ ├── instagram.svg
│ └── twitter.svg
└── img
│ └── completed.jpg
├── components
├── account
│ ├── EditProfile.js
│ ├── MyOrders.js
│ └── _account.scss
├── admin
│ ├── OrderItemsTable.js
│ ├── OrderTable.js
│ ├── OrdersTable.js
│ ├── ProductsTable.js
│ ├── SizesTable.js
│ ├── UsersTable.js
│ └── _admin.scss
├── auth
│ ├── LoginForm.js
│ ├── RegisterForm.js
│ └── _auth.scss
├── cart
│ ├── CartItem.js
│ └── _cart.scss
├── checkout
│ ├── Complete.js
│ ├── Confirmation.js
│ ├── LogIn.js
│ ├── OrderSummary.js
│ ├── Payment.js
│ ├── Shipping.js
│ ├── _checkout.scss
│ └── _confirm.scss
├── home
│ └── _home.scss
├── layout
│ ├── Footer.js
│ ├── Header.js
│ ├── _footer.scss
│ └── _header.scss
├── product
│ ├── ProductItem.js
│ ├── ProductList.js
│ ├── _filter.scss
│ └── _product.scss
└── wishlist
│ └── _wishlist.scss
├── index.js
├── pages
├── Account.js
├── AdminPanel.js
├── Authentication.js
├── Cart.js
├── Checkout.js
├── Home.js
├── ProductDetail.js
├── Shop.js
└── Wishlist.js
├── reportWebVitals.js
├── store
├── actions
│ ├── orderActions.js
│ ├── productActions.js
│ ├── sizeActions.js
│ └── userActions.js
├── reducers
│ ├── cartSlice.js
│ ├── orderSlice.js
│ ├── productSlice.js
│ ├── reducers.js
│ ├── sizeSlice.js
│ ├── userSlice.js
│ └── wishlistSlice.js
└── store.js
├── styles
├── _global.scss
├── _utilities.scss
└── main.scss
└── utils
├── api
├── orderApi.js
├── orderItemApi.js
├── productApi.js
├── sizeApi.js
├── userApi.js
└── variables.js
└── hooks
├── useCart.js
├── useProduct.js
├── useSize.js
├── useUser.js
├── useUtil.js
└── useWishlist.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sneaker Ecommerce
2 |
3 | > I've improved and updated the front-end code on the improve/legacy-code branch. If you're having issues starting the project with the backend, use this branch. I'll be improving the backend code and connecting it soon.
4 | >
5 | > See it live here: https://ecommerce-app-ms-sql-net-react.vercel.app/
6 |
7 |
8 |
9 |
10 | Table of Contents
11 |
12 |
13 | About The Project
14 |
17 |
18 |
19 | Getting Started
20 |
23 |
24 | Previews
25 | Roadmap
26 | License
27 | Contact
28 | Acknowledgments
29 |
30 |
33 | ## About The Project
34 |
35 | This project was an opportunity for me to utilize my self-taught front-end skills and existing back-end knowledge to build an ecommerce platform. However, my skills in both areas were not sufficient, and I lacked full-stack knowledge, including how to establish communication between the front end and back end. Despite these challenges, I took pride in this project because it motivated me to learn and explore various technologies, such as C# Web API, Redux, JWT tokens, and HTTP methods and API development in general.
36 |
37 | ### Built With
38 |
39 | * React.js
40 | * Microsoft SQL Server
41 | * .NET Web API
42 | * Redux
43 | * SCSS
44 |
45 |
46 | ## Getting Started
47 |
48 | To get a local copy up and running, follow these steps.
49 |
50 | ### Installation
51 |
52 | 1. Clone the repo
53 | ```sh
54 | git clone https://github.com/mariangle/ecommerce-app-ms-sql-net-react.git
55 | ```
56 |
57 | 2. Back end:
58 | * Configure your database connection details in an `app.config` file.
59 | * Build and run the solution file from the "Backend" folder in Visual Studio.
60 |
61 | > Note: This project is dated, and there are many aspects I would approach differently now. I am no longer actively developing it. However, if you intend to clone or fork it: The database was initially set up using SQL statements, which were not saved. Starting the project may be challenging due to this. I recommend using Entity Framework's update-database command to recreate the database schema based on the models.
62 |
63 |
64 | 3. Front end:
65 | * Navigate to the "Frontend" directory
66 | * Install required npm packages
67 |
68 | ```sh
69 | npm install
70 | ```
71 |
72 | * Start the front end development server
73 |
74 | ```sh
75 | npm start
76 | ```
77 |
78 |
79 | ## Previews
80 |
81 | **Browsing and Shopping**
82 |
83 | https://user-images.githubusercontent.com/124585244/232165426-5b6ef0fe-9d9c-44b1-a7a6-00236a7ac21e.mp4
84 |
85 |
86 | **Complete Checkout and User Profile**
87 |
88 | https://user-images.githubusercontent.com/124585244/232165800-c91f324c-d68a-4244-be8e-42a71947e062.mp4
89 |
90 |
91 |
92 | **Managing Products**
93 |
94 | https://user-images.githubusercontent.com/124585244/232165214-5f6338a3-c6ee-4018-98c7-ae8ef98efa30.mp4
95 |
96 |
97 | ## Roadmap
98 | - [x] Store cart and wishlist items in local storage
99 | - [x] Filtering and sorting options
100 | - [x] Implement promotional offers and free shipping for orders exceeding a specific amount.
101 | - [x] Integrate PayPal as a payment method
102 | - [ ] Improve the application's responsiveness for smaller screens
103 | - [ ] Implement encryption and decryption for user passwords
104 | - [ ] Hash passwords
105 | - [ ] Add guest checkout functionality
106 |
107 |
108 | ## License
109 |
110 |
111 | MIT License
112 |
113 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
114 |
115 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
116 |
117 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
118 |
119 |
120 |
121 |
122 | ## Contact
123 |
124 | [](https://www.linkedin.com/in/maria-nguyen-le/)
125 |
126 |
(back to top )
127 |
128 | ---
129 |
--------------------------------------------------------------------------------
/backend/.vs/ProjectEvaluation/backend.metadata.v5.2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/ProjectEvaluation/backend.metadata.v5.2
--------------------------------------------------------------------------------
/backend/.vs/ProjectEvaluation/backend.projects.v5.2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/ProjectEvaluation/backend.projects.v5.2
--------------------------------------------------------------------------------
/backend/.vs/VSWorkspaceState.json:
--------------------------------------------------------------------------------
1 | {
2 | "ExpandedNodes": [
3 | "",
4 | "\\Controllers",
5 | "\\Models",
6 | "\\Repositories"
7 | ],
8 | "SelectedNode": "\\JwtService.cs",
9 | "PreviewInSolutionExplorer": false
10 | }
--------------------------------------------------------------------------------
/backend/.vs/backend/DesignTimeBuild/.dtbcache.v2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/DesignTimeBuild/.dtbcache.v2
--------------------------------------------------------------------------------
/backend/.vs/backend/FileContentIndex/5abb3df0-7788-474f-b8d8-8294849ba414.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/FileContentIndex/5abb3df0-7788-474f-b8d8-8294849ba414.vsidx
--------------------------------------------------------------------------------
/backend/.vs/backend/FileContentIndex/709da6fa-206d-4d71-bc1d-7ee88ee45dcc.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/FileContentIndex/709da6fa-206d-4d71-bc1d-7ee88ee45dcc.vsidx
--------------------------------------------------------------------------------
/backend/.vs/backend/FileContentIndex/a88236e6-3776-449b-806f-6afe13117d63.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/FileContentIndex/a88236e6-3776-449b-806f-6afe13117d63.vsidx
--------------------------------------------------------------------------------
/backend/.vs/backend/FileContentIndex/b74b1431-114f-4bb5-97dc-505a4873c342.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/FileContentIndex/b74b1431-114f-4bb5-97dc-505a4873c342.vsidx
--------------------------------------------------------------------------------
/backend/.vs/backend/FileContentIndex/read.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/FileContentIndex/read.lock
--------------------------------------------------------------------------------
/backend/.vs/backend/v17/.futdcache.v2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/v17/.futdcache.v2
--------------------------------------------------------------------------------
/backend/.vs/backend/v17/.suo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/v17/.suo
--------------------------------------------------------------------------------
/backend/.vs/backend/v17/.wsuo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/backend/v17/.wsuo
--------------------------------------------------------------------------------
/backend/.vs/server/FileContentIndex/352ded9e-6022-48da-8df6-fdea0968bd98.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/server/FileContentIndex/352ded9e-6022-48da-8df6-fdea0968bd98.vsidx
--------------------------------------------------------------------------------
/backend/.vs/server/FileContentIndex/715e4efa-1d4a-4cfe-a07e-9c587a42fe43.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/server/FileContentIndex/715e4efa-1d4a-4cfe-a07e-9c587a42fe43.vsidx
--------------------------------------------------------------------------------
/backend/.vs/server/FileContentIndex/95302e9c-f47a-43be-9670-f389dc036be4.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/server/FileContentIndex/95302e9c-f47a-43be-9670-f389dc036be4.vsidx
--------------------------------------------------------------------------------
/backend/.vs/server/FileContentIndex/bd2029c9-8f84-4008-b573-efcc80815d56.vsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/server/FileContentIndex/bd2029c9-8f84-4008-b573-efcc80815d56.vsidx
--------------------------------------------------------------------------------
/backend/.vs/server/FileContentIndex/read.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/server/FileContentIndex/read.lock
--------------------------------------------------------------------------------
/backend/.vs/server/v17/.wsuo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/server/v17/.wsuo
--------------------------------------------------------------------------------
/backend/.vs/slnx.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/.vs/slnx.sqlite
--------------------------------------------------------------------------------
/backend/Controllers/OrderController.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using backend.Repositories;
3 | using Microsoft.AspNetCore.Cors;
4 | using Microsoft.AspNetCore.Mvc;
5 |
6 | namespace backend.Controllers
7 | {
8 | [Route("api/[controller]")]
9 | [ApiController]
10 | [EnableCors("_myAllowSpecificOrigins")]
11 | public class OrderController : ControllerBase
12 | {
13 | private readonly IListRepository _orderRepository;
14 |
15 | public OrderController(IListRepository orderRepo)
16 | {
17 | _orderRepository = orderRepo;
18 | }
19 |
20 | [HttpGet]
21 | public IActionResult Get()
22 | {
23 | IEnumerable orders = _orderRepository.GetAll();
24 | return Ok(orders);
25 | }
26 |
27 | [HttpGet("{userId}")]
28 | public IActionResult Get(int userId)
29 | {
30 | var orders = _orderRepository.GetById(userId);
31 | if (orders == null)
32 | {
33 | return NotFound();
34 | }
35 | return Ok(orders);
36 | }
37 |
38 | [HttpPost]
39 | public IActionResult Post(Order newOrder)
40 | {
41 | bool added = _orderRepository.Add(newOrder);
42 | if (!added)
43 | {
44 | return BadRequest("Failed to create Order");
45 | }
46 |
47 | return Ok();
48 | }
49 |
50 | [HttpPut("{orderId}")]
51 | public IActionResult Put(Order updatedOrder)
52 | {
53 | bool updated = _orderRepository.Update(updatedOrder);
54 | if (updated)
55 | {
56 | return Ok();
57 | }
58 | else
59 | {
60 | return NotFound();
61 | }
62 | }
63 |
64 | [HttpDelete("{orderId}")]
65 | public IActionResult Delete(int orderId)
66 | {
67 | bool deleted = _orderRepository.Delete(orderId);
68 | if (deleted)
69 | {
70 | return Ok();
71 | }
72 | else
73 | {
74 | return NotFound();
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/backend/Controllers/OrderItemController.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using backend.Repositories;
3 | using Microsoft.AspNetCore.Cors;
4 | using Microsoft.AspNetCore.Mvc;
5 |
6 | namespace backend.Controllers
7 | {
8 | [Route("api/[controller]")]
9 | [ApiController]
10 | [EnableCors("_myAllowSpecificOrigins")]
11 | public class OrderItemController : ControllerBase
12 | {
13 | private readonly IListRepository _orderItemRepo;
14 |
15 | public OrderItemController(IListRepository orderItemRepo)
16 | {
17 | _orderItemRepo = orderItemRepo;
18 | }
19 |
20 | [HttpGet]
21 | public IActionResult Get()
22 | {
23 | IEnumerable orderItems = _orderItemRepo.GetAll();
24 | return Ok(orderItems);
25 | }
26 |
27 | [HttpGet("{orderId}")]
28 | public IActionResult Get(int orderId)
29 | {
30 | var orderItems = _orderItemRepo.GetById(orderId);
31 | if (orderItems == null)
32 | {
33 | return NotFound();
34 | }
35 | return Ok(orderItems);
36 | }
37 |
38 | [HttpPost]
39 | public IActionResult Post(OrderItem orderItem)
40 | {
41 | bool added = _orderItemRepo.Add(orderItem);
42 | if (!added)
43 | {
44 | return BadRequest("Failed to add Order Item");
45 | }
46 |
47 | return Ok();
48 | }
49 |
50 | [HttpPut("{orderItemId}")]
51 | public IActionResult Put(OrderItem orderItem)
52 | {
53 | bool updated = _orderItemRepo.Update(orderItem);
54 | if (updated)
55 | {
56 | return Ok();
57 | }
58 | else
59 | {
60 | return NotFound();
61 | }
62 | }
63 |
64 | [HttpDelete("{orderItemId}")]
65 | public IActionResult Delete(int orderItemId)
66 | {
67 | bool deleted = _orderItemRepo.Delete(orderItemId);
68 | if (deleted)
69 | {
70 | return Ok();
71 | }
72 | else
73 | {
74 | return NotFound();
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/backend/Controllers/ProductController.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using backend.Repositories;
3 | using Microsoft.AspNetCore.Cors;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Extensions.Configuration;
6 | using Newtonsoft.Json;
7 | using System.Collections.Generic;
8 | using System.Data;
9 | using System.Data.SqlClient;
10 |
11 |
12 | namespace backend.Controllers
13 | {
14 | [Route("api/[controller]")]
15 | [ApiController]
16 | [EnableCors("_myAllowSpecificOrigins")]
17 |
18 | public class ProductController : ControllerBase
19 | {
20 | private readonly IRepository _productRepository;
21 |
22 | public ProductController(IRepository productRepository)
23 | {
24 | _productRepository = productRepository;
25 | }
26 |
27 | [HttpGet]
28 | public IActionResult Get()
29 | {
30 | IEnumerable products = _productRepository.GetAll();
31 | return Ok(products);
32 | }
33 |
34 | [HttpGet("{productId}")]
35 | public IActionResult Get(int productId)
36 | {
37 | var product = _productRepository.GetById(productId);
38 | if (product == null)
39 | {
40 | return NotFound();
41 | }
42 | return Ok(product);
43 | }
44 |
45 | [HttpPost]
46 | public IActionResult Post(Product product)
47 | {
48 | _productRepository.Add(product);
49 | return Ok();
50 | }
51 |
52 | [HttpPut("{productId}")]
53 | public IActionResult Put(Product updatedProduct)
54 | {
55 | bool updated = _productRepository.Update(updatedProduct);
56 | if (updated)
57 | {
58 | return Ok();
59 | }
60 | else
61 | {
62 | return NotFound();
63 | }
64 | }
65 |
66 | [HttpDelete("{productId}")]
67 | public IActionResult Delete(int productId)
68 | {
69 | bool deleted = _productRepository.Delete(productId);
70 | if (deleted)
71 | {
72 | return Ok();
73 | }
74 | else
75 | {
76 | return NotFound();
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/backend/Controllers/ProductSizeController.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using backend.Repositories;
3 | using Microsoft.AspNetCore.Cors;
4 | using Microsoft.AspNetCore.Mvc;
5 |
6 | namespace backend.Controllers
7 | {
8 | [Route("api/[controller]")]
9 | [ApiController]
10 | [EnableCors("_myAllowSpecificOrigins")]
11 | public class ProductSizeController : ControllerBase
12 | {
13 | private readonly IListRepository _psRepository;
14 |
15 | public ProductSizeController(IListRepository productSizeRepo)
16 | {
17 | _psRepository = productSizeRepo;
18 | }
19 |
20 | [HttpGet]
21 | public IActionResult Get()
22 | {
23 | IEnumerable productSizes = _psRepository.GetAll();
24 | return Ok(productSizes);
25 | }
26 |
27 | [HttpGet("{productId}")]
28 | public IActionResult Get(int productId)
29 | {
30 | var productSizes = _psRepository.GetById(productId);
31 | if (productSizes == null)
32 | {
33 | return NotFound();
34 | }
35 | return Ok(productSizes);
36 | }
37 |
38 | [HttpGet("{id}/size")]
39 | public IActionResult GetProductSizeById(int id)
40 | {
41 | var productSizes = _psRepository.GetObjById(id);
42 | if (productSizes == null)
43 | {
44 | return NotFound();
45 | }
46 | return Ok(productSizes);
47 | }
48 |
49 | [HttpPost]
50 | public IActionResult Post(ProductSize newProductSize)
51 | {
52 | bool added = _psRepository.Add(newProductSize);
53 | if (!added)
54 | {
55 | return BadRequest("Failed to add Product Size");
56 | }
57 |
58 | return Ok();
59 | }
60 |
61 | [HttpPut("{productSizeId}")]
62 | public IActionResult Put(ProductSize updatedProductSize)
63 | {
64 | bool updated = _psRepository.Update(updatedProductSize);
65 | if (updated)
66 | {
67 | return Ok();
68 | }
69 | else
70 | {
71 | return NotFound();
72 | }
73 | }
74 |
75 | [HttpDelete("{productSizeId}")]
76 | public IActionResult Delete(int productSizeId)
77 | {
78 | bool deleted = _psRepository.Delete(productSizeId);
79 | if (deleted)
80 | {
81 | return Ok();
82 | }
83 | else
84 | {
85 | return NotFound();
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/backend/Controllers/UserController.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using backend.Repositories;
3 | using Microsoft.AspNetCore.Cors;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.IdentityModel.Tokens;
7 | using System.IdentityModel.Tokens.Jwt;
8 | using System.Security.Claims;
9 | using System.Text;
10 | using backend;
11 |
12 | namespace backend.Controllers
13 | {
14 | [Route("api/[controller]")]
15 | [ApiController]
16 | [EnableCors("_myAllowSpecificOrigins")]
17 |
18 | public class UserController : ControllerBase
19 | {
20 | private readonly IConfiguration _configuration;
21 | private readonly IRepository _userRepository;
22 |
23 | public UserController(IConfiguration configuration, IRepository userRepository)
24 | {
25 | _configuration = configuration;
26 | _userRepository = userRepository;
27 | }
28 |
29 | [HttpGet]
30 | public IActionResult Get()
31 | {
32 | IEnumerable users = _userRepository.GetAll();
33 | return Ok(users);
34 | }
35 |
36 | [HttpGet("{userId}")]
37 | public IActionResult Get(int userId)
38 | {
39 | var user = _userRepository.GetById(userId);
40 | if (user == null)
41 | {
42 | return NotFound();
43 | }
44 | return Ok(user);
45 | }
46 |
47 | [HttpPost]
48 | public IActionResult Post(User newUser)
49 | {
50 | bool added = _userRepository.Add(newUser);
51 | if (!added)
52 | {
53 | return BadRequest("Failed to add user");
54 | }
55 |
56 | return Ok();
57 | }
58 |
59 | [HttpPut("{userId}")]
60 | public IActionResult Put(User updatedUser)
61 | {
62 | bool updated = _userRepository.Update(updatedUser);
63 | if (updated)
64 | {
65 | return Ok();
66 | }
67 | else
68 | {
69 | return NotFound();
70 | }
71 | }
72 |
73 | [HttpDelete("{userId}")]
74 | public IActionResult Delete(int userId)
75 | {
76 | bool deleted = _userRepository.Delete(userId);
77 | if (deleted)
78 | {
79 | return Ok();
80 | }
81 | else
82 | {
83 | return NotFound();
84 | }
85 | }
86 |
87 | [HttpPost("login")]
88 | public IActionResult Login(UserLoginRequest loginRequest)
89 | {
90 | var user = _userRepository.GetAll().FirstOrDefault(u => u.Email == loginRequest.Email);
91 |
92 | if (user == null || !user.CheckPassword(loginRequest.Password))
93 | {
94 | return Unauthorized("Invalid username or password");
95 | }
96 |
97 | var jwtService = new JwtService(_configuration);
98 | var token = jwtService.GenerateJwtToken(user);
99 |
100 | return Ok(new { Token = token });
101 | }
102 |
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/backend/JwtService.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using Microsoft.IdentityModel.Tokens;
3 | using System.IdentityModel.Tokens.Jwt;
4 | using System.Security.Claims;
5 | using System.Text;
6 |
7 | namespace backend
8 | {
9 | public interface IJwtService
10 | {
11 | string GenerateJwtToken(User user);
12 | }
13 |
14 | public class JwtService : IJwtService
15 | {
16 | private readonly IConfiguration _configuration;
17 |
18 | public JwtService(IConfiguration configuration)
19 | {
20 | _configuration = configuration;
21 | }
22 |
23 | public string GenerateJwtToken(User user)
24 | {
25 | var tokenHandler = new JwtSecurityTokenHandler();
26 | var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]);
27 | var tokenDescriptor = new SecurityTokenDescriptor
28 | {
29 | Subject = new ClaimsIdentity(new Claim[]
30 | {
31 | new Claim(ClaimTypes.NameIdentifier, user.UserID.ToString()),
32 | new Claim(ClaimTypes.Name, user.FirstName),
33 | new Claim(ClaimTypes.Surname, user.LastName),
34 | new Claim(ClaimTypes.Email, user.Email),
35 | new Claim(ClaimTypes.MobilePhone, user.Phone),
36 | new Claim(ClaimTypes.StreetAddress, user.Address ?? ""),
37 | new Claim(ClaimTypes.Locality, user.City ?? ""),
38 | new Claim(ClaimTypes.PostalCode, user.PostalCode ?? "")
39 | }),
40 | Expires = DateTime.UtcNow.AddHours(1),
41 | NotBefore = DateTime.UtcNow,
42 | SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
43 | };
44 | var token = tokenHandler.CreateToken(tokenDescriptor);
45 | return tokenHandler.WriteToken(token);
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/backend/Models/Order.cs:
--------------------------------------------------------------------------------
1 | namespace backend.Models
2 | {
3 | public enum OrderStatus
4 | {
5 | Pending,
6 | Processing,
7 | Shipped,
8 | Delivered,
9 | Cancelled,
10 | }
11 |
12 | public class Order
13 | {
14 | public int OrderID { get; set; }
15 | public DateTime DateTime { get; set; }
16 | public decimal TotalPrice { get; set; }
17 | public OrderStatus Status { get; set; }
18 | public int UserID { get; set; }
19 |
20 | public Order(int orderID, DateTime dateTime, decimal totalPrice, OrderStatus status, int userID)
21 | {
22 | OrderID = orderID;
23 | DateTime = dateTime;
24 | TotalPrice = totalPrice;
25 | Status = status;
26 | UserID = userID;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/backend/Models/OrderItem.cs:
--------------------------------------------------------------------------------
1 | namespace backend.Models
2 | {
3 | public class OrderItem
4 | {
5 | public int OrderItemID { get; set; }
6 | public int Quantity { get; set; }
7 | public int OrderID { get; set; }
8 | public int ProductSizeID { get; set; }
9 |
10 | public OrderItem(int orderItemID, int quantity, int orderID, int productSizeID)
11 | {
12 | OrderItemID = orderItemID;
13 | Quantity = quantity;
14 | OrderID = orderID;
15 | ProductSizeID = productSizeID;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/backend/Models/Product.cs:
--------------------------------------------------------------------------------
1 | namespace backend.Models
2 | {
3 | public class Product
4 | {
5 | public int ProductID { get; set; }
6 | public string Name { get; set; }
7 | public string Brand { get; set; }
8 | public string Description { get; set; }
9 | public string ImageURL { get; set; }
10 |
11 | public Product(int productID, string name, string brand, string description, string imageURL)
12 | {
13 | ProductID = productID;
14 | Name = name;
15 | Brand = brand;
16 | Description = description;
17 | ImageURL = imageURL;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/backend/Models/ProductSize.cs:
--------------------------------------------------------------------------------
1 | namespace backend.Models
2 | {
3 | public class ProductSize
4 | {
5 | public int ProductSizeID { get; set; }
6 | public int Size { get; set; }
7 | public decimal Price { get; set; }
8 | public int Quantity { get; set; }
9 | public int ProductID { get; set; }
10 |
11 | public ProductSize(int productSizeID, int size, decimal price, int quantity, int productID )
12 | {
13 | ProductSizeID = productSizeID;
14 | Size = size;
15 | Price = price;
16 | Quantity = quantity;
17 | ProductID = productID;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/backend/Models/User.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace backend.Models
4 | {
5 | public class User
6 | {
7 | public int UserID { get; }
8 | public string FirstName { get; set; }
9 | public string LastName { get; set; }
10 | public string Email { get; set; }
11 | public string Phone { get; set; }
12 | public string Password { get; set; }
13 |
14 | public string Address { get; set; }
15 | public string City { get; set; }
16 | public string PostalCode { get; set; }
17 |
18 | public User(int userId, string firstName, string lastName, string email, string phone, string password, string address = null, string city = null, string postalCode = null)
19 | {
20 | UserID = userId;
21 | FirstName = firstName;
22 | LastName = lastName;
23 | Email = email;
24 | Phone = phone;
25 | Password = password;
26 | Address = address;
27 | City = city;
28 | PostalCode = postalCode;
29 | }
30 | public bool CheckPassword(string password)
31 | {
32 | return Password == password; // temporary
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/backend/Models/UserLoginRequest.cs:
--------------------------------------------------------------------------------
1 | namespace backend.Models
2 | {
3 | public class UserLoginRequest
4 | {
5 | public string Email { get; set; }
6 | public string Password { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/backend/Program.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 | using backend.Repositories;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.Logging;
5 | using Serilog;
6 |
7 | namespace backend
8 | {
9 | public class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 |
14 | var builder = WebApplication.CreateBuilder(args);
15 |
16 | var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
17 |
18 |
19 | // Add services to the container.
20 | builder.Services.AddCors(options =>
21 | {
22 | options.AddPolicy(name: MyAllowSpecificOrigins,
23 | policy =>
24 | {
25 | policy.AllowAnyOrigin()
26 | .AllowAnyHeader()
27 | .AllowAnyMethod();
28 | });
29 | });
30 |
31 | builder.Services.AddScoped, UserRepository>();
32 | builder.Services.AddScoped, ProductRepository>();
33 |
34 | builder.Services.AddScoped, OrderRepository>();
35 | builder.Services.AddScoped, ProductSizeRepository>();
36 | builder.Services.AddScoped, OrderItemRepository>();
37 |
38 | builder.Services.AddControllers();
39 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
40 | builder.Services.AddEndpointsApiExplorer();
41 | builder.Services.AddSwaggerGen();
42 |
43 | var app = builder.Build();
44 |
45 | // Configure the HTTP request pipeline.
46 | if (app.Environment.IsDevelopment())
47 | {
48 | app.UseSwagger();
49 | app.UseSwaggerUI();
50 | }
51 |
52 | app.UseCors("_myAllowSpecificOrig ins");
53 |
54 | app.UseHttpsRedirection();
55 |
56 | app.UseAuthorization();
57 |
58 |
59 | app.MapControllers();
60 |
61 | app.Run();
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/backend/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:16804",
8 | "sslPort": 44394
9 | }
10 | },
11 | "profiles": {
12 | "backend": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": true,
16 | "launchUrl": "swagger",
17 | "applicationUrl": "https://localhost:7089;http://localhost:5214",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "IIS Express": {
23 | "commandName": "IISExpress",
24 | "launchBrowser": true,
25 | "launchUrl": "swagger",
26 | "environmentVariables": {
27 | "ASPNETCORE_ENVIRONMENT": "Development"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/backend/Repositories/IListRepository.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 |
3 | namespace backend.Repositories
4 | {
5 | public interface IListRepository
6 | {
7 | IEnumerable GetAll();
8 | public List GetById(int id);
9 | T GetObjById(int id);
10 | bool Add(T item);
11 | bool Update(T item);
12 | bool Delete(int id);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/backend/Repositories/IRepository.cs:
--------------------------------------------------------------------------------
1 | using backend.Models;
2 |
3 | namespace backend.Repositories
4 | {
5 | public interface IRepository
6 | {
7 | IEnumerable GetAll();
8 | T GetById(int id);
9 | bool Add(T item);
10 | bool Update(T item);
11 | bool Delete(int id);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/backend/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using backend;
5 | using Newtonsoft.Json.Serialization;
6 | using System.Text.Json.Serialization;
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.FileProviders;
9 | using backend.Repositories;
10 | using backend.Controllers;
11 | using Microsoft.AspNetCore.Authentication.JwtBearer;
12 | using Microsoft.IdentityModel.Tokens;
13 | using System.Text;
14 |
15 | public class Startup
16 | {
17 | public Startup(IConfiguration configuration)
18 | {
19 | Configuration = configuration;
20 | }
21 | public IConfiguration Configuration { get; }
22 |
23 | public void ConfigureServices(IServiceCollection services)
24 | {
25 | // Add configuration to access appsettings.json
26 | IConfiguration configuration = new ConfigurationBuilder()
27 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
28 | .Build();
29 |
30 | // Retrieve the secret key from the configuration
31 | var secretKey = configuration["Jwt:Secret"];
32 |
33 | // Add authentication services
34 | services.AddAuthentication(options =>
35 | {
36 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
37 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
38 | })
39 | .AddJwtBearer(options =>
40 | {
41 | options.RequireHttpsMetadata = false;
42 | options.SaveToken = true;
43 | options.TokenValidationParameters = new TokenValidationParameters
44 | {
45 | ValidateIssuerSigningKey = true,
46 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)),
47 | ValidateIssuer = false,
48 | ValidateAudience = false
49 | };
50 | });
51 |
52 | // Enable CORS
53 | services.AddCors(options =>
54 | {
55 | options.AddPolicy("AllowOrigin", builder =>
56 | {
57 | builder.AllowAnyOrigin()
58 | .AllowAnyMethod()
59 | .AllowAnyHeader();
60 | });
61 | });
62 |
63 | // Add MVC services and configure JSON serialization
64 | services.AddControllersWithViews()
65 | .AddNewtonsoftJson(options =>
66 | {
67 | options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
68 | options.SerializerSettings.ContractResolver = new DefaultContractResolver();
69 | });
70 |
71 | // Add JWT authentication services
72 | services.AddAuthentication(options =>
73 | {
74 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
75 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
76 | })
77 | .AddJwtBearer(options =>
78 | {
79 | options.TokenValidationParameters = new TokenValidationParameters
80 | {
81 | ValidateIssuer = true,
82 | ValidateAudience = true,
83 | ValidateLifetime = true,
84 | ValidateIssuerSigningKey = true,
85 | ValidIssuer = configuration["JwtIssuer"],
86 | ValidAudience = configuration["JwtIssuer"],
87 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtKey"]))
88 | };
89 | });
90 |
91 | // Add repositories
92 | services.AddScoped(typeof(IRepository<>), typeof(UserRepository));
93 | services.AddScoped(typeof(IRepository<>), typeof(ProductRepository));
94 |
95 | services.AddScoped(typeof(IListRepository<>), typeof(OrderRepository));
96 | services.AddScoped(typeof(IListRepository<>), typeof(ProductSizeRepository));
97 | services.AddScoped(typeof(IListRepository<>), typeof(OrderItemRepository));
98 |
99 | // Add controllers
100 | services.AddControllers();
101 | }
102 |
103 |
104 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
105 | {
106 | app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
107 |
108 | if (env.IsDevelopment())
109 | {
110 | app.UseDeveloperExceptionPage();
111 | }
112 |
113 | app.UseHttpsRedirection();
114 |
115 | app.UseRouting();
116 |
117 | app.UseAuthentication();
118 |
119 | app.UseAuthorization();
120 |
121 | app.UseEndpoints(endpoints =>
122 | {
123 | endpoints.MapControllers();
124 | });
125 | }
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/backend/TypeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace backend
5 | {
6 | public class TypeConverter : JsonConverter
7 | {
8 | public override Type Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
9 | {
10 | throw new NotImplementedException();
11 | }
12 |
13 | public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
14 | {
15 | writer.WriteStringValue(value.FullName);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/backend/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/backend/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "UserAppCon": "Server=10.56.8.36;Database=DB_2023_49;User Id=STUDENT_49;Password=OPENDB_49;TrustServerCertificate=true"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft.AspNetCore": "Warning"
9 | }
10 | },
11 | "Jwt": {
12 | "Secret": "your_secret_key_here"
13 | },
14 | "AllowedHosts": "*"
15 | }
16 |
--------------------------------------------------------------------------------
/backend/backend.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/backend/backend.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ApiControllerEmptyScaffolder
5 | root/Common/Api
6 | backend
7 |
8 |
9 | ProjectDebugger
10 |
11 |
--------------------------------------------------------------------------------
/backend/backend.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33110.190
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "backend", "backend.csproj", "{721300E2-7102-49C2-A30F-070B168BB6CB}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {721300E2-7102-49C2-A30F-070B168BB6CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {721300E2-7102-49C2-A30F-070B168BB6CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {721300E2-7102-49C2-A30F-070B168BB6CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {721300E2-7102-49C2-A30F-070B168BB6CB}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {8D18A7A7-DE14-496B-82FE-86B28E381894}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.AspNetCore.JsonPatch.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.AspNetCore.JsonPatch.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Abstractions.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Abstractions.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Logging.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Logging.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Protocols.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Protocols.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Tokens.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.IdentityModel.Tokens.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Microsoft.OpenApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Microsoft.OpenApi.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Newtonsoft.Json.Bson.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Newtonsoft.Json.Bson.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Serilog.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Serilog.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Swashbuckle.AspNetCore.Swagger.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Swashbuckle.AspNetCore.Swagger.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Swashbuckle.AspNetCore.SwaggerGen.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Swashbuckle.AspNetCore.SwaggerGen.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/Swashbuckle.AspNetCore.SwaggerUI.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/Swashbuckle.AspNetCore.SwaggerUI.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/System.Data.SqlClient.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/System.Data.SqlClient.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/System.IdentityModel.Tokens.Jwt.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/System.IdentityModel.Tokens.Jwt.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "UserAppCon": "Server=10.56.8.36;Database=DB_2023_49;User Id=STUDENT_49;Password=OPENDB_49;TrustServerCertificate=true"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft.AspNetCore": "Warning"
9 | }
10 | },
11 | "Jwt": {
12 | "Secret": "your_secret_key_here"
13 | },
14 | "AllowedHosts": "*"
15 | }
16 |
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/backend.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/backend.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/backend.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/backend.exe
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/backend.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/backend.pdb
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/backend.runtimeconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "runtimeOptions": {
3 | "tfm": "net6.0",
4 | "frameworks": [
5 | {
6 | "name": "Microsoft.NETCore.App",
7 | "version": "6.0.0"
8 | },
9 | {
10 | "name": "Microsoft.AspNetCore.App",
11 | "version": "6.0.0"
12 | }
13 | ],
14 | "configProperties": {
15 | "System.GC.Server": true,
16 | "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/runtimes/win-arm64/native/sni.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/runtimes/win-arm64/native/sni.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/runtimes/win-x64/native/sni.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/runtimes/win-x64/native/sni.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/runtimes/win-x86/native/sni.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/runtimes/win-x86/native/sni.dll
--------------------------------------------------------------------------------
/backend/bin/Debug/net6.0/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/bin/Debug/net6.0/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using System.Reflection;
4 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
5 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/apphost.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/apphost.exe
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | using System;
12 | using System.Reflection;
13 |
14 | [assembly: System.Reflection.AssemblyCompanyAttribute("backend")]
15 | [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
16 | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
17 | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
18 | [assembly: System.Reflection.AssemblyProductAttribute("backend")]
19 | [assembly: System.Reflection.AssemblyTitleAttribute("backend")]
20 | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
21 |
22 | // Generated by the MSBuild WriteCodeFragment class.
23 |
24 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.AssemblyInfoInputs.cache:
--------------------------------------------------------------------------------
1 | 7fb580ac5935226850acbb534b0b7a5d226f25ae
2 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.GeneratedMSBuildEditorConfig.editorconfig:
--------------------------------------------------------------------------------
1 | is_global = true
2 | build_property.TargetFramework = net6.0
3 | build_property.TargetPlatformMinVersion =
4 | build_property.UsingMicrosoftNETSdkWeb = true
5 | build_property.ProjectTypeGuids =
6 | build_property.InvariantGlobalization =
7 | build_property.PlatformNeutralAssembly =
8 | build_property.EnforceExtendedAnalyzerRules =
9 | build_property._SupportedPlatformList = Linux,macOS,Windows
10 | build_property.RootNamespace = backend
11 | build_property.RootNamespace = backend
12 | build_property.ProjectDir = C:\Users\45313\Documents\GitHub\Ecommerce\backend\
13 | build_property.RazorLangVersion = 6.0
14 | build_property.SupportLocalizedComponentNames =
15 | build_property.GenerateRazorMetadataSourceChecksumAttributes =
16 | build_property.MSBuildProjectDirectory = C:\Users\45313\Documents\GitHub\Ecommerce\backend
17 | build_property._RazorSourceGeneratorDebug =
18 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.GlobalUsings.g.cs:
--------------------------------------------------------------------------------
1 | //
2 | global using global::Microsoft.AspNetCore.Builder;
3 | global using global::Microsoft.AspNetCore.Hosting;
4 | global using global::Microsoft.AspNetCore.Http;
5 | global using global::Microsoft.AspNetCore.Routing;
6 | global using global::Microsoft.Extensions.Configuration;
7 | global using global::Microsoft.Extensions.DependencyInjection;
8 | global using global::Microsoft.Extensions.Hosting;
9 | global using global::Microsoft.Extensions.Logging;
10 | global using global::System;
11 | global using global::System.Collections.Generic;
12 | global using global::System.IO;
13 | global using global::System.Linq;
14 | global using global::System.Net.Http;
15 | global using global::System.Net.Http.Json;
16 | global using global::System.Threading;
17 | global using global::System.Threading.Tasks;
18 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.MvcApplicationPartsAssemblyInfo.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.MvcApplicationPartsAssemblyInfo.cache
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.MvcApplicationPartsAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | using System;
12 | using System.Reflection;
13 |
14 | [assembly: Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartAttribute("Swashbuckle.AspNetCore.SwaggerGen")]
15 |
16 | // Generated by the MSBuild WriteCodeFragment class.
17 |
18 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.assets.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.assets.cache
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.csproj.AssemblyReference.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.csproj.AssemblyReference.cache
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.csproj.BuildWithSkipAnalyzers:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.csproj.BuildWithSkipAnalyzers
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.csproj.CopyComplete:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.csproj.CopyComplete
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.csproj.CoreCompileInputs.cache:
--------------------------------------------------------------------------------
1 | 6dd538e82bb119b1909a916c9a6c61f0b3a0b124
2 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.dll
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.genruntimeconfig.cache:
--------------------------------------------------------------------------------
1 | c3ae1b7ac69b1ac82ffd1d172dea6abf638e1cec
2 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/backend.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/backend.pdb
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/ref/backend.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/ref/backend.dll
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/refint/backend.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Debug/net6.0/refint/backend.dll
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/staticwebassets.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 1,
3 | "Hash": "7/yvsnDRTixX0vVer35KSjS1HzfM4rGxA/gc1emwSdw=",
4 | "Source": "backend",
5 | "BasePath": "_content/backend",
6 | "Mode": "Default",
7 | "ManifestType": "Build",
8 | "ReferencedProjectsConfiguration": [],
9 | "DiscoveryPatterns": [],
10 | "Assets": []
11 | }
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/staticwebassets/msbuild.build.backend.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/staticwebassets/msbuild.buildMultiTargeting.backend.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/backend/obj/Debug/net6.0/staticwebassets/msbuild.buildTransitive.backend.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/.NETCoreApp,Version=v6.0.AssemblyAttributes.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using System.Reflection;
4 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
5 |
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/backend.AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | using System;
12 | using System.Reflection;
13 |
14 | [assembly: System.Reflection.AssemblyCompanyAttribute("backend")]
15 | [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
16 | [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
17 | [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
18 | [assembly: System.Reflection.AssemblyProductAttribute("backend")]
19 | [assembly: System.Reflection.AssemblyTitleAttribute("backend")]
20 | [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
21 |
22 | // Generated by the MSBuild WriteCodeFragment class.
23 |
24 |
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/backend.AssemblyInfoInputs.cache:
--------------------------------------------------------------------------------
1 | 0ec2ec676054acf07573ee4a5afb30cc45394a47
2 |
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/backend.GeneratedMSBuildEditorConfig.editorconfig:
--------------------------------------------------------------------------------
1 | is_global = true
2 | build_property.TargetFramework = net6.0
3 | build_property.TargetPlatformMinVersion =
4 | build_property.UsingMicrosoftNETSdkWeb = true
5 | build_property.ProjectTypeGuids =
6 | build_property.InvariantGlobalization =
7 | build_property.PlatformNeutralAssembly =
8 | build_property.EnforceExtendedAnalyzerRules =
9 | build_property._SupportedPlatformList = Linux,macOS,Windows
10 | build_property.RootNamespace = backend
11 | build_property.RootNamespace = backend
12 | build_property.ProjectDir = C:\Users\45313\Documents\GitHub\Ecommerce\backend\
13 | build_property.RazorLangVersion = 6.0
14 | build_property.SupportLocalizedComponentNames =
15 | build_property.GenerateRazorMetadataSourceChecksumAttributes =
16 | build_property.MSBuildProjectDirectory = C:\Users\45313\Documents\GitHub\Ecommerce\backend
17 | build_property._RazorSourceGeneratorDebug =
18 |
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/backend.GlobalUsings.g.cs:
--------------------------------------------------------------------------------
1 | //
2 | global using global::Microsoft.AspNetCore.Builder;
3 | global using global::Microsoft.AspNetCore.Hosting;
4 | global using global::Microsoft.AspNetCore.Http;
5 | global using global::Microsoft.AspNetCore.Routing;
6 | global using global::Microsoft.Extensions.Configuration;
7 | global using global::Microsoft.Extensions.DependencyInjection;
8 | global using global::Microsoft.Extensions.Hosting;
9 | global using global::Microsoft.Extensions.Logging;
10 | global using global::System;
11 | global using global::System.Collections.Generic;
12 | global using global::System.IO;
13 | global using global::System.Linq;
14 | global using global::System.Net.Http;
15 | global using global::System.Net.Http.Json;
16 | global using global::System.Threading;
17 | global using global::System.Threading.Tasks;
18 |
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/backend.assets.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Release/net6.0/backend.assets.cache
--------------------------------------------------------------------------------
/backend/obj/Release/net6.0/backend.csproj.AssemblyReference.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/backend/obj/Release/net6.0/backend.csproj.AssemblyReference.cache
--------------------------------------------------------------------------------
/backend/obj/backend.csproj.nuget.dgspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "format": 1,
3 | "restore": {
4 | "C:\\Users\\45313\\Documents\\GitHub\\Ecommerce\\backend\\backend.csproj": {}
5 | },
6 | "projects": {
7 | "C:\\Users\\45313\\Documents\\GitHub\\Ecommerce\\backend\\backend.csproj": {
8 | "version": "1.0.0",
9 | "restore": {
10 | "projectUniqueName": "C:\\Users\\45313\\Documents\\GitHub\\Ecommerce\\backend\\backend.csproj",
11 | "projectName": "backend",
12 | "projectPath": "C:\\Users\\45313\\Documents\\GitHub\\Ecommerce\\backend\\backend.csproj",
13 | "packagesPath": "C:\\Users\\45313\\.nuget\\packages\\",
14 | "outputPath": "C:\\Users\\45313\\Documents\\GitHub\\Ecommerce\\backend\\obj\\",
15 | "projectStyle": "PackageReference",
16 | "configFilePaths": [
17 | "C:\\Users\\45313\\AppData\\Roaming\\NuGet\\NuGet.Config",
18 | "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
19 | ],
20 | "originalTargetFrameworks": [
21 | "net6.0"
22 | ],
23 | "sources": {
24 | "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
25 | "https://api.nuget.org/v3/index.json": {}
26 | },
27 | "frameworks": {
28 | "net6.0": {
29 | "targetAlias": "net6.0",
30 | "projectReferences": {}
31 | }
32 | },
33 | "warningProperties": {
34 | "warnAsError": [
35 | "NU1605"
36 | ]
37 | }
38 | },
39 | "frameworks": {
40 | "net6.0": {
41 | "targetAlias": "net6.0",
42 | "dependencies": {
43 | "Microsoft.AspNetCore.Authentication.JwtBearer": {
44 | "target": "Package",
45 | "version": "[6.0.15, )"
46 | },
47 | "Microsoft.AspNetCore.Mvc.NewtonsoftJson": {
48 | "target": "Package",
49 | "version": "[6.0.15, )"
50 | },
51 | "Serilog": {
52 | "target": "Package",
53 | "version": "[2.12.0, )"
54 | },
55 | "Swashbuckle.AspNetCore": {
56 | "target": "Package",
57 | "version": "[6.2.3, )"
58 | },
59 | "System.Data.SqlClient": {
60 | "target": "Package",
61 | "version": "[4.8.5, )"
62 | },
63 | "System.IdentityModel.Tokens.Jwt": {
64 | "target": "Package",
65 | "version": "[6.28.1, )"
66 | }
67 | },
68 | "imports": [
69 | "net461",
70 | "net462",
71 | "net47",
72 | "net471",
73 | "net472",
74 | "net48",
75 | "net481"
76 | ],
77 | "assetTargetFallback": true,
78 | "warn": true,
79 | "frameworkReferences": {
80 | "Microsoft.AspNetCore.App": {
81 | "privateAssets": "none"
82 | },
83 | "Microsoft.NETCore.App": {
84 | "privateAssets": "all"
85 | }
86 | },
87 | "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\7.0.100\\RuntimeIdentifierGraph.json"
88 | }
89 | }
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/backend/obj/backend.csproj.nuget.g.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | True
5 | NuGet
6 | $(MSBuildThisFileDirectory)project.assets.json
7 | $(UserProfile)\.nuget\packages\
8 | C:\Users\45313\.nuget\packages\
9 | PackageReference
10 | 6.4.0
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | C:\Users\45313\.nuget\packages\microsoft.extensions.apidescription.server\3.0.0
21 |
22 |
--------------------------------------------------------------------------------
/backend/obj/backend.csproj.nuget.g.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/backend/obj/project.nuget.cache:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "dgSpecHash": "dJZAsVTYsg/dgMknTi6VXlAL5tNMgHtjyaqdbUHrV8uWYYUbGpwSQwNIIW6jaSxz+P7S2T53GZr0OtJqzmJcxA==",
4 | "success": true,
5 | "projectFilePath": "C:\\Users\\45313\\Documents\\GitHub\\Ecommerce\\backend\\backend.csproj",
6 | "expectedPackageFiles": [
7 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.aspnetcore.authentication.jwtbearer\\6.0.15\\microsoft.aspnetcore.authentication.jwtbearer.6.0.15.nupkg.sha512",
8 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.aspnetcore.jsonpatch\\6.0.15\\microsoft.aspnetcore.jsonpatch.6.0.15.nupkg.sha512",
9 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.aspnetcore.mvc.newtonsoftjson\\6.0.15\\microsoft.aspnetcore.mvc.newtonsoftjson.6.0.15.nupkg.sha512",
10 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.csharp\\4.7.0\\microsoft.csharp.4.7.0.nupkg.sha512",
11 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.extensions.apidescription.server\\3.0.0\\microsoft.extensions.apidescription.server.3.0.0.nupkg.sha512",
12 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.identitymodel.abstractions\\6.28.1\\microsoft.identitymodel.abstractions.6.28.1.nupkg.sha512",
13 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\6.28.1\\microsoft.identitymodel.jsonwebtokens.6.28.1.nupkg.sha512",
14 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.identitymodel.logging\\6.28.1\\microsoft.identitymodel.logging.6.28.1.nupkg.sha512",
15 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.identitymodel.protocols\\6.10.0\\microsoft.identitymodel.protocols.6.10.0.nupkg.sha512",
16 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.identitymodel.protocols.openidconnect\\6.10.0\\microsoft.identitymodel.protocols.openidconnect.6.10.0.nupkg.sha512",
17 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.identitymodel.tokens\\6.28.1\\microsoft.identitymodel.tokens.6.28.1.nupkg.sha512",
18 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.netcore.platforms\\3.1.0\\microsoft.netcore.platforms.3.1.0.nupkg.sha512",
19 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.netcore.targets\\1.1.0\\microsoft.netcore.targets.1.1.0.nupkg.sha512",
20 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.openapi\\1.2.3\\microsoft.openapi.1.2.3.nupkg.sha512",
21 | "C:\\Users\\45313\\.nuget\\packages\\microsoft.win32.registry\\4.7.0\\microsoft.win32.registry.4.7.0.nupkg.sha512",
22 | "C:\\Users\\45313\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512",
23 | "C:\\Users\\45313\\.nuget\\packages\\newtonsoft.json.bson\\1.0.2\\newtonsoft.json.bson.1.0.2.nupkg.sha512",
24 | "C:\\Users\\45313\\.nuget\\packages\\runtime.native.system.data.sqlclient.sni\\4.7.0\\runtime.native.system.data.sqlclient.sni.4.7.0.nupkg.sha512",
25 | "C:\\Users\\45313\\.nuget\\packages\\runtime.win-arm64.runtime.native.system.data.sqlclient.sni\\4.4.0\\runtime.win-arm64.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512",
26 | "C:\\Users\\45313\\.nuget\\packages\\runtime.win-x64.runtime.native.system.data.sqlclient.sni\\4.4.0\\runtime.win-x64.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512",
27 | "C:\\Users\\45313\\.nuget\\packages\\runtime.win-x86.runtime.native.system.data.sqlclient.sni\\4.4.0\\runtime.win-x86.runtime.native.system.data.sqlclient.sni.4.4.0.nupkg.sha512",
28 | "C:\\Users\\45313\\.nuget\\packages\\serilog\\2.12.0\\serilog.2.12.0.nupkg.sha512",
29 | "C:\\Users\\45313\\.nuget\\packages\\swashbuckle.aspnetcore\\6.2.3\\swashbuckle.aspnetcore.6.2.3.nupkg.sha512",
30 | "C:\\Users\\45313\\.nuget\\packages\\swashbuckle.aspnetcore.swagger\\6.2.3\\swashbuckle.aspnetcore.swagger.6.2.3.nupkg.sha512",
31 | "C:\\Users\\45313\\.nuget\\packages\\swashbuckle.aspnetcore.swaggergen\\6.2.3\\swashbuckle.aspnetcore.swaggergen.6.2.3.nupkg.sha512",
32 | "C:\\Users\\45313\\.nuget\\packages\\swashbuckle.aspnetcore.swaggerui\\6.2.3\\swashbuckle.aspnetcore.swaggerui.6.2.3.nupkg.sha512",
33 | "C:\\Users\\45313\\.nuget\\packages\\system.data.sqlclient\\4.8.5\\system.data.sqlclient.4.8.5.nupkg.sha512",
34 | "C:\\Users\\45313\\.nuget\\packages\\system.identitymodel.tokens.jwt\\6.28.1\\system.identitymodel.tokens.jwt.6.28.1.nupkg.sha512",
35 | "C:\\Users\\45313\\.nuget\\packages\\system.runtime\\4.3.0\\system.runtime.4.3.0.nupkg.sha512",
36 | "C:\\Users\\45313\\.nuget\\packages\\system.security.accesscontrol\\4.7.0\\system.security.accesscontrol.4.7.0.nupkg.sha512",
37 | "C:\\Users\\45313\\.nuget\\packages\\system.security.cryptography.cng\\4.5.0\\system.security.cryptography.cng.4.5.0.nupkg.sha512",
38 | "C:\\Users\\45313\\.nuget\\packages\\system.security.principal.windows\\4.7.0\\system.security.principal.windows.4.7.0.nupkg.sha512",
39 | "C:\\Users\\45313\\.nuget\\packages\\system.text.encoding\\4.3.0\\system.text.encoding.4.3.0.nupkg.sha512",
40 | "C:\\Users\\45313\\.nuget\\packages\\system.text.encodings.web\\4.7.2\\system.text.encodings.web.4.7.2.nupkg.sha512",
41 | "C:\\Users\\45313\\.nuget\\packages\\system.text.json\\4.7.2\\system.text.json.4.7.2.nupkg.sha512"
42 | ],
43 | "logs": []
44 | }
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-free": "^6.4.0",
7 | "@fortawesome/fontawesome-svg-core": "^6.4.0",
8 | "@fortawesome/free-regular-svg-icons": "^6.4.0",
9 | "@fortawesome/free-solid-svg-icons": "^6.4.0",
10 | "@fortawesome/react-fontawesome": "^0.2.0",
11 | "@paypal/react-paypal-js": "^7.8.3",
12 | "@reduxjs/toolkit": "^1.9.3",
13 | "@testing-library/jest-dom": "^5.16.5",
14 | "@testing-library/react": "^13.4.0",
15 | "@testing-library/user-event": "^13.5.0",
16 | "axios": "^1.3.4",
17 | "buffer": "^6.0.3",
18 | "jsonwebtoken": "^8.5.1",
19 | "jwt-decode": "^3.1.2",
20 | "react": "^18.2.0",
21 | "react-dom": "^18.2.0",
22 | "react-hook-form": "^7.43.8",
23 | "react-redux": "^8.0.5",
24 | "react-router-dom": "^6.9.0",
25 | "react-scripts": "5.0.1",
26 | "redux": "^4.2.1",
27 | "redux-devtools-extension": "^2.13.9",
28 | "sass": "^1.60.0",
29 | "styled-components": "^5.3.9",
30 | "util": "^0.12.5",
31 | "web-vitals": "^2.1.4"
32 | },
33 | "scripts": {
34 | "start": "react-scripts start",
35 | "build": "react-scripts build",
36 | "test": "react-scripts test",
37 | "eject": "react-scripts eject"
38 | },
39 | "eslintConfig": {
40 | "extends": [
41 | "react-app",
42 | "react-app/jest"
43 | ]
44 | },
45 | "browserslist": {
46 | "production": [
47 | ">0.2%",
48 | "not dead",
49 | "not op_mini all"
50 | ],
51 | "development": [
52 | "last 1 chrome version",
53 | "last 1 firefox version",
54 | "last 1 safari version"
55 | ]
56 | },
57 | "devDependencies": {
58 | "@babel/core": "^7.21.4",
59 | "@babel/preset-env": "^7.21.4",
60 | "@babel/preset-react": "^7.18.6",
61 | "babel-loader": "^9.1.2",
62 | "crypto-browserify": "^3.12.0",
63 | "resolve-url-loader": "^5.0.0",
64 | "stream-browserify": "^3.0.0",
65 | "svg-inline-loader": "^0.8.2",
66 | "webpack-cli": "^5.0.1"
67 | },
68 | "browser": {
69 | "crypto": false,
70 | "stream": false
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
28 | React App
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import './styles/main.scss';
2 | // import components
3 | import Header from './components/layout/Header';
4 | import Footer from './components/layout/Footer';
5 | // import pages
6 | import Home from "./pages/Home";
7 | import Shop from './pages/Shop';
8 | import ProductPage from './pages/ProductDetail';
9 | import CartPage from "./pages/Cart"
10 | import CheckoutPage from './pages/Checkout';
11 | import Admin from "./pages/AdminPanel"
12 | import Account from './pages/Account';
13 | import Authentication from './pages/Authentication';
14 | import Wishlist from './pages/Wishlist';
15 |
16 | import { Routes, Route, useLocation } from 'react-router-dom';
17 |
18 |
19 | function App() {
20 | const location = useLocation();
21 | const showHeaderFooter = location.pathname !== '/checkout';
22 |
23 | return (
24 | <>
25 | {showHeaderFooter && }
26 |
27 | } />
28 | } />
29 | } />
30 | } />
31 | } />
32 | } />
33 | } />
34 | } />
35 | } />
36 |
37 | {showHeaderFooter && }
38 | >
39 | );
40 | }
41 |
42 | export default App;
--------------------------------------------------------------------------------
/frontend/src/assets/fonts/Montserrat-Italic-VariableFont_wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/src/assets/fonts/Montserrat-Italic-VariableFont_wght.ttf
--------------------------------------------------------------------------------
/frontend/src/assets/fonts/Montserrat-VariableFont_wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/src/assets/fonts/Montserrat-VariableFont_wght.ttf
--------------------------------------------------------------------------------
/frontend/src/assets/icons/facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/assets/icons/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/src/assets/icons/hero.png
--------------------------------------------------------------------------------
/frontend/src/assets/icons/hero1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/src/assets/icons/hero1.png
--------------------------------------------------------------------------------
/frontend/src/assets/icons/hero2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/src/assets/icons/hero2.png
--------------------------------------------------------------------------------
/frontend/src/assets/icons/icons.js:
--------------------------------------------------------------------------------
1 | import { faUser, faCircleCheck, faHeart as faHeartRegular, faSave, faCreditCard, faAddressCard, faEdit } from '@fortawesome/free-regular-svg-icons';
2 | import { faBasketShopping, faSearch, faBars, faEnvelope, faLock, faTimes, faHeart as faHeartSolid, faSlidersH } from '@fortawesome/free-solid-svg-icons';
3 |
4 | export const icons = {
5 | user: faUser,
6 | heart: faHeartRegular,
7 | heartFull: faHeartSolid,
8 | cart: faBasketShopping,
9 | search: faSearch,
10 | edit: faEdit,
11 | hamburger: faBars,
12 | email: faEnvelope,
13 | lock: faLock,
14 | trash: faTimes,
15 | save: faSave,
16 | creditCard: faCreditCard,
17 | idCard: faAddressCard,
18 | check: faCircleCheck,
19 | filter: faSlidersH
20 | };
--------------------------------------------------------------------------------
/frontend/src/assets/icons/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/assets/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/assets/img/completed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariangle/ecommerce-app-ms-sql-net-react/26bb1a033284f8baa2df447f29ecb0e26b851262/frontend/src/assets/img/completed.jpg
--------------------------------------------------------------------------------
/frontend/src/components/account/EditProfile.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function Profile({currentUser}) {
4 |
5 | return (
6 | <>
7 | Edit Profile
8 |
28 | Shipping Details
29 |
30 | Address
31 |
32 |
33 |
34 |
35 | Postal Code
36 |
37 |
38 |
39 | City
40 |
41 |
42 |
43 |
44 | CANCEL
45 | SAVE CHANGES
46 |
47 | >
48 | );
49 | }
50 |
51 | export default Profile;
52 |
--------------------------------------------------------------------------------
/frontend/src/components/account/MyOrders.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { fetchOrdersByUserId } from '../../store/actions/orderActions';
4 | import orderItemApi from '../../utils/api/orderItemApi';
5 | import sizeApi from '../../utils/api/sizeApi';
6 | import productApi from '../../utils/api/productApi';
7 | import { useStatusString, formatPrice, formatDateTime } from '../../utils/hooks/useUtil';
8 |
9 | function MyOrders({ currentUser }) {
10 | const [orders, setOrders] = useState([]);
11 | const getStatusString = useStatusString();
12 | const dispatch = useDispatch();
13 |
14 |
15 | useEffect(() => {
16 | dispatch(fetchOrdersByUserId(currentUser.userID)).then(async (response) => {
17 | const ordersWithItems = await Promise.all(response.payload.map(async (order, index) => {
18 | const orderItems = await orderItemApi.getOrderItemsByOrderId(order.orderID);
19 |
20 | const productSizePromises = orderItems.map((orderItem) =>
21 | sizeApi.getProductSize(orderItem.productSizeID)
22 | );
23 | const productSizes = await Promise.all(productSizePromises);
24 |
25 | const productPromises = productSizes.map((productSize) =>
26 | productApi.getProduct(productSize.productID)
27 | );
28 | const products = await Promise.all(productPromises);
29 |
30 | return {
31 | ...order,
32 | orderItems: orderItems.map((orderItem, index) => ({
33 | ...orderItem,
34 | productSize: productSizes[index],
35 | product: products[index]
36 | })),
37 | index
38 | };
39 | }));
40 | setOrders(ordersWithItems);
41 | });
42 | }, [dispatch, currentUser.userID]);
43 |
44 | return (
45 | <>
46 | My Orders
47 | {orders.map((order) => (
48 |
49 |
50 |
51 |
55 |
56 | {order.status === 0 && Cancel }
57 |
58 | {getStatusString(order.status)}
59 |
60 |
61 |
62 |
63 | {order.orderItems.map((orderItem) => (
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
{orderItem.product.brand} {orderItem.product.name}
72 |
Size: {orderItem.productSize.size}
73 |
Quantity: {orderItem.quantity}
74 |
Price: {formatPrice(orderItem.productSize.price)}
75 |
76 |
77 |
78 |
79 | ))}
80 |
81 |
82 | ))}
83 | >
84 | );
85 | }
86 |
87 | export default MyOrders;
88 |
--------------------------------------------------------------------------------
/frontend/src/components/account/_account.scss:
--------------------------------------------------------------------------------
1 | .account{
2 | padding: 5rem 0;
3 | gap: 5rem;
4 | @include flex(center, flex-start, row);
5 | @media (max-width: 850px) {
6 | padding: 5rem 1rem;
7 | gap: 2rem;
8 | @include flex(flex-start, center, column);
9 | ul{
10 | @include flex(center, center, row);
11 | li{
12 | padding: 1rem 1rem 1rem 0rem;
13 | }
14 | }
15 | }
16 | }
17 |
18 | .account-menu{
19 | li{
20 | border-bottom: none;
21 | cursor: pointer;
22 | }
23 | li.active {
24 | font-weight: bold;
25 | }
26 | }
27 |
28 | .profile-info, .profile-container{
29 | width: 100%;
30 | max-width: 500px;
31 | h2, button{
32 | margin-top: 1rem;
33 | }
34 | input, textarea, select{
35 | width: 100%;
36 | }
37 | }
38 |
39 | .my-orders-div{
40 | margin-bottom: 2rem;
41 | }
42 |
43 | .my-orders-about{
44 | ul{
45 | gap: 1rem;
46 | display: flex;
47 | margin-bottom: 0.5rem;
48 | li{
49 | padding: 1rem 00;
50 | @include flex(center, center, row)
51 | }
52 | }
53 | }
54 |
55 | td p {
56 | display: inline;
57 | }
--------------------------------------------------------------------------------
/frontend/src/components/admin/OrderItemsTable.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import orderItemApi from '../../utils/api/orderItemApi';
3 | import sizeApi from '../../utils/api/sizeApi';
4 | import { Link } from 'react-router-dom';
5 | import { useProduct } from "../../utils/hooks/useProduct"
6 | import { formatPrice } from '../../utils/hooks/useUtil';
7 |
8 |
9 | function OrderItemsTable({ selectedOrder }) {
10 | const [orderItems, setOrderItems] = useState([]);
11 | const [productSizes, setProductSizes] = useState([]);
12 | const { products }= useProduct();
13 |
14 | useEffect(() => {
15 | if (selectedOrder) {
16 | orderItemApi.getOrderItemsByOrderId(selectedOrder.orderID).then((orderItems) => {
17 | setOrderItems(orderItems);
18 |
19 | const productSizePromises = orderItems.map((orderItem) =>
20 | sizeApi.getProductSize(orderItem.productSizeID)
21 | );
22 | Promise.all(productSizePromises).then((productSizes) => {
23 | setProductSizes(productSizes);
24 | });
25 |
26 | });
27 | }
28 | }, [selectedOrder]);
29 |
30 | return (
31 |
32 | {orderItems.map((orderItem, index) => (
33 |
34 | {products[index] && (
35 |
36 |
37 |
38 | )}
39 |
40 |
41 | {products[index] && (
42 |
{products[index].brand} {products[index].name}
43 | )}
44 | {productSizes[index] && (
45 |
Size: {productSizes[index].size}
46 | )}
47 |
Quantity: {orderItem.quantity}
48 |
49 | {productSizes[index] && (
50 |
51 |
{formatPrice(productSizes[index].price)}
52 |
53 | )}
54 |
55 |
56 | ))}
57 |
58 | );
59 | }
60 |
61 |
62 | export default OrderItemsTable;
63 |
--------------------------------------------------------------------------------
/frontend/src/components/admin/OrderTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function OrderTable({user, selectedOrder}) {
4 |
5 | const handleStatusChange = (event) => {
6 | const newStatus = parseInt(event.target.value);
7 | };
8 |
9 | return (
10 | <>
11 |
12 |
13 | First Name
14 |
15 |
16 |
17 | Last Name
18 |
19 |
20 |
21 |
22 | Email
23 |
24 |
25 |
26 | Phone
27 |
28 |
29 |
30 |
31 | Postal Code
32 |
33 |
34 |
35 | City
36 |
37 |
38 |
39 |
40 | Addresss
41 |
42 |
43 |
44 |
45 | Update Status
46 | handleStatusChange(e)}>
47 | Pending
48 | Processing
49 | Shipped
50 | Delivered
51 | Cancelled
52 |
53 |
54 |
55 |
56 | Total Price
57 |
58 |
59 |
60 | >
61 | )
62 | }
63 |
64 | export default OrderTable
--------------------------------------------------------------------------------
/frontend/src/components/admin/OrdersTable.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { fetchOrders } from "../../store/actions/orderActions"
4 | import { useStatusString, formatDate } from '../../utils/hooks/useUtil';
5 |
6 | import OrderItemsTable from './OrderItemsTable';
7 | import OrderTable from './OrderTable';
8 |
9 | import userApi from '../../utils/api/userApi';
10 |
11 | function Orders() {
12 | const dispatch = useDispatch();
13 | const [orders, setOrders] = useState([])
14 | const [selectedOrder, setSelectedOrder] = useState(null)
15 | const [user, setUser] = useState([]);
16 | const getStatusString = useStatusString();
17 |
18 | useEffect(() => {
19 | dispatch(fetchOrders()).then((response) => setOrders(response.payload));
20 | }, [dispatch]);
21 |
22 | useEffect(() => {
23 | if (selectedOrder) {
24 | userApi.getUser(selectedOrder.userID).then(setUser);
25 | }
26 | }, [selectedOrder]);
27 |
28 | return (
29 |
30 |
31 |
32 |
33 | ID
34 | Order Date
35 | Status
36 | UserID
37 |
38 |
39 |
40 | {orders.map((order, index) => (
41 | setSelectedOrder(order)}>
42 | {order.orderID}
43 | {formatDate(order.dateTime)}
44 | {getStatusString(order.status)}
45 | {order.userID}
46 |
47 | ))}
48 |
49 |
50 | {selectedOrder &&
51 |
52 |
53 |
54 |
55 | }
56 |
57 | );
58 | }
59 |
60 | export default Orders;
61 |
--------------------------------------------------------------------------------
/frontend/src/components/admin/ProductsTable.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import ProductSizes from './SizesTable';
3 | import { useProduct } from '../../utils/hooks/useProduct';
4 | import { useStock } from '../../utils/hooks/useUtil';
5 |
6 |
7 | function Products() {
8 | const { products, fetchProducts, createProduct, updateExistingProduct, removeProduct } = useProduct();
9 | const [localProduct, setLocalProduct] = useState({});
10 | const getStock = useStock();
11 | const product = products.find((product) => product?.productID === localProduct?.productID);
12 |
13 | useEffect(() => {
14 | fetchProducts();
15 | }, [products])
16 |
17 | const handleInputChange = (e) => {
18 | const { name, value } = e.target;
19 | setLocalProduct(prevState => ({ ...prevState, [name]: value }));
20 | };
21 |
22 | return (
23 |
24 |
25 |
26 |
27 | ID
28 | Model
29 | Stock
30 |
31 |
32 |
33 | {products.map((product, index) => (
34 | setLocalProduct(product)}>
35 | {product.productID}
36 | {product.brand} {product.name}
37 | {getStock(product.inStock)}
38 |
39 | ))}
40 |
41 |
42 |
43 |
{localProduct?.brand} {localProduct?.name}
44 | {product && (
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | { product && (
52 | ID
53 |
54 | )}
55 |
56 | Brand
57 |
58 |
59 |
60 | Model
61 |
62 |
63 |
64 |
65 | Description
66 |
67 |
68 |
69 | Image URL
70 |
71 |
72 |
73 | setLocalProduct(null) }>CLEAR
74 | { product && ( removeProduct(product?.productID)}>DELETE )}
75 | { product?.productID && ( updateExistingProduct({ productId: product?.productID, product: localProduct })}>SAVE CHANGES )}
76 | { !product?.productID && ( createProduct(localProduct)}>Add Product )}
77 |
78 | {product && (
)}
79 |
80 |
81 | );
82 | }
83 |
84 | export default Products;
85 |
--------------------------------------------------------------------------------
/frontend/src/components/admin/SizesTable.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { icons } from '../../assets/icons/icons';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { useSize } from '../../utils/hooks/useSize';
5 |
6 | function ProductSizes({ product }) {
7 | const { addSize, updateSize, deleteSize } = useSize();
8 | const [newSize, setNewSize] = useState({ size: '', price: '', quantity: '' });
9 | const [editedSize, setEditedSize] = useState({});
10 |
11 | const handleInputChange = (e, index) => {
12 | const { name, value } = e.target;
13 | const updatedSize = { ...product.sizes[index], [name]: value };
14 | setEditedSize(updatedSize);
15 | };
16 |
17 | return (
18 | <>
19 |
20 |
21 |
22 | Size
23 | Price
24 | Quantity
25 | Edit
26 |
27 |
28 |
29 |
30 |
31 | setNewSize({ ...newSize, size: e.target.value })}>
32 | Add size
33 | {Array.from({ length: 16 }, (_, i) => i + 35)
34 | .filter((size) => !product?.sizes.find((ps) => ps.size === size))
35 | .map((size, index) => (
36 |
37 | {size}
38 |
39 | ))}
40 |
41 |
42 |
43 |
44 | setNewSize((prevSize) => ({
45 | ...prevSize,
46 | price: e.target.value,
47 | }))
48 | }/>
49 |
50 |
51 |
52 | setNewSize((prevSize) => ({
53 | ...prevSize,
54 | quantity: e.target.value,
55 | }))
56 | }/>
57 |
58 |
59 | addSize({ ...newSize, productId: product.productID })}>ADD
60 |
61 |
62 | {product?.sizes.map((size, index) => (
63 |
64 | {size.size}
65 |
66 | handleInputChange(e, index)}
71 | />
72 |
73 |
74 | handleInputChange(e, index)}
79 | />
80 |
81 |
82 | deleteSize(size.productSizeID, e)}>
83 |
84 |
85 | updateSize({ sizeId: size.productSizeID, size: editedSize})}>
86 |
87 |
88 |
89 |
90 | ))}
91 |
92 |
93 | >
94 | );
95 | }
96 |
97 | export default ProductSizes;
98 |
--------------------------------------------------------------------------------
/frontend/src/components/admin/UsersTable.js:
--------------------------------------------------------------------------------
1 | import userApi from '../../utils/api/userApi';
2 | import React, { useEffect, useState } from 'react';
3 |
4 | function Users() {
5 | const [data, setData] = useState([]);
6 |
7 | useEffect(() => {
8 | const fetchData = async () => {
9 | const users = await userApi.getUsers();
10 | setData(users);
11 | };
12 | fetchData();
13 | }, []);
14 |
15 | return (
16 |
17 |
18 |
19 | UserID
20 | Full Name
21 | Email
22 | Phone
23 | Address
24 |
25 |
26 |
27 | {data.map((user, index) => (
28 |
29 | {user.userID}
30 | {user.firstName} {user.lastName}
31 | {user.email}
32 | {user.phone}
33 | {user.postalCode} {user.city} {user.address}
34 |
35 | ))}
36 |
37 |
38 | );
39 | }
40 |
41 | export default Users;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/admin/_admin.scss:
--------------------------------------------------------------------------------
1 | .admin{
2 | width: 100%;
3 | ul {
4 | display: flex;
5 | list-style-type: none;
6 | border: 1px solid $color-dark;
7 | }
8 | li {
9 | border-bottom: none;
10 | cursor: pointer;
11 | text-align: center;
12 | }
13 |
14 | li.active {
15 | color: white;
16 | background-color: $color-dark;
17 | }
18 | input, textarea, select{
19 | width: 100%;
20 | }
21 | }
22 |
23 | .admin-product{
24 | @include flex(space-between, flex-start, row);
25 | width: 100%;
26 | @media (max-width: 1000px) {
27 | flex-direction: column-reverse;
28 | }
29 | }
30 |
31 | .product-table{
32 | width: 100%;
33 | @media (max-width: 600px) {
34 | display: initial;
35 | }
36 | }
37 |
38 | .product-panel{
39 | padding: 1rem;
40 | border-top: 3.4rem solid $color-dark;
41 | @include flex(center, center, column);
42 | width: 100%;
43 | max-width: 30rem;
44 | h2{
45 | font-size: 1.2rem;
46 | margin-bottom: 12px;
47 | }
48 | input{
49 | padding: 0.5rem;
50 | }
51 | @media (max-width: 1000px) {
52 | padding: 1rem 0;
53 | max-width: none;
54 | }
55 | }
56 | .product-panel-img{
57 | width: 100%;
58 | background: $color-smoke;
59 | @include flex(center, center, row);
60 | margin-bottom: 1rem;
61 | img{
62 | width: 80%;
63 | padding-bottom: 1.8rem;
64 | }
65 | }
66 |
67 | .size-table{
68 | input, select{
69 | margin-bottom: 0rem;
70 | padding: 0.5rem;
71 | }
72 | }
73 |
74 | .product-panel-info{
75 | display: flex;
76 | gap: 1rem;
77 | width: 100%;
78 | }
79 |
80 | .label-small{
81 | width: 8rem;
82 | display: block;
83 | }
84 |
85 | td a:first-child svg{
86 | margin-right: 1rem;
87 | }
88 |
89 | .order-items{
90 | width: 100%;
91 | }
--------------------------------------------------------------------------------
/frontend/src/components/auth/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useForm } from 'react-hook-form';
3 | import { useNavigate, useLocation } from 'react-router-dom';
4 | import { icons } from '../../assets/icons/icons';
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6 | import useToggle from '../../utils/hooks/useUtil';
7 | import { useUser } from '../../utils/hooks/useUser';
8 |
9 | function Login() {
10 | const navigate = useNavigate();
11 | const location = useLocation();
12 |
13 | const { login } = useUser();
14 | const { register, handleSubmit, formState: { errors } } = useForm();
15 |
16 | const onSubmit = async (loginData) => {
17 | const user = await login(loginData);
18 | if (user && location.pathname === '/authentication') {
19 | navigate('/account');
20 | }
21 | };
22 |
23 | return (
24 |
46 | )
47 | }
48 |
49 | export default Login;
50 |
--------------------------------------------------------------------------------
/frontend/src/components/auth/RegisterForm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useForm } from 'react-hook-form';
3 |
4 |
5 | function Register() {
6 | const { register, handleSubmit, formState: { errors } } = useForm();
7 |
8 | const onSubmit = async (registerData) => {
9 | console.log(registerData)
10 | };
11 |
12 | return (
13 |
58 | )
59 | }
60 |
61 | export default Register;
62 |
--------------------------------------------------------------------------------
/frontend/src/components/auth/_auth.scss:
--------------------------------------------------------------------------------
1 | .auth{
2 | max-width: 30rem;
3 | @include flex(center, flex-start, column);
4 | h1{
5 | margin-bottom: 0rem;
6 | }
7 | a{
8 | text-decoration: underline;
9 | &.active{
10 | display: none;
11 | }
12 | }
13 | }
14 |
15 |
16 | .login, .register{
17 | @include flex(center, center, column);
18 | button{
19 | margin: 2rem 0 1rem;
20 | width: 100%;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/components/cart/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom';
3 | import { useCart } from '../../utils/hooks/useCart';
4 | import { formatPrice } from '../../utils/hooks/useUtil';
5 |
6 |
7 | function CartItem() {
8 | const { removeFromCart, updateQuantity, items } = useCart();
9 |
10 | return (
11 | <>
12 | {items.map((item) => (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
{formatPrice(item.price)}
29 |
39 |
40 |
41 |
42 | ))}
43 | >
44 | );
45 | }
46 |
47 | export default CartItem;
48 |
--------------------------------------------------------------------------------
/frontend/src/components/cart/_cart.scss:
--------------------------------------------------------------------------------
1 |
2 | .cart-container{
3 | h1{
4 | font-size: 1.4rem;
5 | margin-bottom: 1.6rem;
6 | }
7 | @media (max-width: 850px) {
8 | max-width: none;
9 | }
10 | }
11 | .cart-items{
12 | a:nth-child(2){
13 | text-decoration: underline;
14 | }
15 | }
16 |
17 | .cart-summary{
18 | max-width: 18rem;
19 | width: 100%;
20 | margin-left: 5rem;
21 | @media (max-width: 850px) {
22 | margin: 2rem 0 0 0rem;
23 | max-width: none;
24 | }
25 | }
26 | .summary-content{
27 | margin-bottom: 1.5rem;
28 | button{
29 | width: 100%;
30 | }
31 | }
32 |
33 | .bold{
34 | font-weight: bold;
35 | color: $color-dark;
36 | }
37 |
38 | .cart-item{
39 | display: flex;
40 | flex-direction: row;
41 | width: 100%;
42 | margin-bottom: 1rem;
43 | }
44 |
45 | .cart-item-img{
46 | width: 150px;
47 | height: 150px;
48 | @include flex(center, center, row);
49 | background: #f0f0f0;
50 | margin-right: 1rem;
51 | img{
52 | width: 100%;
53 | object-fit: cover;
54 | }
55 | @media (max-width: 850px) {
56 | width: 120px;
57 | height: 120px;
58 | }
59 | }
60 | .cart-item-about{
61 | display: flex;
62 | justify-content: space-between;
63 | width: 100%;
64 | h3{
65 | font-size: 1rem;
66 | margin-bottom: 0rem;
67 | }
68 | p{
69 | color: $color-grey;
70 | }
71 | }
72 |
73 | .cart-item-left{
74 | @include flex(flex-start, flex-start, column);
75 | p:first-child{
76 | color: $color-dark;
77 | font-weight: bold;
78 | }
79 | a:last-child{
80 | text-decoration: underline;
81 | color: $color-grey;
82 | }
83 | }
84 |
85 | .cart-item-quantity{
86 | @include flex(center, center, row);
87 | border: 1px solid $color-grey;
88 | margin-top: 0.5rem;
89 | input{
90 | text-align: center;
91 | width: 20px;
92 | border: none;
93 | padding: 0rem;
94 | margin: 0rem;
95 | }
96 | a{
97 | text-align: center;
98 | padding: 0 0.2rem;
99 | }
100 | }
101 |
102 | .cart-item-right{
103 | @include flex(flex-start, flex-end, column);
104 | p{
105 | font-weight: bold;
106 | color: black;
107 | }
108 | }
--------------------------------------------------------------------------------
/frontend/src/components/checkout/Complete.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
3 | import { icons } from '../../assets/icons/icons'
4 | import { useUser} from "../../utils/hooks/useUser"
5 | import { Link } from 'react-router-dom'
6 |
7 | function Complete() {
8 | const { currentUser } = useUser();
9 |
10 | return (
11 |
12 |
13 |
Your order is confirmed!
14 |
Thank you for your order, {currentUser.firstName}.
15 |
CONTINUE SHOPPING
16 |
17 | )
18 | }
19 |
20 | export default Complete
--------------------------------------------------------------------------------
/frontend/src/components/checkout/Confirmation.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import OrderSummary from './OrderSummary'
3 | import CartItem from "../cart/CartItem";
4 | import { useUser} from "../../utils/hooks/useUser"
5 | import { useCart } from "../../utils/hooks/useCart"
6 |
7 |
8 | function Confirmation({onPaymentComplete}) {
9 | const { currentUser } = useUser();
10 | const { discount, applyDiscount } = useCart();
11 | const [discountCode, setDiscountCode] = useState('');
12 |
13 | return (
14 |
15 |
16 |
REVIEW YOUR ORDER
17 |
18 |
19 |
20 |
21 |
SHIPPING ADDRESS
22 |
23 |
{currentUser?.firstName} {currentUser?.lastName}
24 |
{currentUser?.address}
25 |
{currentUser?.postalCode} {currentUser?.city}
26 |
Denmark
27 | { !discount > 0 &&
28 |
29 | setDiscountCode(e.target.value)} />
30 | applyDiscount(discountCode)}>Apply
31 |
32 | }
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default Confirmation
--------------------------------------------------------------------------------
/frontend/src/components/checkout/LogIn.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useUser} from "../../utils/hooks/useUser"
3 | import Login from '../auth/LoginForm';
4 |
5 | function Account() {
6 | const { currentUser } = useUser();
7 |
8 | return (
9 |
10 | {currentUser ? (
11 |
12 |
You are currently logged in as {currentUser.firstName}.
13 |
14 | ) : (
15 |
16 |
17 |
18 | )}
19 |
20 | );
21 | }
22 |
23 |
24 | export default Account
--------------------------------------------------------------------------------
/frontend/src/components/checkout/OrderSummary.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from 'react'
2 | import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
3 | import { useCart } from "../../utils/hooks/useCart"
4 |
5 |
6 | function OrderSummary({onPaymentComplete}) {
7 | const buttonStyles = {
8 | layout: 'vertical',
9 | color: 'blue',
10 | label: 'checkout',
11 | };
12 | const { subtotal, delivery, discount, defaultTotal, clearCart } = useCart();
13 |
14 | const onApprove = async (data, actions) => {
15 | const order = await actions.order.capture();
16 | console.log('Order details:', order);
17 | const email = order.payer.email_address;
18 | const transactionId = order.purchase_units[0].payments.captures[0].id;
19 | clearCart();
20 | alert(`An order confirmation will be sent to email: ${email}. Transaction ID: ${transactionId}.`);
21 | onPaymentComplete();
22 | };
23 |
24 | return (
25 |
26 |
27 |
Subtotal
28 |
{subtotal}
29 |
30 | {discount > 0 && (
31 |
32 |
Discount
33 |
-10%
34 |
35 | )}
36 |
37 |
Delivery
38 |
{delivery}
39 |
40 |
41 |
42 |
Total
43 |
{defaultTotal}
44 |
45 |
46 | {
49 | return actions.order.create({
50 | purchase_units: [
51 | {
52 | amount: {
53 | currency_code: "DKK",
54 | value: defaultTotal,
55 | },
56 | },
57 | ],
58 | });
59 | }}
60 | onApprove={onApprove}
61 | />
62 |
63 |
64 | )
65 | }
66 |
67 | export default OrderSummary
--------------------------------------------------------------------------------
/frontend/src/components/checkout/Payment.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | function Payment() {
4 |
5 | return (
6 |
7 |
PAYMENT OPTIONS
8 |
9 |
10 |
11 |
12 | Credit Card (Free)
13 |
14 |
15 |
You will be redirected to PayPal to complete your payment.
16 |
17 | )
18 | }
19 |
20 | export default Payment;
21 |
--------------------------------------------------------------------------------
/frontend/src/components/checkout/Shipping.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useUser} from "../../utils/hooks/useUser"
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { icons } from '../../assets/icons/icons';
5 | import useToggle from "../../utils/hooks/useUtil"
6 |
7 | function Details() {
8 | const { currentUser } = useUser();
9 | const { toggle, isToggled } = useToggle();
10 |
11 | return (
12 |
13 |
14 |
SHIPPING ADDRESS
15 | toggle()} icon={icons.edit}>
16 |
17 |
18 |
{currentUser?.firstName} {currentUser?.lastName}
19 |
{currentUser?.address}
20 |
{currentUser?.postalCode} {currentUser?.city}
21 |
Denmark
22 | { isToggled() &&
}
54 |
55 | );
56 | }
57 |
58 | export default Details;
59 |
--------------------------------------------------------------------------------
/frontend/src/components/checkout/_checkout.scss:
--------------------------------------------------------------------------------
1 | .checkout{
2 | @include flex(center, flex-start, row);
3 | margin: auto;
4 | width: 100%;
5 | padding: 5rem 1rem;
6 | @media (max-width: 850px) {
7 | display: block;
8 | padding: 3rem 1rem;
9 | }
10 | }
11 |
12 | .checkout-nav{
13 | @include flex(flex-start, center, row);
14 | width: 100%;
15 | p.separator{
16 | padding: 0 0.5rem;
17 | }
18 | }
19 |
20 | .checkout-tab{
21 | @include flex(center, center, column);
22 | flex: 1;
23 | p, span{
24 | font-size: 0.7rem;
25 | text-align: center;
26 | }
27 | span{
28 | border: 1px solid $color-lightgrey;
29 | background: $color-smoke;
30 | height: 35px;
31 | margin: 0 0.5rem;
32 | width: 35px;
33 | border-radius: 50%;
34 | @include flex(center, center, column);
35 | &.completed{
36 | background: $color-blue;
37 | color: white;
38 | }
39 | }
40 | &.active{
41 | p, span{
42 | color: $color-blue;
43 | }
44 | span{
45 | border: 1px solid $color-blue;
46 | color: $color-blue;
47 | }
48 | }
49 | }
50 |
51 | .checkout-tab-line{
52 | height: 2px;
53 | width: 100%;
54 | background: $color-smoke;
55 | transform: translateY(-10px);
56 | &.completed{
57 | background: $color-blue;
58 | }
59 | }
60 |
61 | .checkout-container{
62 | @include flex(flex-start, center, column);
63 | width: 100%;
64 | max-width: 800px;
65 | padding: 1rem;
66 | min-height: 80vh;
67 | }
68 |
69 | .checkout-content{
70 | min-height: 20vh;
71 | margin-top: 2rem;
72 | width: 100%;
73 | max-width: 800px;
74 | button {
75 | display: inline-block;
76 | }
77 | h1, h2{
78 | font-size: 1rem;
79 | margin: 0.5rem 0 0rem;
80 | }
81 | .line-divider{
82 | margin: 1rem 0;
83 | }
84 | }
85 |
86 | .shipping-content{
87 | input{
88 | width: 100%;
89 | }
90 | }
91 |
92 | .payment-option{
93 | @include flex(flex-start, center, row);
94 | input{
95 | width: 5%;
96 | margin-bottom: 0rem;
97 | }
98 | }
99 |
100 | .checkout-bottom{
101 | width: 100%;
102 | margin-top: 2rem;
103 | display: flex;
104 | justify-content: flex-end;
105 | gap: 1rem;
106 | }
107 |
108 | .checkout-contentbox{
109 | width: 100%;
110 | }
111 |
112 |
113 | .checkout-left{
114 | width: 100%;
115 | }
--------------------------------------------------------------------------------
/frontend/src/components/checkout/_confirm.scss:
--------------------------------------------------------------------------------
1 | .order-summary{
2 | color: $color-grey;
3 | background: $color-smoke;
4 | border: 1px solid $color-lightgrey;
5 | padding: 1rem;
6 | width: 100%;
7 | margin-top: 1rem;
8 | button{
9 | border-radius: 0px;
10 | }
11 | .line{
12 | background: $color-lightgrey;
13 | }
14 | @media (max-width: 850px) {
15 | flex-wrap: wrap-reverse;
16 | margin: 1rem 0 0 0;
17 | max-width: none;
18 | width: 100%;
19 | }
20 | }
21 |
22 | .checkout-right{
23 | @include flex(flex-start, flex-start, column);
24 | width: 100%;
25 | max-width: 300px;
26 | margin-left: 2rem;
27 | }
28 |
29 | .payment-method{
30 | width: 100%;
31 | margin-bottom: 1rem;
32 | }
33 |
34 | .discount-code{
35 | @include flex(space-between, center, row);
36 | gap: 1rem;
37 | width: 100%;
38 | margin-top: 1rem;
39 | button, input{
40 | border-radius: 0px;
41 | margin: 0;
42 | }
43 | button{
44 | border: none;
45 | }
46 | }
47 |
48 |
49 | .complete{
50 | width: 100%;
51 | @include flex(center, center, column);
52 | svg{
53 | color: $color-blue;
54 | height: 100px;
55 | margin: 4rem 0 1rem;
56 | }
57 | button{
58 | margin-top: 1rem;
59 | }
60 | }
--------------------------------------------------------------------------------
/frontend/src/components/home/_home.scss:
--------------------------------------------------------------------------------
1 |
2 | .hero{
3 | z-index: -1;
4 | position: absolute;
5 | width: 100%;
6 | max-width: 1200px;
7 | margin: auto;
8 | height: 25rem;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | border-radius: 0 0 50px 50px;
13 | background: $color-smoke;
14 | }
15 |
16 | .hero-img{
17 | position: absolute;
18 | width: 40%;
19 | min-width: 30rem;
20 | bottom: 0;
21 | right: 15%;
22 | transform: rotate(-15deg);
23 | transform-origin: center center;
24 | @media (max-width: 850px) {
25 | right: -5%;
26 | transform: scale(80%) rotate(-15deg);
27 | bottom: 10%;
28 | }
29 | }
30 |
31 | .hero-circle{
32 | overflow: hidden;
33 | position: absolute;
34 | top: 0;
35 | right: 0;
36 | transform: translate(-5%, -60%);
37 | z-index: -1;
38 | border-radius: 50%;
39 | height: 50rem;
40 | width: 50rem;
41 | background: white;
42 | }
43 |
44 | .hero-about{
45 | position: sticky;
46 | display: flex;
47 | padding: 5rem 0 5rem 1rem;
48 | max-width: 1080px;
49 | width: 100%;
50 | margin: auto;
51 | h3{
52 | padding: 0rem 0rem;
53 | font-size: 3rem;
54 | margin: 1rem 0;
55 | }
56 | p{
57 | font-size: 1rem;
58 | }
59 | button{
60 | margin-top: 1rem;
61 | width: 100%;
62 | background: $color-dark;
63 | border: 2px solid $color-dark;
64 | font-size: 1rem;
65 | }
66 | @media (max-width: 800px) {
67 | padding-top: 2rem;
68 | h3{
69 | font-size: 2.5rem;
70 | }
71 | }
72 | }
73 |
74 |
75 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Link } from 'react-router-dom';
4 | import FacebookIcon from "../../assets/icons/facebook.svg"
5 | import InstagramIcon from "../../assets/icons/instagram.svg"
6 | import TwitterIcon from "../../assets/icons/twitter.svg"
7 |
8 | const Footer = () => {
9 | return (
10 |
11 |
12 |
SHOP
13 |
14 | Men's sneakers
15 | Women's sneakers
16 | Kid's sneakers
17 | Sale items
18 |
19 |
20 |
21 |
ABOUT US
22 |
23 | Our story
24 | Our team
25 | Press
26 |
27 |
28 |
29 |
CUSTOMER SERVICE
30 |
31 | Shipping & Delivery
32 | Returns & Exchanges
33 | FAQs
34 | Contact Us
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default Footer;
--------------------------------------------------------------------------------
/frontend/src/components/layout/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { useDispatch } from 'react-redux';
4 |
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6 | import { icons } from '../../assets/icons/icons';
7 | import { useLocation } from 'react-router-dom';
8 | import { useCart } from '../../utils/hooks/useCart';
9 | import { useWishlist } from '../../utils/hooks/useWishlist';
10 | import { searchProducts} from '../../store/reducers/productSlice';
11 |
12 | function Header() {
13 | const location = useLocation();
14 | const { quantity } = useCart();
15 | const { wishlistCount } = useWishlist();
16 | const dispatch = useDispatch();
17 |
18 | const isHome = location.pathname === '/';
19 | const isShop = location.pathname === '/shop';
20 |
21 | const handleSearchChange = (e) => {
22 | dispatch(searchProducts(e.target.value))
23 | }
24 |
25 | return (
26 |
27 |
28 |
29 |
SECURE PAYMENT THROUGH PAYPAL
30 |
FREE SHIPPING ON ORDERS OVER 1.200,00 KR
31 |
100% AUTHENTIC
32 |
33 |
34 |
35 |
STORE
36 |
37 | SHOP
38 | CONTACT US
39 | ABOUT US
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {wishlistCount > 0 && {wishlistCount > 9 ? "9+": wishlistCount } }
51 |
52 |
53 |
54 |
55 |
56 | {quantity > 0 && {quantity > 9 ? "9+": quantity } }
57 |
58 |
59 |
60 |
61 |
62 |
68 |
70 |
71 | );
72 | }
73 |
74 | export default Header
--------------------------------------------------------------------------------
/frontend/src/components/layout/_footer.scss:
--------------------------------------------------------------------------------
1 |
2 | .footer{
3 | padding: 2rem 1rem 1.5rem;
4 | @include flex(center, flex-start, row);
5 | width: 100%;
6 | max-width: 1080px;
7 | margin: auto;
8 | min-height: 10vh;
9 | border-top: 1px solid $color-lightgrey;
10 | @media (max-width: 700px) {
11 | display: block;
12 | }
13 | }
14 |
15 | .footer-section{
16 | flex: 1 1 12rem;
17 | margin-right: 2rem;
18 | h3{
19 | font-size: 0.8rem;
20 | margin-bottom: 0.8rem;
21 | }
22 | ul{
23 | gap: 5rem;
24 | li{
25 | padding: 0 0 0.5rem 0;
26 | }
27 | li a{
28 | font-size: 0.7rem;
29 | }
30 | }
31 | @media (max-width: 850px) {
32 | margin-top: 1rem;
33 | }
34 |
35 | }
36 |
37 | .footer-socials{
38 | flex: 3 1 30rem;
39 | flex-direction: row;
40 | display: flex;
41 | align-items: flex-start;
42 | justify-content: flex-end;
43 | gap: 1rem;
44 | @media (max-width: 1000px) {
45 | flex: 1 1 20rem;
46 | }
47 | }
48 |
49 | .social-icon-container{
50 | border: 1px solid $color-grey;
51 | padding: 0.3rem ;
52 | border-radius: 50%;
53 | a{
54 | @include flex(center, center, row);
55 | img{
56 | height: 24px;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/_header.scss:
--------------------------------------------------------------------------------
1 | .header-container{
2 | width: 100%;
3 | margin: auto;
4 | max-width: 1080px;
5 | margin: auto;
6 | padding: 1rem;
7 | flex: 1;
8 | @include flex(space-between, center, row);
9 | ul{
10 | @include flex(space-between, center, row);
11 | gap: 1.5rem;
12 | @media (max-width: 850px) {
13 | display: none;
14 | }
15 | }
16 | a h1{
17 | margin-bottom: 0;
18 | font-size: 1.2rem;
19 | }
20 | }
21 | .burger{
22 | font-size: 2rem;
23 | margin-left: 1rem;
24 | display: none;
25 | @media (max-width: 850px) {
26 | display: block;
27 | }
28 | }
29 | .header-tools{
30 | flex: 1;
31 | @include flex(flex-end, flex-end, row);
32 | svg{
33 | padding: 0.5rem;
34 | }
35 | }
36 |
37 | .svg-icon{
38 | height: 40px;
39 | width: 35px;
40 | position: relative;
41 | @include flex(center, center, row);
42 | span{
43 | @include flex(center, center, row);
44 | z-index: 9999;
45 | top: 10%;
46 | right: 0;
47 | border: 2px solid white;
48 | height: 18px;
49 | width: 18px;
50 | text-align: center;
51 | border-radius: 50%;
52 | background: $color-blue;
53 | color: white;
54 | font-size: 0.6rem;
55 | position: absolute;
56 | }
57 | }
58 |
59 | .header-second{
60 | width: 100%;
61 | margin: auto;
62 | @include flex(center, center, row);
63 | background: $color-smoke;
64 | border: 1px solid $color-lightgrey;
65 | @media (max-width: 850px) {
66 | display: none;
67 | }
68 | }
69 |
70 | .header-second-msg{
71 | @include flex(space-between, center, row);
72 | max-width: 1080px;
73 | width: 100%;
74 | padding: 0.25rem 1rem;
75 | p{
76 | font-weight: bold;
77 | color: #666666;
78 | font-size: 0.7rem;
79 | }
80 | p span{
81 | color: $color-blue;
82 | }
83 | }
84 |
85 | .header-second-msg p:first-child {
86 | flex: 1;
87 | text-align: left;
88 | }
89 |
90 | .header-second-msg p:nth-child(2) {
91 | flex: 1;
92 | text-align: center;
93 | }
94 |
95 | .header-second-msg p:last-child {
96 | flex: 1;
97 | text-align: right;
98 | }
99 |
100 | .header-main{
101 | flex: 1;
102 |
103 | }
104 |
105 | .header-search{
106 | display: none;
107 | width: 100%;
108 | border-top: 1px solid $color-lightgrey;
109 | border-bottom: 1px solid $color-lightgrey;
110 | .input-wrapper{
111 | border: none;
112 | max-width: 1080px;
113 | margin: auto;
114 | input{
115 | width: 100%;
116 | }
117 | }
118 | &.active{
119 | display: block;
120 | }
121 | }
122 |
123 | .header-line{
124 | height: 1px;
125 | background: $color-lightgrey;
126 | &.active{
127 | display: none;
128 | }
129 | }
--------------------------------------------------------------------------------
/frontend/src/components/product/ProductItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { icons } from '../../assets/icons/icons';
4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5 | import { useWishlist } from '../../utils/hooks/useWishlist';
6 | import { formatPrice } from '../../utils/hooks/useUtil';
7 |
8 | function ProductCard({product, index}) {
9 | const { wishlistItems, toggleWishlistItem } = useWishlist();
10 | const itemExists = wishlistItems.find((item) => item.productID === product.productID);
11 |
12 | return (
13 | <>
14 | {product.inStock &&
15 |
16 |
toggleWishlistItem(product)}
19 | />
20 |
21 |
22 |
23 |
24 |
25 |
{product.brand}
26 |
{product.name}
27 |
{formatPrice(product.defaultPrice)}
28 |
29 |
30 |
31 | }
32 | >
33 | );}
34 |
35 | export default ProductCard;
--------------------------------------------------------------------------------
/frontend/src/components/product/ProductList.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import ProductCard from './ProductItem';
3 | import { useProduct } from '../../utils/hooks/useProduct';
4 | import { useSelector } from 'react-redux';
5 | import { icons } from '../../assets/icons/icons';
6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
7 | import useToggle from "../../utils/hooks/useUtil"
8 |
9 | function ProductList() {
10 | const { toggle, isToggled } = useToggle();
11 | const { products, fetchProducts } = useProduct();
12 | const { minPrice: filterMinPrice, maxPrice: filterMaxPrice } = useSelector((state) => state.product.filter);
13 |
14 | const [minPrice, setMinPrice] = useState(filterMinPrice || '');
15 | const [maxPrice, setMaxPrice] = useState(filterMaxPrice || '');
16 | const [sortOrder, setSortOrder] = useState('asc');
17 | const [size, setSize] = useState('');
18 |
19 | useEffect(() => {
20 | fetchProducts();
21 | }, []);
22 |
23 | const filteredProducts = products.filter((product) => {
24 | const minPriceFilter = minPrice === '' || product.defaultPrice >= parseFloat(minPrice);
25 | const maxPriceFilter = maxPrice === '' || product.defaultPrice <= parseFloat(maxPrice);
26 | const sizeFilter = size === '' || product.sizes.map(ps => ps.size).includes(parseInt(size));
27 | return minPriceFilter && maxPriceFilter && sizeFilter;
28 | }).sort((a, b) => {
29 | if (sortOrder === 'lowToHigh') {
30 | return a.defaultPrice - b.defaultPrice;
31 | } else if (sortOrder === 'highToLow') {
32 | return b.defaultPrice - a.defaultPrice;
33 | } else {
34 | return 0;
35 | }
36 | });
37 |
38 | return (
39 |
40 |
41 |
44 | { isToggled() &&
45 |
46 |
47 | Size:
48 | setSize(event.target.value)}>
49 | All
50 | {Array.from({ length: 16 }, (_, i) => i + 35).map(size => (
51 | {size}
52 | ))}
53 |
54 |
55 |
56 |
Pris:
57 |
setMinPrice(event.target.value)} />
58 |
-
59 |
setMaxPrice(event.target.value)} />
60 |
61 |
62 | Sort by:
63 | setSortOrder(event.target.value)}>
64 | Newest
65 | Lowest to Highest
66 | Highest to Lowest
67 |
68 |
69 |
70 | }
71 |
72 |
73 | {filteredProducts.reverse().map((product, index) =>
)}
74 |
75 |
76 | );
77 | }
78 |
79 | export default ProductList
--------------------------------------------------------------------------------
/frontend/src/components/product/_filter.scss:
--------------------------------------------------------------------------------
1 | .filter-control{
2 | @include flex(flex-start, center, row);
3 | width: 100%;
4 | gap: 0.5rem;
5 | margin-bottom: 1rem;
6 | select, input{
7 | margin-bottom: 0rem;
8 | height: 100%;
9 | padding: 0.5rem;
10 | }
11 | }
12 |
13 | .filter-search{
14 | .input-wrapper{
15 | max-width: 500px;
16 | margin: auto;
17 | margin-bottom: 2rem;
18 | }
19 |
20 | }
21 |
22 | .filter-div{
23 | margin-right: 1rem;
24 | a{
25 | display: block;
26 | padding: 0.25rem;
27 | margin-top: 0.6rem;
28 | transform: translateY(-2px);
29 | }
30 | label{
31 | margin-right: 0.5rem;
32 | }
33 | }
34 |
35 | .filter-option{
36 | width: 100%;
37 | display: flex;
38 | }
39 |
40 | .filter-spec{
41 | @include flex(flex-start, center, row);
42 | p{
43 | padding: 0 0.5rem;
44 | }
45 | input{
46 | width: 100px;
47 | }
48 | }
49 |
50 | input[id=maxPrice], input[id=minPrice] {
51 | width: 80px;
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/components/product/_product.scss:
--------------------------------------------------------------------------------
1 | .shop{
2 | padding: 2rem 1rem;
3 | max-width: 1080px;
4 | width: 100%;
5 | margin: auto;
6 | }
7 |
8 | .product-grid {
9 | display: grid;
10 | grid-template-columns: repeat(4, 1fr);
11 | grid-gap: 1rem;
12 | }
13 | @media screen and (max-width: 768px) {
14 | .product-grid {
15 | grid-template-columns: repeat(2, 1fr);
16 | }
17 | }
18 |
19 |
20 | .product-card{
21 | position: relative;
22 | width: 100%;
23 | max-width: 320px;
24 | a{
25 | @include flex(center, center, column)
26 | }
27 | svg{
28 | position: absolute;
29 | z-index: 9999;
30 | top: 0;
31 | right: 0;
32 | background: white;
33 | margin: 0.5rem;
34 | padding: 0.3rem;
35 | border-radius: 50%;
36 | color: $color-dark;
37 | z-index: 9999;
38 | }
39 | @media (max-width: 600px) {
40 | margin-bottom: 1rem;
41 | max-width: none;
42 | }
43 | }
44 |
45 | .product-img{
46 | height: 250px;
47 | @include flex(center, center, row);
48 | background: $color-smoke;
49 | img{
50 | max-width: 90%;
51 | object-fit: cover;
52 | }
53 | a{
54 | height: 100%;
55 | }
56 | }
57 |
58 | .product-info{
59 | margin-top: 1rem;
60 | h3, p{
61 | text-align: center;
62 | font-size: 0.8rem;
63 | margin: 0rem;
64 | }
65 | h3{
66 | white-space: normal;
67 | overflow: hidden;
68 | text-overflow: ellipsis;
69 | text-transform: capitalize;
70 | }
71 | }
72 |
73 |
74 | .product-detail-img{
75 | @include flex(center, center, row);
76 | }
77 |
78 | .product-detail-img{
79 | min-height: 60vh;
80 | background: $color-smoke;
81 | flex: 2;
82 | img{
83 | max-width: 80%;
84 | }
85 | }
86 |
87 | .product-detail-about{
88 | input, select{
89 | width: 100%;
90 | }
91 | h2{
92 | font-size: 1rem;
93 | font-weight: 600;
94 | }
95 | h1{
96 | font-size: 1.6rem;
97 | margin-top: 1rem;
98 | line-height: 100%;
99 | }
100 | p:nth-child(3){
101 | margin: 2.5rem 0;
102 | font-size: 1.4rem;
103 | }
104 | p{
105 | margin-top: 1rem;
106 | }
107 | }
--------------------------------------------------------------------------------
/frontend/src/components/wishlist/_wishlist.scss:
--------------------------------------------------------------------------------
1 | .wishlist-control{
2 | a{
3 | display: block;
4 | margin-bottom: 2rem;
5 | text-decoration: underline;
6 | }
7 | }
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 | import reportWebVitals from './reportWebVitals';
5 | import { BrowserRouter } from 'react-router-dom';
6 | import { Provider } from 'react-redux';
7 | import store from './store/store';
8 |
9 | global.Buffer = global.Buffer || require('buffer').Buffer;
10 |
11 | const root = ReactDOM.createRoot(document.getElementById('root'));
12 | root.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
22 | // If you want to start measuring performance in your app, pass a function
23 | // to log results (for example: reportWebVitals(console.log))
24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
25 | reportWebVitals();
--------------------------------------------------------------------------------
/frontend/src/pages/Account.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Profile from '../components/account/EditProfile';
3 | import Orders from '../components/account/MyOrders';
4 | import { useNavigate } from 'react-router-dom';
5 | import { useSelector, useDispatch } from 'react-redux';
6 | import { selectCurrentUser, setUser } from "../store/reducers/userSlice"
7 |
8 | const tabs = ['Profile', 'My Orders', "Log Out"];
9 |
10 | function MyAccount() {
11 | const dispatch = useDispatch();
12 | const navigate = useNavigate();
13 | const currentUser = useSelector((selectCurrentUser));
14 | const [activeTab, setActiveTab] = useState(0);
15 |
16 | useEffect(() => {
17 | if (!currentUser) {
18 | navigate('/authentication');
19 |
20 | }
21 | }, [currentUser, navigate]);
22 |
23 | const handleTabClick = (index) => {
24 | if (index === 2){
25 | dispatch(setUser(null))
26 | }
27 | setActiveTab(index);
28 | };
29 |
30 | return (
31 | <>
32 | {currentUser && (
33 |
34 |
35 | {tabs.map((tab, index) => (
36 | handleTabClick(index)}
40 | >
41 | {tab}
42 |
43 | ))}
44 |
45 |
46 | {activeTab === 0 &&
}
47 | {activeTab === 1 &&
}
48 |
49 |
50 | )}
51 | >
52 | );
53 |
54 | }
55 |
56 | export default MyAccount
--------------------------------------------------------------------------------
/frontend/src/pages/AdminPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Users from '../components/admin/UsersTable';
3 | import Orders from '../components/admin/OrdersTable';
4 | import Products from '../components/admin/ProductsTable';
5 |
6 | const tabs = ['Products', 'Orders', 'Users',];
7 |
8 | function AdminPanel() {
9 | const [activeTab, setActiveTab] = useState(0);
10 |
11 | const handleTabClick = (index) => {
12 | setActiveTab(index);
13 | };
14 |
15 | return (
16 |
17 |
18 | {tabs.map((tab, index) => (
19 | handleTabClick(index)}
23 | >
24 | {tab}
25 |
26 | ))}
27 |
28 | {activeTab === 0 &&
}
29 | {activeTab === 1 &&
}
30 | {activeTab === 2 &&
}
31 |
32 | );
33 | }
34 |
35 |
36 |
37 | export default AdminPanel
--------------------------------------------------------------------------------
/frontend/src/pages/Authentication.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Register from "../components/auth/RegisterForm";
3 | import Login from "../components/auth/LoginForm";
4 |
5 | function Authentication() {
6 | const [activeTab, setActiveTab] = useState('login');
7 |
8 | return (
9 |
10 |
11 | {activeTab === 'login' && }
12 | {activeTab === 'signup' && }
13 |
14 |
26 |
27 | );
28 | }
29 |
30 | export default Authentication;
31 |
--------------------------------------------------------------------------------
/frontend/src/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import CartItems from "../components/cart/CartItem";
3 | import { useCart } from '../utils/hooks/useCart';
4 | import { Link } from 'react-router-dom';
5 | import { formatPrice } from '../utils/hooks/useUtil';
6 | import {DELIVERY_THRESHOLD} from '../store/reducers/cartSlice';
7 |
8 | function CartPage() {
9 | const { discount, applyDiscount, clearCart, items, subtotal, defaultSubtotal, delivery, total, quantity } = useCart();
10 | const [discountCode, setDiscountCode] = useState('');
11 |
12 | return (
13 |
14 |
15 |
Shopping Bag {quantity > 0 ? "(" + quantity + " " + (quantity === 1 ? "product" : "products") + ")" : ""}
16 | {items.length === 0 ? (
17 |
There’s nothing in your bag yet.
18 | ) : (
19 |
23 | )}
24 |
25 | {quantity > 0 && (
26 |
27 |
28 |
Summary
29 |
30 |
Subtotal
31 |
{subtotal ? subtotal : 0}
32 |
33 | {discount > 0 && (
34 |
35 |
Discount
36 |
-10%
37 |
38 | )}
39 |
40 |
Delivery
41 |
{delivery ? delivery : "Free"}
42 |
43 |
44 |
45 |
Total
46 |
{total ? total : 0}
47 |
48 |
CHECKOUT
49 | {defaultSubtotal < DELIVERY_THRESHOLD ? (
50 |
51 | Spend {formatPrice(DELIVERY_THRESHOLD - defaultSubtotal)} more and get free shipping!
52 |
53 | ) : (
Your order is eligible for free shipping.
)
54 | }
55 |
56 | { !discount > 0 &&
57 |
58 | setDiscountCode(e.target.value)} />
59 | applyDiscount(discountCode)}>Apply
60 |
61 | }
62 |
63 | )}
64 |
65 | );
66 | }
67 |
68 | export default CartPage
--------------------------------------------------------------------------------
/frontend/src/pages/Checkout.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Payment from '../components/checkout/Payment';
3 | import LogIn from '../components/checkout/LogIn';
4 | import Shipping from '../components/checkout/Shipping';
5 | import Confirmation from '../components/checkout/Confirmation';
6 | import Complete from "../components/checkout/Complete"
7 |
8 | import { Link } from 'react-router-dom';
9 | import { selectCurrentUser } from '../store/reducers/userSlice';
10 | import { useSelector } from 'react-redux';
11 |
12 | const tabs = ["Log In", "Shipping", "Payment", "Confirm", "Done"];
13 |
14 | function Checkout() {
15 | const [activeTab, setActiveTab] = useState(0);
16 | const currentUser = useSelector(selectCurrentUser);
17 |
18 | useEffect(() => {
19 | if (currentUser) {
20 | setActiveTab(1);
21 | }
22 | }, [currentUser]);
23 |
24 | const isLastTab = activeTab === tabs.length - 1;
25 | const isSecondLastTab = activeTab === tabs.length - 2;
26 |
27 | const onPaymentComplete = () => {
28 | setActiveTab(4);
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 | {tabs.map((tab, index) => (
36 |
37 |
38 |
39 | {index+1}
40 |
41 |
{tab}
42 |
43 | {index < tabs.length - 1 && (
44 |
45 | )}
46 |
47 | ))}
48 |
49 |
50 | {activeTab === 0 &&
}
51 | {activeTab === 1 &&
}
52 | {activeTab === 2 &&
}
53 | {activeTab === 3 &&
}
54 | {activeTab === 4 && }
55 |
56 |
68 |
69 |
70 | )
71 | }
72 |
73 | export default Checkout;
74 |
--------------------------------------------------------------------------------
/frontend/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import hero from "../assets/icons/hero2.png"
3 |
4 | function Home() {
5 | return (
6 | <>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
LIMITED OFFER
14 |
SAVE 10%
15 |
USE DISCOUNT
16 |
10OFF
17 |
18 |
19 |
20 |
Just arrived...
21 | Shop brand...
22 | News letter...
23 |
24 | >
25 | );
26 | }
27 |
28 | export default Home;
--------------------------------------------------------------------------------
/frontend/src/pages/ProductDetail.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { icons } from '../assets/icons/icons';
5 |
6 | import { useParams } from 'react-router-dom';
7 | import { useCart } from "../utils/hooks/useCart";
8 | import { useProduct } from '../utils/hooks/useProduct';
9 | import { useWishlist } from '../utils/hooks/useWishlist';
10 | import { formatPrice } from '../utils/hooks/useUtil';
11 |
12 | function ProductDetail() {
13 | const { id } = useParams();
14 | const { addToCart } = useCart();
15 | const { products, fetchProducts } = useProduct();
16 | const { wishlistItems, toggleWishlistItem } = useWishlist();
17 | const [selectedSize, setSelectedSize] = useState(null);
18 | const [defaultSize, setDefaultSize] = useState(null);
19 | const product = products.find((product) => product?.productID === Number(id));
20 | const itemExists = wishlistItems.find((item) => item?.productID === product?.productID);
21 |
22 | useEffect(() => {
23 | fetchProducts();
24 | }, []);
25 |
26 | useEffect(() => {
27 | if (product) {
28 | if (!selectedSize) {
29 | const defaultIndex = product?.sizes.findIndex((size) => size.price === product.defaultPrice);
30 | setDefaultSize(defaultIndex);
31 | }
32 | }
33 | }, [selectedSize, product]);
34 |
35 |
36 | return (
37 | <>
38 | {product && (
39 |
40 |
41 |
42 |
43 |
44 |
{product.brand}
45 |
{product.brand} {product.name}
46 |
47 | {selectedSize ? `${formatPrice(selectedSize?.price)}` : `${formatPrice(product.defaultPrice)}`}
48 |
49 |
50 | Available sizes
51 | setSelectedSize(JSON.parse(e.target.value))}>
52 | {product?.sizes?.slice()?.sort((a,b) => a.size - b.size)?.map((size, index) => (
53 |
54 | EU {size?.size} - {formatPrice(size?.price)} {size.quantity <= 3 ? `(only ${size.quantity} left)` : "" }
55 |
56 | ))}
57 |
58 |
59 |
60 | addToCart({ product: product, size: selectedSize?.size || product.sizes[0].size, price: selectedSize?.price || product.defaultPrice })}>ADD TO BASKET
61 | toggleWishlistItem(product)}>
62 | WISHLIST
63 |
64 |
65 |
{product.description}
66 |
67 |
68 | )}
69 | >
70 | );
71 | }
72 |
73 | export default ProductDetail;
74 |
--------------------------------------------------------------------------------
/frontend/src/pages/Shop.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ProductGrid from '../components/product/ProductList'
3 |
4 | function Shop() {
5 | return (
6 |
7 | )
8 | }
9 |
10 | export default Shop
--------------------------------------------------------------------------------
/frontend/src/pages/Wishlist.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ProductCard from '../components/product/ProductItem';
3 | import { useWishlist } from '../utils/hooks/useWishlist'
4 |
5 | function Wishlist() {
6 | const { wishlistItems, clearWishlist} = useWishlist();
7 |
8 | return (
9 |
10 |
13 |
14 | {wishlistItems.map((product, index) =>
15 |
16 | )}
17 |
18 |
19 | )
20 | }
21 |
22 | export default Wishlist
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/frontend/src/store/actions/orderActions.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk } from '@reduxjs/toolkit';
2 | import orderApi from '../../utils/api/orderApi';
3 |
4 | export const fetchOrders = createAsyncThunk(
5 | 'orders/fetchOrders',
6 | async () => {
7 | const orders = await orderApi.getOrders();
8 | return orders;
9 | }
10 | );
11 |
12 | export const fetchOrdersByUserId = createAsyncThunk('products/fetchProductById', async (userId) => {
13 | const orders = await orderApi.getOrdersByUserId(userId);
14 | return orders;
15 | });
--------------------------------------------------------------------------------
/frontend/src/store/actions/productActions.js:
--------------------------------------------------------------------------------
1 |
2 | import { createAsyncThunk } from '@reduxjs/toolkit';
3 | import productApi from '../../utils/api/productApi';
4 | import sizeApi from '../../utils/api/sizeApi';
5 |
6 | export const fetchProducts = createAsyncThunk(
7 | 'products/fetchProducts',
8 | async () => {
9 | const products = await productApi.getProducts();
10 | const productSizes = await sizeApi.getProductSizes();
11 | const productSizesMap = productSizes.reduce((map, size) => {
12 | if (!map[size.productID]) {
13 | map[size.productID] = [];
14 | }
15 | map[size.productID].push(size);
16 | return map;
17 | }, {});
18 | const productsWithSizes = products.map(product => {
19 | const { productID } = product;
20 | const sizes = productSizesMap[productID] || [];
21 | const minPrice = Math.min(...sizes.map(({ price }) => price));
22 | const inStock = sizes.length > 0;
23 | return { ...product, sizes, defaultPrice: minPrice, inStock };
24 | });
25 | return productsWithSizes;
26 | }
27 | );
28 |
29 |
30 | export const fetchProductById = createAsyncThunk('products/fetchProductById', async (productId) => {
31 | const product = await productApi.getProduct(productId);
32 | return product;
33 | });
34 |
35 | export const createProduct = createAsyncThunk('products/createProduct', async (product) => {
36 | const createdProduct = await productApi.addProduct(product);
37 | return createdProduct;
38 | });
39 |
40 | export const updateExistingProduct = createAsyncThunk(
41 | 'products/updateExistingProduct',
42 | async ({ productId, product }) => {
43 | const updatedProduct = await productApi.updateProduct(productId, product);
44 | return updatedProduct;
45 | }
46 | );
47 |
48 | export const removeProduct = createAsyncThunk('products/removeProduct', async (productId) => {
49 | const deletedProduct = await productApi.deleteProduct(productId);
50 | return deletedProduct;
51 | });
--------------------------------------------------------------------------------
/frontend/src/store/actions/sizeActions.js:
--------------------------------------------------------------------------------
1 | import sizeApi from '../../utils/api/sizeApi';
2 | import { createAsyncThunk } from '@reduxjs/toolkit';
3 |
4 | export const fetchProductSizes = createAsyncThunk( 'productSizes/fetchProductSizes', async () => {
5 | const productSizes = await sizeApi.getProductSizes();
6 | return productSizes;
7 | }
8 | );
9 |
10 | export const fetchProductSizesByProductId = createAsyncThunk( 'productSizes/fetchProductSizes', async (productId) => {
11 | const productSizes = await sizeApi.getProductSizesByProductId(productId);
12 | return { productId, productSizes };
13 | }
14 | );
15 |
16 | export const addSize = createAsyncThunk('productSizes/addSize', async ({ size, price, quantity, productId }) => {
17 | const newSize = { size, price, quantity, productId };
18 | const createdProductSize = await sizeApi.addProductSize(newSize);
19 | return createdProductSize;
20 | }
21 | );
22 |
23 | export const updateSize = createAsyncThunk(
24 | 'products/updateExistingSize',
25 | async ({ sizeId, size }) => {
26 | try {
27 | const updatedSize = await sizeApi.updateProductSize(sizeId, size);
28 | return updatedSize;
29 | } catch (error) {
30 | console.error('Error updating size: ', error);
31 | throw error;
32 | }
33 | }
34 | );
35 |
36 | export const deleteSize = createAsyncThunk('products/deleteSize', async (productSizeId) => {
37 | const deletedSize = await sizeApi.deleteProductSize(productSizeId);
38 | return deletedSize;
39 | });
--------------------------------------------------------------------------------
/frontend/src/store/actions/userActions.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk } from "@reduxjs/toolkit";
2 | import userApi from "../../utils/api/userApi";
3 |
4 | export const getUsers = createAsyncThunk("user/login", async () => {
5 | const users = await userApi.getUsers();
6 | return users;
7 |
8 | });
9 |
10 | export const getUser = createAsyncThunk("user/login", async (userId) => {
11 | const user = await userApi.getUser(userId);
12 | return user;
13 |
14 | });
15 |
16 | export const login = createAsyncThunk("user/login", async (loginData) => {
17 | console.log("in slice", loginData)
18 | const userId = await userApi.login(loginData);
19 | console.log("in slice userid", userId)
20 | return userId;
21 |
22 | });
--------------------------------------------------------------------------------
/frontend/src/store/reducers/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | export const DELIVERY_THRESHOLD = 1200;
4 |
5 | const cartSlice = createSlice({
6 | name: 'cart',
7 | initialState: {
8 | items: localStorage.getItem('cartItems')
9 | ? JSON.parse(localStorage.getItem('cartItems'))
10 | : [],
11 | subtotal: 0,
12 | delivery: 0,
13 | discount: 0,
14 | total: 0,
15 | },
16 | reducers: {
17 | addToCart: (state, action) => {
18 | const { product, size, quantity = 1, price } = action.payload;
19 | const existingItem = state.items.find(item => item.product.id === product.id && item.size === size);
20 | if (existingItem) {
21 | existingItem.quantity += quantity;
22 | } else {
23 | state.items.push({ product, size, quantity, price });
24 | }
25 | localStorage.setItem('cartItems', JSON.stringify(state.items));
26 | },
27 |
28 | removeFromCart: (state, action) => {
29 | const { product, size } = action.payload;
30 | state.items = state.items.filter(item => item.product.id !== product.id || item.size !== size);
31 | localStorage.setItem('cartItems', JSON.stringify(state.items));
32 | },
33 | updateQuantity: (state, action) => {
34 | const { productId, size, quantity } = action.payload;
35 | const cartItemIndex = state.items.findIndex(item => item.product.id === productId && item.size === size);
36 | if (cartItemIndex !== -1) {
37 | state.items[cartItemIndex].quantity = quantity;
38 | localStorage.setItem('cartItems', JSON.stringify(state.items));
39 | }
40 | },
41 | clearCart: (state, action) => {
42 | state.items = [];
43 | localStorage.removeItem('cartItems');
44 | },
45 | calculateSubtotal: (state, action) => {
46 | const subtotal = state.items.reduce((acc, item) => acc + (item.price * item.quantity), 0);
47 |
48 | if (subtotal >= DELIVERY_THRESHOLD) {
49 | state.delivery = 0;
50 | state.subtotal = subtotal;
51 | } else {
52 | state.delivery = 60;
53 | state.subtotal = subtotal;
54 | }
55 | },
56 | updateDelivery: (state, action) => {
57 | state.delivery = action.payload.deliveryCost;
58 | },
59 | applyDiscount: (state, action) => {
60 | state.discount = action.payload.discount;
61 | console.log("discount sat:",state.discount)
62 | },
63 | getTotal: (state) => {
64 | state.total = state.subtotal - (state.subtotal * state.discount) + state.delivery;
65 | }
66 | }
67 | });
68 |
69 | export const { addToCart, removeFromCart, updateQuantity, clearCart, calculateSubtotal, updateDelivery, applyDiscount, getTotal } = cartSlice.actions;
70 |
71 | export default cartSlice.reducer;
72 |
--------------------------------------------------------------------------------
/frontend/src/store/reducers/orderSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { fetchOrders } from "../actions/orderActions";
3 |
4 | const initialState = {
5 | orders: [],
6 | status: 'idle',
7 | error: null
8 | }
9 |
10 | const orderSlice = createSlice({
11 | name: 'orders',
12 | initialState,
13 | reducers: {},
14 | extraReducers: (builder) => {
15 | builder
16 | .addCase(fetchOrders.pending, (state, action) => {
17 | state.status = 'loading';
18 | })
19 | .addCase(fetchOrders.fulfilled, (state, action) => {
20 | state.status = 'succeeded';
21 | state.orders = action.payload;
22 | })
23 | .addCase(fetchOrders.rejected, (state, action) => {
24 | state.status = 'failed';
25 | state.error = action.error.message;
26 | });
27 | },
28 | });
29 |
30 |
31 | export default orderSlice.reducer;
32 |
--------------------------------------------------------------------------------
/frontend/src/store/reducers/productSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 | import { fetchProducts } from '../actions/productActions';
3 |
4 | const initialState = {
5 | products: [],
6 | loading: false,
7 | error: null,
8 | status: 'idle',
9 | filter: {
10 | minPrice: null,
11 | maxPrice: null,
12 | sizes: [],
13 | },
14 | };
15 |
16 | const productSlice = createSlice({
17 | name: 'product',
18 | status: "idle",
19 | initialState,
20 | reducers: {
21 | filterProducts: (state, action) => {
22 | const { minPrice, maxPrice, sizes } = action.payload;
23 | state.products = state.products.filter(product => {
24 | const isInPriceRange = (minPrice === null || product.defaultPrice >= minPrice) && (maxPrice === null || product.defaultPrice <= maxPrice);
25 | const hasSize = sizes.length === 0 || product.sizes.some(size => sizes.includes(size));
26 | return isInPriceRange && hasSize;
27 | });
28 | },
29 | searchProducts: (state, action) => {
30 | const query = action.payload.toLowerCase();
31 | const originalProducts = state.originalProducts || state.products;
32 | const filteredProducts = originalProducts.filter(product => {
33 | return (
34 | product.name.toLowerCase().includes(query) ||
35 | product.description.toLowerCase().includes(query)
36 | );
37 | });
38 | state.products = filteredProducts;
39 | state.originalProducts = originalProducts;
40 | if (query.length === 0) {
41 | state.products = originalProducts;
42 | state.originalProducts = null;
43 | }
44 | },
45 | },
46 | setLoad: (state, action) => {
47 | state.loading = action.payload;
48 | },
49 | setError: (state, action) => {
50 | state.error = action.payload;
51 | state.loading = false;
52 | },
53 | extraReducers: (builder) => {
54 | builder
55 | .addCase(fetchProducts.pending, (state) => {
56 | state.status = 'loading';
57 | })
58 | .addCase(fetchProducts.fulfilled, (state, action) => {
59 | state.status = 'succeeded';
60 | state.products = action.payload;
61 | })
62 | .addCase(fetchProducts.rejected, (state, action) => {
63 | state.status = 'failed';
64 | state.error = action.error.message;
65 | });
66 | },
67 | });
68 |
69 | export const {
70 | loadProducts,
71 | filterProducts,
72 | searchProducts,
73 | setLoad,
74 | setError,
75 | removeSelectedProduct,
76 | selectedProduct,
77 | setProduct,
78 | } = productSlice.actions;
79 |
80 | export default productSlice.reducer;
--------------------------------------------------------------------------------
/frontend/src/store/reducers/reducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from '@reduxjs/toolkit';
2 | import cartReducer from './cartSlice';
3 | import productReducer from './productSlice';
4 | import sizeReducer from './sizeSlice';
5 | import userReducer from "./userSlice"
6 | import wishlistReducer from "./wishlistSlice"
7 | import searchReducer from "./wishlistSlice"
8 |
9 | const rootReducer = combineReducers({
10 | cart: cartReducer,
11 | product: productReducer,
12 | productSize: sizeReducer,
13 | user: userReducer,
14 | wishlist: wishlistReducer,
15 | });
16 |
17 | export default rootReducer;
--------------------------------------------------------------------------------
/frontend/src/store/reducers/sizeSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 | import { fetchProductSizes, addSize } from '../actions/sizeActions';
3 |
4 | const initialState = {
5 | productSizes: [],
6 | loading: 'idle',
7 | error: null
8 | };
9 |
10 | export const productSizeSlice = createSlice({
11 | name: 'productSizes',
12 | initialState,
13 | reducers: {
14 | },
15 | extraReducers: (builder) => {
16 | builder
17 | .addCase(fetchProductSizes.pending, (state) => {
18 | state.loading = 'pending';
19 | })
20 | .addCase(fetchProductSizes.fulfilled, (state, action) => {
21 | const { productId, productSizes } = action.payload;
22 | if (productId === state.productId) {
23 | state.loading = 'idle';
24 | state.productSizes = productSizes;
25 | }
26 | })
27 | .addCase(fetchProductSizes.rejected, (state, action) => {
28 | state.loading = 'idle';
29 | state.error = action.error.message;
30 | })
31 | }
32 | });
33 |
34 | export const { } = productSizeSlice.actions;
35 |
36 | export default productSizeSlice.reducer;
37 |
--------------------------------------------------------------------------------
/frontend/src/store/reducers/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { login } from "../actions/userActions";
3 |
4 | const initialState = {
5 | currentUser: null,
6 | token: localStorage.getItem("token") || "",
7 | isLoading: false,
8 | error: null,
9 | };
10 |
11 | export const userSlice = createSlice({
12 | name: "user",
13 | initialState,
14 | reducers: {
15 | logout: (state) => {
16 | localStorage.removeItem("token");
17 | state.currentUser = null;
18 | state.token = "";
19 | },
20 | setUser: (state, action) => {
21 | state.currentUser = action.payload;
22 | },
23 | setLoading: (state, action) => {
24 | state.loading = action.payload;
25 | },
26 | setError: (state, action) => {
27 | state.error = action.payload;
28 | },
29 | },
30 | extraReducers: (builder) => {
31 | builder
32 | .addCase(login.pending, (state) => {
33 | state.isLoading = true;
34 | state.error = null;
35 |
36 | })
37 | .addCase(login.fulfilled, (state, action) => {
38 | state.isLoading = false;
39 | state.error = null;
40 | state.token = action.payload.token;
41 | state.currentUser = action.payload.user;
42 | })
43 | .addCase(login.rejected, (state, action) => {
44 | state.isLoading = false;
45 | state.error = action.error.message;
46 | state.token = "";
47 | state.currentUser = null;
48 | console.log("rejected")
49 | });
50 | },
51 | });
52 |
53 | export const { logout } = userSlice.actions;
54 |
55 | export const selectToken = (state) => state.user.token;
56 | export const selectCurrentUser = (state) => state.user.currentUser;
57 | export const selectIsLoading = (state) => state.user.isLoading;
58 | export const selectError = (state) => state.user.error;
59 |
60 | export const { setUser, setLoading, setError } = userSlice.actions;
61 |
62 |
63 | export default userSlice.reducer;
64 |
--------------------------------------------------------------------------------
/frontend/src/store/reducers/wishlistSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const wishlistSlice1 = createSlice({
4 | name: 'wishlist',
5 | initialState: {
6 | items: localStorage.getItem('wishlist')
7 | ? JSON.parse(localStorage.getItem('wishlist'))
8 | : [],
9 | },
10 | reducers: {
11 | addToWishlist: (state, action) => {
12 | const product = action.payload;
13 | const itemIndex = state.items.findIndex((item) => item.productID === product.productID);
14 | if (itemIndex === -1) {
15 | state.items.push(product);
16 | }
17 | localStorage.setItem('wishlist', JSON.stringify(state.items));
18 | },
19 | removeFromWishlist: (state, action) => {
20 | const product = action.payload;
21 | state.items = state.items.filter((item) => item.productID !== product.productID);
22 | localStorage.setItem('wishlist', JSON.stringify(state.items));
23 | },
24 | clearWishlist: (state) => {
25 | state.items = [];
26 | localStorage.removeItem('wishlist');
27 | },
28 | },
29 | });
30 |
31 | export const { addToWishlist, removeFromWishlist, clearWishlist } =
32 | wishlistSlice1.actions;
33 |
34 | export default wishlistSlice1.reducer;
35 |
--------------------------------------------------------------------------------
/frontend/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import rootReducer from './reducers/reducers';
3 |
4 |
5 | const store = configureStore({
6 | reducer: rootReducer,
7 | devTools: process.env.NODE_ENV !== 'production'
8 | });
9 |
10 | export default store;
--------------------------------------------------------------------------------
/frontend/src/styles/_global.scss:
--------------------------------------------------------------------------------
1 | *{
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 | body {
7 | line-height: 150%;
8 | font-family: 'Montserrat', sans-serif;
9 | overflow-x: hidden;
10 | color: $color-dark;
11 | }
12 | h1, h2{
13 | font-size: 1.4rem;
14 | margin-bottom: 1rem;
15 | }
16 | h3, h4{
17 | font-size: 1.2rem;
18 | margin-bottom: 1rem;
19 | }
20 | ul{
21 | list-style: none;
22 | }
23 | li{
24 | font-size: 0.8rem;
25 | list-style: none;
26 | padding: 0.5rem 1rem;
27 |
28 | }
29 | input, textarea, select{
30 | padding: 0.75rem;
31 | font-family: 'Montserrat', sans-serif;
32 | border: 1px solid $color-lightgrey;
33 | font-size: 0.8rem;
34 | margin-bottom: 1rem;
35 | resize: none;
36 | }
37 | select{
38 | cursor: pointer;
39 | }
40 | select{
41 | padding: 0.5rem;
42 | }
43 | button{
44 | padding: 1rem;
45 | cursor: pointer;
46 | margin-bottom : 1rem;
47 | background: $color-blue;
48 | font-size: 0.6rem;
49 | font-weight: bold;
50 | color: white;
51 | border: 2px solid $color-blue;
52 | border-radius: 5px;
53 | flex-grow: 1;
54 | flex-shrink: 0;
55 | letter-spacing: 1px;
56 | transition: all 0.1s ease-in-out;
57 | &:hover{
58 | background: $lighter-main;
59 | border: 2px solid $lighter-main;
60 | }
61 | }
62 | label{
63 | font-size: 0.8rem;
64 | width: 100%;
65 |
66 | }
67 | svg.svg-inline--fa{
68 | height: 1rem;
69 | }
70 | img{
71 | max-width: 100%;
72 | max-height: 100%;
73 | object-fit: cover;
74 | }
75 | p{
76 | font-size: 0.8rem;
77 | }
78 | a{
79 | font-size: 0.8rem;
80 | text-decoration: none;
81 | color: black;
82 | cursor: pointer;
83 | }
84 | input[type=number] {
85 | -moz-appearance: textfield;
86 | appearance: text;
87 | }
88 | input[type=radio]:checked {
89 | font-weight: bold;
90 | }
91 | input::-webkit-outer-spin-button,
92 | input::-webkit-inner-spin-button {
93 | -webkit-appearance: none;
94 | margin: 0;
95 | }
96 | input:-webkit-autofill,
97 | input:-webkit-autofill:hover,
98 | input:-webkit-autofill:focus,
99 | input:-webkit-autofill:active{
100 | -webkit-box-shadow: 0 0 0 30px white inset !important;
101 | }
102 |
103 | table{
104 | width: 100%;
105 | box-sizing: border-box;
106 | border-spacing: 0px;
107 | button{
108 | margin: 0;
109 | padding: 0.5rem;
110 | }
111 | }
112 | th, td {
113 | padding:15px;
114 | }
115 | tr{
116 | th{
117 | font-size: 0.8rem;
118 | font-weight: lighter;
119 | background: $color-dark;
120 | text-align: left;
121 | color: white;
122 |
123 | }
124 | &:nth-child(2n){
125 | background: $color-smoke;
126 | }
127 | }
128 | td{
129 | font-size: 0.8rem;
130 | white-space: nowrap;
131 | overflow: hidden;
132 | text-overflow: ellipsis;
133 | }
134 |
135 | @media (max-width: 600px) {
136 | table,
137 | thead,
138 | tbody,
139 | th,
140 | td,
141 | tr {
142 | display: block;
143 | }
144 |
145 | th {
146 | text-align: center;
147 | }
148 |
149 | td {
150 | border-bottom: none;
151 | }
152 |
153 | td:before {
154 | content: attr(data-label);
155 | float: left;
156 | font-weight: bold;
157 | }
158 | }
--------------------------------------------------------------------------------
/frontend/src/styles/_utilities.scss:
--------------------------------------------------------------------------------
1 | .container{
2 | min-height: 90vh;
3 | width: 100%;
4 | margin: auto;
5 | max-width: 1080px;
6 | padding: 5rem 1rem;
7 | @media (max-width: 850px) {
8 | padding: 1rem;
9 | }
10 | }
11 |
12 | .flex{
13 | @include flex(center, flex-start, row);
14 | @media (max-width: 850px) {
15 | display: block;
16 | }
17 | }
18 | .flex-start{
19 | @include flex(flex-start, flex-start, row);
20 | @media (max-width: 850px) {
21 | display: block;
22 | }
23 | }
24 |
25 | .space-between{
26 | margin-bottom: 0.5rem;
27 | width: 100%;
28 | @include flex(space-between, center, row);
29 | }
30 |
31 | .side-container{
32 | flex: 1;
33 | }
34 |
35 | .flex-1{
36 | margin-left: 3rem;
37 | min-width: 20rem;
38 | flex: 1;
39 | @media (max-width: 850px) {
40 | margin: 2rem 0 0 0;
41 | }
42 | }
43 |
44 | .flex-2{
45 | flex: 2;
46 | }
47 |
48 | .divider{
49 | width: 100%;
50 | display: flex;
51 | gap: 1rem;
52 | }
53 |
54 | .line{
55 | margin: 0.5rem 0;
56 | height: 1px;
57 | width: 100%;
58 | background: $color-grey;
59 | }
60 |
61 | .line-divider{
62 | margin: 2rem 0;
63 | height: 1px;
64 | width: 100%;
65 | background: $color-lightgrey;
66 | }
67 |
68 |
69 | .second-button{
70 | max-width: 10rem;
71 | border: 2px solid $color-smoke;
72 | background: $color-smoke;
73 | color: $color-blue;
74 | gap: 1rem;
75 | flex-grow: 0;
76 | flex-shrink: 1;
77 | transition: all 0.1s ease-in-out;
78 | &:hover{
79 | background: $color-blue;
80 | color: white;
81 | border: 2px solid $color-blue;
82 | }
83 | @include flex(center, center, row);
84 | @media (max-width: 1000px) {
85 | span{
86 | display: none;
87 | }
88 |
89 | }
90 | }
91 |
92 | .txt{
93 | padding: 0.5rem 1rem;
94 | border-radius: 20px;
95 | }
96 |
97 | .grey{
98 | background: #f4f4f4;
99 | color: $color-grey;
100 | }
101 | .red{
102 | background: #f2c4c6;
103 | color: #6e3a39;
104 | }
105 | .yellow{
106 | background: #fcee8e;
107 | color: #7c6d29;
108 | }
109 | .green{
110 | background: #c3f0d2;
111 | color: #3b5f45;
112 | }
113 |
114 | .input-wrapper {
115 | display: flex;
116 | align-items: center;
117 | border: 1px solid $color-lightgrey;
118 | background: white;
119 | border-radius: 3px;
120 | input{
121 | margin-bottom: 0;
122 | border: none;
123 | }
124 | }
125 |
126 | .input-label{
127 | display:inline-block;
128 | margin-top: 1rem;
129 | }
130 |
131 | .input-wrapper > svg {
132 | margin: 1rem 0 1rem 1rem;
133 | }
134 |
135 | form span{
136 | color: rgb(180, 0, 0);
137 | font-size: 0.8rem;
138 | }
--------------------------------------------------------------------------------
/frontend/src/styles/main.scss:
--------------------------------------------------------------------------------
1 | $lighter-main: #0b92e6;
2 | $color-dark: #222;
3 | $color-grey: #6d6c6c;
4 | $color-lightgrey: #dbdbdb;
5 | $color-smoke: #f6f6f6;
6 | $color-blue: #0070ba;
7 |
8 | @mixin flex($justify, $align, $direction){
9 | display: flex;
10 | justify-content: $justify;
11 | align-items: $align;
12 | flex-direction: $direction;
13 | }
14 |
15 | @import "global";
16 | @import "utilities";
17 |
18 | // components
19 | @import "../components/layout/header";
20 | @import "../components/layout/footer";
21 | @import "../components/checkout/confirm";
22 | @import "../components/product/filter";
23 |
24 | // pages
25 | @import "../components/wishlist/wishlist";
26 | @import "../components/account/account";
27 | @import "../components/admin/admin";
28 | @import "../components/auth/auth";
29 | @import "../components/cart/cart";
30 | @import "../components/checkout/checkout";
31 | @import "../components/home/home";
32 | @import "../components/product/product";
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/frontend/src/utils/api/orderApi.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { variables } from './variables.js';
3 |
4 | const API_URL = variables.ORDER_API
5 |
6 | const getOrders = async () => {
7 | const response = await axios.get(API_URL);
8 | return response.data;
9 | }
10 |
11 | const getOrdersByUserId = async (userId) => {
12 | const response = await axios.get(`${API_URL}/${userId}`);
13 | return response.data;
14 | }
15 |
16 | const createOrder = async (order) => {
17 | const response = await axios.post(API_URL, order);
18 | return response.data;
19 | }
20 |
21 | const updateOrder = async (orderId, order) => {
22 | const response = await axios.put(`${API_URL}/${orderId}`, order);
23 | return response.data;
24 | }
25 |
26 | const deleteOrder = async (orderId) => {
27 | const response = await axios.delete(`${API_URL}/${orderId}`);
28 | return response.data;
29 | }
30 |
31 | export default {
32 | getOrders,
33 | getOrdersByUserId,
34 | createOrder,
35 | updateOrder,
36 | deleteOrder
37 | };
--------------------------------------------------------------------------------
/frontend/src/utils/api/orderItemApi.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { variables } from './variables.js';
3 |
4 | const API_URL = variables.ORDERITEM_API
5 |
6 | const getOrderItems = async () => {
7 | const response = await axios.get(`${API_URL}`);
8 | return response.data;
9 | };
10 |
11 | const getOrderItemsByOrderId = async (orderId) => {
12 | const response = await axios.get(`${API_URL}/${orderId}`);
13 | return response.data;
14 | };
15 |
16 | const addOrderItem = async (orderItemData) => {
17 | const response = await axios.post(`${API_URL}`, orderItemData);
18 | return response.data;
19 | };
20 |
21 | const updateOrderItem = async (orderItemId, orderItemIata) => {
22 | const response = await axios.put(`${API_URL}/${orderItemId}`, orderItemIata);
23 | return response.data;
24 | };
25 |
26 | const deleteOrderItem = async (productSizeId) => {
27 | const response = await axios.delete(`${API_URL}/${productSizeId}`);
28 | return response.data;
29 | };
30 |
31 | export default {
32 | getOrderItems,
33 | getOrderItemsByOrderId,
34 | addOrderItem,
35 | updateOrderItem,
36 | deleteOrderItem
37 | };
--------------------------------------------------------------------------------
/frontend/src/utils/api/productApi.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { variables } from './variables.js';
3 |
4 | const API_URL = variables.PRODUCT_API
5 |
6 | const getProducts = async () => {
7 | const response = await axios.get(API_URL);
8 | return response.data;
9 | }
10 |
11 | const getProduct = async (productId) => {
12 | const response = await axios.get(`${API_URL}/${productId}`);
13 | return response.data;
14 | }
15 |
16 | const addProduct = async (product) => {
17 | const response = await axios.post(API_URL, product);
18 | return response.data;
19 | }
20 |
21 | const updateProduct = async (productId, product) => {
22 | const response = await axios.put(`${API_URL}/${productId}`, product);
23 | return response.data;
24 | }
25 |
26 | const deleteProduct = async (productId) => {
27 | const response = await axios.delete(`${API_URL}/${productId}`);
28 | return response.data;
29 | }
30 |
31 | export default {
32 | getProducts,
33 | getProduct,
34 | addProduct,
35 | updateProduct,
36 | deleteProduct
37 | };
--------------------------------------------------------------------------------
/frontend/src/utils/api/sizeApi.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { variables } from './variables.js';
3 |
4 | const API_URL = variables.PRODUCTSIZE_API
5 |
6 | const getProductSizes = async () => {
7 | const response = await axios.get(`${API_URL}`);
8 | return response.data;
9 | };
10 |
11 | const getProductSizesByProductId = async (productId) => {
12 | const response = await axios.get(`${API_URL}/${productId}`);
13 | return response.data;
14 | };
15 |
16 | const getProductSize = async (productSizeId) => {
17 | const response = await axios.get(`${API_URL}/${productSizeId}/size`);
18 | return response.data;
19 | };
20 |
21 | const addProductSize = async (productSizeData) => {
22 | const response = await axios.post(`${API_URL}`, productSizeData);
23 | return response.data;
24 | };
25 |
26 | const updateProductSize = async (productSizeId, productSizeData) => {
27 | const response = await axios.put(`${API_URL}/${productSizeId}`, productSizeData);
28 | console.log("in api", response)
29 | return response.data;
30 | };
31 |
32 | const deleteProductSize = async (productSizeId) => {
33 | const response = await axios.delete(`${API_URL}/${productSizeId}`);
34 | return response.data;
35 | };
36 |
37 | export default {
38 | getProductSizes,
39 | getProductSizesByProductId,
40 | getProductSize,
41 | addProductSize,
42 | updateProductSize,
43 | deleteProductSize
44 | };
--------------------------------------------------------------------------------
/frontend/src/utils/api/userApi.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { variables } from './variables.js';
3 | import jwtDecode from 'jwt-decode';
4 |
5 | const API_URL = variables.USER_API
6 |
7 | const getUsers = async () => {
8 | const response = await axios.get(API_URL);
9 | return response.data;
10 | }
11 |
12 | const getUser = async (userId) => {
13 | const response = await axios.get(`${API_URL}/${userId}`);
14 | return response.data;
15 | }
16 |
17 | const createUser = async (user) => {
18 | const response = await axios.post(API_URL, user);
19 | return response.data;
20 | }
21 |
22 | const updateUser = async (userId, user) => {
23 | const response = await axios.put(`${API_URL}/${userId}`, user);
24 | return response.data;
25 | }
26 |
27 | const deleteUser = async (userId) => {
28 | const response = await axios.delete(`${API_URL}/${userId}`);
29 | return response.data;
30 | }
31 |
32 | const login = async (loginData) => {
33 | try {
34 | const response = await axios.post(`${API_URL}/login`, loginData);
35 | if (response.data.token) {
36 | localStorage.setItem('user', JSON.stringify(response.data));
37 | const token = response.data.token;
38 | const decodedToken = jwtDecode(token);
39 | return decodedToken.nameid;
40 | }
41 | return { token: null };
42 | } catch (error) {
43 | if (error.response && error.response.status === 401) {
44 | alert('Invalid email or password');
45 | } else {
46 | alert('An error occurred');
47 | }
48 | return { token: null };
49 | }
50 |
51 | };
52 |
53 | export default {
54 | login,
55 | getUser,
56 | getUsers,
57 | createUser,
58 | updateUser,
59 | deleteUser
60 | };
--------------------------------------------------------------------------------
/frontend/src/utils/api/variables.js:
--------------------------------------------------------------------------------
1 | export const variables = {
2 | BASE_URL: "https://localhost:7089/api/",
3 | USER_API: "https://localhost:7089/api/user",
4 | PRODUCT_API: "https://localhost:7089/api/product",
5 | ORDER_API: "https://localhost:7089/api/order",
6 | PRODUCTSIZE_API: "https://localhost:7089/api/productsize",
7 | ORDERITEM_API: "https://localhost:7089/api/orderitem",
8 | }
--------------------------------------------------------------------------------
/frontend/src/utils/hooks/useCart.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { addToCart, removeFromCart, updateQuantity, calculateSubtotal, getTotal, applyDiscount, clearCart } from '../../store/reducers/cartSlice';
4 | import { formatPrice } from './useUtil';
5 |
6 | export const useCart = () => {
7 | const dispatch = useDispatch();
8 | const items = useSelector(state => state.cart.items);
9 | const subtotal = useSelector(state => state.cart.subtotal);
10 | const delivery = useSelector(state => state.cart.delivery);
11 | const discount = useSelector(state => state.cart.discount);
12 | const total = useSelector(state => state.cart.total);
13 | const quantity = items.reduce((acc, item) => acc + item.quantity, 0);
14 |
15 | useEffect(() => {
16 | dispatch(calculateSubtotal());
17 | }, [items, dispatch, quantity]);
18 |
19 | useEffect(() => {
20 | dispatch(getTotal());
21 | }, [subtotal, delivery, dispatch, discount]);
22 |
23 | const addToCartHandler = (item) => {
24 | dispatch(addToCart(item));
25 | };
26 |
27 | const removeFromCartHandler = (itemId, size) => {
28 | const item = items.find((item) => item.product.id === itemId && item.size === size);
29 | dispatch(removeFromCart({ product: item.product, size }));
30 | };
31 |
32 | const updateQuantityHandler = (productId, size, newQuantity) => {
33 | if (newQuantity === 0) {
34 | const item = items.find((item) => item.product.id === productId && item.size === size);
35 | if (item) {
36 | dispatch(removeFromCart({ product: item.product, size }));
37 | }
38 | } else if (newQuantity <= 10) {
39 | dispatch(updateQuantity({ productId, size, quantity: newQuantity }));
40 | }
41 | };
42 |
43 | const clearCartHandler = () => {
44 | dispatch(clearCart());
45 | };
46 |
47 | const applyDiscountHandler = (discountCode) => {
48 | const discount = 0.1;
49 | if(discountCode.toLowerCase() === "10off"){
50 | dispatch(applyDiscount({ discount }));
51 | } else{
52 | alert("Wrong discount")
53 | }
54 | };
55 |
56 | return {
57 | addToCart: addToCartHandler,
58 | removeFromCart: removeFromCartHandler,
59 | updateQuantity: updateQuantityHandler,
60 | clearCart: clearCartHandler,
61 | applyDiscount: applyDiscountHandler,
62 | items,
63 | defaultSubtotal: subtotal,
64 | defaultTotal: total,
65 | subtotal: formatPrice(subtotal),
66 | delivery: formatPrice(delivery),
67 | total: formatPrice(total),
68 | quantity,
69 | discount
70 | };
71 | };
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/frontend/src/utils/hooks/useProduct.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from 'react-redux';
2 | import { fetchProducts, createProduct, updateExistingProduct, removeProduct } from '../../store/actions/productActions';
3 |
4 | export const useProduct = () => {
5 | const dispatch = useDispatch();
6 | const products = useSelector((state) => state.product.products);
7 | const loading = useSelector((state) => state.product.loading);
8 | const error = useSelector((state) => state.product.error);
9 | const status = useSelector((state) => state.product.status);
10 |
11 | const fetchProductsHandler = () => {
12 | dispatch(fetchProducts());
13 | };
14 |
15 | const createProductHandler = (product) => {
16 | dispatch(createProduct(product))
17 | .then(() => {
18 | alert("Product has been added.")
19 | })
20 | };
21 |
22 | const updateExistingProductHandler = (productId, product) => {
23 | dispatch(updateExistingProduct( productId, product ));
24 | };
25 |
26 | const removeProductHandler = (productId) => {
27 | console.log("hey")
28 | if (window.confirm("Are you sure you want to delete this product?")) {
29 | dispatch(removeProduct(productId))
30 | .then(() => {
31 | alert("Product has been deleted.");
32 | });
33 | }
34 | };
35 |
36 | return {
37 | fetchProducts: fetchProductsHandler,
38 | createProduct: createProductHandler,
39 | updateExistingProduct: updateExistingProductHandler,
40 | removeProduct: removeProductHandler,
41 | products,
42 | loading,
43 | error,
44 | status,
45 | };
46 | };
47 |
48 |
--------------------------------------------------------------------------------
/frontend/src/utils/hooks/useSize.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDispatch } from 'react-redux';
3 | import { deleteSize, addSize, updateSize } from '../../store/actions/sizeActions';
4 |
5 | export const useSize = () => {
6 | const dispatch = useDispatch();
7 |
8 | useEffect(() => {
9 | }, []);
10 |
11 | const addSizeHandler = ({ size, price, quantity, productId }, e) => {
12 | dispatch(addSize({ size, price, quantity, productId }))
13 | };
14 |
15 | const updateSizeHandler = ({ sizeId, size }) => {
16 | dispatch(updateSize({ sizeId, size }));
17 | };
18 |
19 | const deleteSizeHandler = (sizeId, e) => {
20 | e.preventDefault();
21 | console.log("sizehook", sizeId )
22 | if (window.confirm("Are you sure you want to delete this size?")) {
23 | dispatch(deleteSize(sizeId)).then(() => {
24 | alert("Size has been deleted.");
25 | });
26 | }
27 | };
28 |
29 | return {
30 | addSize: addSizeHandler,
31 | updateSize: updateSizeHandler,
32 | deleteSize: deleteSizeHandler,
33 | };
34 | };
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/frontend/src/utils/hooks/useUser.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { getUsers, getUser, login } from "../../store/actions/userActions";
4 | import { selectCurrentUser, selectError, selectIsLoading, selectToken, setUser } from "../../store/reducers/userSlice";
5 |
6 | export const useUser = () => {
7 | const dispatch = useDispatch();
8 | const token = useSelector(selectToken);
9 | const currentUser = useSelector(selectCurrentUser);
10 | const isLoading = useSelector(selectIsLoading);
11 | const error = useSelector(selectError);
12 |
13 | const getUsersHandler = async () => {
14 | await dispatch(getUsers());
15 | };
16 |
17 | const getUserHandler = async (userId) => {
18 | await dispatch(getUser(userId));
19 | };
20 |
21 | const loginHandler = async (loginData) => {
22 | const { payload: userId } = await dispatch(login(loginData));
23 | const { payload: user } = await dispatch(getUser(userId));
24 | if (user){
25 | dispatch(setUser(user));
26 | }
27 | return user;
28 | };
29 |
30 | useEffect(() => {
31 | if (token) {
32 | }
33 | }, [token]);
34 |
35 | return {
36 | getUsers: getUsersHandler,
37 | getUser: getUserHandler,
38 | login: loginHandler,
39 | // logout: logoutHandler,
40 | token,
41 | currentUser,
42 | isLoading,
43 | error };
44 | };
45 |
--------------------------------------------------------------------------------
/frontend/src/utils/hooks/useUtil.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export const useStatusString = () => {
4 | return (status) => {
5 | const statusMap = {
6 | 0: { statusString: 'Pending', className: 'yellow' },
7 | 1: { statusString: 'Processing', className: 'green' },
8 | 2: { statusString: 'Shipped', className: 'green' },
9 | 3: { statusString: 'Delivered', className: 'green' },
10 | 4: { statusString: 'Cancelled', className: 'red' }
11 | };
12 | const statusObj = statusMap[status] ?? { statusString: '', className: '' };
13 | return {statusObj.statusString}
;
14 | };
15 | };
16 |
17 | export const useStock = () => {
18 | return (stock) => {
19 | const stockMap = {
20 | true: { statusString: 'In stock', className: 'green' },
21 | false: { statusString: 'Out of stock', className: 'red' },
22 | };
23 | const stockObj = stockMap[stock] ?? { statusString: '', className: '' };
24 | return {stockObj.statusString}
;
25 | };
26 | };
27 |
28 | export default function useToggle(initialValue = false) {
29 | const [toggled, setToggled] = useState(initialValue);
30 |
31 | function toggle() {
32 | setToggled(!toggled);
33 | }
34 |
35 | function isToggled() {
36 | return toggled;
37 | }
38 |
39 | return { toggle, isToggled };
40 | }
41 |
42 | export function formatPrice(price) {
43 | return price.toLocaleString('da-DK', { style: 'currency', currency: 'DKK' });
44 | }
45 |
46 | export function formatDate(date) {
47 | const options = { year: 'numeric', month: 'long', day: 'numeric' };
48 | return new Date(date).toLocaleDateString('us-US', options);
49 | }
50 |
51 | export function formatDateTime(dateTime) {
52 | const options = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' };
53 | return new Date(dateTime).toLocaleDateString('us-US', options);
54 | }
--------------------------------------------------------------------------------
/frontend/src/utils/hooks/useWishlist.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from 'react-redux';
2 | import { addToWishlist, clearWishlist, removeFromWishlist } from '../../store/reducers/wishlistSlice';
3 |
4 | export const useWishlist = () => {
5 | const dispatch = useDispatch();
6 | const wishlistItems = useSelector(state => state.wishlist && state.wishlist.items);
7 | const wishlistCount = wishlistItems.length;
8 |
9 | const toggleWishlistItemHandler = (product) => {
10 | const itemExists = wishlistItems.find((item) => item.productID === product.productID);
11 | if (!itemExists){
12 | dispatch(addToWishlist(product));
13 | } else{
14 | dispatch(removeFromWishlist(product));
15 | }
16 | };
17 |
18 | const clearWishlistItemsHandler = () => {
19 | dispatch(clearWishlist());
20 | };
21 |
22 | return {
23 | toggleWishlistItem: toggleWishlistItemHandler,
24 | clearWishlist: clearWishlistItemsHandler,
25 | wishlistCount,
26 | wishlistItems,
27 | };
28 | };
29 |
--------------------------------------------------------------------------------