Add Automapper
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Products.Dtos;
|
using Imprink.Application.Products.Dtos;
|
||||||
using Imprink.Domain.Entities.Product;
|
using Imprink.Domain.Entities.Product;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -16,7 +17,7 @@ public class CreateProductVariantCommand : IRequest<ProductVariantDto>
|
|||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateProductVariantHandler(IUnitOfWork unitOfWork)
|
public class CreateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper)
|
||||||
: IRequestHandler<CreateProductVariantCommand, ProductVariantDto>
|
: IRequestHandler<CreateProductVariantCommand, ProductVariantDto>
|
||||||
{
|
{
|
||||||
public async Task<ProductVariantDto> Handle(CreateProductVariantCommand request, CancellationToken cancellationToken)
|
public async Task<ProductVariantDto> Handle(CreateProductVariantCommand request, CancellationToken cancellationToken)
|
||||||
@@ -25,36 +26,16 @@ public class CreateProductVariantHandler(IUnitOfWork unitOfWork)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var productVariant = new ProductVariant
|
var productVariant = mapper.Map<ProductVariant>(request);
|
||||||
{
|
|
||||||
ProductId = request.ProductId,
|
productVariant.Product = null!;
|
||||||
Size = request.Size,
|
|
||||||
Color = request.Color,
|
|
||||||
Price = request.Price,
|
|
||||||
ImageUrl = request.ImageUrl,
|
|
||||||
Sku = request.Sku,
|
|
||||||
StockQuantity = request.StockQuantity,
|
|
||||||
IsActive = request.IsActive,
|
|
||||||
Product = null!
|
|
||||||
};
|
|
||||||
|
|
||||||
var createdVariant = await unitOfWork.ProductVariantRepository.AddAsync(productVariant, cancellationToken);
|
var createdVariant = await unitOfWork.ProductVariantRepository.AddAsync(productVariant, cancellationToken);
|
||||||
|
|
||||||
|
await unitOfWork.SaveAsync(cancellationToken);
|
||||||
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new ProductVariantDto
|
return mapper.Map<ProductVariantDto>(createdVariant);
|
||||||
{
|
|
||||||
Id = createdVariant.Id,
|
|
||||||
ProductId = createdVariant.ProductId,
|
|
||||||
Size = createdVariant.Size,
|
|
||||||
Color = createdVariant.Color,
|
|
||||||
Price = createdVariant.Price,
|
|
||||||
ImageUrl = createdVariant.ImageUrl,
|
|
||||||
Sku = createdVariant.Sku,
|
|
||||||
StockQuantity = createdVariant.StockQuantity,
|
|
||||||
IsActive = createdVariant.IsActive,
|
|
||||||
CreatedAt = createdVariant.CreatedAt,
|
|
||||||
ModifiedAt = createdVariant.ModifiedAt
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandl
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var exists = await unitOfWork.ProductVariantRepository.ExistsAsync(request.Id, cancellationToken);
|
var exists = await unitOfWork.ProductVariantRepository.ExistsAsync(request.Id, cancellationToken);
|
||||||
|
|
||||||
if (!exists)
|
if (!exists)
|
||||||
{
|
{
|
||||||
await unitOfWork.RollbackTransactionAsync(cancellationToken);
|
await unitOfWork.RollbackTransactionAsync(cancellationToken);
|
||||||
@@ -23,7 +24,10 @@ public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandl
|
|||||||
}
|
}
|
||||||
|
|
||||||
await unitOfWork.ProductVariantRepository.DeleteAsync(request.Id, cancellationToken);
|
await unitOfWork.ProductVariantRepository.DeleteAsync(request.Id, cancellationToken);
|
||||||
|
|
||||||
|
await unitOfWork.SaveAsync(cancellationToken);
|
||||||
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Products.Dtos;
|
using Imprink.Application.Products.Dtos;
|
||||||
using Imprink.Domain.Entities.Product;
|
using Imprink.Domain.Entities.Product;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Imprink.Application.Domains.ProductVariants;
|
namespace Imprink.Application.Domains.ProductVariants;
|
||||||
|
|
||||||
@@ -11,7 +13,7 @@ public class GetProductVariantsQuery : IRequest<IEnumerable<ProductVariantDto>>
|
|||||||
public bool InStockOnly { get; set; } = false;
|
public bool InStockOnly { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetProductVariantsHandler(IUnitOfWork unitOfWork)
|
public class GetProductVariantsHandler(IUnitOfWork unitOfWork, IMapper mapper, ILogger<GetProductVariantsHandler> logger)
|
||||||
: IRequestHandler<GetProductVariantsQuery, IEnumerable<ProductVariantDto>>
|
: IRequestHandler<GetProductVariantsQuery, IEnumerable<ProductVariantDto>>
|
||||||
{
|
{
|
||||||
public async Task<IEnumerable<ProductVariantDto>> Handle(GetProductVariantsQuery request, CancellationToken cancellationToken)
|
public async Task<IEnumerable<ProductVariantDto>> Handle(GetProductVariantsQuery request, CancellationToken cancellationToken)
|
||||||
@@ -37,33 +39,7 @@ public class GetProductVariantsHandler(IUnitOfWork unitOfWork)
|
|||||||
{
|
{
|
||||||
variants = new List<ProductVariant>();
|
variants = new List<ProductVariant>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return variants.Select(pv => new ProductVariantDto
|
return mapper.Map<IEnumerable<ProductVariantDto>>(variants);
|
||||||
{
|
|
||||||
Id = pv.Id,
|
|
||||||
ProductId = pv.ProductId,
|
|
||||||
Size = pv.Size,
|
|
||||||
Color = pv.Color,
|
|
||||||
Price = pv.Price,
|
|
||||||
ImageUrl = pv.ImageUrl,
|
|
||||||
Sku = pv.Sku,
|
|
||||||
StockQuantity = pv.StockQuantity,
|
|
||||||
IsActive = pv.IsActive,
|
|
||||||
Product = new ProductDto
|
|
||||||
{
|
|
||||||
Id = pv.Product.Id,
|
|
||||||
Name = pv.Product.Name,
|
|
||||||
Description = pv.Product.Description,
|
|
||||||
BasePrice = pv.Product.BasePrice,
|
|
||||||
IsCustomizable = pv.Product.IsCustomizable,
|
|
||||||
IsActive = pv.Product.IsActive,
|
|
||||||
ImageUrl = pv.Product.ImageUrl,
|
|
||||||
CategoryId = pv.Product.CategoryId,
|
|
||||||
CreatedAt = pv.Product.CreatedAt,
|
|
||||||
ModifiedAt = pv.Product.ModifiedAt
|
|
||||||
},
|
|
||||||
CreatedAt = pv.CreatedAt,
|
|
||||||
ModifiedAt = pv.ModifiedAt
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Exceptions;
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Products.Dtos;
|
using Imprink.Application.Products.Dtos;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -17,7 +18,7 @@ public class UpdateProductVariantCommand : IRequest<ProductVariantDto>
|
|||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateProductVariantHandler(IUnitOfWork unitOfWork)
|
public class UpdateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper)
|
||||||
: IRequestHandler<UpdateProductVariantCommand, ProductVariantDto>
|
: IRequestHandler<UpdateProductVariantCommand, ProductVariantDto>
|
||||||
{
|
{
|
||||||
public async Task<ProductVariantDto> Handle(UpdateProductVariantCommand request, CancellationToken cancellationToken)
|
public async Task<ProductVariantDto> Handle(UpdateProductVariantCommand request, CancellationToken cancellationToken)
|
||||||
@@ -29,36 +30,16 @@ public class UpdateProductVariantHandler(IUnitOfWork unitOfWork)
|
|||||||
var existingVariant = await unitOfWork.ProductVariantRepository.GetByIdAsync(request.Id, cancellationToken);
|
var existingVariant = await unitOfWork.ProductVariantRepository.GetByIdAsync(request.Id, cancellationToken);
|
||||||
|
|
||||||
if (existingVariant == null)
|
if (existingVariant == null)
|
||||||
{
|
|
||||||
throw new NotFoundException($"Product variant with ID {request.Id} not found.");
|
throw new NotFoundException($"Product variant with ID {request.Id} not found.");
|
||||||
}
|
|
||||||
|
mapper.Map(request, existingVariant);
|
||||||
existingVariant.ProductId = request.ProductId;
|
|
||||||
existingVariant.Size = request.Size;
|
|
||||||
existingVariant.Color = request.Color;
|
|
||||||
existingVariant.Price = request.Price;
|
|
||||||
existingVariant.ImageUrl = request.ImageUrl;
|
|
||||||
existingVariant.Sku = request.Sku;
|
|
||||||
existingVariant.StockQuantity = request.StockQuantity;
|
|
||||||
existingVariant.IsActive = request.IsActive;
|
|
||||||
|
|
||||||
var updatedVariant = await unitOfWork.ProductVariantRepository.UpdateAsync(existingVariant, cancellationToken);
|
var updatedVariant = await unitOfWork.ProductVariantRepository.UpdateAsync(existingVariant, cancellationToken);
|
||||||
|
|
||||||
|
await unitOfWork.SaveAsync(cancellationToken);
|
||||||
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new ProductVariantDto
|
return mapper.Map<ProductVariantDto>(updatedVariant);
|
||||||
{
|
|
||||||
Id = updatedVariant.Id,
|
|
||||||
ProductId = updatedVariant.ProductId,
|
|
||||||
Size = updatedVariant.Size,
|
|
||||||
Color = updatedVariant.Color,
|
|
||||||
Price = updatedVariant.Price,
|
|
||||||
ImageUrl = updatedVariant.ImageUrl,
|
|
||||||
Sku = updatedVariant.Sku,
|
|
||||||
StockQuantity = updatedVariant.StockQuantity,
|
|
||||||
IsActive = updatedVariant.IsActive,
|
|
||||||
CreatedAt = updatedVariant.CreatedAt,
|
|
||||||
ModifiedAt = updatedVariant.ModifiedAt
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Products.Dtos;
|
using Imprink.Application.Products.Dtos;
|
||||||
using Imprink.Domain.Entities.Product;
|
using Imprink.Domain.Entities.Product;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -15,7 +16,7 @@ public class CreateProductCommand : IRequest<ProductDto>
|
|||||||
public Guid? CategoryId { get; set; }
|
public Guid? CategoryId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler<CreateProductCommand, ProductDto>
|
public class CreateProductHandler(IUnitOfWork unitOfWork, IMapper mapper) : IRequestHandler<CreateProductCommand, ProductDto>
|
||||||
{
|
{
|
||||||
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
|
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -23,49 +24,18 @@ public class CreateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler<Crea
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var product = new Product
|
var product = mapper.Map<Product>(request);
|
||||||
{
|
|
||||||
Name = request.Name,
|
|
||||||
Description = request.Description,
|
|
||||||
BasePrice = request.BasePrice,
|
|
||||||
IsCustomizable = request.IsCustomizable,
|
|
||||||
IsActive = request.IsActive,
|
|
||||||
ImageUrl = request.ImageUrl,
|
|
||||||
CategoryId = request.CategoryId,
|
|
||||||
Category = null!
|
|
||||||
};
|
|
||||||
|
|
||||||
var createdProduct = await unitOfWork.ProductRepository.AddAsync(product, cancellationToken);
|
var createdProduct = await unitOfWork.ProductRepository.AddAsync(product, cancellationToken);
|
||||||
|
|
||||||
var categoryDto = new CategoryDto
|
if (createdProduct.CategoryId.HasValue)
|
||||||
{
|
{
|
||||||
Id = createdProduct.Category.Id,
|
createdProduct.Category = (await unitOfWork.CategoryRepository.GetByIdAsync(createdProduct.CategoryId.Value, cancellationToken))!;
|
||||||
Name = createdProduct.Category.Name,
|
}
|
||||||
Description = createdProduct.Category.Description,
|
|
||||||
ImageUrl = createdProduct.Category.ImageUrl,
|
|
||||||
SortOrder = createdProduct.Category.SortOrder,
|
|
||||||
IsActive = createdProduct.Category.IsActive,
|
|
||||||
ParentCategoryId = createdProduct.Category.ParentCategoryId,
|
|
||||||
CreatedAt = createdProduct.Category.CreatedAt,
|
|
||||||
ModifiedAt = createdProduct.Category.ModifiedAt
|
|
||||||
};
|
|
||||||
|
|
||||||
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
await unitOfWork.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new ProductDto
|
return mapper.Map<ProductDto>(createdProduct);
|
||||||
{
|
|
||||||
Id = createdProduct.Id,
|
|
||||||
Name = createdProduct.Name,
|
|
||||||
Description = createdProduct.Description,
|
|
||||||
BasePrice = createdProduct.BasePrice,
|
|
||||||
IsCustomizable = createdProduct.IsCustomizable,
|
|
||||||
IsActive = createdProduct.IsActive,
|
|
||||||
ImageUrl = createdProduct.ImageUrl,
|
|
||||||
CategoryId = createdProduct.CategoryId,
|
|
||||||
Category = categoryDto,
|
|
||||||
CreatedAt = createdProduct.CreatedAt,
|
|
||||||
ModifiedAt = createdProduct.ModifiedAt
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Exceptions;
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -6,7 +7,7 @@ namespace Imprink.Application.Domains.Users;
|
|||||||
|
|
||||||
public record DeleteUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;
|
public record DeleteUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;
|
||||||
|
|
||||||
public class DeleteUserRoleHandler(IUnitOfWork uw) : IRequestHandler<DeleteUserRoleCommand, UserRoleDto?>
|
public class DeleteUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<DeleteUserRoleCommand, UserRoleDto?>
|
||||||
{
|
{
|
||||||
public async Task<UserRoleDto?> Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken)
|
public async Task<UserRoleDto?> Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -27,11 +28,7 @@ public class DeleteUserRoleHandler(IUnitOfWork uw) : IRequestHandler<DeleteUserR
|
|||||||
await uw.SaveAsync(cancellationToken);
|
await uw.SaveAsync(cancellationToken);
|
||||||
await uw.CommitTransactionAsync(cancellationToken);
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new UserRoleDto
|
return mapper.Map<UserRoleDto>(removedRole);
|
||||||
{
|
|
||||||
UserId = removedRole.UserId,
|
|
||||||
RoleId = removedRole.RoleId
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
17
src/Imprink.Application/Domains/Users/GetAllRolesHandler.cs
Normal file
17
src/Imprink.Application/Domains/Users/GetAllRolesHandler.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Imprink.Application.Users.Dtos;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Imprink.Application.Domains.Users;
|
||||||
|
|
||||||
|
public record GetAllRolesCommand : IRequest<IEnumerable<RoleDto>>;
|
||||||
|
|
||||||
|
public class GetAllRolesHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler<GetAllRolesCommand, IEnumerable<RoleDto>>
|
||||||
|
{
|
||||||
|
public async Task<IEnumerable<RoleDto>> Handle(GetAllRolesCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var roles = await uw.RoleRepository.GetAllRolesAsync(cancellationToken);
|
||||||
|
|
||||||
|
return mapper.Map<IEnumerable<RoleDto>>(roles);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Exceptions;
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -6,7 +7,7 @@ namespace Imprink.Application.Domains.Users;
|
|||||||
|
|
||||||
public record GetUserRolesCommand(string Sub) : IRequest<IEnumerable<RoleDto>>;
|
public record GetUserRolesCommand(string Sub) : IRequest<IEnumerable<RoleDto>>;
|
||||||
|
|
||||||
public class GetUserRolesHandler(IUnitOfWork uw): IRequestHandler<GetUserRolesCommand, IEnumerable<RoleDto>>
|
public class GetUserRolesHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler<GetUserRolesCommand, IEnumerable<RoleDto>>
|
||||||
{
|
{
|
||||||
public async Task<IEnumerable<RoleDto>> Handle(GetUserRolesCommand request, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RoleDto>> Handle(GetUserRolesCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -15,10 +16,6 @@ public class GetUserRolesHandler(IUnitOfWork uw): IRequestHandler<GetUserRolesCo
|
|||||||
|
|
||||||
var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken);
|
var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken);
|
||||||
|
|
||||||
return roles.Select(role => new RoleDto
|
return mapper.Map<IEnumerable<RoleDto>>(roles);
|
||||||
{
|
|
||||||
RoleId = role.Id,
|
|
||||||
RoleName = role.RoleName
|
|
||||||
}).ToList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Exceptions;
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Services;
|
using Imprink.Application.Services;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
@@ -7,7 +8,7 @@ namespace Imprink.Application.Domains.Users;
|
|||||||
|
|
||||||
public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest<UserDto?>;
|
public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest<UserDto?>;
|
||||||
|
|
||||||
public class SetUserFullNameHandler(IUnitOfWork uw, ICurrentUserService userService) : IRequestHandler<SetUserFullNameCommand, UserDto?>
|
public class SetUserFullNameHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler<SetUserFullNameCommand, UserDto?>
|
||||||
{
|
{
|
||||||
public async Task<UserDto?> Handle(SetUserFullNameCommand request, CancellationToken cancellationToken)
|
public async Task<UserDto?> Handle(SetUserFullNameCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -16,28 +17,19 @@ public class SetUserFullNameHandler(IUnitOfWork uw, ICurrentUserService userServ
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var currentUser = userService.GetCurrentUserId();
|
var currentUser = userService.GetCurrentUserId();
|
||||||
|
|
||||||
if (currentUser == null)
|
if (currentUser == null)
|
||||||
throw new NotFoundException("User token could not be accessed.");
|
throw new NotFoundException("User token could not be accessed.");
|
||||||
|
|
||||||
var user = await uw.UserRepository.SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken);
|
var user = await uw.UserRepository.SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new DataUpdateException("User name could not be updated.");
|
throw new DataUpdateException("User name could not be updated.");
|
||||||
|
|
||||||
await uw.SaveAsync(cancellationToken);
|
await uw.SaveAsync(cancellationToken);
|
||||||
await uw.CommitTransactionAsync(cancellationToken);
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new UserDto
|
return mapper.Map<UserDto>(user);
|
||||||
{
|
|
||||||
Id = user.Id,
|
|
||||||
Name = user.Name,
|
|
||||||
Nickname = user.Nickname,
|
|
||||||
Email = user.Email,
|
|
||||||
EmailVerified = user.EmailVerified,
|
|
||||||
FirstName = user.FirstName,
|
|
||||||
LastName = user.LastName,
|
|
||||||
PhoneNumber = user.PhoneNumber,
|
|
||||||
IsActive = user.IsActive
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Exceptions;
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Services;
|
using Imprink.Application.Services;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
@@ -7,7 +8,7 @@ namespace Imprink.Application.Domains.Users;
|
|||||||
|
|
||||||
public record SetUserPhoneCommand(string PhoneNumber) : IRequest<UserDto?>;
|
public record SetUserPhoneCommand(string PhoneNumber) : IRequest<UserDto?>;
|
||||||
|
|
||||||
public class SetUserPhoneHandler(IUnitOfWork uw, ICurrentUserService userService) : IRequestHandler<SetUserPhoneCommand, UserDto?>
|
public class SetUserPhoneHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler<SetUserPhoneCommand, UserDto?>
|
||||||
{
|
{
|
||||||
public async Task<UserDto?> Handle(SetUserPhoneCommand request, CancellationToken cancellationToken)
|
public async Task<UserDto?> Handle(SetUserPhoneCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -16,28 +17,19 @@ public class SetUserPhoneHandler(IUnitOfWork uw, ICurrentUserService userService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var currentUser = userService.GetCurrentUserId();
|
var currentUser = userService.GetCurrentUserId();
|
||||||
|
|
||||||
if (currentUser == null)
|
if (currentUser == null)
|
||||||
throw new NotFoundException("User token could not be accessed.");
|
throw new NotFoundException("User token could not be accessed.");
|
||||||
|
|
||||||
var user = await uw.UserRepository.SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken);
|
var user = await uw.UserRepository.SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new DataUpdateException("User phone could not be updated.");
|
throw new DataUpdateException("User phone could not be updated.");
|
||||||
|
|
||||||
await uw.SaveAsync(cancellationToken);
|
await uw.SaveAsync(cancellationToken);
|
||||||
await uw.CommitTransactionAsync(cancellationToken);
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new UserDto
|
return mapper.Map<UserDto>(user);
|
||||||
{
|
|
||||||
Id = user.Id,
|
|
||||||
Name = user.Name,
|
|
||||||
Nickname = user.Nickname,
|
|
||||||
Email = user.Email,
|
|
||||||
EmailVerified = user.EmailVerified,
|
|
||||||
FirstName = user.FirstName,
|
|
||||||
LastName = user.LastName,
|
|
||||||
PhoneNumber = user.PhoneNumber,
|
|
||||||
IsActive = user.IsActive
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Exceptions;
|
using Imprink.Application.Exceptions;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
using Imprink.Domain.Entities.Users;
|
using Imprink.Domain.Entities.Users;
|
||||||
@@ -7,7 +8,7 @@ namespace Imprink.Application.Domains.Users;
|
|||||||
|
|
||||||
public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;
|
public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest<UserRoleDto?>;
|
||||||
|
|
||||||
public class SetUserRoleHandler(IUnitOfWork uw) : IRequestHandler<SetUserRoleCommand, UserRoleDto?>
|
public class SetUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler<SetUserRoleCommand, UserRoleDto?>
|
||||||
{
|
{
|
||||||
public async Task<UserRoleDto?> Handle(SetUserRoleCommand request, CancellationToken cancellationToken)
|
public async Task<UserRoleDto?> Handle(SetUserRoleCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -29,11 +30,7 @@ public class SetUserRoleHandler(IUnitOfWork uw) : IRequestHandler<SetUserRoleCom
|
|||||||
await uw.SaveAsync(cancellationToken);
|
await uw.SaveAsync(cancellationToken);
|
||||||
await uw.CommitTransactionAsync(cancellationToken);
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new UserRoleDto
|
return mapper.Map<UserRoleDto>(addedRole);
|
||||||
{
|
|
||||||
UserId = addedRole.UserId,
|
|
||||||
RoleId = addedRole.RoleId
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Users.Dtos;
|
using Imprink.Application.Users.Dtos;
|
||||||
using Imprink.Domain.Models;
|
using Imprink.Domain.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -6,7 +7,7 @@ namespace Imprink.Application.Domains.Users;
|
|||||||
|
|
||||||
public record SyncUserCommand(Auth0User User) : IRequest<UserDto?>;
|
public record SyncUserCommand(Auth0User User) : IRequest<UserDto?>;
|
||||||
|
|
||||||
public class SyncUserHandler(IUnitOfWork uw): IRequestHandler<SyncUserCommand, UserDto?>
|
public class SyncUserHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler<SyncUserCommand, UserDto?>
|
||||||
{
|
{
|
||||||
public async Task<UserDto?> Handle(SyncUserCommand request, CancellationToken cancellationToken)
|
public async Task<UserDto?> Handle(SyncUserCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -16,23 +17,13 @@ public class SyncUserHandler(IUnitOfWork uw): IRequestHandler<SyncUserCommand, U
|
|||||||
{
|
{
|
||||||
var user = await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken);
|
var user = await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken);
|
||||||
|
|
||||||
if (user == null) throw new Exception("User exists but could not be updated");
|
if (user == null)
|
||||||
|
throw new Exception("User exists but could not be updated");
|
||||||
|
|
||||||
await uw.SaveAsync(cancellationToken);
|
await uw.SaveAsync(cancellationToken);
|
||||||
await uw.CommitTransactionAsync(cancellationToken);
|
await uw.CommitTransactionAsync(cancellationToken);
|
||||||
|
|
||||||
return new UserDto
|
return mapper.Map<UserDto>(user);
|
||||||
{
|
|
||||||
Id = user.Id,
|
|
||||||
Name = user.Name,
|
|
||||||
Nickname = user.Nickname,
|
|
||||||
Email = user.Email,
|
|
||||||
EmailVerified = user.EmailVerified,
|
|
||||||
FirstName = user.FirstName,
|
|
||||||
LastName = user.LastName,
|
|
||||||
PhoneNumber = user.PhoneNumber,
|
|
||||||
IsActive = user.IsActive
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,8 +19,4 @@
|
|||||||
<ProjectReference Include="..\Imprink.Domain\Imprink.Domain.csproj" />
|
<ProjectReference Include="..\Imprink.Domain\Imprink.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Domains\Orders\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
5
src/Imprink.Application/Mappings/MappingProfile.cs
Normal file
5
src/Imprink.Application/Mappings/MappingProfile.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
|
||||||
|
namespace Imprink.Application.Mappings;
|
||||||
|
|
||||||
|
public abstract class MappingProfile : Profile { }
|
||||||
42
src/Imprink.Application/Mappings/ProductMappingProfile.cs
Normal file
42
src/Imprink.Application/Mappings/ProductMappingProfile.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Imprink.Application.Domains.Products;
|
||||||
|
using Imprink.Application.Domains.ProductVariants;
|
||||||
|
using Imprink.Application.Products.Dtos;
|
||||||
|
using Imprink.Domain.Entities.Product;
|
||||||
|
|
||||||
|
namespace Imprink.Application.Mappings;
|
||||||
|
|
||||||
|
public class ProductMappingProfile: Profile
|
||||||
|
{
|
||||||
|
public ProductMappingProfile()
|
||||||
|
{
|
||||||
|
CreateMap<CreateProductVariantCommand, ProductVariant>()
|
||||||
|
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.ModifiedAt, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.ModifiedBy, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.Product, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.OrderItems, opt => opt.Ignore());
|
||||||
|
|
||||||
|
CreateMap<ProductVariant, ProductVariantDto>();
|
||||||
|
CreateMap<ProductVariantDto, ProductVariant>()
|
||||||
|
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.ModifiedBy, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.OrderItems, opt => opt.Ignore());
|
||||||
|
|
||||||
|
CreateMap<Product, ProductDto>();
|
||||||
|
|
||||||
|
CreateMap<CreateProductCommand, Product>()
|
||||||
|
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.ModifiedAt, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.ModifiedBy, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.Category, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.ProductVariants, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.OrderItems, opt => opt.Ignore());
|
||||||
|
|
||||||
|
CreateMap<Category, CategoryDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/Imprink.Application/Mappings/UserMappingProfile.cs
Normal file
42
src/Imprink.Application/Mappings/UserMappingProfile.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using AutoMapper;
|
||||||
|
using Imprink.Application.Users.Dtos;
|
||||||
|
using Imprink.Domain.Entities.Users;
|
||||||
|
using Imprink.Domain.Models;
|
||||||
|
|
||||||
|
namespace Imprink.Application.Mappings;
|
||||||
|
|
||||||
|
public class UserMappingProfile: Profile
|
||||||
|
{
|
||||||
|
public UserMappingProfile()
|
||||||
|
{
|
||||||
|
CreateMap<User, UserDto>()
|
||||||
|
.ForMember(dest => dest.DefaultAddress, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.Roles, opt => opt.Ignore());
|
||||||
|
|
||||||
|
CreateMap<UserDto, User>()
|
||||||
|
.ForMember(dest => dest.DefaultAddress, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.Roles, opt => opt.Ignore());
|
||||||
|
|
||||||
|
CreateMap<UserRole, UserRoleDto>();
|
||||||
|
CreateMap<UserRoleDto, UserRole>();
|
||||||
|
|
||||||
|
CreateMap<Role, RoleDto>()
|
||||||
|
.ForMember(dest => dest.RoleId, opt => opt.MapFrom(src => src.Id));
|
||||||
|
CreateMap<RoleDto, Role>()
|
||||||
|
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.RoleId))
|
||||||
|
.ForMember(dest => dest.UserRoles, opt => opt.Ignore());
|
||||||
|
|
||||||
|
CreateMap<ClaimsPrincipal, Auth0User>()
|
||||||
|
.ForMember(dest => dest.Sub, opt => opt.MapFrom(src =>
|
||||||
|
src.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)!.Value))
|
||||||
|
.ForMember(dest => dest.Name, opt => opt.MapFrom(src =>
|
||||||
|
src.Claims.FirstOrDefault(c => c.Type == "name")!.Value))
|
||||||
|
.ForMember(dest => dest.Nickname, opt => opt.MapFrom(src =>
|
||||||
|
src.Claims.FirstOrDefault(c => c.Type == "nickname")!.Value))
|
||||||
|
.ForMember(dest => dest.Email, opt => opt.MapFrom(src =>
|
||||||
|
src.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)!.Value))
|
||||||
|
.ForMember(dest => dest.EmailVerified, opt => opt.MapFrom(src =>
|
||||||
|
src.Claims.FirstOrDefault(c => c.Type == "email_verified")!.Value == "true"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using AutoMapper;
|
||||||
using Imprink.Application.Domains.Users;
|
using Imprink.Application.Domains.Users;
|
||||||
|
using Imprink.Application.Users.Dtos;
|
||||||
using Imprink.Domain.Models;
|
using Imprink.Domain.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -9,30 +11,21 @@ namespace Imprink.WebApi.Controllers.Users;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/api/users")]
|
[Route("/api/users")]
|
||||||
public class UsersController(IMediator mediator) : ControllerBase
|
public class UsersController(IMediator mediator, IMapper mapper) : ControllerBase
|
||||||
{
|
{
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPost("me/sync")]
|
[HttpPost("me/sync")]
|
||||||
public async Task<IActionResult> SyncMyProfile()
|
public async Task<IActionResult> SyncMyProfile()
|
||||||
{
|
{
|
||||||
var claims = User.Claims as Claim[] ?? User.Claims.ToArray();
|
var auth0User = mapper.Map<Auth0User>(User);
|
||||||
|
|
||||||
var auth0User = new Auth0User
|
|
||||||
{
|
|
||||||
Sub = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty,
|
|
||||||
Name = claims.FirstOrDefault(c => c.Type == "name")?.Value ?? string.Empty,
|
|
||||||
Nickname = claims.FirstOrDefault(c => c.Type == "nickname")?.Value ?? string.Empty,
|
|
||||||
Email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
|
|
||||||
EmailVerified = bool.TryParse(claims.FirstOrDefault(c => c.Type == "email_verified")?.Value, out var emailVerified) && emailVerified
|
|
||||||
};
|
|
||||||
|
|
||||||
await mediator.Send(new SyncUserCommand(auth0User));
|
await mediator.Send(new SyncUserCommand(auth0User));
|
||||||
return Ok("User profile synchronized.");
|
return Ok("Synced");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("me/roles")]
|
[HttpGet("me/roles")]
|
||||||
public async Task<IActionResult> GetMyRoles()
|
public async Task<ActionResult<IEnumerable<RoleDto>>> GetMyRoles()
|
||||||
{
|
{
|
||||||
var sub = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
|
var sub = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
|
||||||
return Ok(await mediator.Send(new GetUserRolesCommand(sub)));
|
return Ok(await mediator.Send(new GetUserRolesCommand(sub)));
|
||||||
@@ -40,21 +33,29 @@ public class UsersController(IMediator mediator) : ControllerBase
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPut("me/phone")]
|
[HttpPut("me/phone")]
|
||||||
public async Task<IActionResult> UpdateMyPhone([FromBody] SetUserPhoneCommand command)
|
public async Task<ActionResult<UserDto?>> UpdateMyPhone([FromBody] SetUserPhoneCommand command)
|
||||||
{
|
{
|
||||||
return Ok(await mediator.Send(command));
|
return Ok(await mediator.Send(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPut("me/fullname")]
|
[HttpPut("me/fullname")]
|
||||||
public async Task<IActionResult> UpdateMyFullName([FromBody] SetUserFullNameCommand command)
|
public async Task<ActionResult<UserDto?>> UpdateMyFullName([FromBody] SetUserFullNameCommand command)
|
||||||
{
|
{
|
||||||
return Ok(await mediator.Send(command));
|
return Ok(await mediator.Send(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("roles")]
|
||||||
|
public async Task<ActionResult<UserRoleDto?>> GetAllRoles()
|
||||||
|
{
|
||||||
|
var command = new GetAllRolesCommand();
|
||||||
|
return Ok(await mediator.Send(command));
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize(Roles = "Admin")]
|
[Authorize(Roles = "Admin")]
|
||||||
[HttpPut("{userId}/roles/{roleId:guid}")]
|
[HttpPut("{userId}/roles/{roleId:guid}")]
|
||||||
public async Task<IActionResult> AddUserRole(string userId, Guid roleId)
|
public async Task<ActionResult<UserRoleDto?>> AddUserRole(string userId, Guid roleId)
|
||||||
{
|
{
|
||||||
var command = new SetUserRoleCommand(userId, roleId);
|
var command = new SetUserRoleCommand(userId, roleId);
|
||||||
return Ok(await mediator.Send(command));
|
return Ok(await mediator.Send(command));
|
||||||
@@ -62,7 +63,7 @@ public class UsersController(IMediator mediator) : ControllerBase
|
|||||||
|
|
||||||
[Authorize(Roles = "Admin")]
|
[Authorize(Roles = "Admin")]
|
||||||
[HttpDelete("{userId}/roles/{roleId:guid}")]
|
[HttpDelete("{userId}/roles/{roleId:guid}")]
|
||||||
public async Task<IActionResult> RemoveUserRole(string userId, Guid roleId)
|
public async Task<ActionResult<UserRoleDto?>> RemoveUserRole(string userId, Guid roleId)
|
||||||
{
|
{
|
||||||
var command = new DeleteUserRoleCommand(userId, roleId);
|
var command = new DeleteUserRoleCommand(userId, roleId);
|
||||||
return Ok(await mediator.Send(command));
|
return Ok(await mediator.Send(command));
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public class ExceptionHandlingMiddleware(
|
|||||||
return exception switch
|
return exception switch
|
||||||
{
|
{
|
||||||
NotFoundException => (HttpStatusCode.NotFound, exception.Message, false),
|
NotFoundException => (HttpStatusCode.NotFound, exception.Message, false),
|
||||||
|
DataUpdateException => (HttpStatusCode.Conflict, exception.Message, false),
|
||||||
_ => (HttpStatusCode.InternalServerError, "An internal server error occurred", true)
|
_ => (HttpStatusCode.InternalServerError, "An internal server error occurred", true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,3 @@ var app = builder.Build();
|
|||||||
Startup.Configure(app, app.Environment);
|
Startup.Configure(app, app.Environment);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
public partial class Program { }
|
|
||||||
@@ -2,6 +2,7 @@ using System.Security.Claims;
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Imprink.Application;
|
using Imprink.Application;
|
||||||
using Imprink.Application.Domains.Products;
|
using Imprink.Application.Domains.Products;
|
||||||
|
using Imprink.Application.Mappings;
|
||||||
using Imprink.Application.Products;
|
using Imprink.Application.Products;
|
||||||
using Imprink.Application.Services;
|
using Imprink.Application.Services;
|
||||||
using Imprink.Application.Validation.Models;
|
using Imprink.Application.Validation.Models;
|
||||||
@@ -39,8 +40,9 @@ public static class Startup
|
|||||||
services.AddScoped<IOrderItemRepository, OrderItemRepository>();
|
services.AddScoped<IOrderItemRepository, OrderItemRepository>();
|
||||||
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
||||||
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||||
|
|
||||||
services.AddScoped<Seeder>();
|
services.AddScoped<Seeder>();
|
||||||
|
|
||||||
|
services.AddAutoMapper(typeof(MappingProfile).Assembly);
|
||||||
|
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
|
|||||||
@@ -1,423 +1,192 @@
|
|||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import {useEffect, useState} from 'react';
|
import { useUser } from "@auth0/nextjs-auth0";
|
||||||
import axios from 'axios';
|
import {useEffect, useState} from "react";
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
AppBar,
|
|
||||||
Badge,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardActions,
|
|
||||||
CardContent,
|
|
||||||
CardMedia,
|
|
||||||
Chip,
|
|
||||||
Container,
|
|
||||||
Fab,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Skeleton,
|
|
||||||
Toolbar,
|
|
||||||
Typography
|
|
||||||
} from '@mui/material';
|
|
||||||
import {createTheme, ThemeProvider} from '@mui/material/styles';
|
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
|
||||||
import {Menu, Palette, Search, ShoppingCart} from 'lucide-react';
|
|
||||||
|
|
||||||
const theme = createTheme({
|
export default function Home() {
|
||||||
palette: {
|
const { user, error, isLoading } = useUser();
|
||||||
mode: 'dark',
|
|
||||||
primary: {
|
|
||||||
main: '#D0BCFF',
|
|
||||||
light: '#EADDFF',
|
|
||||||
dark: '#9A82DB',
|
|
||||||
contrastText: '#21005D'
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
main: '#CCC2DC',
|
|
||||||
light: '#E8DEF8',
|
|
||||||
dark: '#A8A2BA',
|
|
||||||
contrastText: '#332D41'
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
default: '#101418',
|
|
||||||
paper: '#1D1B20'
|
|
||||||
},
|
|
||||||
surface: {
|
|
||||||
main: '#1D1B20',
|
|
||||||
variant: '#49454F'
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
main: '#F2B8B5',
|
|
||||||
light: '#FFDAD6',
|
|
||||||
dark: '#BA1A1A',
|
|
||||||
contrastText: '#410002'
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
main: '#A6D4A3',
|
|
||||||
light: '#C4F0B8',
|
|
||||||
dark: '#52B788'
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
primary: '#E6E0E9',
|
|
||||||
secondary: '#CAC4D0'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shape: {
|
|
||||||
borderRadius: 16
|
|
||||||
},
|
|
||||||
typography: {
|
|
||||||
fontFamily: '"Google Sans", "Roboto", "Helvetica", "Arial", sans-serif',
|
|
||||||
h4: {
|
|
||||||
fontWeight: 400,
|
|
||||||
fontSize: '2rem'
|
|
||||||
},
|
|
||||||
h6: {
|
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: '1.25rem'
|
|
||||||
},
|
|
||||||
body1: {
|
|
||||||
fontSize: '1rem',
|
|
||||||
lineHeight: 1.5
|
|
||||||
},
|
|
||||||
body2: {
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
lineHeight: 1.43
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
MuiCard: {
|
|
||||||
styleOverrides: {
|
|
||||||
root: {
|
|
||||||
backgroundImage: 'none',
|
|
||||||
backgroundColor: '#1D1B20',
|
|
||||||
border: '1px solid #49454F',
|
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
'&:hover': {
|
|
||||||
transform: 'translateY(-4px)',
|
|
||||||
borderColor: '#D0BCFF',
|
|
||||||
boxShadow: '0 8px 32px rgba(208, 188, 255, 0.1)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiButton: {
|
|
||||||
styleOverrides: {
|
|
||||||
root: {
|
|
||||||
textTransform: 'none',
|
|
||||||
fontWeight: 500,
|
|
||||||
borderRadius: 20,
|
|
||||||
paddingLeft: 24,
|
|
||||||
paddingRight: 24,
|
|
||||||
height: 40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiTextField: {
|
|
||||||
styleOverrides: {
|
|
||||||
root: {
|
|
||||||
'& .MuiOutlinedInput-root': {
|
|
||||||
borderRadius: 12,
|
|
||||||
backgroundColor: '#1D1B20',
|
|
||||||
'& fieldset': {
|
|
||||||
borderColor: '#49454F'
|
|
||||||
},
|
|
||||||
'&:hover fieldset': {
|
|
||||||
borderColor: '#CAC4D0'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MuiChip: {
|
|
||||||
styleOverrides: {
|
|
||||||
root: {
|
|
||||||
borderRadius: 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ProductCard = ({ product }) => {
|
|
||||||
const [imageLoaded, setImageLoaded] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
||||||
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
|
|
||||||
{!imageLoaded && (
|
|
||||||
<Skeleton variant="rectangular" height={200} />
|
|
||||||
)}
|
|
||||||
<CardMedia
|
|
||||||
component="img"
|
|
||||||
height="200"
|
|
||||||
image={product.imageUrl}
|
|
||||||
alt={product.name}
|
|
||||||
onLoad={() => setImageLoaded(true)}
|
|
||||||
sx={{
|
|
||||||
display: imageLoaded ? 'block' : 'none',
|
|
||||||
objectFit: 'cover',
|
|
||||||
transition: 'transform 0.3s ease',
|
|
||||||
'&:hover': {
|
|
||||||
transform: 'scale(1.05)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 12,
|
|
||||||
right: 12,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 1
|
|
||||||
}}>
|
|
||||||
{product.isCustomizable && (
|
|
||||||
<Chip
|
|
||||||
icon={<Palette size={16} />}
|
|
||||||
label="Customizable"
|
|
||||||
size="small"
|
|
||||||
color="primary"
|
|
||||||
sx={{ fontSize: '0.75rem' }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Chip
|
|
||||||
label={product.category.name}
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
sx={{ fontSize: '0.75rem' }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<CardContent sx={{ flexGrow: 1, pb: 1 }}>
|
|
||||||
<Typography variant="h6" component="h3" gutterBottom>
|
|
||||||
{product.name}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
||||||
{product.description}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h5" color="primary.main" fontWeight="bold">
|
|
||||||
${product.basePrice.toFixed(2)}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
|
|
||||||
<CardActions sx={{ p: 2, pt: 0 }}>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
startIcon={<ShoppingCart size={18} />}
|
|
||||||
sx={{
|
|
||||||
background: 'linear-gradient(45deg, #D0BCFF 30%, #EADDFF 90%)',
|
|
||||||
color: '#21005D'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add to Cart
|
|
||||||
</Button>
|
|
||||||
</CardActions>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const LoadingSkeleton = () => (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{[...Array(8)].map((_, index) => (
|
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
|
||||||
<Card>
|
|
||||||
<Skeleton variant="rectangular" height={200} />
|
|
||||||
<CardContent>
|
|
||||||
<Skeleton variant="text" height={32} />
|
|
||||||
<Skeleton variant="text" height={20} />
|
|
||||||
<Skeleton variant="text" height={20} width="60%" />
|
|
||||||
<Skeleton variant="text" height={28} width="40%" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ImprintLanding = () => {
|
|
||||||
const [products, setProducts] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
const [cartCount, setCartCount] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProducts = async () => {
|
const fetchAccessToken = async () => {
|
||||||
try {
|
if (user) {
|
||||||
setLoading(true);
|
try {
|
||||||
const response = await axios.get('https://impr.ink/api/products', {
|
await fetch('/token');
|
||||||
params: {
|
} catch (error) {
|
||||||
PageNumber: 1,
|
console.error("Error fetching token");
|
||||||
PageSize: 20
|
}
|
||||||
}
|
} else {
|
||||||
});
|
try {
|
||||||
setProducts(response.data.items);
|
await fetch('/untoken');
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
setError('Failed to load products. Please try again later.');
|
console.error('Error in /api/untoken:', e);
|
||||||
console.error('Error fetching products:', err);
|
}
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchProducts().then(r => console.log(r));
|
fetchAccessToken().then(r => console.log(r));
|
||||||
}, []);
|
}, [user]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 flex items-center justify-center">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-16 h-16 border-4 border-white/20 border-t-white rounded-full animate-spin"></div>
|
||||||
|
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<div className="w-10 h-10 border-4 border-transparent border-t-purple-400 rounded-full animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-red-900 via-pink-900 to-purple-900 flex items-center justify-center p-4">
|
||||||
|
<div className="bg-white/10 backdrop-blur-xl rounded-2xl p-6 border border-white/20 shadow-2xl">
|
||||||
|
<div className="text-white/80 mb-4">{error.message}</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<a
|
||||||
|
href="/auth/login"
|
||||||
|
className="group relative inline-flex items-center gap-2 px-8 py-3 bg-gradient-to-r from-purple-500 to-blue-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-purple-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gradient-to-r from-purple-600 to-blue-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<span className="relative flex items-center gap-2">
|
||||||
|
Sign In
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
onClick={() => checkValidity()}
|
||||||
|
className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-gradient-to-r from-red-600 to-pink-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<span className="relative flex items-center gap-2">
|
||||||
|
Check
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const featuredProducts = products.slice(0, 4);
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<div
|
||||||
<CssBaseline />
|
className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900 relative overflow-hidden">
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<div className="relative z-10 min-h-screen flex items-center justify-center p-4">
|
||||||
{/* App Bar */}
|
{user ? (
|
||||||
<AppBar position="sticky" sx={{ backgroundColor: 'background.paper', borderBottom: '1px solid #49454F' }}>
|
<div className="w-full max-w-5xl">
|
||||||
<Toolbar>
|
<div className="text-center mb-6">
|
||||||
<IconButton edge="start" color="inherit" sx={{ mr: 2 }}>
|
<div
|
||||||
<Menu />
|
className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full mb-3 shadow-2xl">
|
||||||
</IconButton>
|
{user.picture ? (
|
||||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1, color: 'primary.main', fontWeight: 'bold' }}>
|
<img
|
||||||
impr.ink
|
src={user.picture}
|
||||||
</Typography>
|
alt="Profile"
|
||||||
<IconButton color="inherit" sx={{ mr: 1 }}>
|
className="w-full h-full rounded-full object-cover border-3 border-white/20"
|
||||||
<Search />
|
/>
|
||||||
</IconButton>
|
) : (
|
||||||
<IconButton color="inherit">
|
<div className="text-white text-xl font-bold">
|
||||||
<Badge badgeContent={cartCount} color="primary">
|
{user.name?.charAt(0) || user.email?.charAt(0) || '👤'}
|
||||||
<ShoppingCart />
|
</div>
|
||||||
</Badge>
|
)}
|
||||||
</IconButton>
|
</div>
|
||||||
</Toolbar>
|
<h1 className="text-2xl pb-1 font-bold bg-gradient-to-r from-white via-purple-200 to-blue-200 bg-clip-text text-transparent">
|
||||||
</AppBar>
|
Just testing :P
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Hero Section */}
|
<div className="bg-white/10 backdrop-blur-xl rounded-2xl border border-white/20 shadow-2xl overflow-hidden mb-4">
|
||||||
<Box sx={{
|
<div className="bg-gradient-to-r from-purple-500/20 to-blue-500/20 p-4 border-b border-white/10">
|
||||||
background: 'linear-gradient(135deg, #1D1B20 0%, #2D1B69 50%, #1D1B20 100%)',
|
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
||||||
py: 8,
|
Auth Details
|
||||||
position: 'relative',
|
</h2>
|
||||||
overflow: 'hidden'
|
</div>
|
||||||
}}>
|
<div className="p-5">
|
||||||
<Container maxWidth="lg">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<Box sx={{ textAlign: 'center', position: 'relative', zIndex: 1 }}>
|
<div className="space-y-4">
|
||||||
<Typography variant="h2" component="h1" gutterBottom sx={{
|
<div>
|
||||||
fontWeight: 300,
|
<label
|
||||||
background: 'linear-gradient(45deg, #D0BCFF, #EADDFF)',
|
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">Name</label>
|
||||||
backgroundClip: 'text',
|
<div
|
||||||
WebkitBackgroundClip: 'text',
|
className="text-white text-base mt-1 p-2 bg-white/5 rounded-lg border border-white/10">
|
||||||
WebkitTextFillColor: 'transparent',
|
{user.name || 'Not provided'}
|
||||||
mb: 2
|
</div>
|
||||||
}}>
|
</div>
|
||||||
Custom Printing Made Beautiful
|
<div>
|
||||||
</Typography>
|
<label
|
||||||
<Typography variant="h5" color="text.secondary" sx={{ mb: 4, maxWidth: 600, mx: 'auto' }}>
|
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">Email</label>
|
||||||
High-quality custom printing solutions for caps, flyers, coasters, and more.
|
<div
|
||||||
Premium materials with excellent print adhesion and longevity.
|
className="text-white text-base mt-1 p-2 bg-white/5 rounded-lg border border-white/10">
|
||||||
</Typography>
|
{user.email || 'Not provided'}
|
||||||
<Button
|
</div>
|
||||||
variant="contained"
|
</div>
|
||||||
size="large"
|
<div>
|
||||||
sx={{
|
<label
|
||||||
background: 'linear-gradient(45deg, #D0BCFF 30%, #EADDFF 90%)',
|
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">User
|
||||||
color: '#21005D',
|
ID</label>
|
||||||
px: 4,
|
<div
|
||||||
py: 1.5,
|
className="text-white/80 text-xs mt-1 p-2 bg-white/5 rounded-lg border border-white/10 font-mono break-all">
|
||||||
fontSize: '1.1rem'
|
{user.sub || 'Not available'}
|
||||||
}}
|
</div>
|
||||||
|
</div>
|
||||||
|
{user.nickname && (
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
className="text-purple-300 text-xs font-semibold uppercase tracking-wider">Nickname</label>
|
||||||
|
<div
|
||||||
|
className="text-white text-base mt-1 p-2 bg-white/5 rounded-lg border border-white/10">
|
||||||
|
{user.nickname}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
className="text-purple-300 text-xs font-semibold uppercase tracking-wider mb-2 block">
|
||||||
|
Raw User Data
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="bg-black/30 rounded-lg p-3 border border-white/10 h-64 overflow-auto">
|
||||||
|
<pre
|
||||||
|
className="text-green-300 text-xs font-mono leading-tight whitespace-pre-wrap">
|
||||||
|
{JSON.stringify(user, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<a
|
||||||
|
onClick={() => checkValidity()}
|
||||||
|
className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
||||||
>
|
>
|
||||||
Explore Products
|
<div
|
||||||
</Button>
|
className="absolute inset-0 bg-gradient-to-r from-red-600 to-pink-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
</Box>
|
<span className="relative flex items-center gap-2">
|
||||||
</Container>
|
Check
|
||||||
</Box>
|
</span>
|
||||||
|
</a>
|
||||||
<Container maxWidth="lg" sx={{ py: 6 }}>
|
<a
|
||||||
{error && (
|
href="/auth/logout"
|
||||||
<Alert severity="error" sx={{ mb: 4 }}>
|
className="group relative px-6 py-3 bg-gradient-to-r from-red-500 to-pink-500 rounded-xl font-bold text-white shadow-2xl hover:shadow-red-500/25 transition-all duration-300 hover:scale-105 active:scale-95"
|
||||||
{error}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Featured Products */}
|
|
||||||
<Box sx={{ mb: 6 }}>
|
|
||||||
<Typography variant="h4" component="h2" gutterBottom sx={{ mb: 4, textAlign: 'center' }}>
|
|
||||||
Featured Products
|
|
||||||
</Typography>
|
|
||||||
{loading ? (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{[...Array(4)].map((_, index) => (
|
|
||||||
<Grid item xs={12} sm={6} md={3} key={index}>
|
|
||||||
<Card>
|
|
||||||
<Skeleton variant="rectangular" height={200} />
|
|
||||||
<CardContent>
|
|
||||||
<Skeleton variant="text" height={32} />
|
|
||||||
<Skeleton variant="text" height={20} />
|
|
||||||
<Skeleton variant="text" height={28} width="40%" />
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
) : (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{featuredProducts.map((product) => (
|
|
||||||
<Grid item xs={12} sm={6} md={3} key={product.id}>
|
|
||||||
<ProductCard product={product} />
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* All Products */}
|
|
||||||
<Box>
|
|
||||||
<Typography variant="h4" component="h2" gutterBottom sx={{ mb: 4, textAlign: 'center' }}>
|
|
||||||
All Products
|
|
||||||
</Typography>
|
|
||||||
{loading ? (
|
|
||||||
<LoadingSkeleton />
|
|
||||||
) : (
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
{products.map((product) => (
|
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={product.id}>
|
|
||||||
<ProductCard product={product} />
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Load More Button */}
|
|
||||||
{!loading && products.length > 0 && (
|
|
||||||
<Box sx={{ textAlign: 'center', mt: 6 }}>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
size="large"
|
|
||||||
sx={{ px: 4 }}
|
|
||||||
>
|
>
|
||||||
Load More Products
|
<div
|
||||||
</Button>
|
className="absolute inset-0 bg-gradient-to-r from-red-600 to-pink-600 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
</Box>
|
<span className="relative flex items-center gap-2">
|
||||||
)}
|
Sign Out
|
||||||
</Container>
|
</span>
|
||||||
|
</a>
|
||||||
{/* Floating Action Button */}
|
</div>
|
||||||
<Fab
|
</div>
|
||||||
color="primary"
|
) : (
|
||||||
sx={{
|
<div></div>
|
||||||
position: 'fixed',
|
)}
|
||||||
bottom: 16,
|
</div>
|
||||||
right: 16,
|
</div>
|
||||||
background: 'linear-gradient(45deg, #D0BCFF 30%, #EADDFF 90%)',
|
|
||||||
color: '#21005D'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Badge badgeContent={cartCount} color="error">
|
|
||||||
<ShoppingCart />
|
|
||||||
</Badge>
|
|
||||||
</Fab>
|
|
||||||
</Box>
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ImprintLanding;
|
|
||||||
Reference in New Issue
Block a user