Compare commits
92 Commits
countries_
...
front-styl
| Author | SHA1 | Date | |
|---|---|---|---|
| d115fec237 | |||
| 62aafdc11a | |||
| 5b6ee6d57a | |||
| 574e241c8a | |||
| 7bce04e05a | |||
| 9a90de3f11 | |||
|
|
754bf2fe01 | ||
|
|
2ca07f03ce | ||
| 6efb39edf7 | |||
| e9af4bf311 | |||
| cc570cc6a8 | |||
| 1bf706dcd0 | |||
| e335c3aa6f | |||
| 5ebf21c559 | |||
| 84b4c70ffb | |||
| 66df535317 | |||
| e31ecda582 | |||
| c97251c15b | |||
| 5663c4e126 | |||
| fa85c34794 | |||
|
|
e0a86febc4 | ||
| 1d257d82d3 | |||
|
|
40154ec861 | ||
|
|
bb507036db | ||
| 8e063978a8 | |||
| 31a2744131 | |||
| f55d59a0fd | |||
| 059808665f | |||
| c59428adaa | |||
| b54645830f | |||
| 1973189525 | |||
| 5d97d537cd | |||
| f1a2f4c0b2 | |||
| bfd20aaa7b | |||
| 1fb2a33cfd | |||
| 0d2bf3a27f | |||
| c7692bc817 | |||
| 8824796846 | |||
| 9eb8fc6625 | |||
| a290a72d1d | |||
| b0d0338d23 | |||
| 849b18c4db | |||
| a874a063d8 | |||
| 11dab263fa | |||
| 5bda48b94a | |||
| 2f4ccbaf92 | |||
| fb910b9ac6 | |||
| 4c505c0520 | |||
| 2f7e313c95 | |||
| 110946a924 | |||
| 3083330fcd | |||
| b7c4b6e3fd | |||
| e9016baf5b | |||
| 68f31952da | |||
| c1efcf1b86 | |||
| 5cab7573a8 | |||
| 5f5c3641f9 | |||
| d655e22173 | |||
| 5255ece1b4 | |||
| e82279926f | |||
| 855721b4b1 | |||
| 9fcecfb209 | |||
| ee375b7cbd | |||
| f58abda5cd | |||
| c20cdf2b63 | |||
| 2ce2556685 | |||
| e961967a49 | |||
|
|
a4eb3b52cf | ||
| 7c69d56a48 | |||
| 1494af985a | |||
| 9513feba37 | |||
| 551b5ef77d | |||
| a7f69c854a | |||
|
|
2bfe97f6fe | ||
| 7e8e9897e1 | |||
| 9c8ce43998 | |||
| 3652a78783 | |||
| ecf51e92e8 | |||
| d0c055a819 | |||
| 272c6066fb | |||
| 79730eb826 | |||
| 6846ffc0dc | |||
| d8f71bd8ff | |||
| aa57d38bd6 | |||
| f9ae1e491e | |||
| 8bf5a1cf8b | |||
| b48a143b40 | |||
| 729b54ca1a | |||
| 980fb1543b | |||
| b829bf2185 | |||
| d6066e39ce | |||
| 12f6249721 |
@@ -28,7 +28,7 @@ tmp_dir = "tmp"
|
|||||||
rerun = false
|
rerun = false
|
||||||
rerun_delay = 500
|
rerun_delay = 500
|
||||||
send_interrupt = false
|
send_interrupt = false
|
||||||
stop_on_error = false
|
stop_on_error = true
|
||||||
|
|
||||||
[color]
|
[color]
|
||||||
app = ""
|
app = ""
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ func CartsHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
handler := NewCartsHandler()
|
handler := NewCartsHandler()
|
||||||
|
|
||||||
r.Get("/add-new-cart", handler.AddNewCart)
|
r.Get("/add-new-cart", handler.AddNewCart)
|
||||||
|
r.Delete("/remove-cart", handler.RemoveCart)
|
||||||
r.Get("/change-cart-name", handler.ChangeCartName)
|
r.Get("/change-cart-name", handler.ChangeCartName)
|
||||||
r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo)
|
r.Get("/retrieve-carts-info", handler.RetrieveCartsInfo)
|
||||||
r.Get("/retrieve-cart", handler.RetrieveCart)
|
r.Get("/retrieve-cart", handler.RetrieveCart)
|
||||||
r.Get("/add-product-to-cart", handler.AddProduct)
|
r.Get("/add-product-to-cart", handler.AddProduct)
|
||||||
|
r.Delete("/remove-product-from-cart", handler.RemoveProduct)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -44,7 +46,8 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
new_cart, err := h.cartsService.CreateNewCart(userID)
|
name := c.Query("name")
|
||||||
|
new_cart, err := h.cartsService.CreateNewCart(userID, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
@@ -53,6 +56,29 @@ func (h *CartsHandler) AddNewCart(c fiber.Ctx) error {
|
|||||||
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&new_cart, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *CartsHandler) RemoveCart(c fiber.Ctx) error {
|
||||||
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.RemoveCart(userID, uint(cart_id))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
|
func (h *CartsHandler) ChangeCartName(c fiber.Ctx) error {
|
||||||
userID, ok := localeExtractor.GetUserID(c)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -117,6 +143,7 @@ func (h *CartsHandler) RetrieveCart(c fiber.Ctx) error {
|
|||||||
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(cart, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adds or sets given amount of products to the cart
|
||||||
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
|
func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
|
||||||
userID, ok := localeExtractor.GetUserID(c)
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -159,7 +186,59 @@ func (h *CartsHandler) AddProduct(c fiber.Ctx) error {
|
|||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, uint(amount))
|
set_amount_attribute := c.Query("set_amount")
|
||||||
|
set_amount, err := strconv.ParseBool(set_amount_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.AddProduct(userID, uint(cart_id), uint(product_id), product_attribute_id, amount, set_amount)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes product from the cart.
|
||||||
|
func (h *CartsHandler) RemoveProduct(c fiber.Ctx) error {
|
||||||
|
userID, ok := localeExtractor.GetUserID(c)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cart_id_attribute := c.Query("cart_id")
|
||||||
|
cart_id, err := strconv.Atoi(cart_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
product_id_attribute := c.Query("product_id")
|
||||||
|
product_id, err := strconv.Atoi(product_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
product_attribute_id_attribute := c.Query("product_attribute_id")
|
||||||
|
var product_attribute_id *uint
|
||||||
|
if product_attribute_id_attribute == "" {
|
||||||
|
product_attribute_id = nil
|
||||||
|
} else {
|
||||||
|
val, err := strconv.Atoi(product_attribute_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
uval := uint(val)
|
||||||
|
product_attribute_id = &uval
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.cartsService.RemoveProduct(userID, uint(cart_id), uint(product_id), product_attribute_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func CustomerHandlerRoutes(r fiber.Router) fiber.Router {
|
|||||||
|
|
||||||
r.Get("", handler.customerData)
|
r.Get("", handler.customerData)
|
||||||
r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers)
|
r.Get("/list", middleware.Require(perms.UserReadAny), handler.listCustomers)
|
||||||
|
r.Patch("/no-vat", middleware.Require(perms.UserWriteAny), handler.setCustomerNoVatStatus)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,3 +101,28 @@ var columnMappingListUsers map[string]string = map[string]string{
|
|||||||
"first_name": "users.first_name",
|
"first_name": "users.first_name",
|
||||||
"last_name": "users.last_name",
|
"last_name": "users.last_name",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *customerHandler) setCustomerNoVatStatus(fc fiber.Ctx) error {
|
||||||
|
user, ok := localeExtractor.GetCustomer(fc)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrInvalidBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrInvalidBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
CustomerID uint `json:"customer_id"`
|
||||||
|
IsNoVat bool `json:"is_no_vat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fc.Bind().Body(&req); err != nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(responseErrors.ErrJSONBody)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, responseErrors.ErrJSONBody)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.service.SetCustomerNoVatStatus(req.CustomerID, req.IsNoVat); err != nil {
|
||||||
|
return fc.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(fc, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fc.JSON(response.Make(nullable.GetNil(""), 0, i18n.T_(fc, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ var columnMappingListProducts map[string]string = map[string]string{
|
|||||||
"quantity": "bp.quantity",
|
"quantity": "bp.quantity",
|
||||||
"is_favorite": "bp.is_favorite",
|
"is_favorite": "bp.is_favorite",
|
||||||
"is_new": "bp.is_new",
|
"is_new": "bp.is_new",
|
||||||
|
"is_oem": "bp.is_oem",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
|
func (h *ProductsHandler) AddToFavorites(c fiber.Ctx) error {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type Customer struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
IsNoVat bool `gorm:"default:false" json:"is_no_vat"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Customer) HasPermission(permission perms.Permission) bool {
|
func (u *Customer) HasPermission(permission perms.Permission) bool {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ type ProductInList struct {
|
|||||||
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
|
PriceTaxExcl float64 `gorm:"column:price_tax_excl" json:"price_tax_excl"`
|
||||||
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
|
PriceTaxIncl float64 `gorm:"column:price_tax_incl" json:"price_tax_incl"`
|
||||||
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
||||||
IsNew uint `gorm:"column:is_new" json:"is_new"`
|
IsNew bool `gorm:"column:is_new" json:"is_new"`
|
||||||
|
IsOEM bool `gorm:"column:is_oem" json:"is_oem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductFilters struct {
|
type ProductFilters struct {
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
package cartsRepo
|
package cartsRepo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UICartsRepo interface {
|
type UICartsRepo interface {
|
||||||
CartsAmount(user_id uint) (uint, error)
|
CartsAmount(user_id uint) (uint, error)
|
||||||
CreateNewCart(user_id uint) (model.CustomerCart, error)
|
CreateNewCart(user_id uint, name string) (model.CustomerCart, error)
|
||||||
RemoveCart(user_id uint, cart_id uint) error
|
RemoveCart(user_id uint, cart_id uint) error
|
||||||
UserHasCart(user_id uint, cart_id uint) (bool, error)
|
UserHasCart(user_id uint, cart_id uint) (bool, error)
|
||||||
UpdateCartName(user_id uint, cart_id uint, new_name string) error
|
UpdateCartName(user_id uint, cart_id uint, new_name string) error
|
||||||
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
|
RetrieveCartsInfo(user_id uint) ([]model.CustomerCart, error)
|
||||||
RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error)
|
RetrieveCart(user_id uint, cart_id uint) (*model.CustomerCart, error)
|
||||||
CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error)
|
CheckProductExists(product_id uint, product_attribute_id *uint) (bool, error)
|
||||||
AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error
|
AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error
|
||||||
|
RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type CartsRepo struct{}
|
type CartsRepo struct{}
|
||||||
@@ -37,10 +42,7 @@ func (repo *CartsRepo) CartsAmount(user_id uint) (uint, error) {
|
|||||||
return amt, err
|
return amt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
func (repo *CartsRepo) CreateNewCart(user_id uint, name string) (model.CustomerCart, error) {
|
||||||
var name string
|
|
||||||
name = constdata.DEFAULT_NEW_CART_NAME
|
|
||||||
|
|
||||||
cart := model.CustomerCart{
|
cart := model.CustomerCart{
|
||||||
UserID: user_id,
|
UserID: user_id,
|
||||||
Name: &name,
|
Name: &name,
|
||||||
@@ -129,14 +131,61 @@ func (repo *CartsRepo) CheckProductExists(product_id uint, product_attribute_id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *CartsRepo) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
func (repo *CartsRepo) AddProduct(cart_id uint, product_id uint, product_attribute_id *uint, amount uint, set_amount bool) error {
|
||||||
product := model.CartProduct{
|
var product model.CartProduct
|
||||||
|
|
||||||
|
err := db.DB.
|
||||||
|
Where(&model.CartProduct{
|
||||||
|
CartID: cart_id,
|
||||||
|
ProductID: product_id,
|
||||||
|
ProductAttributeID: product_attribute_id,
|
||||||
|
}).
|
||||||
|
First(&product).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if amount < 1 {
|
||||||
|
return responseErrors.ErrAmountMustBePositive
|
||||||
|
} else if amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
|
||||||
|
return responseErrors.ErrAmountMustBeReasonable
|
||||||
|
}
|
||||||
|
|
||||||
|
product = model.CartProduct{
|
||||||
CartID: cart_id,
|
CartID: cart_id,
|
||||||
ProductID: product_id,
|
ProductID: product_id,
|
||||||
ProductAttributeID: product_attribute_id,
|
ProductAttributeID: product_attribute_id,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
}
|
}
|
||||||
err := db.DB.Create(&product).Error
|
|
||||||
|
|
||||||
|
return db.DB.Create(&product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some other DB error
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product already exists in cart
|
||||||
|
if set_amount {
|
||||||
|
product.Amount = amount
|
||||||
|
} else {
|
||||||
|
product.Amount = product.Amount + amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if product.Amount < 1 {
|
||||||
|
return responseErrors.ErrAmountMustBePositive
|
||||||
|
} else if product.Amount > constdata.MAX_AMOUNT_OF_PRODUCT_IN_CART {
|
||||||
|
return responseErrors.ErrAmountMustBeReasonable
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.DB.Save(&product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *CartsRepo) RemoveProduct(cart_id uint, product_id uint, product_attribute_id *uint) error {
|
||||||
|
return db.DB.
|
||||||
|
Where(&model.CartProduct{
|
||||||
|
CartID: cart_id,
|
||||||
|
ProductID: product_id,
|
||||||
|
ProductAttributeID: product_attribute_id,
|
||||||
|
}).
|
||||||
|
Delete(&model.CartProduct{}).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type UICustomerRepo interface {
|
|||||||
Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error)
|
Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error)
|
||||||
Save(customer *model.Customer) error
|
Save(customer *model.Customer) error
|
||||||
Create(customer *model.Customer) error
|
Create(customer *model.Customer) error
|
||||||
|
SetCustomerNoVatStatus(customerID uint, isNoVat bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerRepo struct{}
|
type CustomerRepo struct{}
|
||||||
@@ -114,3 +115,7 @@ func (repo *CustomerRepo) Save(customer *model.Customer) error {
|
|||||||
func (repo *CustomerRepo) Create(customer *model.Customer) error {
|
func (repo *CustomerRepo) Create(customer *model.Customer) error {
|
||||||
return db.DB.Create(customer).Error
|
return db.DB.Create(customer).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *CustomerRepo) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
|
||||||
|
return db.DB.Model(&model.Customer{}).Where("id = ?", customerID).Update("is_no_vat", isNoVat).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,6 +122,19 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
|
|||||||
Group("product_id"),
|
Group("product_id"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "oems",
|
||||||
|
Subquery: exclause.Subquery{
|
||||||
|
DB: db.DB.
|
||||||
|
Table("b2b_oems").
|
||||||
|
Select(`
|
||||||
|
product_id AS product_id,
|
||||||
|
COUNT(*) > 0 AS is_customers_oem
|
||||||
|
`).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Group("product_id"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "new_product_days",
|
Name: "new_product_days",
|
||||||
Subquery: exclause.Subquery{
|
Subquery: exclause.Subquery{
|
||||||
@@ -150,6 +163,7 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
|
|||||||
pl.name AS name,
|
pl.name AS name,
|
||||||
ps.id_category_default AS category_id,
|
ps.id_category_default AS category_id,
|
||||||
p.reference AS reference,
|
p.reference AS reference,
|
||||||
|
p.is_oem AS is_oem,
|
||||||
sa.quantity AS quantity,
|
sa.quantity AS quantity,
|
||||||
COALESCE(f.is_favorite, 0) AS is_favorite,
|
COALESCE(f.is_favorite, 0) AS is_favorite,
|
||||||
CASE
|
CASE
|
||||||
@@ -166,7 +180,9 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
|
|||||||
Joins("LEFT JOIN favorites f ON f.product_id = ps.id_product").
|
Joins("LEFT JOIN favorites f ON f.product_id = ps.id_product").
|
||||||
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0").
|
Joins("LEFT JOIN ps_stock_available sa ON sa.id_product = ps.id_product AND sa.id_product_attribute = 0").
|
||||||
Joins("LEFT JOIN new_product_days npd ON 1 = 1").
|
Joins("LEFT JOIN new_product_days npd ON 1 = 1").
|
||||||
|
Joins("LEFT JOIN oems ON oems.product_id = ps.id_product").
|
||||||
Where("ps.active = ?", 1).
|
Where("ps.active = ?", 1).
|
||||||
|
Where("(p.is_oem = 0 OR oems.is_customers_oem > 0)").
|
||||||
Group("ps.id_product"),
|
Group("ps.id_product"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -182,7 +198,8 @@ func (repo *ProductsRepo) Find(langID uint, userID uint, p find.Paging, filt *fi
|
|||||||
COALESCE(v.variants_number, 0) AS variants_number,
|
COALESCE(v.variants_number, 0) AS variants_number,
|
||||||
bp.quantity AS quantity,
|
bp.quantity AS quantity,
|
||||||
bp.is_favorite AS is_favorite,
|
bp.is_favorite AS is_favorite,
|
||||||
bp.is_new AS is_new
|
bp.is_new AS is_new,
|
||||||
|
bp.is_oem AS is_oem
|
||||||
`, config.Get().Image.ImagePrefix).
|
`, config.Get().Image.ImagePrefix).
|
||||||
Joins("JOIN ps_product_lang pl ON pl.id_product = bp.product_id AND pl.id_lang = ?", langID).
|
Joins("JOIN ps_product_lang pl ON pl.id_product = bp.product_id AND pl.id_lang = ?", langID).
|
||||||
Joins("JOIN ps_image_shop ims ON ims.id_product = bp.product_id AND ims.cover = 1").
|
Joins("JOIN ps_image_shop ims ON ims.id_product = bp.product_id AND ims.cover = 1").
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func New() *CartsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
func (s *CartsService) CreateNewCart(user_id uint, name string) (model.CustomerCart, error) {
|
||||||
var cart model.CustomerCart
|
var cart model.CustomerCart
|
||||||
|
|
||||||
customers_carts_amount, err := s.repo.CartsAmount(user_id)
|
customers_carts_amount, err := s.repo.CartsAmount(user_id)
|
||||||
@@ -28,8 +28,12 @@ func (s *CartsService) CreateNewCart(user_id uint) (model.CustomerCart, error) {
|
|||||||
return cart, responseErrors.ErrMaxAmtOfCartsReached
|
return cart, responseErrors.ErrMaxAmtOfCartsReached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
name = constdata.DEFAULT_NEW_CART_NAME
|
||||||
|
}
|
||||||
|
|
||||||
// create new cart for customer
|
// create new cart for customer
|
||||||
cart, err = s.repo.CreateNewCart(user_id)
|
cart, err = s.repo.CreateNewCart(user_id, name)
|
||||||
|
|
||||||
return cart, nil
|
return cart, nil
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,7 @@ func (s *CartsService) RetrieveCart(user_id uint, cart_id uint) (*model.Customer
|
|||||||
return s.repo.RetrieveCart(user_id, cart_id)
|
return s.repo.RetrieveCart(user_id, cart_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount uint) error {
|
func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint, amount int, set_amount bool) error {
|
||||||
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -91,5 +95,17 @@ func (s *CartsService) AddProduct(user_id uint, cart_id uint, product_id uint, p
|
|||||||
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
return responseErrors.ErrProductOrItsVariationDoesNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.repo.AddProduct(user_id, cart_id, product_id, product_attribute_id, amount)
|
return s.repo.AddProduct(cart_id, product_id, product_attribute_id, uint(amount), set_amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CartsService) RemoveProduct(user_id uint, cart_id uint, product_id uint, product_attribute_id *uint) error {
|
||||||
|
exists, err := s.repo.UserHasCart(user_id, cart_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return responseErrors.ErrUserHasNoSuchCart
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.RemoveProduct(cart_id, product_id, product_attribute_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,3 +24,7 @@ func (s *CustomerService) GetById(id uint) (*model.Customer, error) {
|
|||||||
func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) {
|
func (s *CustomerService) Find(langId uint, p find.Paging, filt *filters.FiltersList, search string) (*find.Found[model.UserInList], error) {
|
||||||
return s.repo.Find(langId, p, filt, search)
|
return s.repo.Find(langId, p, filt, search)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CustomerService) SetCustomerNoVatStatus(customerID uint, isNoVat bool) error {
|
||||||
|
return s.repo.SetCustomerNoVatStatus(customerID, isNoVat)
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,6 +153,6 @@ func (s *EmailService) newUserAdminNotificationTemplate(userEmail, userName, bas
|
|||||||
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
// newUserAdminNotificationTemplate returns the HTML template for admin notification
|
||||||
func (s *EmailService) newOrderPlacedTemplate(userID uint) string {
|
func (s *EmailService) newOrderPlacedTemplate(userID uint) string {
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
emails.EmailNewOrderPlacedWrapper(view.EmailLayout[view.EmailNewOrderPlacedData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailNewOrderPlacedData{UserID: userID}}).Render(context.Background(), &buf)
|
// emails.EmailNewOrderPlacedWrapper(view.EmailLayout[view.EmailNewOrderPlacedData]{LangID: constdata.ADMIN_NOTIFICATION_LANGUAGE, Data: view.EmailNewOrderPlacedData{UserID: userID}}).Render(context.Background(), &buf)
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,21 +231,54 @@ func (s *MenuService) GetTopMenu(languageId uint, roleId uint) ([]*model.B2BTopM
|
|||||||
|
|
||||||
func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) {
|
func (s *MenuService) appendAdditional(all_categories *[]model.ScannedCategory, id_lang uint, iso_code string) {
|
||||||
for i := 0; i < len(*all_categories); i++ {
|
for i := 0; i < len(*all_categories); i++ {
|
||||||
(*all_categories)[i].Filter = "category_id_in=" + strconv.Itoa(int((*all_categories)[i].CategoryID))
|
(*all_categories)[i].Filter = "category_id_eq=" + strconv.Itoa(int((*all_categories)[i].CategoryID))
|
||||||
}
|
}
|
||||||
|
|
||||||
var additional model.ScannedCategory
|
// the new products category
|
||||||
additional.CategoryID = 10001
|
var new_products_category model.ScannedCategory
|
||||||
additional.Name = "New Products"
|
new_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 1
|
||||||
additional.Active = 1
|
new_products_category.Name = "New Products"
|
||||||
additional.Position = 10
|
new_products_category.Active = 1
|
||||||
additional.ParentID = 2
|
new_products_category.Position = 10
|
||||||
additional.IsRoot = 0
|
new_products_category.ParentID = 2
|
||||||
additional.LinkRewrite = i18n.T___(id_lang, "category.new_products")
|
new_products_category.IsRoot = 0
|
||||||
additional.IsoCode = iso_code
|
new_products_category.LinkRewrite = i18n.T___(id_lang, "category.new_products")
|
||||||
|
new_products_category.IsoCode = iso_code
|
||||||
|
|
||||||
additional.Visited = false
|
new_products_category.Visited = false
|
||||||
additional.Filter = "is_new_in=true"
|
new_products_category.Filter = "is_new_eq=true"
|
||||||
|
|
||||||
*all_categories = append(*all_categories, additional)
|
*all_categories = append(*all_categories, new_products_category)
|
||||||
|
|
||||||
|
// the oem products category
|
||||||
|
var oem_products_category model.ScannedCategory
|
||||||
|
oem_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 2
|
||||||
|
oem_products_category.Name = "OEM Products"
|
||||||
|
oem_products_category.Active = 1
|
||||||
|
oem_products_category.Position = 11
|
||||||
|
oem_products_category.ParentID = 2
|
||||||
|
oem_products_category.IsRoot = 0
|
||||||
|
oem_products_category.LinkRewrite = i18n.T___(id_lang, "category.oem_products")
|
||||||
|
oem_products_category.IsoCode = iso_code
|
||||||
|
|
||||||
|
oem_products_category.Visited = false
|
||||||
|
oem_products_category.Filter = "is_oem_eq=true"
|
||||||
|
|
||||||
|
*all_categories = append(*all_categories, oem_products_category)
|
||||||
|
|
||||||
|
// the favorite products category
|
||||||
|
var favorite_products_category model.ScannedCategory
|
||||||
|
favorite_products_category.CategoryID = constdata.ADDITIONAL_CATEGORIES_INDEX + 3
|
||||||
|
favorite_products_category.Name = "Favourite Products" // British English version.
|
||||||
|
favorite_products_category.Active = 1
|
||||||
|
favorite_products_category.Position = 12
|
||||||
|
favorite_products_category.ParentID = 2
|
||||||
|
favorite_products_category.IsRoot = 0
|
||||||
|
favorite_products_category.LinkRewrite = i18n.T___(id_lang, "category.favorite_products")
|
||||||
|
favorite_products_category.IsoCode = iso_code
|
||||||
|
|
||||||
|
favorite_products_category.Visited = false
|
||||||
|
favorite_products_category.Filter = "is_favorite_eq=true"
|
||||||
|
|
||||||
|
*all_categories = append(*all_categories, favorite_products_category)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -110,8 +111,9 @@ func (s *ProductTranslationService) SaveProductDescription(userID uint, productI
|
|||||||
// check that fields description, description_short and usage, if they exist, have a valid html format
|
// check that fields description, description_short and usage, if they exist, have a valid html format
|
||||||
mustBeHTML := []string{"description", "description_short", "usage"}
|
mustBeHTML := []string{"description", "description_short", "usage"}
|
||||||
for i := 0; i < len(mustBeHTML); i++ {
|
for i := 0; i < len(mustBeHTML); i++ {
|
||||||
if text, exists := updates[mustBeHTML[i]]; exists {
|
if _, exists := updates[mustBeHTML[i]]; exists {
|
||||||
if !isValidXHTML(text) {
|
updates[mustBeHTML[i]] = parseAutoCloseTags(updates[mustBeHTML[i]])
|
||||||
|
if !isValidXHTML(updates[mustBeHTML[i]]) {
|
||||||
return responseErrors.ErrInvalidXHTML
|
return responseErrors.ErrInvalidXHTML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,9 +266,17 @@ func cleanForPrompt(s string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if slices.Contains(xml.HTMLAutoClose, v.Name.Local) {
|
||||||
|
prompt += "/>"
|
||||||
|
} else {
|
||||||
prompt += ">"
|
prompt += ">"
|
||||||
|
}
|
||||||
|
|
||||||
case xml.EndElement:
|
case xml.EndElement:
|
||||||
|
if !slices.Contains(xml.HTMLAutoClose, v.Name.Local) {
|
||||||
prompt += "</" + attrName(v.Name) + ">"
|
prompt += "</" + attrName(v.Name) + ">"
|
||||||
|
}
|
||||||
|
|
||||||
case xml.CharData:
|
case xml.CharData:
|
||||||
prompt += string(v)
|
prompt += string(v)
|
||||||
case xml.Comment:
|
case xml.Comment:
|
||||||
@@ -307,6 +317,43 @@ func getStringInBetween(str string, start string, end string) (success bool, res
|
|||||||
return true, str[s : s+e]
|
return true, str[s : s+e]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this converts input into HTML4 format.
|
||||||
|
// this really is ad-hoc solution, but it works.
|
||||||
|
func parseAutoCloseTags(s string) string {
|
||||||
|
alts := ""
|
||||||
|
for i, name := range xml.HTMLAutoClose {
|
||||||
|
if i > 0 {
|
||||||
|
alts += "|"
|
||||||
|
}
|
||||||
|
alts += name
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove closing </img> tags
|
||||||
|
reClose := regexp.MustCompile(`(?i)<\s*\/\s*(?:` + alts + `)\s*>`)
|
||||||
|
s = reClose.ReplaceAllString(s, "")
|
||||||
|
|
||||||
|
// convert <img ...> → <img ... />
|
||||||
|
// matches <img ...> that do NOT already end with />
|
||||||
|
reOpen := regexp.MustCompile(`(?i)<\s*(` + alts + `)\b([^>]*?)>`)
|
||||||
|
s = reOpen.ReplaceAllStringFunc(s, func(tag string) string {
|
||||||
|
trimmed := strings.TrimSpace(tag)
|
||||||
|
|
||||||
|
// Already self-closed: <img ... />, <br/>
|
||||||
|
if strings.HasSuffix(trimmed, "/>") {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace final > with />
|
||||||
|
i := strings.LastIndex(tag, ">")
|
||||||
|
if i < 0 {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
return tag[:i] + " />"
|
||||||
|
})
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// isValidXHTML checks if the string obeys the XHTML format
|
// isValidXHTML checks if the string obeys the XHTML format
|
||||||
func isValidXHTML(s string) bool {
|
func isValidXHTML(s string) bool {
|
||||||
r := strings.NewReader(s)
|
r := strings.NewReader(s)
|
||||||
@@ -382,7 +429,12 @@ func rebuildFromResponse(s_original string, s_response string) (bool, string) {
|
|||||||
result += fmt.Sprintf(` %s="%s"`, attrName(attr.Name), attr.Value)
|
result += fmt.Sprintf(` %s="%s"`, attrName(attr.Name), attr.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if slices.Contains(xml.HTMLAutoClose, v_original.Name.Local) {
|
||||||
|
result += "/>"
|
||||||
|
} else {
|
||||||
result += ">"
|
result += ">"
|
||||||
|
}
|
||||||
|
|
||||||
case xml.CharData:
|
case xml.CharData:
|
||||||
result += string(v_response)
|
result += string(v_response)
|
||||||
@@ -400,7 +452,7 @@ func rebuildFromResponse(s_original string, s_response string) (bool, string) {
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if v_original.Name.Local != "img" {
|
if !slices.Contains(xml.HTMLAutoClose, v_original.Name.Local) {
|
||||||
result += "</" + attrName(v_original.Name) + ">"
|
result += "</" + attrName(v_original.Name) + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ const ADMIN_NOTIFICATION_LANGUAGE = 2
|
|||||||
|
|
||||||
// CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1
|
// CATEGORY_TREE_ROOT_ID corresponds to id_category in ps_category which has is_root_category=1
|
||||||
const CATEGORY_TREE_ROOT_ID = 2
|
const CATEGORY_TREE_ROOT_ID = 2
|
||||||
|
const ADDITIONAL_CATEGORIES_INDEX = 10000
|
||||||
|
|
||||||
// since arrays can not be const
|
// since arrays can not be const
|
||||||
var CATEGORY_BLACKLIST = []uint{250}
|
var CATEGORY_BLACKLIST = []uint{250}
|
||||||
|
|
||||||
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
||||||
const DEFAULT_NEW_CART_NAME = "new cart"
|
const DEFAULT_NEW_CART_NAME = "new cart"
|
||||||
|
const MAX_AMOUNT_OF_PRODUCT_IN_CART = 1024
|
||||||
|
|
||||||
const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
|
const MAX_AMOUNT_OF_ADDRESSES_PER_USER = 10
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ var (
|
|||||||
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
||||||
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
ErrUserHasNoSuchCart = errors.New("user does not have cart with given id")
|
||||||
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
|
ErrProductOrItsVariationDoesNotExist = errors.New("product or its variation with given ids does not exist")
|
||||||
|
ErrAmountMustBePositive = errors.New("amount must be positive")
|
||||||
|
ErrAmountMustBeReasonable = errors.New("amount must be reasonable")
|
||||||
|
|
||||||
// Typed errors for orders handler
|
// Typed errors for orders handler
|
||||||
ErrEmptyCart = errors.New("the cart is empty")
|
ErrEmptyCart = errors.New("the cart is empty")
|
||||||
@@ -205,6 +207,10 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
return i18n.T_(c, "error.err_user_has_no_such_cart")
|
return i18n.T_(c, "error.err_user_has_no_such_cart")
|
||||||
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
case errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
||||||
return i18n.T_(c, "error.err_product_or_its_variation_does_not_exist")
|
return i18n.T_(c, "error.err_product_or_its_variation_does_not_exist")
|
||||||
|
case errors.Is(err, ErrAmountMustBePositive):
|
||||||
|
return i18n.T_(c, "error.err_amount_must_be_positive")
|
||||||
|
case errors.Is(err, ErrAmountMustBeReasonable):
|
||||||
|
return i18n.T_(c, "error.err_amount_must_be_reasonable")
|
||||||
|
|
||||||
case errors.Is(err, ErrEmptyCart):
|
case errors.Is(err, ErrEmptyCart):
|
||||||
return i18n.T_(c, "error.err_cart_is_empty")
|
return i18n.T_(c, "error.err_cart_is_empty")
|
||||||
@@ -292,6 +298,8 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrMaxAmtOfCartsReached),
|
errors.Is(err, ErrMaxAmtOfCartsReached),
|
||||||
errors.Is(err, ErrUserHasNoSuchCart),
|
errors.Is(err, ErrUserHasNoSuchCart),
|
||||||
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
|
errors.Is(err, ErrProductOrItsVariationDoesNotExist),
|
||||||
|
errors.Is(err, ErrAmountMustBePositive),
|
||||||
|
errors.Is(err, ErrAmountMustBeReasonable),
|
||||||
errors.Is(err, ErrEmptyCart),
|
errors.Is(err, ErrEmptyCart),
|
||||||
errors.Is(err, ErrUserHasNoSuchOrder),
|
errors.Is(err, ErrUserHasNoSuchOrder),
|
||||||
errors.Is(err, ErrInvalidReductionType),
|
errors.Is(err, ErrInvalidReductionType),
|
||||||
|
|||||||
@@ -95,4 +95,6 @@ type Product struct {
|
|||||||
Category string `gorm:"column:category" json:"category"`
|
Category string `gorm:"column:category" json:"category"`
|
||||||
|
|
||||||
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
IsFavorite bool `gorm:"column:is_favorite" json:"is_favorite"`
|
||||||
|
IsOEM bool `gorm:"column:is_oem" json:"is_oem"`
|
||||||
|
IsNew bool `gorm:"column:is_new" json:"is_new"`
|
||||||
}
|
}
|
||||||
|
|||||||
55
bo/auto-imports.d.ts
vendored
55
bo/auto-imports.d.ts
vendored
@@ -7,10 +7,10 @@
|
|||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const avatarGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').avatarGroupInjectionKey
|
const avatarGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').avatarGroupInjectionKey
|
||||||
const defineLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale.js').defineLocale
|
const defineLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale').defineLocale
|
||||||
const defineShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js').defineShortcuts
|
const defineShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts').defineShortcuts
|
||||||
const extendLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale.js').extendLocale
|
const extendLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineLocale').extendLocale
|
||||||
const extractShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.js').extractShortcuts
|
const extractShortcuts: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts').extractShortcuts
|
||||||
const fieldGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').fieldGroupInjectionKey
|
const fieldGroupInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').fieldGroupInjectionKey
|
||||||
const formBusInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formBusInjectionKey
|
const formBusInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formBusInjectionKey
|
||||||
const formErrorsInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formErrorsInjectionKey
|
const formErrorsInjectionKey: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').formErrorsInjectionKey
|
||||||
@@ -29,46 +29,17 @@ declare global {
|
|||||||
const useAvatarGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').useAvatarGroup
|
const useAvatarGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useAvatarGroup.js').useAvatarGroup
|
||||||
const useComponentIcons: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.js').useComponentIcons
|
const useComponentIcons: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.js').useComponentIcons
|
||||||
const useComponentUI: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.js').useComponentUI
|
const useComponentUI: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.js').useComponentUI
|
||||||
const useContentSearch: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useContentSearch.js').useContentSearch
|
const useContentSearch: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useContentSearch').useContentSearch
|
||||||
const useEditorMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.js').useEditorMenu
|
const useEditorMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.js').useEditorMenu
|
||||||
const useFieldGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').useFieldGroup
|
const useFieldGroup: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFieldGroup.js').useFieldGroup
|
||||||
const useFileUpload: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.js').useFileUpload
|
const useFileUpload: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload').useFileUpload
|
||||||
const useFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField.js').useFormField
|
const useFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useFormField').useFormField
|
||||||
const useKbd: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useKbd.js').useKbd
|
const useKbd: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useKbd').useKbd
|
||||||
const useLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useLocale.js').useLocale
|
const useLocale: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useLocale.js').useLocale
|
||||||
const useOverlay: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.js').useOverlay
|
const useOverlay: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useOverlay').useOverlay
|
||||||
const usePortal: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/usePortal.js').usePortal
|
const usePortal: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/usePortal.js').usePortal
|
||||||
const useResizable: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useResizable.js').useResizable
|
const useResizable: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useResizable').useResizable
|
||||||
const useScrollspy: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useScrollspy.js').useScrollspy
|
const useScrollShadow: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useScrollShadow').useScrollShadow
|
||||||
const useToast: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useToast.js').useToast
|
const useScrollspy: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useScrollspy').useScrollspy
|
||||||
}
|
const useToast: typeof import('./node_modules/@nuxt/ui/dist/runtime/composables/useToast').useToast
|
||||||
// for type re-export
|
|
||||||
declare global {
|
|
||||||
// @ts-ignore
|
|
||||||
export type { ShortcutConfig, ShortcutsConfig, ShortcutsOptions } from './node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/defineShortcuts.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { UseComponentIconsProps } from './node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentIcons.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { ThemeUI, ThemeRootContext } from './node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useComponentUI.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { EditorMenuOptions } from './node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useEditorMenu.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { UseFileUploadOptions } from './node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useFileUpload.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { KbdKey, KbdKeySpecific } from './node_modules/@nuxt/ui/dist/runtime/composables/useKbd.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useKbd.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { OverlayOptions, Overlay } from './node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useOverlay.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { UseResizableProps, UseResizableReturn } from './node_modules/@nuxt/ui/dist/runtime/composables/useResizable.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useResizable.d')
|
|
||||||
// @ts-ignore
|
|
||||||
export type { Toast } from './node_modules/@nuxt/ui/dist/runtime/composables/useToast.d'
|
|
||||||
import('./node_modules/@nuxt/ui/dist/runtime/composables/useToast.d')
|
|
||||||
}
|
}
|
||||||
|
|||||||
392
bo/bun.lock
392
bo/bun.lock
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 1,
|
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bo",
|
"name": "bo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.5.0",
|
"@nuxt/ui": "^4.6.0",
|
||||||
"@tailwindcss/vite": "^4.2.0",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwindcss": "^4.2.0",
|
"reka-ui": "^2.9.3",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
"vue": "beta",
|
"vue": "beta",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
"vue-i18n": "11",
|
"vue-i18n": "^11.3.0",
|
||||||
"vue-router": "^5.0.2",
|
"vue-router": "^5.0.4",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/node": "^24.10.13",
|
"@types/node": "^24.12.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.5",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "beta",
|
"vite": "beta",
|
||||||
"vite-plugin-vue-devtools": "^8.0.6",
|
"vite-plugin-vue-devtools": "^8.1.1",
|
||||||
"vue-tsc": "^3.2.4",
|
"vue-tsc": "^3.2.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -88,9 +88,9 @@
|
|||||||
|
|
||||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||||
|
|
||||||
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
|
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
|
||||||
|
|
||||||
@@ -114,63 +114,63 @@
|
|||||||
|
|
||||||
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
|
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||||
|
|
||||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
|
||||||
|
|
||||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
|
||||||
|
|
||||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
|
||||||
|
|
||||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
|
||||||
|
|
||||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
|
||||||
|
|
||||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
|
||||||
|
|
||||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
|
||||||
|
|
||||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
|
||||||
|
|
||||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
|
||||||
|
|
||||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
|
||||||
|
|
||||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
|
||||||
|
|
||||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
|
||||||
|
|
||||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
|
||||||
|
|
||||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
|
||||||
|
|
||||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
|
||||||
|
|
||||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
|
||||||
|
|
||||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
|
||||||
|
|
||||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
|
||||||
|
|
||||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
|
||||||
|
|
||||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
|
||||||
|
|
||||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
|
|
||||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||||
|
|
||||||
"@iconify/collections": ["@iconify/collections@1.0.659", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-gD9i7zfQpr3ZAv1BywaEri8o008KQT9iYaMw5EY4Y/y0qeEZB12FJMMyJGS3NvSeG80udu4kx8aZZLRsAdcANw=="],
|
"@iconify/collections": ["@iconify/collections@1.0.668", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-d4ygrDoTqb02p7YNHdNzy64y4VQG0bd3w26EH+mJ0eKAj+DSWxDJ1Qw/WdCAqhKfjb+mRp1n9+YwcELs2YWVlg=="],
|
||||||
|
|
||||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
|
|
||||||
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
|
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -242,17 +242,17 @@
|
|||||||
|
|
||||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||||
|
|
||||||
"@nuxt/devtools-kit": ["@nuxt/devtools-kit@3.2.3", "", { "dependencies": { "@nuxt/kit": "^4.3.1", "execa": "^8.0.1" }, "peerDependencies": { "vite": ">=6.0" } }, "sha512-5zj7Xx5CDI6P84kMalXoxGLd470buF6ncsRhiEPq8UlwdpVeR7bwi8QnparZNFBdG79bZ5KUkfi5YDXpLYPoIA=="],
|
"@nuxt/devtools-kit": ["@nuxt/devtools-kit@3.2.4", "", { "dependencies": { "@nuxt/kit": "^4.4.2", "execa": "^8.0.1" }, "peerDependencies": { "vite": ">=6.0" } }, "sha512-Yxy2Xgmq5hf3dQy983V0xh0OJV2mYwRZz9eVIGc3EaribdFGPDNGMMbYqX9qCty3Pbxn/bCF3J0UyPaNlHVayQ=="],
|
||||||
|
|
||||||
"@nuxt/fonts": ["@nuxt/fonts@0.14.0", "", { "dependencies": { "@nuxt/devtools-kit": "^3.2.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "defu": "^6.1.4", "fontless": "^0.2.1", "h3": "^1.15.5", "magic-regexp": "^0.10.0", "ofetch": "^1.5.1", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unifont": "^0.7.4", "unplugin": "^3.0.0", "unstorage": "^1.17.4" } }, "sha512-4uXQl9fa5F4ibdgU8zomoOcyMdnwgdem+Pi8JEqeDYI5yPR32Kam6HnuRr47dTb97CstaepAvXPWQUUHMtjsFQ=="],
|
"@nuxt/fonts": ["@nuxt/fonts@0.14.0", "", { "dependencies": { "@nuxt/devtools-kit": "^3.2.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "defu": "^6.1.4", "fontless": "^0.2.1", "h3": "^1.15.5", "magic-regexp": "^0.10.0", "ofetch": "^1.5.1", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unifont": "^0.7.4", "unplugin": "^3.0.0", "unstorage": "^1.17.4" } }, "sha512-4uXQl9fa5F4ibdgU8zomoOcyMdnwgdem+Pi8JEqeDYI5yPR32Kam6HnuRr47dTb97CstaepAvXPWQUUHMtjsFQ=="],
|
||||||
|
|
||||||
"@nuxt/icon": ["@nuxt/icon@2.2.1", "", { "dependencies": { "@iconify/collections": "^1.0.641", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0", "@iconify/vue": "^5.0.0", "@nuxt/devtools-kit": "^3.1.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "local-pkg": "^1.1.2", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinyglobby": "^0.2.15" } }, "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw=="],
|
"@nuxt/icon": ["@nuxt/icon@2.2.1", "", { "dependencies": { "@iconify/collections": "^1.0.641", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0", "@iconify/vue": "^5.0.0", "@nuxt/devtools-kit": "^3.1.1", "@nuxt/kit": "^4.2.2", "consola": "^3.4.2", "local-pkg": "^1.1.2", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinyglobby": "^0.2.15" } }, "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw=="],
|
||||||
|
|
||||||
"@nuxt/kit": ["@nuxt/kit@4.3.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA=="],
|
"@nuxt/kit": ["@nuxt/kit@4.4.2", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "mlly": "^1.8.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog=="],
|
||||||
|
|
||||||
"@nuxt/schema": ["@nuxt/schema@4.3.1", "", { "dependencies": { "@vue/shared": "^3.5.27", "defu": "^6.1.4", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "std-env": "^3.10.0" } }, "sha512-S+wHJdYDuyk9I43Ej27y5BeWMZgi7R/UVql3b3qtT35d0fbpXW7fUenzhLRCCDC6O10sjguc6fcMcR9sMKvV8g=="],
|
"@nuxt/schema": ["@nuxt/schema@4.4.2", "", { "dependencies": { "@vue/shared": "^3.5.30", "defu": "^6.1.4", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "std-env": "^4.0.0" } }, "sha512-/q6C7Qhiricgi+PKR7ovBnJlKTL0memCbA1CzRT+itCW/oeYzUfeMdQ35mGntlBoyRPNrMXbzuSUhfDbSCU57w=="],
|
||||||
|
|
||||||
"@nuxt/ui": ["@nuxt/ui@4.5.1", "", { "dependencies": { "@floating-ui/dom": "^1.7.5", "@iconify/vue": "^5.0.0", "@internationalized/date": "^3.11.0", "@internationalized/number": "^3.6.5", "@nuxt/fonts": "^0.14.0", "@nuxt/icon": "^2.2.1", "@nuxt/kit": "^4.3.1", "@nuxt/schema": "^4.3.1", "@nuxtjs/color-mode": "^3.5.2", "@standard-schema/spec": "^1.1.0", "@tailwindcss/postcss": "^4.2.1", "@tailwindcss/vite": "^4.2.1", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.19", "@tiptap/core": "^3.20.0", "@tiptap/extension-bubble-menu": "^3.20.0", "@tiptap/extension-code": "^3.20.0", "@tiptap/extension-collaboration": "^3.20.0", "@tiptap/extension-drag-handle": "^3.20.0", "@tiptap/extension-drag-handle-vue-3": "^3.20.0", "@tiptap/extension-floating-menu": "^3.20.0", "@tiptap/extension-horizontal-rule": "^3.20.0", "@tiptap/extension-image": "^3.20.0", "@tiptap/extension-mention": "^3.20.0", "@tiptap/extension-node-range": "^3.20.0", "@tiptap/extension-placeholder": "^3.20.0", "@tiptap/markdown": "^3.20.0", "@tiptap/pm": "^3.20.0", "@tiptap/starter-kit": "^3.20.0", "@tiptap/suggestion": "^3.20.0", "@tiptap/vue-3": "^3.20.0", "@unhead/vue": "^2.1.10", "@vueuse/core": "^14.2.1", "@vueuse/integrations": "^14.2.1", "@vueuse/shared": "^14.2.1", "colortranslator": "^5.0.0", "consola": "^3.4.2", "defu": "^6.1.4", "embla-carousel-auto-height": "^8.6.0", "embla-carousel-auto-scroll": "^8.6.0", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-class-names": "^8.6.0", "embla-carousel-fade": "^8.6.0", "embla-carousel-vue": "^8.6.0", "embla-carousel-wheel-gestures": "^8.1.0", "fuse.js": "^7.1.0", "hookable": "^5.5.3", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.0", "motion-v": "^1.10.3", "ohash": "^2.0.11", "pathe": "^2.0.3", "reka-ui": "2.8.2", "scule": "^1.3.0", "tailwind-merge": "^3.5.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.2.1", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unplugin": "^3.0.0", "unplugin-auto-import": "^21.0.0", "unplugin-vue-components": "^31.0.0", "vaul-vue": "0.4.1", "vue-component-type-helpers": "^3.2.5" }, "peerDependencies": { "@inertiajs/vue3": "^2.0.7", "@nuxt/content": "^3.0.0", "joi": "^18.0.0", "superstruct": "^2.0.0", "typescript": "^5.9.3", "valibot": "^1.0.0", "vue-router": "^4.5.0", "yup": "^1.7.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@inertiajs/vue3", "@nuxt/content", "joi", "superstruct", "valibot", "vue-router", "yup", "zod"], "bin": { "nuxt-ui": "cli/index.mjs" } }, "sha512-5hWgreVPX6EsNCZNoOd2o7m9fTA3fwUMDw+zeYTSAjhSKtAuvkZrBtmez4MUeTv+LO1gknesgvErdIvlUnElTg=="],
|
"@nuxt/ui": ["@nuxt/ui@4.6.0", "", { "dependencies": { "@floating-ui/dom": "^1.7.6", "@iconify/vue": "^5.0.0", "@internationalized/date": "^3.12.0", "@internationalized/number": "^3.6.5", "@nuxt/fonts": "^0.14.0", "@nuxt/icon": "^2.2.1", "@nuxt/kit": "^4.4.2", "@nuxt/schema": "^4.4.2", "@nuxtjs/color-mode": "^3.5.2", "@standard-schema/spec": "^1.1.0", "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/vite": "^4.2.2", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.23", "@tiptap/core": "^3.20.4", "@tiptap/extension-bubble-menu": "^3.20.4", "@tiptap/extension-code": "^3.20.4", "@tiptap/extension-collaboration": "^3.20.4", "@tiptap/extension-drag-handle": "^3.20.4", "@tiptap/extension-drag-handle-vue-3": "^3.20.4", "@tiptap/extension-floating-menu": "^3.20.4", "@tiptap/extension-horizontal-rule": "^3.20.4", "@tiptap/extension-image": "^3.20.4", "@tiptap/extension-mention": "^3.20.4", "@tiptap/extension-node-range": "^3.20.4", "@tiptap/extension-placeholder": "^3.20.4", "@tiptap/markdown": "^3.20.4", "@tiptap/pm": "^3.20.4", "@tiptap/starter-kit": "^3.20.4", "@tiptap/suggestion": "^3.20.4", "@tiptap/vue-3": "^3.20.4", "@unhead/vue": "^2.1.12", "@vueuse/core": "^14.2.1", "@vueuse/integrations": "^14.2.1", "@vueuse/shared": "^14.2.1", "colortranslator": "^5.0.0", "consola": "^3.4.2", "defu": "^6.1.4", "embla-carousel-auto-height": "^8.6.0", "embla-carousel-auto-scroll": "^8.6.0", "embla-carousel-autoplay": "^8.6.0", "embla-carousel-class-names": "^8.6.0", "embla-carousel-fade": "^8.6.0", "embla-carousel-vue": "^8.6.0", "embla-carousel-wheel-gestures": "^8.1.0", "fuse.js": "^7.1.0", "hookable": "^6.1.0", "knitwork": "^1.3.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "motion-v": "^2.2.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "reka-ui": "2.9.2", "scule": "^1.3.0", "tailwind-merge": "^3.5.0", "tailwind-variants": "^3.2.2", "tailwindcss": "^4.2.2", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unplugin": "^3.0.0", "unplugin-auto-import": "^21.0.0", "unplugin-vue-components": "^31.1.0", "vaul-vue": "0.4.1", "vue-component-type-helpers": "^3.2.6" }, "peerDependencies": { "@inertiajs/vue3": "^2.0.7", "@nuxt/content": "^3.0.0", "joi": "^18.0.0", "superstruct": "^2.0.0", "typescript": "^5.6.3", "valibot": "^1.0.0", "vue-router": "^4.5.0 || ^5.0.0", "yup": "^1.7.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@inertiajs/vue3", "@nuxt/content", "joi", "superstruct", "valibot", "vue-router", "yup", "zod"], "bin": { "nuxt-ui": "cli/index.mjs" } }, "sha512-AcoM3bljDvxd4PGfzDbY4zyyy9Zsajfagr3il1iki7G8Ji3SG6Xsgy+weug+Hyp76YM5919oc6ciJmram5bf6A=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode": ["@nuxtjs/color-mode@3.5.2", "", { "dependencies": { "@nuxt/kit": "^3.13.2", "pathe": "^1.1.2", "pkg-types": "^1.2.1", "semver": "^7.6.3" } }, "sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA=="],
|
"@nuxtjs/color-mode": ["@nuxtjs/color-mode@3.5.2", "", { "dependencies": { "@nuxt/kit": "^3.13.2", "pathe": "^1.1.2", "pkg-types": "^1.2.1", "semver": "^7.6.3" } }, "sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA=="],
|
||||||
|
|
||||||
@@ -336,121 +336,121 @@
|
|||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.19", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA=="],
|
"@swc/helpers": ["@swc/helpers@0.5.20", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
|
||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="],
|
||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
|
||||||
|
|
||||||
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
|
||||||
|
|
||||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.21", "", {}, "sha512-ww+fmLHyCbPSf7JNbWZP3g7wl6SdNo3ah5Aiw+0e9FDErkVHLKprYUrwTm7dF646FtEkN/KkAKPYezxpmvOjxw=="],
|
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="],
|
||||||
|
|
||||||
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
"@tanstack/vue-table": ["@tanstack/vue-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "vue": ">=3.2" } }, "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw=="],
|
||||||
|
|
||||||
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.21", "", { "dependencies": { "@tanstack/virtual-core": "3.13.21" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-zneUNdQTcUhoDl6+ek+/O4S9gSZRAc2q7VLscZ4WZnFfZcHc3M7OyVCfSDC3hGuwFqzfL8Cx5bZF6zbGCYwXmw=="],
|
"@tanstack/vue-virtual": ["@tanstack/vue-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "vue": "^2.7.0 || ^3.0.0" } }, "sha512-b5jPluAR6U3eOq6GWAYSpj3ugnAIZgGR0e6aGAgyRse0Yu6MVQQ0ZWm9SArSXWtageogn6bkVD8D//c4IjW3xQ=="],
|
||||||
|
|
||||||
"@tiptap/core": ["@tiptap/core@3.20.1", "", { "peerDependencies": { "@tiptap/pm": "^3.20.1" } }, "sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA=="],
|
"@tiptap/core": ["@tiptap/core@3.22.0", "", { "peerDependencies": { "@tiptap/pm": "^3.22.0" } }, "sha512-EA/XFbvvz0yRyccqrgOwB9RQe6+uJ8NszjLKH9+3xPE2/+Sa2imax0IqWl7YOXkWihdQVrlpP+EpQF9APKx3jg=="],
|
||||||
|
|
||||||
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-WzNXk/63PQI2fav4Ta6P0GmYRyu8Gap1pV3VUqaVK829iJ6Zt1T21xayATHEHWMK27VT1GLPJkx9Ycr2jfDyQw=="],
|
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-WF7K1jtEhkhCZFOoei3QrUHMsM6i9eqXw1IuL6cAX3+CBpqVg89KbP/cJp05dYKU0SO0LJkn87biKVqcnAcN7A=="],
|
||||||
|
|
||||||
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-fz++Qv6Rk/Hov0IYG/r7TJ1Y4zWkuGONe0UN5g0KY32NIMg3HeOHicbi4xsNWTm9uAOl3eawWDkezEMrleObMw=="],
|
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-mPG1FzOy2DVaJHHuX/eQPIuYie0kqG07M04nElBY8QlV0oYB4/kd0Aubz+m9czqHx/F9u/L98kmMFhCh2DWk2w=="],
|
||||||
|
|
||||||
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.20.1", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-XaPvO6aCoWdFnCBus0s88lnj17NR/OopV79i8Qhgz3WMR0vrsL5zsd45l0lZuu9pSvm5VW47SoxakkJiZC1suw=="],
|
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.22.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-792CUdP0roO17jQJ+fflSJEWfw2cAric61nV2291a2iL7L/6mNJGP4QFim1FqZzfx3/FwHSXEI3NYT6wdUlh8w=="],
|
||||||
|
|
||||||
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-mbrlvOZo5OF3vLhp+3fk9KuL/6J/wsN0QxF6ZFRAHzQ9NkJdtdfARcBeBnkWXGN8inB6YxbTGY1/E4lmBkOpOw=="],
|
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-GUBYUXlNMxfJVpbDQnwNU54rqIBHEIQ7KyPptghSzvmnwMlr6n10OmYSGSVvNpOAIYkHkuhzkwgUDeU+YzWbjg=="],
|
||||||
|
|
||||||
"@tiptap/extension-code": ["@tiptap/extension-code@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-509DHINIA/Gg+eTG7TEkfsS8RUiPLH5xZNyLRT0A1oaoaJmECKfrV6aAm05IdfTyqDqz6LW5pbnX6DdUC4keug=="],
|
"@tiptap/extension-code": ["@tiptap/extension-code@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-JGxByyyUdR0yRt1mOxnA2dp6PmI9pr6C846UkZtuOCwhBOBLkoBGkZqW4FytLPOfWGVJpm7w5tx7h1n5uNwfag=="],
|
||||||
|
|
||||||
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-vKejwBq+Nlj4Ybd3qOyDxIQKzYymdNH+8eXkKwGShk2nfLJIxq69DCyGvmuHgipIO1qcYPJ149UNpGN+YGcdmA=="],
|
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-HtnYHj6yHVy2dKs02j5dyEehWQMGOGRMiZBkefY3TwSSNzGESVcFfDV+Xr87j7zDGYvY16vOtVmyRTOTqPn49A=="],
|
||||||
|
|
||||||
"@tiptap/extension-collaboration": ["@tiptap/extension-collaboration@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/y-tiptap": "^3.0.2", "yjs": "^13" } }, "sha512-JnwLvyzrutBffHp6YPnf0XyTnhAgqZ9D8JSUKFp0edvai+dxsb+UMlawesBrgAuoQXw3B8YZUo2VFDVdKas1xQ=="],
|
"@tiptap/extension-collaboration": ["@tiptap/extension-collaboration@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/y-tiptap": "^3.0.2", "yjs": "^13" } }, "sha512-S/nPIth4/Dr5wmxROk4ELWRYhBXxWt7O6cIi8Fca1rYDBdXofjgt4VDO2iCjnOF0LkIekjQudSOFLWadbM2Z+w=="],
|
||||||
|
|
||||||
"@tiptap/extension-document": ["@tiptap/extension-document@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-9vrqdGmRV7bQCSY3NLgu7UhIwgOCDp4sKqMNsoNRX0aZ021QQMTvBQDPkiRkCf7MNsnWrNNnr52PVnULEn3vFQ=="],
|
"@tiptap/extension-document": ["@tiptap/extension-document@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-ZdtuBt2KnxIYBtp/VrKiWQ5cLPw1qDKb+sieipBaDWuvhgDNi1kfr//ByEP8xPhcjJfH/C3PCdYYVwIUJwzdqQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-drag-handle": ["@tiptap/extension-drag-handle@3.20.1", "", { "dependencies": { "@floating-ui/dom": "^1.6.13" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/extension-collaboration": "^3.20.1", "@tiptap/extension-node-range": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/y-tiptap": "^3.0.2" } }, "sha512-sIfu5Gt1aZVAagvzr+3Qx+8GE294eU3G6/pYjqNHvf71sDCpdN2gFpeI7WF05foVsCGsH6ieu8x/kTXf5GbNZA=="],
|
"@tiptap/extension-drag-handle": ["@tiptap/extension-drag-handle@3.22.0", "", { "dependencies": { "@floating-ui/dom": "^1.6.13" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/extension-collaboration": "^3.22.0", "@tiptap/extension-node-range": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/y-tiptap": "^3.0.2" } }, "sha512-OdOCqdgprFrvVhbLCCGX1D0hXo/2SL8o1o50Gy0l1lgiH9vqi/B2LjUJZwYYneQa1K0ME1J4dL4Vp89V4jej0A=="],
|
||||||
|
|
||||||
"@tiptap/extension-drag-handle-vue-3": ["@tiptap/extension-drag-handle-vue-3@3.20.1", "", { "peerDependencies": { "@tiptap/extension-drag-handle": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/vue-3": "^3.20.1", "vue": "^3.0.0" } }, "sha512-FJonOxcj/I7uBpJBSqfEfU+Fqem5aanEj+EsqG5ZQnxhp7cxbUYZkGH0tBBR7jeIIkt2Jk+1LoCAlYzCWbDLcQ=="],
|
"@tiptap/extension-drag-handle-vue-3": ["@tiptap/extension-drag-handle-vue-3@3.22.0", "", { "peerDependencies": { "@tiptap/extension-drag-handle": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/vue-3": "^3.22.0", "vue": "^3.0.0" } }, "sha512-QuA3Fxe/jxJAW1LzUazN5FEA0i8UyUQCRQ46IzpA878RI2yzeaOfxTiY26s0oA7uUaoAyPg/pLgVjIdnc1epfA=="],
|
||||||
|
|
||||||
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.20.1", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.1" } }, "sha512-K18L9FX4znn+ViPSIbTLOGcIaXMx/gLNwAPE8wPLwswbHhQqdiY1zzdBw6drgOc1Hicvebo2dIoUlSXOZsOEcw=="],
|
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.22.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.22.0" } }, "sha512-yI0aMD4szbNdy/dlglPbZ9Ddc2UucRatJSifmtenCLg7YWyIIYton0T6Uym+FXAEUZ6KsaoNqEKiUbK5cRzZMQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.20.1", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-BeDC6nfOesIMn5pFuUnkEjOxGv80sOJ8uk1mdt9/3Fkvra8cB9NIYYCVtd6PU8oQFmJ8vFqPrRkUWrG5tbqnOg=="],
|
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.22.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-6Gg3I6n+YaCJyvpcKheWiOtU9Oy0M3lbwUGdLK7jTxgAG2YOJxEcx2CzDv3PtNcoyAVUVk9Eio21awVMOECLAQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.20.1", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.1" } }, "sha512-kZOtttV6Ai8VUAgEng3h4WKFbtdSNJ6ps7r0cRPY+FctWhVmgNb/JJwwyC+vSilR7nRENAhrA/Cv/RxVlvLw+g=="],
|
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.22.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.22.0" } }, "sha512-VsnaTU88PlA/eG9DtUvuB90z5gVZIaH6T/JVTxGasxR4CFsv0L4Zq5awwr0+SsYH9dKepRMgbanVU03c6k1SuA=="],
|
||||||
|
|
||||||
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-9sKpmg/IIdlLXimYWUZ3PplIRcehv4Oc7V1miTqlnAthMzjMqigDkjjgte4JZV67RdnDJTQkRw8TklCAU28Emg=="],
|
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-F51pt3fgjbtWrY0Uud+5HoJW4f7w/aBZvmoCk19nrEY955vvuQQ2PD/DZtecl4A8fF50PpRjgilrYnnh99l0ew=="],
|
||||||
|
|
||||||
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-unudyfQP6FxnyWinxvPqe/51DG91J6AaJm666RnAubgYMCgym+33kBftx4j4A6qf+ddWYbD00thMNKOnVLjAEQ=="],
|
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-SnOUBXzh9Dft7HY0rqaSL/kZKg4W9wlHfpnFPW8aIuewXvFDLKa6PisqxPpHsXSbG21kfs5E0MLdwdXtNP89XA=="],
|
||||||
|
|
||||||
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-rjFKFXNntdl0jay8oIGFvvykHlpyQTLmrH3Ag2fj3i8yh6MVvqhtaDomYQbw5sxECd5hBkL+T4n2d2DRuVw/QQ=="],
|
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-9v08PcmJOumVmgGgcuFPZpAk+tf+m7+vaCNsNyf8Ce1i0m3GPSle1ZmxzjDU2FlpaCFrcgoUKlEjKYaFYFCJIg=="],
|
||||||
|
|
||||||
"@tiptap/extension-image": ["@tiptap/extension-image@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-/GPFSLNdYSZQ0E6VBXSAk0UFtDdn98HT1Aa2tO+STELqc5jAdFB42dfFnTC6KQzTvcUOUYkE2S1Q22YC5H2XNQ=="],
|
"@tiptap/extension-image": ["@tiptap/extension-image@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-b3n86lWF3thywUrWmZv27EzTGhCBTjifXY/NzPfri3D22cKkxardrJi2WZIc0J65je91oTzqY7SkdQnyIrZksw=="],
|
||||||
|
|
||||||
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-ZYRX13Kt8tR8JOzSXirH3pRpi8x30o7LHxZY58uXBdUvr3tFzOkh03qbN523+diidSVeHP/aMd/+IrplHRkQug=="],
|
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-+qq9QZF44O1MRqk6w1AMDZ8oDBs5AtdDdNEcdXpzVU54cJAtWyEPEfXtD0B68hOUp/RdZjMdL27fp+4Id7C1YA=="],
|
||||||
|
|
||||||
"@tiptap/extension-link": ["@tiptap/extension-link@3.20.1", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-oYTTIgsQMqpkSnJAuAc+UtIKMuI4lv9e1y4LfI1iYm6NkEUHhONppU59smhxHLzb3Ww7YpDffbp5IgDTAiJztA=="],
|
"@tiptap/extension-link": ["@tiptap/extension-link@3.22.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-tGMBUAmni532G6R5gnaRvTb6c7+ST1qCHBV0p5kGGzdHaQTDd1R7S8fnuA3M7+6Sruc82iIY+Ur+6Tusvo/vLA=="],
|
||||||
|
|
||||||
"@tiptap/extension-list": ["@tiptap/extension-list@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-euBRAn0mkV7R2VEE+AuOt3R0j9RHEMFXamPFmtvTo8IInxDClusrm6mJoDjS8gCGAXsQCRiAe1SCQBPgGbOOwg=="],
|
"@tiptap/extension-list": ["@tiptap/extension-list@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-NfSCAgX44NVLib6aN4HmsP1wi6fFfK3dt6TBb9EgcR82nzq6n7dq7VEBw9V1aKqeXQEtNpqMnQFd0SDayweyfQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-tzgnyTW82lYJkUnadYbatwkI9dLz/OWRSWuFpQPRje/ItmFMWuQ9c9NDD8qLbXPdEYnvrgSAA+ipCD/1G0qA0Q=="],
|
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-9cFvFLEtf0bnOc/LaGeX2D+c9wOxeqhzgabUl2Ztz8Xzoby4JtamXnrIpb7DG7hZMf3luJMF8bz0HSvAnNiISQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-Dr0xsQKx0XPOgDg7xqoWwfv7FFwZ3WeF3eOjqh3rDXlNHMj1v+UW5cj1HLphrsAZHTrVTn2C+VWPJkMZrSbpvQ=="],
|
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-uCtr5/g+Cwkmsb/VLctgo4VjKm0jv52moAmDyr/TLRjW94gnSLhwXFKzyd7BNIXBQHDyS44UEIJFD3ul4dUKdw=="],
|
||||||
|
|
||||||
"@tiptap/extension-mention": ["@tiptap/extension-mention@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1", "@tiptap/suggestion": "^3.20.1" } }, "sha512-KOGokj7oH1QpcM8P02V+o6wHsVE0g7XEtdIy2vtq2vlFE3npNNNFkMa8F8VWX6qyC+VeVrNU6SIzS5MFY2TORA=="],
|
"@tiptap/extension-mention": ["@tiptap/extension-mention@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0", "@tiptap/suggestion": "^3.22.0" } }, "sha512-Ndjn8YTcZMyyEV5cm+3TupRsQ4b8+4gRrZXfsqbEbXN/IGXgl+ObBx+bxBU+aAe2ZRgqEeuVIBIVWBzYaxLxrg=="],
|
||||||
|
|
||||||
"@tiptap/extension-node-range": ["@tiptap/extension-node-range@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-+W/mQJxlkXMcwldWUqwdoR0eniJ1S9cVJoAy2Lkis0NhILZDWVNGKl9J4WFoCOXn8Myr17IllIxRYvAXJJ4FHQ=="],
|
"@tiptap/extension-node-range": ["@tiptap/extension-node-range@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-tize9T79ABbOmkw3+7k8cFjSU/JMpzv1JyvFXuGD7lmiHSrFH9Ll8uOGJC7qhCGJWMsjVvrKmBHSOGbzfJI5hQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.20.1", "", { "peerDependencies": { "@tiptap/extension-list": "^3.20.1" } }, "sha512-Y+3Ad7OwAdagqdYwCnbqf7/to5ypD4NnUNHA0TXRCs7cAHRA8AdgPoIcGFpaaSpV86oosNU3yfeJouYeroffog=="],
|
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.22.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.22.0" } }, "sha512-B5JSJ2Xe2KPIYYG7jpZHVeAku/VJB+CCgPYl+qIHjZ4JGTnW23qkIA+6dWk6WljGmhQL1qusxZZn4UAnZtBLeQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-QFrAtXNyv7JSnomMQc1nx5AnG9mMznfbYJAbdOQYVdbLtAzTfiTuNPNbQrufy5ZGtGaHxDCoaygu2QEfzaKG+Q=="],
|
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-fwkPvbGI3xvzWrTJVGZVocgA99Pgqd5kW7iv7MEWlI9uOUa6Ifu31/seHV7j+QDW3y3mADcx+zyhxcMVELtLjA=="],
|
||||||
|
|
||||||
"@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.20.1", "", { "peerDependencies": { "@tiptap/extensions": "^3.20.1" } }, "sha512-k+jfbCugYGuIFBdojukgEopGazIMOgHrw46FnyN2X/6ICOIjQP2rh2ObslrsUOsJYoEevxCsNF9hZl1HvWX66g=="],
|
"@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.22.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.22.0" } }, "sha512-1GCc87DIPG3jgLH1Xg3ZW11LEzEWnpinLBIZ8RQqrEH0UwtRUW3QsbvwnKKgbbAEtA91GAdnc1eUQ0dpn+tSeQ=="],
|
||||||
|
|
||||||
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-EYgyma10lpsY+rwbVQL9u+gA7hBlKLSMFH7Zgd37FSxukOjr+HE8iKPQQ+SwbGejyDsPlLT8Z5Jnuxo5Ng90Pg=="],
|
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-gCgFr1sIcqrJeV5Gdrh8KVZHA+0B1FpFBuOi6FzMyVfBB2sBBqKnjoInYTkPXXdP49Qu8L8hi4luFQtoj4zGzA=="],
|
||||||
|
|
||||||
"@tiptap/extension-text": ["@tiptap/extension-text@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-7PlIbYW8UenV6NPOXHmv8IcmPGlGx6HFq66RmkJAOJRPXPkTLAiX0N8rQtzUJ6jDEHqoJpaHFEHJw0xzW1yF+A=="],
|
"@tiptap/extension-text": ["@tiptap/extension-text@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-FQ3lBRswZbSEbtxOnDF4T7pdsZRmKh/8q+M29zXaDHGfBc6nuGNPlNKSIy0Iryjhf/YmMVaWDpHvzk56KD7QtA=="],
|
||||||
|
|
||||||
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1" } }, "sha512-fmHvDKzwCgnZUwRreq8tYkb1YyEwgzZ6QQkAQ0CsCRtvRMqzerr3Duz0Als4i8voZTuGDEL3VR6nAJbLAb/wPg=="],
|
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0" } }, "sha512-AxQOnXQwYmZNjagkEoCZZqbpJbLVmBcu1ivJ9dE0SAQsr1wRUp7mAg+g1SqhbMAvrXvv7yhhNevSdQKmXsnFyA=="],
|
||||||
|
|
||||||
"@tiptap/extensions": ["@tiptap/extensions@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-JRc/v+OBH0qLTdvQ7HvHWTxGJH73QOf1MC0R8NhOX2QnAbg2mPFv1h+FjGa2gfLGuCXBdWQomjekWkUKbC4e5A=="],
|
"@tiptap/extensions": ["@tiptap/extensions@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-En8p1FiFBT3V9CduErCyLPFxDRsYLISb2cCtLKTeYVeCRn2vQZK4B8WuOgHI4IBipz3I3XidmDhra4yt8mmi/w=="],
|
||||||
|
|
||||||
"@tiptap/markdown": ["@tiptap/markdown@3.20.1", "", { "dependencies": { "marked": "^17.0.1" }, "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-dNrtP7kmabDomgjv9G/6+JSFL6WraPaFbmKh1eHSYKdDGvIwBfJnVPTV2VS3bP1OuYJEDJN/2ydtiCHyOTrQsQ=="],
|
"@tiptap/markdown": ["@tiptap/markdown@3.22.0", "", { "dependencies": { "marked": "^17.0.1" }, "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-lvYqID2ui4+HEgh/zULQJoKfb7ILe6ahB9IeaJ5R48mrVyeudciqoJV6VmNOaucm/UpKExlDC3I+QlA9sLpYPw=="],
|
||||||
|
|
||||||
"@tiptap/pm": ["@tiptap/pm@3.20.1", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw=="],
|
"@tiptap/pm": ["@tiptap/pm@3.22.0", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-O9kpzNnFX5837kFevwAM8yr7ImLHu8noIwIpoci0AwfJjiBMzfZBejhbzxnKEfTpFWnkvZ8rWohlb6CQdJ6Crg=="],
|
||||||
|
|
||||||
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.20.1", "", { "dependencies": { "@tiptap/core": "^3.20.1", "@tiptap/extension-blockquote": "^3.20.1", "@tiptap/extension-bold": "^3.20.1", "@tiptap/extension-bullet-list": "^3.20.1", "@tiptap/extension-code": "^3.20.1", "@tiptap/extension-code-block": "^3.20.1", "@tiptap/extension-document": "^3.20.1", "@tiptap/extension-dropcursor": "^3.20.1", "@tiptap/extension-gapcursor": "^3.20.1", "@tiptap/extension-hard-break": "^3.20.1", "@tiptap/extension-heading": "^3.20.1", "@tiptap/extension-horizontal-rule": "^3.20.1", "@tiptap/extension-italic": "^3.20.1", "@tiptap/extension-link": "^3.20.1", "@tiptap/extension-list": "^3.20.1", "@tiptap/extension-list-item": "^3.20.1", "@tiptap/extension-list-keymap": "^3.20.1", "@tiptap/extension-ordered-list": "^3.20.1", "@tiptap/extension-paragraph": "^3.20.1", "@tiptap/extension-strike": "^3.20.1", "@tiptap/extension-text": "^3.20.1", "@tiptap/extension-underline": "^3.20.1", "@tiptap/extensions": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-opqWxL/4OTEiqmVC0wsU4o3JhAf6LycJ2G/gRIZVAIFLljI9uHfpPMTFGxZ5w9IVVJaP5PJysfwW/635kKqkrw=="],
|
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.22.0", "", { "dependencies": { "@tiptap/core": "^3.22.0", "@tiptap/extension-blockquote": "^3.22.0", "@tiptap/extension-bold": "^3.22.0", "@tiptap/extension-bullet-list": "^3.22.0", "@tiptap/extension-code": "^3.22.0", "@tiptap/extension-code-block": "^3.22.0", "@tiptap/extension-document": "^3.22.0", "@tiptap/extension-dropcursor": "^3.22.0", "@tiptap/extension-gapcursor": "^3.22.0", "@tiptap/extension-hard-break": "^3.22.0", "@tiptap/extension-heading": "^3.22.0", "@tiptap/extension-horizontal-rule": "^3.22.0", "@tiptap/extension-italic": "^3.22.0", "@tiptap/extension-link": "^3.22.0", "@tiptap/extension-list": "^3.22.0", "@tiptap/extension-list-item": "^3.22.0", "@tiptap/extension-list-keymap": "^3.22.0", "@tiptap/extension-ordered-list": "^3.22.0", "@tiptap/extension-paragraph": "^3.22.0", "@tiptap/extension-strike": "^3.22.0", "@tiptap/extension-text": "^3.22.0", "@tiptap/extension-underline": "^3.22.0", "@tiptap/extensions": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-3V0RysviBKbsvzHuupM30ftb/WLogSgINtIbmGQHZK4Ta1YzwzW63nAWPGKOoFw8r1HRFFO6LjtqrT6iJsPnzQ=="],
|
||||||
|
|
||||||
"@tiptap/suggestion": ["@tiptap/suggestion@3.20.1", "", { "peerDependencies": { "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1" } }, "sha512-ng7olbzgZhWvPJVJygNQK5153CjquR2eJXpkLq7bRjHlahvt4TH4tGFYvGdYZcXuzbe2g9RoqT7NaPGL9CUq9w=="],
|
"@tiptap/suggestion": ["@tiptap/suggestion@3.22.0", "", { "peerDependencies": { "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0" } }, "sha512-qdZ4v7sXkM4p2+M7OWCGVoNmHmiaHhiGQN3lUzaNG7mG1gZ2SDirSaeOfL8lHxpnPlGzmE4tUe5eCWMpNKHypA=="],
|
||||||
|
|
||||||
"@tiptap/vue-3": ["@tiptap/vue-3@3.20.1", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.20.1", "@tiptap/extension-floating-menu": "^3.20.1" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.20.1", "@tiptap/pm": "^3.20.1", "vue": "^3.0.0" } }, "sha512-06IsNzIkAC0HPHNSdXf4AgtExnr02BHBLXmUiNrjjhgHdxXDKIB0Yq3hEWEfbWNPr3lr7jK6EaFu2IKFMhoUtQ=="],
|
"@tiptap/vue-3": ["@tiptap/vue-3@3.22.0", "", { "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.22.0", "@tiptap/extension-floating-menu": "^3.22.0" }, "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.22.0", "@tiptap/pm": "^3.22.0", "vue": "^3.0.0" } }, "sha512-CVThMRtCAYmz+N5RtnxRAjfPZwavdqi0RqjznF3M0Tc5UVrwfJG5YYtE7uzySmzuxW7VrkjTUC+Zx9YM2hQEXQ=="],
|
||||||
|
|
||||||
"@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.2", "", { "dependencies": { "lib0": "^0.2.100" }, "peerDependencies": { "prosemirror-model": "^1.7.1", "prosemirror-state": "^1.2.3", "prosemirror-view": "^1.9.10", "y-protocols": "^1.0.1", "yjs": "^13.5.38" } }, "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA=="],
|
"@tiptap/y-tiptap": ["@tiptap/y-tiptap@3.0.2", "", { "dependencies": { "lib0": "^0.2.100" }, "peerDependencies": { "prosemirror-model": "^1.7.1", "prosemirror-state": "^1.2.3", "prosemirror-view": "^1.9.10", "y-protocols": "^1.0.1", "yjs": "^13.5.38" } }, "sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA=="],
|
||||||
|
|
||||||
@@ -474,29 +474,29 @@
|
|||||||
|
|
||||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/type-utils": "8.57.0", "@typescript-eslint/utils": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/type-utils": "8.58.0", "@typescript-eslint/utils": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.0", "@typescript-eslint/types": "^8.57.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.0", "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg=="],
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0" } }, "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw=="],
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0" } }, "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/utils": "8.57.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.57.0", "", {}, "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.0", "@typescript-eslint/tsconfig-utils": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q=="],
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.0", "@typescript-eslint/tsconfig-utils": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ=="],
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.0", "", { "dependencies": { "@typescript-eslint/types": "8.57.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="],
|
||||||
|
|
||||||
"@unhead/vue": ["@unhead/vue@2.1.12", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.12" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-zEWqg0nZM8acpuTZE40wkeUl8AhIe0tU0OkilVi1D4fmVjACrwoh5HP6aNqJ8kUnKsoy6D+R3Vi/O+fmdNGO7g=="],
|
"@unhead/vue": ["@unhead/vue@2.1.12", "", { "dependencies": { "hookable": "^6.0.1", "unhead": "2.1.12" }, "peerDependencies": { "vue": ">=3.5.18" } }, "sha512-zEWqg0nZM8acpuTZE40wkeUl8AhIe0tU0OkilVi1D4fmVjACrwoh5HP6aNqJ8kUnKsoy6D+R3Vi/O+fmdNGO7g=="],
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ=="],
|
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.5", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg=="],
|
||||||
|
|
||||||
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
"@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="],
|
||||||
|
|
||||||
@@ -512,39 +512,39 @@
|
|||||||
|
|
||||||
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="],
|
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="],
|
||||||
|
|
||||||
"@vue/compiler-core": ["@vue/compiler-core@3.6.0-beta.7", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.6.0-beta.7", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-fYOlA9PCFuf5l8AtKJSxAM+K+MLoM8dyN9Xsa/GDE0Uf6wxFe33AEU6xyVqYMPy6uxp4RtgE4KuQ7JdYvE0Jnw=="],
|
"@vue/compiler-core": ["@vue/compiler-core@3.6.0-beta.9", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.6.0-beta.9", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-/qlfk4cbU//tgdwWeAbl4kBvH5ZocjJ6CMGigzRKk1So/0jl6kRpFxVbc6oNsYYH6Xj28ByA+msAes1FjExejg=="],
|
||||||
|
|
||||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-core": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" } }, "sha512-4myAKKPogd6MAA/oKKn1+uDOPGJ6gB+pivaUxaBqfvXJch1QHSpcUgdHCJqDb9O/99EjcwppkTLGe7cRWMSqHQ=="],
|
"@vue/compiler-dom": ["@vue/compiler-dom@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-core": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" } }, "sha512-Bh2h+Co2Vl85jhNjR+yBU1oIujqQ3XIhF7FzODBStvp8yn2WvsXmm5QlvnE7q+smJkfglTbfFcaou8fjR0Y4ag=="],
|
||||||
|
|
||||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.6.0-beta.7", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.6.0-beta.7", "@vue/compiler-dom": "3.6.0-beta.7", "@vue/compiler-ssr": "3.6.0-beta.7", "@vue/compiler-vapor": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-bf0NkJb0Hxoj1K8cZKYgduEY0FPP28WnoUGqHvYA/a1cjJfrhaypbiS5u6pEFVXug/JZJF9sI+c4JTHkgqRKDw=="],
|
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.6.0-beta.9", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-core": "3.6.0-beta.9", "@vue/compiler-dom": "3.6.0-beta.9", "@vue/compiler-ssr": "3.6.0-beta.9", "@vue/compiler-vapor": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "sha512-13MwFqrIN+AFxR5uT88WYaMTxXycpNNI75sIllz5blXbFaBsDfiegQZj10Pj5G3cgH7WFy2Trs1rTlXrmSgG/w=="],
|
||||||
|
|
||||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" } }, "sha512-fwO4qe6iXZ+SZedurh/vLwjBgjAX/wL5aI/eIECv0C79AgTAlJNjD6dwMafukhnZamQgNKYBxXUOkGl8sqn1PA=="],
|
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" } }, "sha512-ufzHVMJoMz7QzMFop03a8KpWAWUh4hiof/tJXvxiDClIFUY7P5OrKk644fV9qlWgTP7V+eQvA7kVSEVRUgYjvA=="],
|
||||||
|
|
||||||
"@vue/compiler-vapor": ["@vue/compiler-vapor@3.6.0-beta.7", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-dom": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-fc6ZAFv9yRSoQQVsgN5gGDaUWdTPctDb6lVUtUGgnrCB7n+hQIBwuId7HIeic3PK6HPVZMoEbluNU/ge3DIveA=="],
|
"@vue/compiler-vapor": ["@vue/compiler-vapor@3.6.0-beta.9", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/compiler-dom": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-1WWCcRjkH/scYJ8dsjNp8+NDf4f9SwQgOcd4Hbl5p0srNPflqrQYvAaJh4N2Szxj5FdkXw2t5uiK35TpFM5uKA=="],
|
||||||
|
|
||||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
||||||
|
|
||||||
"@vue/devtools-core": ["@vue/devtools-core@8.0.7", "", { "dependencies": { "@vue/devtools-kit": "^8.0.7", "@vue/devtools-shared": "^8.0.7" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-PmpiPxvg3Of80ODHVvyckxwEW1Z02VIAvARIZS1xegINn3VuNQLm9iHUmKD+o6cLkMNWV8OG8x7zo0kgydZgdg=="],
|
"@vue/devtools-core": ["@vue/devtools-core@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1", "@vue/devtools-shared": "^8.1.1" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-bCCsSABp1/ot4j8xJEycM6Mtt2wbuucfByr6hMgjbYhrtlscOJypZKvy8f1FyWLYrLTchB5Qz216Lm92wfbq0A=="],
|
||||||
|
|
||||||
"@vue/devtools-kit": ["@vue/devtools-kit@8.0.7", "", { "dependencies": { "@vue/devtools-shared": "^8.0.7", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-H6esJGHGl5q0E9iV3m2EoBQHJ+V83WMW83A0/+Fn95eZ2iIvdsq4+UCS6yT/Fdd4cGZSchx/MdWDreM3WqMsDw=="],
|
"@vue/devtools-kit": ["@vue/devtools-kit@8.1.1", "", { "dependencies": { "@vue/devtools-shared": "^8.1.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg=="],
|
||||||
|
|
||||||
"@vue/devtools-shared": ["@vue/devtools-shared@8.0.7", "", {}, "sha512-CgAb9oJH5NUmbQRdYDj/1zMiaICYSLtm+B1kxcP72LBrifGAjUmt8bx52dDH1gWRPlQgxGPqpAMKavzVirAEhA=="],
|
"@vue/devtools-shared": ["@vue/devtools-shared@8.1.1", "", {}, "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ=="],
|
||||||
|
|
||||||
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.7.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.56.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.56.0", "vue-eslint-parser": "^10.4.0" }, "peerDependencies": { "eslint": "^9.10.0 || ^10.0.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg=="],
|
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.7.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.56.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.56.0", "vue-eslint-parser": "^10.4.0" }, "peerDependencies": { "eslint": "^9.10.0 || ^10.0.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg=="],
|
||||||
|
|
||||||
"@vue/language-core": ["@vue/language-core@3.2.5", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g=="],
|
"@vue/language-core": ["@vue/language-core@3.2.6", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" } }, "sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg=="],
|
||||||
|
|
||||||
"@vue/reactivity": ["@vue/reactivity@3.6.0-beta.7", "", { "dependencies": { "@vue/shared": "3.6.0-beta.7" } }, "sha512-d1Z7U3QqfrpnT1/m8glRDBGlzQVj4J3u7LvCxieyxvrH03ZcR9qZ8hMw9x/Q3K/VcT2BSHp8A8662cqigCiCEA=="],
|
"@vue/reactivity": ["@vue/reactivity@3.6.0-beta.9", "", { "dependencies": { "@vue/shared": "3.6.0-beta.9" } }, "sha512-Mo0GjZB/q7LUJplPjBdKqAAk4mawkzvnk2nNf9CEetp4RQJP5/e/DdmgjYBsr/+UVX5ShasCalwdyknuuIepdw=="],
|
||||||
|
|
||||||
"@vue/runtime-core": ["@vue/runtime-core@3.6.0-beta.7", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" } }, "sha512-z6wXT1w0lFYS2uHAYuNZRutxNmg5i4S6yKYVf58/DVu+j/L1eq2ntPNVpPJpof71izXbv14ImvvPUCTTqHwAUA=="],
|
"@vue/runtime-core": ["@vue/runtime-core@3.6.0-beta.9", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" } }, "sha512-IPbO/cmw0BSHec17fSE8Mbtv0UFBiiiGhvlUGiJWTa/eOlbiO+mZ9/PRRUQ+v/tkKuwFSzKye7KOKj5aFO1dgQ=="],
|
||||||
|
|
||||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.6.0-beta.7", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.7", "@vue/runtime-core": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7", "csstype": "^3.2.3" } }, "sha512-purk/iT3R5y2CizeV+YdxHhqPE1yAk3MSptggsCdO0zkShl2NA1/xec1WgVg1OL6IV0xNrknsMxl9Fit4g44hA=="],
|
"@vue/runtime-dom": ["@vue/runtime-dom@3.6.0-beta.9", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.9", "@vue/runtime-core": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9", "csstype": "^3.2.3" } }, "sha512-+WV0V1s/uf9iFwZt41lOtJHbIqKyYdEO+nmW0UOnQLjG9ELz6rKuZ92fEim6NPcKCsucv2BoB1mK9T82khYqgg=="],
|
||||||
|
|
||||||
"@vue/runtime-vapor": ["@vue/runtime-vapor@3.6.0-beta.7", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" }, "peerDependencies": { "@vue/runtime-dom": "3.6.0-beta.7" } }, "sha512-H+XdbkqtBI+9gsS957Om94qpTx+keklzvO6rllls4TcKlRo/xcXsDnMhWjghX0Irg6iM/MgBBK8bQkfUkF+xSg=="],
|
"@vue/runtime-vapor": ["@vue/runtime-vapor@3.6.0-beta.9", "", { "dependencies": { "@vue/reactivity": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" }, "peerDependencies": { "@vue/runtime-dom": "3.6.0-beta.9" } }, "sha512-UlAziGF77byUa84RgkIdCT4bleZbB4UvQvmxdGHFfscGylSABBZHAMR8WJIwBsy1MsZnlpRSSeDAYB20RicrhQ=="],
|
||||||
|
|
||||||
"@vue/server-renderer": ["@vue/server-renderer@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-ssr": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" }, "peerDependencies": { "vue": "3.6.0-beta.7" } }, "sha512-VqX6+8+NyCC/nqkOvj/vKHeMtBr3Mxo7rccDLNyuSb6o5Mp+itlxdpaD0LD28SqHEaDjqfIUBTnFsfDJLN/cww=="],
|
"@vue/server-renderer": ["@vue/server-renderer@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-ssr": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" }, "peerDependencies": { "vue": "3.6.0-beta.9" } }, "sha512-qyz37g6pmwHvtXbe7CWKm5+VTmCs4mHrC1uDw4OVZlvwDDZb9pWMzW0kOrzhj1iI6sZ4D9k5fYQIdzBqB7aOqw=="],
|
||||||
|
|
||||||
"@vue/shared": ["@vue/shared@3.6.0-beta.7", "", {}, "sha512-BNC0ohwv5hYybiUvZ/7gBjSzWFRxpZ2MzRQ/lH2zYZds085ylmAqMGEwbGlWFD+/WPRBsXQd7+e1hNGFksNuCg=="],
|
"@vue/shared": ["@vue/shared@3.6.0-beta.9", "", {}, "sha512-nY6DXeelJsmveeNk5YZYGd8q0ulWXACL8Qs/qvkPC4HQkyhPgf+Ja00AkG2hYsEI8Uixia2UpQl4CbObzw0hag=="],
|
||||||
|
|
||||||
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
"@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="],
|
||||||
|
|
||||||
@@ -580,23 +580,23 @@
|
|||||||
|
|
||||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw=="],
|
||||||
|
|
||||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||||
|
|
||||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
|
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||||
|
|
||||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
|
|
||||||
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
|
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="],
|
||||||
|
|
||||||
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
|
"chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="],
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@
|
|||||||
|
|
||||||
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.330", "", {}, "sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA=="],
|
||||||
|
|
||||||
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
"embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="],
|
||||||
|
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
|
|
||||||
"embla-carousel-wheel-gestures": ["embla-carousel-wheel-gestures@8.1.0", "", { "dependencies": { "wheel-gestures": "^2.2.5" }, "peerDependencies": { "embla-carousel": "^8.0.0 || ~8.0.0-rc03" } }, "sha512-J68jkYrxbWDmXOm2n2YHl+uMEXzkGSKjWmjaEgL9xVvPb3HqVmg6rJSKfI3sqIDVvm7mkeTy87wtG/5263XqHQ=="],
|
"embla-carousel-wheel-gestures": ["embla-carousel-wheel-gestures@8.1.0", "", { "dependencies": { "wheel-gestures": "^2.2.5" }, "peerDependencies": { "embla-carousel": "^8.0.0 || ~8.0.0-rc03" } }, "sha512-J68jkYrxbWDmXOm2n2YHl+uMEXzkGSKjWmjaEgL9xVvPb3HqVmg6rJSKfI3sqIDVvm7mkeTy87wtG/5263XqHQ=="],
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
|
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
|
||||||
|
|
||||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||||
|
|
||||||
@@ -674,13 +674,13 @@
|
|||||||
|
|
||||||
"errx": ["errx@0.1.0", "", {}, "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q=="],
|
"errx": ["errx@0.1.0", "", {}, "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
"esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@10.0.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.1.1", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ=="],
|
"eslint": ["eslint@10.1.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.3", "@eslint/core": "^1.1.1", "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA=="],
|
||||||
|
|
||||||
"eslint-plugin-vue": ["eslint-plugin-vue@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA=="],
|
"eslint-plugin-vue": ["eslint-plugin-vue@10.8.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA=="],
|
||||||
|
|
||||||
@@ -724,7 +724,7 @@
|
|||||||
|
|
||||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||||
|
|
||||||
"flatted": ["flatted@3.4.1", "", {}, "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ=="],
|
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||||
|
|
||||||
"fontaine": ["fontaine@0.8.0", "", { "dependencies": { "@capsizecss/unpack": "^4.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg=="],
|
"fontaine": ["fontaine@0.8.0", "", { "dependencies": { "@capsizecss/unpack": "^4.0.0", "css-tree": "^3.1.0", "magic-regexp": "^0.10.0", "magic-string": "^0.30.21", "pathe": "^2.0.3", "ufo": "^1.6.1", "unplugin": "^2.3.10" } }, "sha512-eek1GbzOdWIj9FyQH/emqW1aEdfC3lYRCHepzwlFCm5T77fBSRSyNRKE6/antF1/B1M+SfJXVRQTY9GAr7lnDg=="],
|
||||||
|
|
||||||
@@ -732,7 +732,7 @@
|
|||||||
|
|
||||||
"fontless": ["fontless@0.2.1", "", { "dependencies": { "consola": "^3.4.2", "css-tree": "^3.1.0", "defu": "^6.1.4", "esbuild": "^0.27.0", "fontaine": "0.8.0", "jiti": "^2.6.1", "lightningcss": "^1.30.2", "magic-string": "^0.30.21", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1", "unifont": "^0.7.4", "unstorage": "^1.17.1" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-mUWZ8w91/mw2KEcZ6gHNoNNmsAq9Wiw2IypIux5lM03nhXm+WSloXGUNuRETNTLqZexMgpt7Aj/v63qqrsWraQ=="],
|
"fontless": ["fontless@0.2.1", "", { "dependencies": { "consola": "^3.4.2", "css-tree": "^3.1.0", "defu": "^6.1.4", "esbuild": "^0.27.0", "fontaine": "0.8.0", "jiti": "^2.6.1", "lightningcss": "^1.30.2", "magic-string": "^0.30.21", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1", "unifont": "^0.7.4", "unstorage": "^1.17.1" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-mUWZ8w91/mw2KEcZ6gHNoNNmsAq9Wiw2IypIux5lM03nhXm+WSloXGUNuRETNTLqZexMgpt7Aj/v63qqrsWraQ=="],
|
||||||
|
|
||||||
"framer-motion": ["framer-motion@12.35.2", "", { "dependencies": { "motion-dom": "^12.35.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA=="],
|
"framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
@@ -748,11 +748,11 @@
|
|||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"h3": ["h3@1.15.6", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ=="],
|
"h3": ["h3@1.15.10", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg=="],
|
||||||
|
|
||||||
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||||
|
|
||||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"hookable": ["hookable@6.1.0", "", {}, "sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw=="],
|
||||||
|
|
||||||
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
|
||||||
|
|
||||||
@@ -842,7 +842,7 @@
|
|||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
"lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
|
||||||
|
|
||||||
"magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="],
|
"magic-regexp": ["magic-regexp@0.10.0", "", { "dependencies": { "estree-walker": "^3.0.3", "magic-string": "^0.30.12", "mlly": "^1.7.2", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "ufo": "^1.5.4", "unplugin": "^2.0.0" } }, "sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg=="],
|
||||||
|
|
||||||
@@ -852,7 +852,7 @@
|
|||||||
|
|
||||||
"markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="],
|
"markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="],
|
||||||
|
|
||||||
"marked": ["marked@17.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ=="],
|
"marked": ["marked@17.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg=="],
|
||||||
|
|
||||||
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||||
|
|
||||||
@@ -868,17 +868,17 @@
|
|||||||
|
|
||||||
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
"mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="],
|
||||||
|
|
||||||
"minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||||
|
|
||||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||||
|
|
||||||
"mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="],
|
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
|
||||||
|
|
||||||
"motion-dom": ["motion-dom@12.35.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg=="],
|
"motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
|
||||||
|
|
||||||
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
"motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
|
||||||
|
|
||||||
"motion-v": ["motion-v@1.10.3", "", { "dependencies": { "framer-motion": "^12.25.0", "hey-listen": "^1.0.8", "motion-dom": "^12.23.23" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-9Ewo/wwGv7FO3PqYJpllBF/Efc7tbeM1iinVrM73s0RUQrnXHwMZCaRX98u4lu0PQCrZghPPfCsQ14pWKIEbnQ=="],
|
"motion-v": ["motion-v@2.2.0", "", { "dependencies": { "framer-motion": "^12.38.0", "hey-listen": "^1.0.8", "motion-dom": "^12.38.0", "motion-utils": "^12.36.0" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-8rrUBt9UMwy45MfLMwHer9J7xBY/rL31S+0U3ZUAouqjH3AOVHvS0NGuXwF4mfWfvb22rfZEv59ZsBc2f1epxQ=="],
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
@@ -942,7 +942,7 @@
|
|||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||||
|
|
||||||
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
||||||
|
|
||||||
@@ -990,9 +990,9 @@
|
|||||||
|
|
||||||
"prosemirror-trailing-node": ["prosemirror-trailing-node@3.0.0", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="],
|
"prosemirror-trailing-node": ["prosemirror-trailing-node@3.0.0", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="],
|
||||||
|
|
||||||
"prosemirror-transform": ["prosemirror-transform@1.11.0", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw=="],
|
"prosemirror-transform": ["prosemirror-transform@1.12.0", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w=="],
|
||||||
|
|
||||||
"prosemirror-view": ["prosemirror-view@1.41.6", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg=="],
|
"prosemirror-view": ["prosemirror-view@1.41.7", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
@@ -1012,7 +1012,7 @@
|
|||||||
|
|
||||||
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
||||||
|
|
||||||
"reka-ui": ["reka-ui@2.8.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-8lTKcJhmG+D3UyJxhBnNnW/720sLzm0pbA9AC1MWazmJ5YchJAyTSl+O00xP/kxBmEN0fw5JqWVHguiFmsGjzA=="],
|
"reka-ui": ["reka-ui@2.9.3", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-C9lCVxsSC7uYD0Nbgik1+14FNndHNprZmf0zGQt0ZDYIt5KxXV3zD0hEqNcfRUsEEJvVmoRsUkJnASBVBeaaUw=="],
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
@@ -1056,13 +1056,13 @@
|
|||||||
|
|
||||||
"tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="],
|
"tailwind-variants": ["tailwind-variants@3.2.2", "", { "peerDependencies": { "tailwind-merge": ">=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
|
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
||||||
|
|
||||||
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
@@ -1070,7 +1070,7 @@
|
|||||||
|
|
||||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||||
|
|
||||||
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
@@ -1080,7 +1080,7 @@
|
|||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.57.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.0", "@typescript-eslint/parser": "8.57.0", "@typescript-eslint/typescript-estree": "8.57.0", "@typescript-eslint/utils": "8.57.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA=="],
|
"typescript-eslint": ["typescript-eslint@8.58.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", "@typescript-eslint/typescript-estree": "8.58.0", "@typescript-eslint/utils": "8.58.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA=="],
|
||||||
|
|
||||||
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
||||||
|
|
||||||
@@ -1104,9 +1104,9 @@
|
|||||||
|
|
||||||
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
|
||||||
|
|
||||||
"unplugin-vue-components": ["unplugin-vue-components@31.0.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q=="],
|
"unplugin-vue-components": ["unplugin-vue-components@31.1.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.3", "tinyglobby": "^0.2.15", "unplugin": "^2.3.11", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-9EbV5ark21A4BOBt6RJGJXCVD2I1eoxTZL1TAvNgYTokcrFIiuxpufb8owyWn7n+z2x8daz/ltZq6IRRKL3ydQ=="],
|
||||||
|
|
||||||
"unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
|
"unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
|
|
||||||
"untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", "jiti": "^2.4.2", "knitwork": "^1.2.0", "scule": "^1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="],
|
"untyped": ["untyped@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", "jiti": "^2.4.2", "knitwork": "^1.2.0", "scule": "^1.3.0" }, "bin": { "untyped": "dist/cli.mjs" } }, "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g=="],
|
||||||
|
|
||||||
@@ -1126,17 +1126,17 @@
|
|||||||
|
|
||||||
"vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "^4.1.0", "debug": "^4.4.1", "error-stack-parser-es": "^1.0.5", "ohash": "^2.0.11", "open": "^10.2.0", "perfect-debounce": "^2.0.0", "sirv": "^3.0.1", "unplugin-utils": "^0.3.0", "vite-dev-rpc": "^1.1.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="],
|
"vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "^4.1.0", "debug": "^4.4.1", "error-stack-parser-es": "^1.0.5", "ohash": "^2.0.11", "open": "^10.2.0", "perfect-debounce": "^2.0.0", "sirv": "^3.0.1", "unplugin-utils": "^0.3.0", "vite-dev-rpc": "^1.1.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="],
|
||||||
|
|
||||||
"vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.0.7", "", { "dependencies": { "@vue/devtools-core": "^8.0.7", "@vue/devtools-kit": "^8.0.7", "@vue/devtools-shared": "^8.0.7", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" } }, "sha512-BWj/ykGpqVAJVdPyHmSTUm44buz3jPv+6jnvuFdQSRH0kAgP1cEIE4doHiFyqHXOmuB5EQVR/nh2g9YRiRNs9g=="],
|
"vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.1.1", "", { "dependencies": { "@vue/devtools-core": "^8.1.1", "@vue/devtools-kit": "^8.1.1", "@vue/devtools-shared": "^8.1.1", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-9qTpOmZ2vHpvlI9hdVXAQ1Ry4I8GcBArU7aPi0qfIaV7fQIXy0L1nb6X4mFY2Gw0dYshHuLbIl0Ulb572SCjsQ=="],
|
||||||
|
|
||||||
"vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.3.2", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q=="],
|
"vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.4.0", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-Iq/024CydcE46FZqWPU4t4lw4uYOdLnFSO1RNxJVt2qY9zxIjmnkBqhHnYaReWM82kmNnaXs7OkfgRrV2GEjyw=="],
|
||||||
|
|
||||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||||
|
|
||||||
"vue": ["vue@3.6.0-beta.7", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.7", "@vue/compiler-sfc": "3.6.0-beta.7", "@vue/runtime-dom": "3.6.0-beta.7", "@vue/runtime-vapor": "3.6.0-beta.7", "@vue/server-renderer": "3.6.0-beta.7", "@vue/shared": "3.6.0-beta.7" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-SlNdNG+c37xycRFIJ/KB9CmpZmWwKovbDTNl00ck0+ZSNZSgb+n6DKLuXtVvGZImNPu6jj+zzaM1gqNB5NeC6Q=="],
|
"vue": ["vue@3.6.0-beta.9", "", { "dependencies": { "@vue/compiler-dom": "3.6.0-beta.9", "@vue/compiler-sfc": "3.6.0-beta.9", "@vue/runtime-dom": "3.6.0-beta.9", "@vue/runtime-vapor": "3.6.0-beta.9", "@vue/server-renderer": "3.6.0-beta.9", "@vue/shared": "3.6.0-beta.9" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-K/+HT52OAZvaNDDMvT+Lfk36e/97PsyJyDWXM0FDfLQKGbEDXsd4pSQz+BJViGssLcltQLSTlSqQlZ6fkGo5CA=="],
|
||||||
|
|
||||||
"vue-chartjs": ["vue-chartjs@5.3.3", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA=="],
|
"vue-chartjs": ["vue-chartjs@5.3.3", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA=="],
|
||||||
|
|
||||||
"vue-component-type-helpers": ["vue-component-type-helpers@3.2.5", "", {}, "sha512-tkvNr+bU8+xD/onAThIe7CHFvOJ/BO6XCOrxMzeytJq40nTfpGDJuVjyCM8ccGZKfAbGk2YfuZyDMXM56qheZQ=="],
|
"vue-component-type-helpers": ["vue-component-type-helpers@3.2.6", "", {}, "sha512-O02tnvIfOQVmnvoWwuSydwRoHjZVt8UEBR+2p4rT35p8GAy5VTlWP8o5qXfJR/GWCN0nVZoYWsVUvx2jwgdBmQ=="],
|
||||||
|
|
||||||
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
"vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="],
|
||||||
|
|
||||||
@@ -1144,9 +1144,9 @@
|
|||||||
|
|
||||||
"vue-i18n": ["vue-i18n@11.3.0", "", { "dependencies": { "@intlify/core-base": "11.3.0", "@intlify/devtools-types": "11.3.0", "@intlify/shared": "11.3.0", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA=="],
|
"vue-i18n": ["vue-i18n@11.3.0", "", { "dependencies": { "@intlify/core-base": "11.3.0", "@intlify/devtools-types": "11.3.0", "@intlify/shared": "11.3.0", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA=="],
|
||||||
|
|
||||||
"vue-router": ["vue-router@5.0.3", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw=="],
|
"vue-router": ["vue-router@5.0.4", "", { "dependencies": { "@babel/generator": "^7.28.6", "@vue-macros/common": "^3.1.1", "@vue/devtools-api": "^8.0.6", "ast-walker-scope": "^0.8.3", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.1.2", "magic-string": "^0.30.21", "mlly": "^1.8.0", "muggle-string": "^0.4.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "scule": "^1.3.0", "tinyglobby": "^0.2.15", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1", "yaml": "^2.8.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.17", "pinia": "^3.0.4", "vue": "^3.5.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia"] }, "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg=="],
|
||||||
|
|
||||||
"vue-tsc": ["vue-tsc@3.2.5", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.5" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA=="],
|
"vue-tsc": ["vue-tsc@3.2.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.2.6" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q=="],
|
||||||
|
|
||||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
||||||
|
|
||||||
@@ -1166,9 +1166,9 @@
|
|||||||
|
|
||||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
"yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||||
|
|
||||||
"yjs": ["yjs@13.6.29", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ=="],
|
"yjs": ["yjs@13.6.30", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ=="],
|
||||||
|
|
||||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
@@ -1184,21 +1184,23 @@
|
|||||||
|
|
||||||
"@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.21.1", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg=="],
|
"@nuxt/schema/std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
||||||
|
|
||||||
|
"@nuxt/ui/reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
||||||
|
|
||||||
|
"@nuxtjs/color-mode/@nuxt/kit": ["@nuxt/kit@3.21.2", "", { "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", "defu": "^6.1.4", "destr": "^2.0.5", "errx": "^0.1.0", "exsolve": "^1.0.8", "ignore": "^7.0.5", "jiti": "^2.6.1", "klona": "^2.0.6", "knitwork": "^1.3.0", "mlly": "^1.8.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "rc9": "^3.0.0", "scule": "^1.3.0", "semver": "^7.7.4", "tinyglobby": "^0.2.15", "ufo": "^1.6.3", "unctx": "^2.5.0", "untyped": "^2.0.0" } }, "sha512-Bd6m6mrDrqpBEbX+g0rc66/ALd1sxlgdx5nfK9MAYO0yKLTOSK7McSYz1KcOYn3LQFCXOWfvXwaqih/b+REI1g=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
"@nuxtjs/color-mode/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"@nuxtjs/color-mode/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
@@ -1206,11 +1208,11 @@
|
|||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@unhead/vue/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
"@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
|
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||||
|
|
||||||
"c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
"c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||||
|
|
||||||
@@ -1226,7 +1228,7 @@
|
|||||||
|
|
||||||
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||||
|
|
||||||
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||||
|
|
||||||
@@ -1242,8 +1244,6 @@
|
|||||||
|
|
||||||
"unctx/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
"unctx/unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||||
|
|
||||||
"unhead/hookable": ["hookable@6.0.1", "", {}, "sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw=="],
|
|
||||||
|
|
||||||
"unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
"unimport/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||||
|
|
||||||
"unimport/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
"unimport/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||||
@@ -1256,9 +1256,11 @@
|
|||||||
|
|
||||||
"vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
|
"vaul-vue/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="],
|
||||||
|
|
||||||
|
"vaul-vue/reka-ui": ["reka-ui@2.9.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.13", "@floating-ui/vue": "^1.1.6", "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", "@vueuse/core": "^14.1.0", "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" }, "peerDependencies": { "vue": ">= 3.4.0" } }, "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw=="],
|
||||||
|
|
||||||
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.0.7", "", { "dependencies": { "@vue/devtools-kit": "^8.0.7" } }, "sha512-tc1TXAxclsn55JblLkFVcIRG7MeSJC4fWsPjfM7qu/IcmPUYnQ5Q8vzWwBpyDY24ZjmZTUCCwjRSNbx58IhlAA=="],
|
"vue-router/@vue/devtools-api": ["@vue/devtools-api@8.1.1", "", { "dependencies": { "@vue/devtools-kit": "^8.1.1" } }, "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw=="],
|
||||||
|
|
||||||
"@nuxtjs/color-mode/@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@nuxtjs/color-mode/@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
@@ -1270,30 +1272,10 @@
|
|||||||
|
|
||||||
"@nuxtjs/color-mode/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"@nuxtjs/color-mode/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
"@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
||||||
|
|
||||||
|
"@vue/devtools-api/@vue/devtools-kit/hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
"@vue/devtools-api/@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
"@vue/devtools-api/@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||||
|
|
||||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
@@ -1305,5 +1287,7 @@
|
|||||||
"vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
"vaul-vue/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="],
|
||||||
|
|
||||||
"vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
|
"vaul-vue/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="],
|
||||||
|
|
||||||
|
"vaul-vue/reka-ui/@vueuse/core": ["@vueuse/core@14.2.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.2.1", "@vueuse/shared": "14.2.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
bo/components.d.ts
vendored
28
bo/components.d.ts
vendored
@@ -12,35 +12,51 @@ export {}
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
CartDetails: typeof import('./src/components/customer/CartDetails.vue')['default']
|
||||||
CategoryMenu: typeof import('./src/components/inner/categoryMenu.vue')['default']
|
CategoryMenu: typeof import('./src/components/inner/CategoryMenu.vue')['default']
|
||||||
|
copy: typeof import('./src/components/admin/ProductDetailView copy.vue')['default']
|
||||||
|
CountryCurrencySwitch: typeof import('./src/components/inner/CountryCurrencySwitch.vue')['default']
|
||||||
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
Cs_PrivacyPolicyView: typeof import('./src/components/terms/cs_PrivacyPolicyView.vue')['default']
|
||||||
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
Cs_TermsAndConditionsView: typeof import('./src/components/terms/cs_TermsAndConditionsView.vue')['default']
|
||||||
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
En_PrivacyPolicyView: typeof import('./src/components/terms/en_PrivacyPolicyView.vue')['default']
|
||||||
En_TermsAndConditionsView: typeof import('./src/components/terms/en_TermsAndConditionsView.vue')['default']
|
En_TermsAndConditionsView: typeof import('./src/components/terms/en_TermsAndConditionsView.vue')['default']
|
||||||
LangSwitch: typeof import('./src/components/inner/langSwitch.vue')['default']
|
FavoriteProducts: typeof import('./src/components/admin/FavoriteProducts.vue')['default']
|
||||||
|
LangSwitch: typeof import('./src/components/inner/LangSwitch.vue')['default']
|
||||||
PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default']
|
PageAddresses: typeof import('./src/components/customer/PageAddresses.vue')['default']
|
||||||
|
PageCart: typeof import('./src/components/customer/PageCart.vue')['default']
|
||||||
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
PageCarts: typeof import('./src/components/customer/PageCarts.vue')['default']
|
||||||
|
PageCArts: typeof import('./src/components/customer/PageCArts.vue')['default']
|
||||||
|
PageCreateCart: typeof import('./src/components/customer/PageCreateCart.vue')['default']
|
||||||
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
||||||
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
||||||
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
||||||
PageProfileDetails: typeof import('./src/components/customer/PageProfileDetails.vue')['default']
|
PageProfileDetails: typeof import('./src/components/customer/PageProfileDetails.vue')['default']
|
||||||
PageProfileDetailsAddInfo: typeof import('./src/components/customer/PageProfileDetailsAddInfo.vue')['default']
|
PageProfileDetailsAddInfo: typeof import('./src/components/customer/PageProfileDetailsAddInfo.vue')['default']
|
||||||
|
PageSearchProducts: typeof import('./src/components/customer/PageSearchProducts.vue')['default']
|
||||||
PageStatistic: typeof import('./src/components/customer/PageStatistic.vue')['default']
|
PageStatistic: typeof import('./src/components/customer/PageStatistic.vue')['default']
|
||||||
Pl_PrivacyPolicyView: typeof import('./src/components/terms/pl_PrivacyPolicyView.vue')['default']
|
Pl_PrivacyPolicyView: typeof import('./src/components/terms/pl_PrivacyPolicyView.vue')['default']
|
||||||
Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default']
|
Pl_TermsAndConditionsView: typeof import('./src/components/terms/pl_TermsAndConditionsView.vue')['default']
|
||||||
ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default']
|
ProductCustomization: typeof import('./src/components/customer/components/ProductCustomization.vue')['default']
|
||||||
ProductDetailView: typeof import('./src/components/admin/ProductDetailView.vue')['default']
|
ProductDetailView: typeof import('./src/components/admin/ProductDetailView.vue')['default']
|
||||||
|
'ProductDetailView copy': typeof import('./src/components/admin/ProductDetailView copy.vue')['default']
|
||||||
|
ProductEditor: typeof import('./src/components/inner/ProductEditor.vue')['default']
|
||||||
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
|
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
|
||||||
|
Profile: typeof import('./src/components/customer-management/Profile.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
ThemeSwitch: typeof import('./src/components/inner/themeSwitch.vue')['default']
|
StorageFileBrowser: typeof import('./src/components/customer/StorageFileBrowser.vue')['default']
|
||||||
|
ThemeSwitch: typeof import('./src/components/inner/ThemeSwitch.vue')['default']
|
||||||
TopBar: typeof import('./src/components/TopBar.vue')['default']
|
TopBar: typeof import('./src/components/TopBar.vue')['default']
|
||||||
TopBarLogin: typeof import('./src/components/TopBarLogin.vue')['default']
|
TopBarLogin: typeof import('./src/components/TopBarLogin.vue')['default']
|
||||||
UAlert: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Alert.vue')['default']
|
UAlert: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Alert.vue')['default']
|
||||||
|
UApp: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/App.vue')['default']
|
||||||
|
UAvatar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Avatar.vue')['default']
|
||||||
UButton: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Button.vue')['default']
|
UButton: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Button.vue')['default']
|
||||||
UCard: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Card.vue')['default']
|
UCard: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Card.vue')['default']
|
||||||
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
UCheckbox: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Checkbox.vue')['default']
|
||||||
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
UDrawer: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Drawer.vue')['default']
|
||||||
|
UDropdownMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/DropdownMenu.vue')['default']
|
||||||
|
UEditor: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Editor.vue')['default']
|
||||||
|
UEditorToolbar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/EditorToolbar.vue')['default']
|
||||||
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
UForm: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Form.vue')['default']
|
||||||
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
UFormField: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/FormField.vue')['default']
|
||||||
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
UIcon: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/components/Icon.vue')['default']
|
||||||
@@ -51,6 +67,12 @@ declare module 'vue' {
|
|||||||
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
||||||
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
||||||
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
||||||
|
UsersList: typeof import('./src/components/admin/UsersList.vue')['default']
|
||||||
|
UsersSearch: typeof import('./src/components/admin/UsersSearch.vue')['default']
|
||||||
|
USidebar: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Sidebar.vue')['default']
|
||||||
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
UTable: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Table.vue')['default']
|
||||||
|
UTabs: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Tabs.vue')['default']
|
||||||
|
UTextarea: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Textarea.vue')['default']
|
||||||
|
UTree: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Tree.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,21 +13,22 @@
|
|||||||
"format": "prettier --write --experimental-cli src/"
|
"format": "prettier --write --experimental-cli src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "^4.5.0",
|
"@nuxt/ui": "^4.6.0",
|
||||||
"@tailwindcss/vite": "^4.2.0",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"tailwindcss": "^4.2.0",
|
"reka-ui": "^2.9.3",
|
||||||
|
"tailwindcss": "^4.2.2",
|
||||||
"vue": "beta",
|
"vue": "beta",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.3",
|
||||||
"vue-i18n": "11",
|
"vue-i18n": "^11.3.0",
|
||||||
"vue-router": "^5.0.2"
|
"vue-router": "^5.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/node": "^24.10.13",
|
"@types/node": "^24.12.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.5",
|
||||||
"@vue/eslint-config-typescript": "^14.6.0",
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.8.1",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
@@ -35,8 +36,8 @@
|
|||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "beta",
|
"vite": "beta",
|
||||||
"vite-plugin-vue-devtools": "^8.0.6",
|
"vite-plugin-vue-devtools": "^8.1.1",
|
||||||
"vue-tsc": "^3.2.4"
|
"vue-tsc": "^3.2.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterView } from 'vue-router'
|
import { computed } from 'vue'
|
||||||
|
import { TooltipProvider } from 'reka-ui'
|
||||||
|
import { RouterView, useRoute } from 'vue-router'
|
||||||
|
import DefaultLayout from '@/layouts/default.vue'
|
||||||
|
import EmptyLayout from '@/layouts/empty.vue'
|
||||||
|
import ManagementLayout from '@/layouts/management.vue'
|
||||||
|
import { useAuthStore } from './stores/customer/auth'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const layout = computed(() => (route.meta.layout as string) || 'default')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RouterView />
|
<TooltipProvider>
|
||||||
|
<component :is="layout === 'empty' ? EmptyLayout : layout === 'management' ? ManagementLayout : DefaultLayout">
|
||||||
|
<RouterView v-slot="{ Component }">
|
||||||
|
<component :is="Component" />
|
||||||
|
</RouterView>
|
||||||
|
</component>
|
||||||
|
</TooltipProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
slots: {
|
|
||||||
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
slots: {
|
slots: {
|
||||||
@@ -40,16 +37,17 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
},
|
},
|
||||||
selectMenu: {
|
selectMenu: {
|
||||||
slots: {
|
slots: {
|
||||||
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
// base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0!',
|
||||||
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
|
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80',
|
||||||
itemLeadingIcon: 'text-(--black)! dark:text-white!'
|
// content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! z-80 text-(--black)! dark:text-white!',
|
||||||
|
// itemLeadingIcon: 'text-(--black)! dark:text-white!'
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
slots: {
|
slots: {
|
||||||
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
base: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
||||||
tr: 'border-b! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! text-(--black)! dark:text-white!',
|
// tr: 'border-b! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! text-(--black)! dark:text-white!',
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -58,6 +56,11 @@ export const uiOptions: NuxtUIOptions = {
|
|||||||
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
content: 'border! border-(--border-light)! dark:border-(--border-dark)! outline-0! ring-0! bg-(--second-light) dark:bg-(--main-dark)',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
textarea: {
|
||||||
|
slots: {
|
||||||
|
base: 'ring-0! outline-0! rounded-md border border-(--border-light)! dark:border-(--border-dark)! text-base!'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
bo/src/assets/error.svg
Normal file
1
bo/src/assets/error.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none"><path stroke="#5b5b5b" stroke-width="2" d="M3 11c0-3.771 0-5.657 1.172-6.828S7.229 3 11 3h2c3.771 0 5.657 0 6.828 1.172S21 7.229 21 11v2c0 3.771 0 5.657-1.172 6.828S16.771 21 13 21h-2c-3.771 0-5.657 0-6.828-1.172S3 16.771 3 13z"/><path fill="#5b5b5b" fill-rule="evenodd" d="m19 13.585l-.02-.02c-.39-.39-.726-.726-1.026-.979c-.317-.267-.662-.502-1.088-.63a3 3 0 0 0-1.732 0c-.426.129-.77.363-1.088.63c-.3.253-.636.59-1.025.98l-.029.028c-.307.306-.487.486-.628.601l-.02.017l-.012-.023c-.087-.16-.189-.393-.36-.792l-.053-.124l-.022-.052c-.356-.83-.655-1.528-.95-2.054c-.305-.541-.685-1.05-1.277-1.346a3 3 0 0 0-1.597-.307c-.66.055-1.201.386-1.685.775c-.406.327-.86.77-1.388 1.297V13q0 .774.003 1.411l1.114-1.114c.69-.69 1.15-1.147 1.525-1.45c.37-.298.53-.335.6-.34a1 1 0 0 1 .532.102c.061.031.196.124.43.54c.236.42.493 1.016.877 1.912l.053.124l.017.038c.149.348.287.67.425.924c.145.265.355.583.709.802a2 2 0 0 0 1.398.27c.41-.073.723-.29.956-.482c.222-.184.47-.432.738-.7l.03-.03c.425-.425.701-.7.928-.891c.218-.184.32-.228.376-.245a1 1 0 0 1 .578 0c.056.017.158.061.376.245c.227.191.503.466.929.892l1.35 1.35c.046-.718.054-1.61.056-2.773" clip-rule="evenodd"/><circle cx="16.5" cy="7.5" r="1.5" fill="#5b5b5b"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -5,19 +5,22 @@ body {
|
|||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-left: 20px
|
||||||
|
}
|
||||||
|
|
||||||
.inter {
|
.inter {
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container{
|
.container {
|
||||||
max-width: 2100px;
|
max-width: 2100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--main-light: #FFFEFB;
|
--main-light: #FFFEFB;
|
||||||
--second-light: #F5F6FA;
|
--second-light: #F8FAFC;
|
||||||
|
--main-dark: #1A1A1A;
|
||||||
--main-dark: #212121;
|
|
||||||
--black: #1A1A1A;
|
--black: #1A1A1A;
|
||||||
|
|
||||||
/* gray */
|
/* gray */
|
||||||
@@ -25,13 +28,15 @@ body {
|
|||||||
--gray-dark: #6B7280;
|
--gray-dark: #6B7280;
|
||||||
|
|
||||||
/* borders */
|
/* borders */
|
||||||
--border-light: #E8E7E0;
|
--border-light: #E2E8F0;
|
||||||
--border-dark: #3F3E3D;
|
--border-dark: #334155;
|
||||||
|
|
||||||
/* text */
|
/* text */
|
||||||
--accent-blue-dark: #3B82F6;
|
--accent-blue-dark: #0EA5E91A;
|
||||||
--accent-blue-light:#2563EB;
|
--accent-blue-light: #E0F2FE;
|
||||||
--text-dark: #FFFEFB;
|
--text-dark: #FFFEFB;
|
||||||
|
--text-sky-light: #0284C7;
|
||||||
|
--text-sky-dark: #38BDF8;
|
||||||
|
|
||||||
/* placeholder */
|
/* placeholder */
|
||||||
--placeholder: #8C8C8A;
|
--placeholder: #8C8C8A;
|
||||||
@@ -40,11 +45,11 @@ body {
|
|||||||
--ui-border-accented: var(--border-light);
|
--ui-border-accented: var(--border-light);
|
||||||
--ui-border: var(--border-light);
|
--ui-border: var(--border-light);
|
||||||
--ui-color-neutral-700: var(--black);
|
--ui-color-neutral-700: var(--black);
|
||||||
|
--ui-primary: var(--accent-blue-light)
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--ui-bg: var(--black);
|
--ui-bg: var(--black);
|
||||||
--ui-primary: var(--color-gray-500);
|
|
||||||
--ui-secondary: var(--accent-green-dark);
|
--ui-secondary: var(--accent-green-dark);
|
||||||
--ui-border-accented: var(--border-dark);
|
--ui-border-accented: var(--border-dark);
|
||||||
--ui-text-dimmed: var(--gray-dark);
|
--ui-text-dimmed: var(--gray-dark);
|
||||||
@@ -53,21 +58,26 @@ body {
|
|||||||
--ui-error: var(--dark-red);
|
--ui-error: var(--dark-red);
|
||||||
--border: var(--border-dark);
|
--border: var(--border-dark);
|
||||||
--tw-border-style: var(--border-dark);
|
--tw-border-style: var(--border-dark);
|
||||||
|
--ui-text-inverted: #fff;
|
||||||
|
--ui-primary: var(--accent-blue-dark)
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-form {
|
.label-form {
|
||||||
@apply text-(--gray) dark:text-(--gray-dark) pl-0 md:pl-6 leading-none;
|
@apply text-(--gray) dark:text-(--gray-dark) pl-0 md:pl-6 leading-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@apply font-medium text-[19px] sm:text-xl md:text-[22px] leading-none text-(--black) dark:text-(--main-light);
|
@apply font-medium text-[19px] sm:text-xl md:text-[22px] leading-none text-(--black) dark:text-(--main-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-title {
|
.column-title {
|
||||||
@apply md:ml-[25px] mb-[25px] sm:mb-[30px];
|
@apply md:ml-[25px] mb-[25px] sm:mb-[30px];
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-title {
|
.form-title {
|
||||||
@apply text-(--accent-green) dark:text-(--accent-green-dark) font-medium;
|
@apply text-(--accent-green) dark:text-(--accent-green-dark) font-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blue-button {
|
||||||
|
@apply bg-info! text-white!
|
||||||
|
}
|
||||||
@@ -1,13 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<header
|
||||||
|
class="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
||||||
|
<div class="px-4">
|
||||||
|
<div class="flex items-center justify-between h-16">
|
||||||
|
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center">
|
||||||
|
<UIcon name="i-heroicons-clock" class="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-white">{{ settings.app.name }}</span>
|
||||||
|
</RouterLink>
|
||||||
|
|
||||||
|
<UNavigationMenu :type="'trigger'" :ui="{
|
||||||
|
root: 'justify-center',
|
||||||
|
list: 'gap-4 text-(--black)',
|
||||||
|
linkLabel: 'text-(--black) text-sm!'
|
||||||
|
}" :items="menuItems" class="" />
|
||||||
|
<!-- orientation="vertical" -->
|
||||||
|
<div class="flex items-center gap-12">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<CountryCurrencySwitch />
|
||||||
|
<LangSwitch />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ThemeSwitch />
|
||||||
|
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
||||||
|
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
|
||||||
|
{{ $t('general.logout') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import LangSwitch from './inner/langSwitch.vue'
|
import LangSwitch from './inner/LangSwitch.vue'
|
||||||
import ThemeSwitch from './inner/themeSwitch.vue'
|
import ThemeSwitch from './inner/ThemeSwitch.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { currentLang } from '@/router/langs'
|
import { currentLang } from '@/router/langs'
|
||||||
import type { LabelTrans, TopMenuItem } from '@/types'
|
import type { LabelTrans, TopMenuItem } from '@/types'
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui'
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import CountryCurrencySwitch from './inner/CountryCurrencySwitch.vue'
|
||||||
|
import { settings } from '@/router/settings'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
let menu = ref()
|
let menu = ref()
|
||||||
@@ -51,37 +89,3 @@ function transformMenu(items: TopMenuItem[], locale: string | undefined): Naviga
|
|||||||
}
|
}
|
||||||
await getTopMenu()
|
await getTopMenu()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<header
|
|
||||||
class="fixed top-0 left-0 right-0 z-50 bg-white/80 dark:bg-(--black) backdrop-blur-md border-b border-(--border-light) dark:border-(--border-dark)">
|
|
||||||
<!-- px-4 sm:px-6 lg:px-8 -->
|
|
||||||
<div class="container mx-auto px-4">
|
|
||||||
<div class="flex items-center justify-between h-14">
|
|
||||||
<!-- Logo -->
|
|
||||||
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
|
||||||
<div class="w-8 h-8 rounded-lg bg-primary text-white flex items-center justify-center">
|
|
||||||
<UIcon name="i-heroicons-clock" class="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
|
||||||
</RouterLink>
|
|
||||||
|
|
||||||
<UNavigationMenu :type="'trigger'" :ui="{
|
|
||||||
root: 'justify-center',
|
|
||||||
list: 'gap-4'
|
|
||||||
}" :items="menuItems" class="w-full"></UNavigationMenu>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<!-- Language Switcher -->
|
|
||||||
<LangSwitch />
|
|
||||||
<!-- Theme Switcher -->
|
|
||||||
<ThemeSwitch />
|
|
||||||
<!-- Logout Button (only when authenticated) -->
|
|
||||||
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
|
||||||
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark)">
|
|
||||||
{{ $t('general.logout') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LangSwitch from './inner/langSwitch.vue'
|
import LangSwitch from './inner/LangSwitch.vue'
|
||||||
import ThemeSwitch from './inner/themeSwitch.vue'
|
import ThemeSwitch from './inner/ThemeSwitch.vue'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
9
bo/src/components/admin/FavoriteProducts.vue
Normal file
9
bo/src/components/admin/FavoriteProducts.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -1,24 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<suspense>
|
<div class="flex flex-col md:flex-row gap-10">
|
||||||
<component :is="Default || 'div'">
|
|
||||||
<div class="flex gap-10">
|
|
||||||
<CategoryMenu />
|
<CategoryMenu />
|
||||||
<div class="w-full flex flex-col items-center gap-4">
|
<div class="w-full flex flex-col items-center gap-4">
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
|
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" :ui="{
|
||||||
|
root: 'max-w-100wv overflow-auto!'
|
||||||
|
}" />
|
||||||
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</template>
|
||||||
</suspense>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, h, resolveComponent, computed } from 'vue'
|
import { ref, watch, h, resolveComponent, computed } from 'vue'
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
import CategoryMenu from '../inner/categoryMenu.vue'
|
import CategoryMenu from '../inner/CategoryMenu.vue'
|
||||||
import type { Product } from '@/types/product'
|
import type { Product } from '@/types/product'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -141,7 +138,7 @@ async function fetchProductList() {
|
|||||||
if (route.params.category_id)
|
if (route.params.category_id)
|
||||||
params.append('category_id', String(route.params.category_id))
|
params.append('category_id', String(route.params.category_id))
|
||||||
|
|
||||||
const url = `/api/v1/restricted/list/list-products?elems=${perPage.value}&${params.toString()}`
|
const url = `/api/v1/restricted/product/list?elems=${perPage.value}&${params.toString()}`
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ApiResponse>(url)
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
productsList.value = response.items || []
|
productsList.value = response.items || []
|
||||||
@@ -153,10 +150,16 @@ async function fetchProductList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToProduct(productId: number) {
|
function goToProduct(productId: number, linkRewrite: string) {
|
||||||
|
let path = {
|
||||||
|
name: route.name,
|
||||||
|
params: route.params,
|
||||||
|
query: route.query
|
||||||
|
}
|
||||||
|
localStorage.setItem('back_from_product', JSON.stringify(path))
|
||||||
router.push({
|
router.push({
|
||||||
name: 'customer-product-details',
|
name: 'admin-product-details',
|
||||||
params: { product_id: productId }
|
params: { product_id: productId, link_rewrite: linkRewrite }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +176,7 @@ const UInput = resolveComponent('UInput')
|
|||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UIcon = resolveComponent('UIcon')
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
|
||||||
|
import errorImg from '@/assets/error.svg'
|
||||||
const columns: TableColumn<Product>[] = [
|
const columns: TableColumn<Product>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'product_id',
|
accessorKey: 'product_id',
|
||||||
@@ -209,9 +213,13 @@ const columns: TableColumn<Product>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h('img', {
|
return h('img', {
|
||||||
src: row.getValue('image_link') as string,
|
src: row.getValue('image_link') as string,
|
||||||
style: 'width:40px;height:40px;object-fit:cover;'
|
style: 'width:40px;height:40px;object-fit:cover;',
|
||||||
})
|
onError: (e: Event) => {
|
||||||
|
const target = e.target as HTMLImageElement
|
||||||
|
target.src = errorImg
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
@@ -270,10 +278,11 @@ const columns: TableColumn<Product>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h(UButton, {
|
return h(UButton, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
goToProduct(row.original.product_id)
|
goToProduct(row.original.product_id, row.original.link_rewrite)
|
||||||
},
|
},
|
||||||
color: 'primary',
|
class: 'cursor-pointer',
|
||||||
variant: 'solid'
|
color: 'info',
|
||||||
|
variant: 'soft'
|
||||||
}, () => 'Show product')
|
}, () => 'Show product')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
523
bo/src/components/admin/ProductDetailView copy.vue
Normal file
523
bo/src/components/admin/ProductDetailView copy.vue
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<UIcon name="line-md:arrow-left" class="text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
|
<p class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)" @click="backFromProduct()">
|
||||||
|
Back to products</p>
|
||||||
|
</div>
|
||||||
|
<div class="container mx-auto ">
|
||||||
|
<div
|
||||||
|
class="gap-4 mb-6 bg-slate-50 dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex flex-col items-center gap-3" v-if="!isTranslations">
|
||||||
|
<USelectMenu v-model="toLangId" :items="langs" value-key="id"
|
||||||
|
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
||||||
|
<template #default>
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
Selected language
|
||||||
|
</span>
|
||||||
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{langs.find(l => l.id === toLangId)?.name || 'Select language'}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item-leading="{ item }">
|
||||||
|
<div class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<span class="text-lg">{{ item.flag }}</span>
|
||||||
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="toLangId !== settingStore.shopDefaultLanguage && !isTranslations" class="flex gap-7">
|
||||||
|
<UButton @click="translateToSelectedLanguage" color="info" :loading="translating">
|
||||||
|
Translate from Polish to {{langs.find(l => l.id === toLangId)?.name}}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-if="isTranslations" class="flex gap-3 w-full justify-end">
|
||||||
|
<UButton @click="() => {
|
||||||
|
toLangId = settingStore.shopDefaultLanguage
|
||||||
|
isTranslations = false
|
||||||
|
}" color="info" variant="outline">
|
||||||
|
Cancel and back to Polish
|
||||||
|
</UButton>
|
||||||
|
<UButton color="info" @click="productStore.saveProductDescription(productID, toLangId)">
|
||||||
|
Save translations
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <div class="flex gap-7">
|
||||||
|
<div v-if="isTranslations === false">
|
||||||
|
<UButton @click="() => {
|
||||||
|
fetchForLanguage(toLangId)
|
||||||
|
isShowProductView = true
|
||||||
|
}" class="text-(--accent-blue-light) dark:text-(--accent-blue-dark)"
|
||||||
|
color="neutral" variant="outline">
|
||||||
|
<p class="dark:text-white"> Show product</p>
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
||||||
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) flex items-center! justify-center! px-12">
|
||||||
|
Translate
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex gap-3">
|
||||||
|
<UButton @click="() => {
|
||||||
|
toLangId = settingStore.shopDefaultLanguage
|
||||||
|
isTranslations = false
|
||||||
|
}" color="primary" class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark)">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
<UButton color="primary" @click="productStore.saveProductDescription(productID, toLangId)"
|
||||||
|
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark)">
|
||||||
|
Save
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
|
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
||||||
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
|
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div v-if="productStore.loading" class="flex items-center justify-center py-20">
|
||||||
|
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
||||||
|
<p class="text-red-500">{{ productStore.error }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="productStore.productDescription" class="">
|
||||||
|
<div class="flex items-start gap-30">
|
||||||
|
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-[25px] font-bold text-black dark:text-white">
|
||||||
|
{{ productStore.productDescription.name || 'Product Name' }}
|
||||||
|
</p>
|
||||||
|
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
||||||
|
|
||||||
|
<div class="space-y-[10px]">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<UIcon name="lets-icons:done-ring-round-fill"
|
||||||
|
class="text-[20px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
||||||
|
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
||||||
|
{{ productStore.productDescription.available_now }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<UIcon name="marketeq:car-shipping"
|
||||||
|
class="text-[25px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
||||||
|
<p class="text-[18px] font-bold text-black dark:text-white">
|
||||||
|
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UTabs :items="items" v-model="activeTab" color="info" :content="true" :ui="{
|
||||||
|
list : 'w-auto!'
|
||||||
|
}">
|
||||||
|
<template #description>
|
||||||
|
<div v-if="!isTranslations" class="flex justify-end items-center gap-3 mb-4">
|
||||||
|
<UButton v-if="!isEditing" @click="activeTab === 'usage' ? enableEdit() : enableDescriptionEdit()"
|
||||||
|
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||||
|
<p class="text-white">Change Text</p>
|
||||||
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="isEditing" @click="activeTab === 'usage' ? saveText : saveDescription" color="neutral"
|
||||||
|
variant="outline" class="p-2.5 cursor-pointer">
|
||||||
|
<p class="dark:text-white text-black">Save the edited text</p>
|
||||||
|
</UButton>
|
||||||
|
<UButton v-if="isEditing" @click="activeTab === 'usage' ? cancelEdit : cancelDescriptionEdit"
|
||||||
|
color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<UEditor v-if="isTranslations" v-slot="{ editor }" v-model="productStore.productDescription.description"
|
||||||
|
content-type="html" :ui="{ base: 'p-8 sm:px-16' }" class="w-full min-h-74" placeholder="Write there ...">
|
||||||
|
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
|
||||||
|
<template #link>
|
||||||
|
<EditorLinkPopover :editor="editor" auto-open />
|
||||||
|
</template>
|
||||||
|
</UEditorToolbar>
|
||||||
|
</UEditor>
|
||||||
|
<p v-else v-html="productStore.productDescription.description" class="text-black dark:text-white"></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #usage>
|
||||||
|
<div class="px-7">
|
||||||
|
<div class="flex justify-end items-center gap-3 mb-4">
|
||||||
|
<UButton v-if="!isEditing" @click="enableEdit"
|
||||||
|
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
||||||
|
<p class="text-white">Change Text</p>
|
||||||
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline"
|
||||||
|
class="p-2.5 cursor-pointer">
|
||||||
|
<p class="dark:text-white text-black">Save the edited text</p>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
||||||
|
class="p-2.5 cursor-pointer">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UEditor v-if="isTranslations" v-slot="{ editor }" v-model="productStore.productDescription.usage"
|
||||||
|
content-type="html" :ui="{ base: 'p-8 sm:px-16' }" class="w-full min-h-74" placeholder="Write there ...">
|
||||||
|
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8">
|
||||||
|
<template #link>
|
||||||
|
<EditorLinkPopover :editor="editor" auto-open />
|
||||||
|
</template>
|
||||||
|
</UEditorToolbar>
|
||||||
|
</UEditor>
|
||||||
|
<p v-else v-html="productStore.productDescription.usage" class="text-black dark:text-white"></p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useEditable } from '@/composable/useConteditable';
|
||||||
|
import { langs } from '@/router/langs';
|
||||||
|
import { useProductStore } from '@/stores/product';
|
||||||
|
import { useSettingsStore } from '@/stores/admin/settings';
|
||||||
|
import type { EditorToolbarItem } from '@nuxt/ui';
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const toolbarItems: EditorToolbarItem[][] = [
|
||||||
|
// History controls
|
||||||
|
[{
|
||||||
|
kind: 'undo',
|
||||||
|
icon: 'i-lucide-undo',
|
||||||
|
tooltip: { text: 'Undo' }
|
||||||
|
}, {
|
||||||
|
kind: 'redo',
|
||||||
|
icon: 'i-lucide-redo',
|
||||||
|
tooltip: { text: 'Redo' }
|
||||||
|
}],
|
||||||
|
// Block types
|
||||||
|
[{
|
||||||
|
icon: 'i-lucide-heading',
|
||||||
|
tooltip: { text: 'Headings' },
|
||||||
|
content: {
|
||||||
|
align: 'start'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'heading',
|
||||||
|
level: 1,
|
||||||
|
icon: 'i-lucide-heading-1',
|
||||||
|
label: 'Heading 1'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 2,
|
||||||
|
icon: 'i-lucide-heading-2',
|
||||||
|
label: 'Heading 2'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 3,
|
||||||
|
icon: 'i-lucide-heading-3',
|
||||||
|
label: 'Heading 3'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 4,
|
||||||
|
icon: 'i-lucide-heading-4',
|
||||||
|
label: 'Heading 4'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
icon: 'i-lucide-list',
|
||||||
|
tooltip: { text: 'Lists' },
|
||||||
|
content: {
|
||||||
|
align: 'start'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'bulletList',
|
||||||
|
icon: 'i-lucide-list',
|
||||||
|
label: 'Bullet List'
|
||||||
|
}, {
|
||||||
|
kind: 'orderedList',
|
||||||
|
icon: 'i-lucide-list-ordered',
|
||||||
|
label: 'Ordered List'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
kind: 'blockquote',
|
||||||
|
icon: 'i-lucide-text-quote',
|
||||||
|
tooltip: { text: 'Blockquote' }
|
||||||
|
}, {
|
||||||
|
kind: 'codeBlock',
|
||||||
|
icon: 'i-lucide-square-code',
|
||||||
|
tooltip: { text: 'Code Block' }
|
||||||
|
}, {
|
||||||
|
kind: 'horizontalRule',
|
||||||
|
icon: 'i-lucide-separator-horizontal',
|
||||||
|
tooltip: { text: 'Horizontal Rule' }
|
||||||
|
}],
|
||||||
|
// Text formatting
|
||||||
|
[{
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'bold',
|
||||||
|
icon: 'i-lucide-bold',
|
||||||
|
tooltip: { text: 'Bold' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'italic',
|
||||||
|
icon: 'i-lucide-italic',
|
||||||
|
tooltip: { text: 'Italic' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'underline',
|
||||||
|
icon: 'i-lucide-underline',
|
||||||
|
tooltip: { text: 'Underline' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'strike',
|
||||||
|
icon: 'i-lucide-strikethrough',
|
||||||
|
tooltip: { text: 'Strikethrough' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'code',
|
||||||
|
icon: 'i-lucide-code',
|
||||||
|
tooltip: { text: 'Code' }
|
||||||
|
}],
|
||||||
|
// Link
|
||||||
|
[{
|
||||||
|
kind: 'link',
|
||||||
|
icon: 'i-lucide-link',
|
||||||
|
tooltip: { text: 'Link' }
|
||||||
|
}],
|
||||||
|
// Text alignment
|
||||||
|
[{
|
||||||
|
icon: 'i-lucide-align-justify',
|
||||||
|
tooltip: { text: 'Text Align' },
|
||||||
|
content: {
|
||||||
|
align: 'end'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'left',
|
||||||
|
icon: 'i-lucide-align-left',
|
||||||
|
label: 'Align Left'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'center',
|
||||||
|
icon: 'i-lucide-align-center',
|
||||||
|
label: 'Align Center'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'right',
|
||||||
|
icon: 'i-lucide-align-right',
|
||||||
|
label: 'Align Right'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'justify',
|
||||||
|
icon: 'i-lucide-align-justify',
|
||||||
|
label: 'Align Justify'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
function backFromProduct() {
|
||||||
|
let path = localStorage.getItem('back_from_product')
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
let res = JSON.parse(path)
|
||||||
|
router.push({
|
||||||
|
name: res.name,
|
||||||
|
params: res.params,
|
||||||
|
query: res.query
|
||||||
|
})
|
||||||
|
|
||||||
|
localStorage.removeItem('back_from_product')
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'customer-products',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const settingStore = useSettingsStore()
|
||||||
|
const productStore = useProductStore()
|
||||||
|
const isTranslations = ref(false)
|
||||||
|
|
||||||
|
const toLangId = ref<number>(settingStore.shopDefaultLanguage)
|
||||||
|
const productID = ref<number>(0)
|
||||||
|
const translating = ref(false)
|
||||||
|
|
||||||
|
const activeTab = ref('description')
|
||||||
|
const usageRef = ref<HTMLElement | null>(null)
|
||||||
|
const originalUsage = ref('')
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const usageEdit = useEditable(usageRef)
|
||||||
|
const descriptionRef = ref<HTMLElement | null>(null)
|
||||||
|
const descriptionEdit = useEditable(descriptionRef)
|
||||||
|
const originalDescription = ref('')
|
||||||
|
|
||||||
|
const translateToSelectedLanguage = async () => {
|
||||||
|
if (toLangId.value && productID.value) {
|
||||||
|
translating.value = true
|
||||||
|
try {
|
||||||
|
await productStore.translateProductDescription(productID.value, toLangId.value)
|
||||||
|
} finally {
|
||||||
|
translating.value = false
|
||||||
|
enableEdit()
|
||||||
|
enableDescriptionEdit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (productStore.productDescription) {
|
||||||
|
isTranslations.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchForLanguage = async (langId: number | null) => {
|
||||||
|
if (productID.value) {
|
||||||
|
await productStore.getProductDescription(langId, productID.value)
|
||||||
|
removeInlineStylesFromAll(productStore.productDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(toLangId, () => {
|
||||||
|
fetchForLanguage(toLangId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const id = route.params.product_id
|
||||||
|
if (id) {
|
||||||
|
productID.value = Number(id)
|
||||||
|
await fetchForLanguage(toLangId.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = ref([
|
||||||
|
{ label: 'Description', slot: 'description', name: 'description' },
|
||||||
|
{ label: 'Usage', slot: 'usage', name: 'usage' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// text edit
|
||||||
|
const enableEdit = () => {
|
||||||
|
console.log(usageRef.value, 'usageRef');
|
||||||
|
|
||||||
|
if (usageRef.value) {
|
||||||
|
originalUsage.value = usageRef.value.innerHTML
|
||||||
|
}
|
||||||
|
isEditing.value = true
|
||||||
|
usageEdit.enableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableDescriptionEdit = () => {
|
||||||
|
console.log(usageRef.value, 'usageRef');
|
||||||
|
|
||||||
|
if (descriptionRef.value) {
|
||||||
|
originalDescription.value = descriptionRef.value.innerHTML
|
||||||
|
}
|
||||||
|
descriptionEdit.enableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveText = () => {
|
||||||
|
if (usageRef.value) {
|
||||||
|
productStore.productDescription.usage = usageRef.value.innerHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
usageEdit.disableEdit()
|
||||||
|
isEditing.value = false
|
||||||
|
|
||||||
|
productStore.saveProductDescription(productID.value, toLangId.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
if (usageRef.value) {
|
||||||
|
usageRef.value.innerHTML = originalUsage.value
|
||||||
|
}
|
||||||
|
usageEdit.disableEdit()
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveDescription = async () => {
|
||||||
|
if (descriptionRef.value) {
|
||||||
|
productStore.productDescription.description = descriptionRef.value.innerHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptionEdit.disableEdit()
|
||||||
|
|
||||||
|
await productStore.saveProductDescription(productID.value, toLangId.value)
|
||||||
|
toLangId.value = settingStore.shopDefaultLanguage
|
||||||
|
isTranslations.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelDescriptionEdit = () => {
|
||||||
|
if (descriptionRef.value) {
|
||||||
|
descriptionRef.value.innerHTML = originalDescription.value
|
||||||
|
}
|
||||||
|
descriptionEdit.disableEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeInlineStylesFromAll(product) {
|
||||||
|
if (!product) return
|
||||||
|
const removeStyles = (html: string) => {
|
||||||
|
if (!html) return ''
|
||||||
|
const div = document.createElement('div')
|
||||||
|
div.innerHTML = html
|
||||||
|
|
||||||
|
div.querySelectorAll('*').forEach(el => {
|
||||||
|
const bg = el.style.background || el.style.backgroundColor
|
||||||
|
if (bg) {
|
||||||
|
el.style.background = ''
|
||||||
|
el.style.backgroundColor = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
div.querySelectorAll('p').forEach(p => {
|
||||||
|
if (p.querySelector('img')) {
|
||||||
|
p.style.display = 'flex'
|
||||||
|
p.style.gap = '20px'
|
||||||
|
p.style.padding = '30px 0'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
div.querySelectorAll('table').forEach(table => {
|
||||||
|
table.querySelectorAll('tbody').forEach(tbody => {
|
||||||
|
tbody.style.setProperty('background-color', '#F8FAFC', 'important')
|
||||||
|
tbody.querySelectorAll('tr').forEach(tr => {
|
||||||
|
tr.querySelectorAll('td').forEach(td => {
|
||||||
|
td.style.setProperty('padding', '10px', 'important')
|
||||||
|
td.style.setProperty('border-top', '1pt solid black', 'important')
|
||||||
|
td.style.setProperty('border-left', '1pt solid black', 'important')
|
||||||
|
td.style.setProperty('border-bottom', '1pt solid black', 'important')
|
||||||
|
td.style.setProperty('border-right', '1pt solid black', 'important')
|
||||||
|
|
||||||
|
td.querySelectorAll('p').forEach(p => {
|
||||||
|
p.style.setProperty('display', 'flex', 'important')
|
||||||
|
p.style.setProperty('padding', '0px', 'important')
|
||||||
|
p.style.setProperty('align-items', 'start', 'important')
|
||||||
|
p.style.setProperty('color', 'black', 'important')
|
||||||
|
p.style.setProperty('font-size', '14px', 'important')
|
||||||
|
p.querySelectorAll('span').forEach(span => {
|
||||||
|
span.style.setProperty('color', 'black', 'important')
|
||||||
|
span.style.setProperty('font-size', '14px', 'important')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return div.innerHTML
|
||||||
|
}
|
||||||
|
product.description = removeStyles(product.description)
|
||||||
|
product.description_short = removeStyles(product.description_short)
|
||||||
|
product.usage = removeStyles(product.usage)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,37 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="flex items-center gap-2 mb-4">
|
||||||
<div class="container my-10 mx-auto ">
|
<UIcon name="line-md:arrow-left" class="text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
|
<p class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)" @click="backFromProduct()">
|
||||||
|
Back to products</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="gap-4 mb-6 border-b border-(--border-light) dark:border-(--border-dark) py-4">
|
||||||
|
<div class="flex flex-wrap gap-4 items-center justify-between">
|
||||||
|
<div class="flex flex-col items-center gap-3" v-if="!isTranslations">
|
||||||
|
<USelectMenu v-model="toLangId" :items="langs" value-key="id"
|
||||||
|
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
||||||
|
<template #default>
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
Selected language
|
||||||
|
</span>
|
||||||
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ selectedLangName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div
|
|
||||||
class="flex items-end justify-between gap-4 mb-6 bg-(--second-light) dark:bg-(--main-dark) border border-(--border-light) dark:border-(--border-dark) p-4 rounded-md">
|
|
||||||
<div class="flex items-end gap-3">
|
|
||||||
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!"
|
|
||||||
valueKey="iso_code">
|
|
||||||
<template #default="{ modelValue }">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
|
||||||
<span class="font-medium dark:text-white text-black">{{availableLangs.find(x => x.iso_code ==
|
|
||||||
modelValue)?.name}}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #item-leading="{ item }">
|
<template #item-leading="{ item }">
|
||||||
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
<div class="flex items-center gap-2 cursor-pointer">
|
||||||
<span class="text-md">{{ item.flag }}</span>
|
<span class="text-lg">{{ item.flag }}</span>
|
||||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USelect>
|
</USelectMenu>
|
||||||
</div>
|
</div>
|
||||||
<UButton @click="translateToSelectedLanguage" color="primary" :loading="translating"
|
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) px-12!">
|
<div v-if="toLangId !== settingStore.shopDefaultLanguage && !isTranslations"
|
||||||
Translate
|
class="flex flex-wrap-reverse justify-between gap-2">
|
||||||
|
<UButton color="info" variant="outline" v-if="!isEditing" @click="isTranslations = true">
|
||||||
|
<p>Change Text</p>
|
||||||
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-7.5" />
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="translateToSelectedLanguage" color="info" :loading="translating">
|
||||||
|
Translate from Polish to {{langs.find(l => l.id === toLangId)?.name}}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<div v-if="isTranslations" class="flex gap-3 w-full justify-end">
|
||||||
|
<UButton @click="() => {
|
||||||
|
handleCancel()
|
||||||
|
isTranslations = false
|
||||||
|
}" color="info" variant="outline">
|
||||||
|
Cancel
|
||||||
|
</UButton>
|
||||||
|
<UButton color="info" @click="async () => {
|
||||||
|
await productStore.saveProductDescription(productID, toLangId)
|
||||||
|
isTranslations = false
|
||||||
|
}">
|
||||||
|
Save
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="translating" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
|
||||||
<div class="flex flex-col items-center gap-4 p-8 bg-(--main-light) dark:bg-(--main-dark) rounded-lg shadow-xl">
|
|
||||||
<UIcon name="svg-spinners:ring-resize" class="text-4xl text-primary" />
|
|
||||||
<p class="text-lg font-medium dark:text-white text-black">Translating...</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -41,209 +65,179 @@
|
|||||||
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
<div v-else-if="productStore.error" class="flex items-center justify-center py-20">
|
||||||
<p class="text-red-500">{{ productStore.error }}</p>
|
<p class="text-red-500">{{ productStore.error }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="productStore.productDescription" class="flex items-start gap-30">
|
|
||||||
<div class="w-80 h-80 bg-(--second-light) dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
<div v-else-if="productStore.productDescription" class="space-y-7.5">
|
||||||
<span class="text-gray-500 dark:text-gray-400">Product Image</span>
|
<div class="grid grid-cols-1 md:grid-cols-6 h-full w-full gap-6">
|
||||||
</div>
|
<img
|
||||||
<div class="flex flex-col gap-2">
|
class="md:col-span-2 rounded-md bg-white rounded-md border border-(--border-light) dark:border-(--border-dark)"
|
||||||
<p class="text-[25px] font-bold text-black dark:text-white">
|
:src="productStore.productDescription.image_link" :alt="productStore.productDescription.name" @error="(e: Event) => {
|
||||||
{{ productStore.productDescription.name || 'Product Name' }}
|
const target = e.target as HTMLImageElement
|
||||||
|
target.src = errorImg
|
||||||
|
}">
|
||||||
|
<div class="md:col-span-4 flex flex-col justify-between min-h-full w-full space-y-7.5">
|
||||||
|
<div class="space-y-7.5">
|
||||||
|
<div>
|
||||||
|
<p v-if="!isTranslations" class="text-[25px] font-bold text-black dark:text-white">
|
||||||
|
{{ productStore.productDescription.name || 'Product name not provided' }}
|
||||||
</p>
|
</p>
|
||||||
<p v-html="productStore.productDescription.description_short" class="text-black dark:text-white"></p>
|
<div class="" v-if="isTranslations">
|
||||||
<div class="space-y-[10px]">
|
<p>Title:</p>
|
||||||
<div class="flex items-center gap-1">
|
<UTextarea :rows="1" v-model="productStore.productDescription.name" autoresize :ui="{
|
||||||
<UIcon name="lets-icons:done-ring-round-fill" class="text-[20px] text-green-600" />
|
root: 'w-full bg-white!',
|
||||||
<p class="text-[16px] font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
base: 'text-2xl!',
|
||||||
|
}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="" v-if="isTranslations">
|
||||||
|
<p>Link rewrite:</p>
|
||||||
|
<UTextarea :rows="1" v-model="productStore.productDescription.link_rewrite" autoresize :ui="{
|
||||||
|
root: 'w-full',
|
||||||
|
base: 'bg-inherit!',
|
||||||
|
}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="" v-if="isTranslations">
|
||||||
|
<p>Link rewrite:</p>
|
||||||
|
<UTextarea :rows="1" v-model="productStore.productDescription.link_rewrite" autoresize :ui="{
|
||||||
|
root: 'w-full bg-white!'
|
||||||
|
}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p v-if="!isTranslations"
|
||||||
|
v-html="productStore.productDescription.description_short || 'No short description available'"
|
||||||
|
class="text-black dark:text-white" />
|
||||||
|
<div class="" v-if="isTranslations">
|
||||||
|
<p>Short description:</p>
|
||||||
|
<ProductEditor v-model="productStore.productDescription.description_short" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2.5">
|
||||||
|
<div v-if="productStore.productDescription.available_now" class="flex items-center gap-1">
|
||||||
|
<UIcon name="lets-icons:done-ring-round-fill"
|
||||||
|
class="text-[20px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
||||||
|
<p>
|
||||||
{{ productStore.productDescription.available_now }}
|
{{ productStore.productDescription.available_now }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div v-if="productStore.productDescription.delivery_in_stock" class="flex items-center gap-1">
|
||||||
<UIcon name="marketeq:car-shipping" class="text-[25px] text-green-600" />
|
<UIcon name="marketeq:car-shipping"
|
||||||
<p class="text-[18px] font-bold text-black dark:text-white">
|
class="text-[25px] light:text-(--accent-green-light) dark:text-(--accent-green-dark)" />
|
||||||
{{ productStore.productDescription.delivery_in_stock || 'Delivery information' }}
|
<p>
|
||||||
|
{{ productStore.productDescription.delivery_in_stock }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="productStore.productDescription" class="mt-16">
|
<UTabs :items="items" color="info" default-value="0" :ui="{
|
||||||
<div class="flex gap-4 my-6">
|
list: 'w-auto!',
|
||||||
<UButton @click="activeTab = 'description'"
|
root: 'items-start!'
|
||||||
:class="['cursor-pointer', activeTab === 'description' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
}">
|
||||||
variant="outline">
|
<template #description>
|
||||||
<p class="dark:text-white">Description</p>
|
<ProductEditor v-if="isTranslations" v-model="productStore.productDescription.description" />
|
||||||
</UButton>
|
<p v-else v-html="productStore.productDescription.description || 'No description available'"
|
||||||
|
class="text-black dark:text-white" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<UButton @click="activeTab = 'usage'"
|
<template #usage>
|
||||||
:class="['cursor-pointer', activeTab === 'usage' ? 'bg-blue-500 text-white' : '']" color="neutral"
|
<ProductEditor v-if="isTranslations" v-model="productStore.productDescription.usage" />
|
||||||
variant="outline">
|
<p v-else v-html="productStore.productDescription.usage || 'No usage information available'"
|
||||||
<p class="dark:text-white">Usage</p>
|
class="text-black dark:text-white" />
|
||||||
</UButton>
|
</template>
|
||||||
|
</UTabs>
|
||||||
</div>
|
</div>
|
||||||
|
<div class=""></div>
|
||||||
<div v-if="activeTab === 'usage'"
|
|
||||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
|
||||||
|
|
||||||
<div class="flex justify-end items-center gap-3 mb-4">
|
|
||||||
<UButton v-if="!isEditing" @click="enableEdit"
|
|
||||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
|
||||||
<p class="text-white">Change Text</p>
|
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
|
||||||
<p class="dark:text-white text-black">Save the edited text</p>
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
|
||||||
class="p-2.5 cursor-pointer">
|
|
||||||
Cancel
|
|
||||||
</UButton>
|
|
||||||
</div>
|
</div>
|
||||||
<p ref="usageRef" v-html="productStore.productDescription.usage"
|
</template>
|
||||||
class="flex flex-col justify-center w-full text-start dark:text-white! text-black!"></p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'description'"
|
|
||||||
class="px-8 py-4 border border-(--border-light) dark:border-(--border-dark) rounded-md bg-(--second-light) dark:bg-(--main-dark)">
|
|
||||||
<div class="flex items-center justify-end gap-3 mb-4">
|
|
||||||
<UButton v-if="!descriptionEdit.isEditing.value" @click="enableDescriptionEdit"
|
|
||||||
class="flex items-center gap-2 m-2 cursor-pointer bg-(--accent-blue-light)! dark:bg-(--accent-blue-dark)!">
|
|
||||||
<p class="text-white">Change Text</p>
|
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline"
|
|
||||||
class="p-2.5 cursor-pointer">
|
|
||||||
<p class="dark:text-white text-black ">Save the edited text</p>
|
|
||||||
</UButton>
|
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral"
|
|
||||||
variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
|
||||||
</div>
|
|
||||||
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
|
||||||
class="flex flex-col justify-center dark:text-white text-black">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { langs } from '@/router/langs';
|
||||||
import { useRoute } from 'vue-router'
|
import { useProductStore } from '@/stores/admin/product';
|
||||||
import { useProductStore } from '@/stores/product'
|
import { useSettingsStore } from '@/stores/admin/settings';
|
||||||
import { useEditable } from '@/composable/useConteditable'
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import { langs } from '@/router/langs'
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import type { Language } from '@/types'
|
import ProductEditor from '../inner/ProductEditor.vue';
|
||||||
import Default from '@/layouts/default.vue'
|
import errorImg from '@/assets/error.svg'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const activeTab = ref('description')
|
const settingStore = useSettingsStore()
|
||||||
const productStore = useProductStore()
|
const productStore = useProductStore()
|
||||||
const translating = ref(false)
|
|
||||||
|
|
||||||
const isEditing = ref(false)
|
const toLangId = ref<number>(settingStore.shopDefaultLanguage)
|
||||||
|
|
||||||
const availableLangs = computed(() => langs)
|
|
||||||
|
|
||||||
const selectedLanguage = ref('en')
|
|
||||||
|
|
||||||
const currentLangId = ref(2)
|
|
||||||
const productID = ref<number>(0)
|
const productID = ref<number>(0)
|
||||||
|
const translating = ref(false)
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const isTranslations = ref(false)
|
||||||
|
|
||||||
// Watch for language changes and refetch product description
|
const selectedLangName = computed(() => {
|
||||||
watch(selectedLanguage, async (newLang: string) => {
|
return langs.find(l => l.id === toLangId.value)?.name || 'Select language'
|
||||||
if (productID.value) {
|
|
||||||
await fetchForLanguage(newLang)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchForLanguage = async (langCode: string) => {
|
|
||||||
const lang = langs.find((l: Language) => l.iso_code === langCode)
|
function backFromProduct() {
|
||||||
if (lang && productID.value) {
|
let path = localStorage.getItem('back_from_product')
|
||||||
await productStore.getProductDescription(lang.id, productID.value)
|
|
||||||
currentLangId.value = lang.id
|
if (path) {
|
||||||
|
let res = JSON.parse(path)
|
||||||
|
router.push({
|
||||||
|
name: res.name,
|
||||||
|
params: res.params,
|
||||||
|
query: res.query
|
||||||
|
})
|
||||||
|
|
||||||
|
localStorage.removeItem('back_from_product')
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: 'admin-products',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const translateToSelectedLanguage = async () => {
|
const translateToSelectedLanguage = async () => {
|
||||||
const targetLang = langs.find((l: Language) => l.iso_code === selectedLanguage.value)
|
if (toLangId.value && productID.value) {
|
||||||
if (targetLang && currentLangId.value && productID.value) {
|
|
||||||
translating.value = true
|
translating.value = true
|
||||||
try {
|
try {
|
||||||
await productStore.translateProductDescription(productID.value, currentLangId.value, targetLang.id)
|
await productStore.translateProductDescription(productID.value, toLangId.value)
|
||||||
currentLangId.value = targetLang.id
|
|
||||||
} finally {
|
} finally {
|
||||||
translating.value = false
|
translating.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (productStore.productDescription) {
|
||||||
|
isTranslations.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchForLanguage = async (langId: number | null) => {
|
||||||
|
if (productID.value) {
|
||||||
|
await productStore.getProductDescription(langId, productID.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleCancel = async () => {
|
||||||
|
await productStore.getProductDescription(toLangId.value, productID.value)
|
||||||
|
isTranslations.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(toLangId, (lang) => {
|
||||||
|
fetchForLanguage(lang)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const id = route.params.product_id
|
const id = route.params.product_id
|
||||||
if (id) {
|
if (id) {
|
||||||
productID.value = Number(id)
|
productID.value = Number(id)
|
||||||
await fetchForLanguage(selectedLanguage.value)
|
await fetchForLanguage(toLangId.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const descriptionRef = ref<HTMLElement | null>(null)
|
const items = ref([
|
||||||
const usageRef = ref<HTMLElement | null>(null)
|
{ label: 'Description', slot: 'description', name: 'description' },
|
||||||
|
{ label: 'Usage', slot: 'usage', name: 'usage' }
|
||||||
const descriptionEdit = useEditable(descriptionRef)
|
])
|
||||||
const usageEdit = useEditable(usageRef)
|
|
||||||
|
|
||||||
const originalDescription = ref('')
|
|
||||||
const originalUsage = ref('')
|
|
||||||
|
|
||||||
const saveDescription = async () => {
|
|
||||||
descriptionEdit.disableEdit()
|
|
||||||
await productStore.saveProductDescription(productID.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelDescriptionEdit = () => {
|
|
||||||
if (descriptionRef.value) {
|
|
||||||
descriptionRef.value.innerHTML = originalDescription.value
|
|
||||||
}
|
|
||||||
descriptionEdit.disableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableDescriptionEdit = () => {
|
|
||||||
if (descriptionRef.value) {
|
|
||||||
originalDescription.value = descriptionRef.value.innerHTML
|
|
||||||
}
|
|
||||||
descriptionEdit.enableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const enableEdit = () => {
|
|
||||||
if (usageRef.value) {
|
|
||||||
originalUsage.value = usageRef.value.innerHTML
|
|
||||||
}
|
|
||||||
isEditing.value = true
|
|
||||||
usageEdit.enableEdit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveText = () => {
|
|
||||||
usageEdit.disableEdit()
|
|
||||||
isEditing.value = false
|
|
||||||
productStore.saveProductDescription(productID.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelEdit = () => {
|
|
||||||
if (usageRef.value) {
|
|
||||||
usageRef.value.innerHTML = originalUsage.value
|
|
||||||
}
|
|
||||||
usageEdit.disableEdit()
|
|
||||||
isEditing.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.images {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 70px;
|
|
||||||
margin: 20px 0 20px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
208
bo/src/components/admin/UsersList.vue
Normal file
208
bo/src/components/admin/UsersList.vue
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col md:flex-row gap-10">
|
||||||
|
<div class="w-full flex flex-col items-center gap-4">
|
||||||
|
<UTable :data="usersList" :columns="columns" class="flex-1 w-full"
|
||||||
|
:ui="{ root: 'max-w-100wv overflow-auto!' }" />
|
||||||
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, resolveComponent, h } from 'vue'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import CategoryMenu from '../inner/CategoryMenu.vue'
|
||||||
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
|
import type { Customer } from '@/types/user'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const perPage = ref(15)
|
||||||
|
const page = computed({
|
||||||
|
get: () => Number(route.query.p) || 1,
|
||||||
|
set: (val: number) => {
|
||||||
|
router.push({ query: { ...route.query, p: val } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortField = computed({
|
||||||
|
get: () => [
|
||||||
|
route.query.sort as string | undefined,
|
||||||
|
route.query.direction as 'asc' | 'desc' | undefined
|
||||||
|
],
|
||||||
|
set: ([sort]: [string, 'asc' | 'desc']) => {
|
||||||
|
const currentSort = route.query.sort as string | undefined
|
||||||
|
const currentDirection = route.query.direction as 'asc' | 'desc' | undefined
|
||||||
|
const query = { ...route.query }
|
||||||
|
|
||||||
|
if (currentSort === sort) {
|
||||||
|
if (currentDirection === 'asc') query.direction = 'desc'
|
||||||
|
else if (currentDirection === 'desc') {
|
||||||
|
delete query.sort
|
||||||
|
delete query.direction
|
||||||
|
} else {
|
||||||
|
query.direction = 'asc'
|
||||||
|
query.sort = sort
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query.sort = sort
|
||||||
|
query.direction = 'asc'
|
||||||
|
}
|
||||||
|
router.push({ query })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const filters = computed<Record<string, string>>({
|
||||||
|
get: () => {
|
||||||
|
const q = { ...route.query }
|
||||||
|
delete q.page
|
||||||
|
delete q.sort
|
||||||
|
delete q.direction
|
||||||
|
return q as Record<string, string>
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
const baseQuery = { ...route.query }
|
||||||
|
Object.keys(baseQuery).forEach(k => {
|
||||||
|
if (!['page', 'sort', 'direction'].includes(k)) delete baseQuery[k]
|
||||||
|
})
|
||||||
|
router.push({ query: { ...baseQuery, ...val, page: 1 } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function debounce(fn: Function, delay = 400) {
|
||||||
|
let t: any
|
||||||
|
return (...args: any[]) => {
|
||||||
|
clearTimeout(t)
|
||||||
|
t = setTimeout(() => fn(...args), delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFilter = debounce((columnId: string, val: string) => {
|
||||||
|
const newFilters = { ...filters.value }
|
||||||
|
if (val) newFilters[columnId] = val
|
||||||
|
else delete newFilters[columnId]
|
||||||
|
filters.value = newFilters
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
const usersList = ref<Customer[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
async function fetchUsersList() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
const params = new URLSearchParams(route.query as any).toString()
|
||||||
|
const url = `/api/v1/restricted/customer/list?elems=${perPage.value}&${params}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await useFetchJson<{ items: { items: Customer[] }; count: number }>(url)
|
||||||
|
usersList.value = res.items?.items || []
|
||||||
|
total.value = res.count || 0
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to load users'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIcon(name: string) {
|
||||||
|
if (sortField.value[0] === name) {
|
||||||
|
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
||||||
|
if (sortField.value[1] === 'desc') return 'i-lucide-arrow-down-wide-narrow'
|
||||||
|
}
|
||||||
|
return 'i-lucide-arrow-up-down'
|
||||||
|
}
|
||||||
|
|
||||||
|
const UInput = resolveComponent('UInput')
|
||||||
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
const UButton = resolveComponent('UButton')
|
||||||
|
|
||||||
|
const columns: TableColumn<Customer>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'user_id',
|
||||||
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => (sortField.value = ['user_id', 'asc'])
|
||||||
|
}, [h('span', 'Client ID'), h(UIcon, { name: getIcon('user_id') })]),
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cell: ({ row }) => `#${row.getValue('user_id')}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => (sortField.value = ['last_name, first_name', 'asc'])
|
||||||
|
}, [h('span', 'Name/Surname'), h(UIcon, { name: getIcon('name') })]),
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cell: ({ row }) => `${row.original.first_name} ${row.original.last_name}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => (sortField.value = ['email', 'asc'])
|
||||||
|
}, [h('span', 'Email'), h(UIcon, { name: getIcon('email') })]),
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cell: ({ row }) => row.getValue('email')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
// onClick: () => {
|
||||||
|
// goToProduct(row.original.product_id, row.original.link_rewrite)
|
||||||
|
// },
|
||||||
|
class: 'cursor-pointer',
|
||||||
|
color: 'info',
|
||||||
|
variant: 'soft'
|
||||||
|
}, () => 'Manage')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'profile',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const userId = row.original.user_id
|
||||||
|
return h(UButton, {
|
||||||
|
color: 'info',
|
||||||
|
size: 'sm',
|
||||||
|
variant: 'soft',
|
||||||
|
onClick: () => {
|
||||||
|
router.push({
|
||||||
|
name: 'customer-management-profile',
|
||||||
|
params: { user_id: userId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, () => 'Go to profile')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
watch(() => route.query, fetchUsersList, { immediate: true })
|
||||||
|
</script>
|
||||||
242
bo/src/components/admin/UsersSearch.vue
Normal file
242
bo/src/components/admin/UsersSearch.vue
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pt-70! flex flex-col items-center justify-center bg-gray-50 dark:bg-(--main-dark)">
|
||||||
|
<h1 class="text-6xl font-bold text-black dark:text-white mb-14">Search Users</h1>
|
||||||
|
|
||||||
|
<div class="w-full max-w-4xl">
|
||||||
|
<UInput icon="i-lucide-search" type="text" placeholder="Type user name or ID..." v-model="searchQuery"
|
||||||
|
class="w-full!" :ui="{ base: 'py-4! rounded-full!' }" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="loading">Loading...</p>
|
||||||
|
<p v-else-if="error" class="text-red-600">{{ error }}</p>
|
||||||
|
<div v-else-if="clients.length" class="w-full max-w-4xl mt-7">
|
||||||
|
<UTable :columns="columns" :data="clients" :ui="{ root: 'w-full!' }" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-else-if="searchQuery.length" class="pt-4 text-gray-700">
|
||||||
|
No users found with that name or ID
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, resolveComponent, h } from 'vue'
|
||||||
|
import type { TableColumn } from '@nuxt/ui';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson';
|
||||||
|
|
||||||
|
|
||||||
|
interface Customer {
|
||||||
|
user_id: number;
|
||||||
|
email: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const searchQuery = ref('');
|
||||||
|
const clients = ref<Customer[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
const limit = ref(10);
|
||||||
|
const page = ref(1);
|
||||||
|
|
||||||
|
|
||||||
|
function debounce(fn: Function, delay = 400) {
|
||||||
|
let t: any;
|
||||||
|
return (...args: any[]) => {
|
||||||
|
clearTimeout(t);
|
||||||
|
t = setTimeout(() => fn(...args), delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const searchClients = async () => {
|
||||||
|
if (!searchQuery.value) {
|
||||||
|
clients.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await useFetchJson(
|
||||||
|
`/api/v1/restricted/customer/list?search=${encodeURIComponent(searchQuery.value)}&elems=${limit.value}&page=${page.value}`
|
||||||
|
);
|
||||||
|
|
||||||
|
clients.value = result.items?.items || [];
|
||||||
|
} catch (err: unknown) {
|
||||||
|
error.value = err instanceof Error ? err.message : 'Failed to fetch clients';
|
||||||
|
clients.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
watch(searchQuery, debounce(searchClients, 300));
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
|
||||||
|
const UInput = resolveComponent('UInput')
|
||||||
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
const UButton = resolveComponent('UButton')
|
||||||
|
|
||||||
|
|
||||||
|
const sortField = computed({
|
||||||
|
get: () => [
|
||||||
|
route.query.sort as string | undefined,
|
||||||
|
route.query.direction as 'asc' | 'desc' | undefined
|
||||||
|
],
|
||||||
|
set: ([sort]: [string, 'asc' | 'desc']) => {
|
||||||
|
const currentSort = route.query.sort as string | undefined
|
||||||
|
const currentDirection = route.query.direction as 'asc' | 'desc' | undefined
|
||||||
|
const query = { ...route.query }
|
||||||
|
|
||||||
|
if (currentSort === sort) {
|
||||||
|
if (currentDirection === 'asc') query.direction = 'desc'
|
||||||
|
else if (currentDirection === 'desc') {
|
||||||
|
delete query.sort
|
||||||
|
delete query.direction
|
||||||
|
} else {
|
||||||
|
query.direction = 'asc'
|
||||||
|
query.sort = sort
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query.sort = sort
|
||||||
|
query.direction = 'asc'
|
||||||
|
}
|
||||||
|
router.push({ query })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function getIcon(name: string) {
|
||||||
|
if (sortField.value[0] === name) {
|
||||||
|
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
||||||
|
if (sortField.value[1] === 'desc') return 'i-lucide-arrow-down-wide-narrow'
|
||||||
|
}
|
||||||
|
return 'i-lucide-arrow-up-down'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const filters = computed<Record<string, string>>({
|
||||||
|
get: () => {
|
||||||
|
const q = { ...route.query }
|
||||||
|
delete q.page
|
||||||
|
delete q.sort
|
||||||
|
delete q.direction
|
||||||
|
return q as Record<string, string>
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
const baseQuery = { ...route.query }
|
||||||
|
Object.keys(baseQuery).forEach(k => {
|
||||||
|
if (!['page', 'sort', 'direction'].includes(k)) delete baseQuery[k]
|
||||||
|
})
|
||||||
|
router.push({ query: { ...baseQuery, ...val, page: 1 } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const updateFilter = debounce((columnId: string, val: string) => {
|
||||||
|
const newFilters = { ...filters.value }
|
||||||
|
if (val) newFilters[columnId] = val
|
||||||
|
else delete newFilters[columnId]
|
||||||
|
filters.value = newFilters
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
|
||||||
|
const columns: TableColumn<Customer>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'user_id',
|
||||||
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => (sortField.value = ['user_id', 'asc'])
|
||||||
|
}, [h('span', 'Client ID'), h(UIcon, { name: getIcon('user_id') })]),
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cell: ({ row }) => `#${row.getValue('user_id')}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => (sortField.value = ['last_name, first_name', 'asc'])
|
||||||
|
}, [h('span', 'Name/Surname'), h(UIcon, { name: getIcon('name') })]),
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cell: ({ row }) => `${row.original.first_name} ${row.original.last_name}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
h('div', {
|
||||||
|
class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
onClick: () => (sortField.value = ['email', 'asc'])
|
||||||
|
}, [h('span', 'Email'), h(UIcon, { name: getIcon('email') })]),
|
||||||
|
h(UInput, {
|
||||||
|
placeholder: 'Search...',
|
||||||
|
modelValue: filters.value[column.id] ?? '',
|
||||||
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
||||||
|
size: 'xs'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
cell: ({ row }) => row.getValue('email')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
// onClick: () => {
|
||||||
|
// goToProduct(row.original.product_id, row.original.link_rewrite)
|
||||||
|
// },
|
||||||
|
class: 'cursor-pointer',
|
||||||
|
color: 'info',
|
||||||
|
variant: 'soft'
|
||||||
|
}, () => 'Manage')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'profile',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const userId = row.original.user_id
|
||||||
|
return h(UButton, {
|
||||||
|
color: 'info',
|
||||||
|
size: 'sm',
|
||||||
|
variant: 'soft',
|
||||||
|
onClick: () => {
|
||||||
|
router.push({
|
||||||
|
name: 'customer-management-profile',
|
||||||
|
params: { user_id: userId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, () => 'Go to profile')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
bo/src/components/customer-management/Profile.vue
Normal file
7
bo/src/components/customer-management/Profile.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<template>
|
||||||
|
<div>customer-management</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="">
|
||||||
<div class="container mx-auto">
|
|
||||||
<h2
|
<h2
|
||||||
class="font-semibold text-black dark:text-white pb-6 text-2xl">
|
class="font-semibold text-black dark:text-white pb-6 text-2xl">
|
||||||
{{ t('Cart Items') }}
|
{{ t('Cart Items') }}
|
||||||
@@ -48,15 +47,13 @@
|
|||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCartStore } from '@/stores/cart'
|
import { useCartStore } from '@/stores/customer/cart'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
const cartStore = useCartStore()
|
const cartStore = useCartStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -1,182 +1,239 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="flex flex-col gap-5">
|
||||||
<div class="container mx-auto">
|
<div class="flex justify-between items-center">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Addresses') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Addresses') }}</h1>
|
||||||
<div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
|
<UButton color="info" @click="openModal()">
|
||||||
<div class="flex gap-2 items-center">
|
|
||||||
<UInput v-model="searchQuery" type="text" :placeholder="t('Search address')"
|
|
||||||
class="bg-white dark:bg-gray-800 text-black dark:text-white absolute" />
|
|
||||||
<UIcon name="ic:baseline-search"
|
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) relative left-40" />
|
|
||||||
</div>
|
|
||||||
<UButton color="primary" @click="openCreateModal"
|
|
||||||
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
|
||||||
<UIcon name="mdi:add-bold" />
|
<UIcon name="mdi:add-bold" />
|
||||||
{{ t('Add Address') }}
|
{{ t('Add Address') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="store.loading" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('Loading...') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="paginatedAddresses.length" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div v-else-if="store.error" class="text-center py-8 text-red-500">
|
||||||
<div v-for="address in paginatedAddresses" :key="address.id"
|
{{ store.error }}
|
||||||
class="border border-(--border-light) dark:border-(--border-dark) rounded-md p-4 bg-(--second-light) dark:bg-(--main-dark) hover:shadow-md transition-shadow flex justify-between">
|
|
||||||
<div class="flex flex-col gap-2 items-strat justify-end">
|
|
||||||
<p class="text-black dark:text-white">{{ address.street }}</p>
|
|
||||||
<p class="text-black dark:text-white">{{ address.zipCode }}, {{ address.city }}</p>
|
|
||||||
<p class="text-black dark:text-white">{{ address.country }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-end justify-between gap-2">
|
<div v-else-if="store.addresses.length" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
<button @click="confirmDelete(address.id)"
|
<div v-for="addr in store.addresses" :key="addr.id"
|
||||||
class="p-2 text-red-500 bg-red-100 dark:bg-(--main-dark) rounded transition-colors"
|
class="border border-(--border-light) dark:border-(--border-dark) rounded-md p-4 bg-(--second-light) dark:bg-(--main-dark) flex justify-between">
|
||||||
:title="t('Remove')">
|
<div class="flex flex-col gap-1">
|
||||||
|
<p class="font-semibold text-black dark:text-white">{{ addr.address_unparsed.recipient }}</p>
|
||||||
|
<p class="text-sm text-black dark:text-white">
|
||||||
|
{{ addr.address_unparsed.street }} {{ addr.address_unparsed.building_no
|
||||||
|
}}{{ addr.address_unparsed.apartment_no ? '/' + addr.address_unparsed.apartment_no : '' }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-black dark:text-white">
|
||||||
|
{{ addr.address_unparsed.postal_code }}, {{ addr.address_unparsed.city }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-end justify-between">
|
||||||
|
<UButton size="xs" color="error" variant="ghost" :title="t('Delete')"
|
||||||
|
@click="confirmDelete(addr.id)">
|
||||||
<UIcon name="material-symbols:delete" class="text-[18px]" />
|
<UIcon name="material-symbols:delete" class="text-[18px]" />
|
||||||
</button>
|
</UButton>
|
||||||
<UButton size="sm" color="neutral" variant="outline" @click="openEditModal(address)"
|
<UButton size="sm" color="neutral" variant="outline" @click="openModal(addr)">
|
||||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[13px]">
|
|
||||||
{{ t('edit') }}
|
{{ t('edit') }}
|
||||||
<UIcon name="ic:sharp-edit" class="text-[15px]" />
|
<UIcon name="ic:sharp-edit" class="text-[14px]" />
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center py-8 text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</div>
|
<div v-else class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||||
<div class="mt-6 flex justify-center">
|
{{ t('No addresses found') }}
|
||||||
<UPagination v-model:page="page" :total="totalItems" :page-size="pageSize" />
|
|
||||||
</div>
|
</div>
|
||||||
<UModal v-model:open="showModal" :overlay="true" class="max-w-md mx-auto">
|
|
||||||
<template #content>
|
<UModal v-model:open="showModal">
|
||||||
<div class="p-6 flex flex-col gap-6">
|
<template #header>
|
||||||
<p class="text-[20px] text-black dark:text-white ">Address</p>
|
<h3 class="text-lg font-semibold text-black dark:text-white">
|
||||||
<UForm @submit.prevent="saveAddress" class="space-y-4" :validate="validate">
|
{{ editingId ? t('Edit Address') : t('Add Address') }}
|
||||||
<div>
|
</h3>
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">Street *</label>
|
</template>
|
||||||
<UInput v-model="formData.street" placeholder="Enter street" name="street" class="w-full" />
|
<template #body>
|
||||||
|
<div class="flex flex-col gap-5">
|
||||||
|
<USelectMenu v-model="selectedCountry" :items="countries" class="w-full"
|
||||||
|
@update:model-value="onCountryChange" :searchInput="false">
|
||||||
|
<template #default>
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="text-xs text-gray-400">{{ t('Country') }}</span>
|
||||||
|
<span v-if="selectedCountry" class="font-medium text-black dark:text-white">
|
||||||
|
{{ selectedCountry.name }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="text-gray-400">{{ t('Select country') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</template>
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">Zip Code *</label>
|
<template #item-leading="{ item }">
|
||||||
<UInput v-model="formData.zipCode" placeholder="Enter zip code" name="zipCode"
|
<span class="text-lg mr-1">{{ item.flag }} {{ item.name }}</span>
|
||||||
class="w-full" />
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
|
|
||||||
|
<div v-if="templateLoading" class="text-center py-4 text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('Loading...') }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<p v-else-if="!selectedCountry" class="text-center text-sm text-gray-400 dark:text-gray-500">
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">City *</label>
|
{{ t('Select a country to continue') }}
|
||||||
<UInput v-model="formData.city" placeholder="Enter city" name="city" class="w-full" />
|
</p>
|
||||||
</div>
|
<UForm v-else :validate="validate" :state="formData" @submit="save" class="space-y-4">
|
||||||
<div>
|
<UFormField v-for="field in templateKeys" :key="field" :label="fieldLabel(field)" :name="field"
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">Country *</label>
|
:required="!optionalFields.has(field)">
|
||||||
<UInput v-model="formData.country" placeholder="Enter country" name="country"
|
<UInput v-model="formData[field]" :placeholder="fieldLabel(field)" class="w-full" />
|
||||||
class="w-full" />
|
</UFormField>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2 pt-2">
|
||||||
|
<UButton variant="outline" color="neutral" @click="showModal = false">
|
||||||
|
{{ t('Cancel') }}
|
||||||
|
</UButton>
|
||||||
|
<UButton type="submit" color="info">
|
||||||
|
{{ t('Save') }}
|
||||||
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</UForm>
|
</UForm>
|
||||||
<div class="flex justify-end gap-2">
|
|
||||||
<UButton variant="outline" color="neutral" @click="closeModal"
|
|
||||||
class="text-black dark:text-white">{{ t('Cancel') }}</UButton>
|
|
||||||
<UButton variant="outline" color="neutral" @click="saveAddress"
|
|
||||||
class="text-white bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
|
||||||
{{ t('Save') }}</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
<UModal v-model:open="showDeleteConfirm" :overlay="true" class="max-w-md mx-auto">
|
|
||||||
<template #content>
|
<UModal v-model:open="showDeleteConfirm">
|
||||||
<div class="p-6 flex flex-col gap-3">
|
<template #body>
|
||||||
<div class="flex flex-col gap-2 justify-center items-center">
|
<div class="flex flex-col items-center gap-3 py-2">
|
||||||
<p class="flex items-end gap-2 dark:text-white text-black">
|
<UIcon name="f7:exclamationmark-triangle" class="text-[40px] text-red-600" />
|
||||||
<UIcon name='f7:exclamationmark-triangle' class="text-[35px] text-red-700" />
|
<p class="font-semibold text-black dark:text-white">{{ t('Confirm Delete') }}</p>
|
||||||
Confirm Delete
|
<p class="text-sm text-gray-600 dark:text-gray-300">
|
||||||
|
{{ t('Are you sure you want to delete this address?') }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 dark:text-gray-300">
|
|
||||||
{{ t('Are you sure you want to delete this address?') }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center gap-5">
|
</template>
|
||||||
<UButton variant="outline" color="neutral" @click="showDeleteConfirm = false"
|
<template #footer>
|
||||||
class="dark:text-white text-black">{{ t('Cancel') }}
|
<div class="flex justify-center gap-4">
|
||||||
|
<UButton variant="outline" color="neutral" @click="showDeleteConfirm = false">
|
||||||
|
{{ t('Cancel') }}
|
||||||
|
</UButton>
|
||||||
|
<UButton variant="outline" color="error" @click="deleteAddress">
|
||||||
|
{{ t('Delete') }}
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton variant="outline" color="neutral" @click="deleteAddress" class="text-red-700">
|
|
||||||
{{ t('Delete') }}</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { useAddressStore } from '@/stores/address'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Default from '@/layouts/default.vue'
|
import { countries } from '@/router/langs'
|
||||||
const addressStore = useAddressStore()
|
import { useAddressStore } from '@/stores/customer/address'
|
||||||
|
import type { Country } from '@/types'
|
||||||
|
import type { Address } from '@/stores/customer/address'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const searchQuery = ref('')
|
const store = useAddressStore()
|
||||||
|
|
||||||
|
// --- Modal state ---
|
||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
const isEditing = ref(false)
|
const editingId = ref<number | null>(null)
|
||||||
const editingAddressId = ref<number | null>(null)
|
const selectedCountry = ref<Country | null>(null)
|
||||||
const formData = ref({ street: '', zipCode: '', city: '', country: '' })
|
const templateLoading = ref(false)
|
||||||
|
const template = ref<Record<string, string>>({})
|
||||||
|
const formData = reactive<Record<string, string>>({})
|
||||||
|
|
||||||
|
const templateKeys = computed(() => Object.keys(template.value))
|
||||||
|
const optionalFields = new Set(['address_line2'])
|
||||||
|
|
||||||
|
// --- Delete state ---
|
||||||
const showDeleteConfirm = ref(false)
|
const showDeleteConfirm = ref(false)
|
||||||
const addressToDelete = ref<number | null>(null)
|
const deleteId = ref<number | null>(null)
|
||||||
|
|
||||||
const page = ref(addressStore.currentPage)
|
const fieldLabels: Record<string, string> = {
|
||||||
const paginatedAddresses = computed(() => addressStore.paginatedAddresses)
|
recipient: 'Recipient',
|
||||||
const totalItems = computed(() => addressStore.totalItems)
|
street: 'Street',
|
||||||
const pageSize = addressStore.pageSize
|
thoroughfare: 'Street',
|
||||||
|
building_no: 'Building No',
|
||||||
|
building_name: 'Building Name',
|
||||||
|
house_number: 'House Number',
|
||||||
|
orientation_number: 'Orientation Number',
|
||||||
|
apartment_no: 'Apartment No',
|
||||||
|
sub_building: 'Sub Building',
|
||||||
|
postal_code: 'Zip Code',
|
||||||
|
post_town: 'City',
|
||||||
|
city: 'City',
|
||||||
|
county: 'County',
|
||||||
|
region: 'Region',
|
||||||
|
voivodeship: 'Region / Voivodeship',
|
||||||
|
address_line2: 'Address Line 2'
|
||||||
|
}
|
||||||
|
|
||||||
watch(page, (newPage) => addressStore.setPage(newPage))
|
function fieldLabel(key: string) {
|
||||||
watch(searchQuery, (val) => {
|
return t(fieldLabels[key] ?? key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()))
|
||||||
addressStore.setSearchQuery(val)
|
|
||||||
})
|
|
||||||
function openCreateModal() {
|
|
||||||
resetForm()
|
|
||||||
isEditing.value = false
|
|
||||||
showModal.value = true
|
|
||||||
}
|
|
||||||
function openEditModal(address: any) {
|
|
||||||
formData.value = {
|
|
||||||
street: address.street,
|
|
||||||
zipCode: address.zipCode,
|
|
||||||
city: address.city,
|
|
||||||
country: address.country
|
|
||||||
}
|
|
||||||
isEditing.value = true
|
|
||||||
editingAddressId.value = address.id
|
|
||||||
showModal.value = true
|
|
||||||
}
|
|
||||||
function resetForm() {
|
|
||||||
formData.value = { street: '', zipCode: '', city: '', country: '' }
|
|
||||||
editingAddressId.value = null
|
|
||||||
}
|
|
||||||
function closeModal() {
|
|
||||||
showModal.value = false
|
|
||||||
resetForm()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
const errors = []
|
return templateKeys.value
|
||||||
if (!formData.value.street) errors.push({ name: 'street', message: 'Street required' })
|
.filter((key) => !optionalFields.has(key) && !formData[key]?.trim())
|
||||||
if (!formData.value.zipCode) errors.push({ name: 'zipCode', message: 'Zip Code required' })
|
.map((key) => ({ name: key, message: t(`${fieldLabel(key)} is required`) }))
|
||||||
if (!formData.value.city) errors.push({ name: 'city', message: 'City required' })
|
|
||||||
if (!formData.value.country) errors.push({ name: 'country', message: 'Country required' })
|
|
||||||
return errors.length ? errors : null
|
|
||||||
}
|
}
|
||||||
function saveAddress() {
|
|
||||||
if (validate()) return
|
function applyTemplate(tpl: Record<string, string>, existing?: Record<string, string>) {
|
||||||
if (isEditing.value && editingAddressId.value) {
|
Object.keys(formData).forEach((k) => delete formData[k])
|
||||||
addressStore.updateAddress(editingAddressId.value, formData.value)
|
Object.keys(tpl).forEach((k) => {
|
||||||
|
formData[k] = existing?.[k] ?? ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal(addr?: Address) {
|
||||||
|
template.value = {}
|
||||||
|
Object.keys(formData).forEach((k) => delete formData[k])
|
||||||
|
|
||||||
|
if (addr) {
|
||||||
|
editingId.value = addr.id
|
||||||
|
selectedCountry.value = countries.find((c) => c.id === addr.country_id) ?? null
|
||||||
|
loadTemplate(addr.country_id, addr.address_unparsed)
|
||||||
} else {
|
} else {
|
||||||
addressStore.addAddress(formData.value)
|
editingId.value = null
|
||||||
|
selectedCountry.value = null
|
||||||
}
|
}
|
||||||
closeModal()
|
|
||||||
|
showModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onCountryChange(country: Country | null) {
|
||||||
|
if (!country) {
|
||||||
|
template.value = {}
|
||||||
|
Object.keys(formData).forEach((k) => delete formData[k])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await loadTemplate(country.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTemplate(countryId: number, existing?: Record<string, string>) {
|
||||||
|
templateLoading.value = true
|
||||||
|
try {
|
||||||
|
const tpl = await store.getTemplate(countryId)
|
||||||
|
template.value = tpl
|
||||||
|
applyTemplate(tpl, existing)
|
||||||
|
} finally {
|
||||||
|
templateLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
if (!selectedCountry.value) return
|
||||||
|
if (editingId.value) {
|
||||||
|
await store.updateAddress(editingId.value, selectedCountry.value.id, { ...formData })
|
||||||
|
} else {
|
||||||
|
await store.createAddress(selectedCountry.value.id, { ...formData })
|
||||||
|
}
|
||||||
|
showModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
function confirmDelete(id: number) {
|
function confirmDelete(id: number) {
|
||||||
addressToDelete.value = id
|
deleteId.value = id
|
||||||
showDeleteConfirm.value = true
|
showDeleteConfirm.value = true
|
||||||
}
|
}
|
||||||
function deleteAddress() {
|
|
||||||
if (addressToDelete.value) {
|
async function deleteAddress() {
|
||||||
addressStore.deleteAddress(addressToDelete.value)
|
if (deleteId.value) await store.deleteAddress(deleteId.value)
|
||||||
}
|
|
||||||
showDeleteConfirm.value = false
|
showDeleteConfirm.value = false
|
||||||
addressToDelete.value = null
|
deleteId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.fetchAddresses()
|
||||||
</script>
|
</script>
|
||||||
17
bo/src/components/customer/PageCart.vue
Normal file
17
bo/src/components/customer/PageCart.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
<div class="flex flex-col gap-5 md:gap-10">
|
||||||
|
<h1 class="text-2xl font-bold text-black dark:text-white">
|
||||||
|
Shopping Cart
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Default from '@/layouts/default.vue';
|
||||||
|
import { useCartStore } from '@/stores/customer/cart';
|
||||||
|
|
||||||
|
const cartStore =useCartStore()
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -1,204 +1,107 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="flex flex-col gap-5 md:gap-10">
|
||||||
<div class="container mx-auto flex flex-col gap-5 md:gap-10">
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Cart') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Shopping Carts') }}</h1>
|
||||||
<div class="flex flex-col lg:flex-row gap-5 md:gap-10">
|
<div class="flex gap-3">
|
||||||
<div class="flex-1">
|
<UButton color="primary" @click="showCreateModal = true"
|
||||||
|
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light)">
|
||||||
|
<UIcon name="mdi:plus" class="mr-1" />
|
||||||
|
{{ t('New Cart') }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full">
|
||||||
<div
|
<div
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) overflow-hidden">
|
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) overflow-hidden">
|
||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white p-4 border-b border-(--border-light) dark:border-(--border-dark)">
|
class="text-lg font-semibold text-black dark:text-white p-4 border-b border-(--border-light) dark:border-(--border-dark)">
|
||||||
{{ t('Selected Products') }}
|
{{ t('Your Carts') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div v-if="cartStore.items.length > 0">
|
<div v-if="cartStore.carts?.length > 0" class="divide-y divide-(--border-light) dark:divide-(--border-dark)">
|
||||||
<div v-for="item in cartStore.items" :key="item.id"
|
<div v-for="cart in cartStore.carts" :key="cart.cart_id" @click="cartStore.setActiveCart(cart.cart_id)"
|
||||||
class="grid grid-cols-5 items-center p-4 border-b border-(--border-light) dark:border-(--border-dark) w-[100%]">
|
class="p-4 cursor-pointer flex gap-2 items-center justify-between" :class="cartStore.activeCartId === cart.cart_id
|
||||||
<div
|
? 'bg-blue-50 dark:bg-blue-900/20'
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded flex items-center justify-center overflow-hidden">
|
: 'hover:bg-gray-50 dark:hover:bg-gray-800 border-l-4 border-transparent'">
|
||||||
<img v-if="item.image" :src="item.image" :alt="item.name" class="w-full h-full object-cover" />
|
<div class="flex-1 min-w-0">
|
||||||
<UIcon v-else name="mdi:package-variant" class="text-2xl text-gray-400" />
|
<div class="flex items-center gap-2">
|
||||||
</div>
|
<p class="text-red-600 font-medium truncate">{{ cart.cart_id }}</p>
|
||||||
<p class="text-black dark:text-white text-sm font-medium">{{ item.name }}</p>
|
<p class="text-black dark:text-white font-medium truncate cursor-pointer" @click="openCart(cart)">{{
|
||||||
<p class="text-black dark:text-white">${{ item.price.toFixed(2) }}</p>
|
cart.name }}</p>
|
||||||
<p class="text-black dark:text-white font-medium">${{ (item.price * item.quantity).toFixed(2)
|
|
||||||
}}</p>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-10">
|
|
||||||
<UInputNumber v-model="item.quantity" :min="1"
|
|
||||||
@update:model-value="(val: number) => cartStore.updateQuantity(item.id, val)" />
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<button @click="removeItem(item.id)"
|
|
||||||
class="p-2 text-red-500 bg-red-100 dark:bg-(--main-dark) rounded transition-colors"
|
|
||||||
:title="t('Remove')">
|
|
||||||
<UIcon name="material-symbols:delete" class="text-[20px]" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<input type="checkbox" :checked="cartStore.activeCartId === cart.cart_id"
|
||||||
|
@change="toggleCart(cart.cart_id)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="p-8 text-center">
|
<div v-else class="p-8 text-center">
|
||||||
<UIcon name="mdi:cart-outline" class="text-6xl text-gray-300 dark:text-gray-600 mb-4" />
|
<UIcon name="mdi:cart-outline" class="text-4xl text-gray-300 dark:text-gray-600 mb-2" />
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('Your cart is empty') }}</p>
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No carts yet') }}</p>
|
||||||
<RouterLink :to="{
|
|
||||||
name: 'customer-product', params: {
|
|
||||||
product_id: '51'
|
|
||||||
}
|
|
||||||
}" class="inline-block mt-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
|
||||||
{{ t('Continue Shopping') }}
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="lg:w-80">
|
|
||||||
<div
|
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6 sticky top-24">
|
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Order Summary') }}</h2>
|
|
||||||
<div class="space-y-3 border-b border-(--border-light) dark:border-(--border-dark) pb-4 mb-4">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600 dark:text-gray-400">{{ t('Products total') }}</span>
|
|
||||||
<span class="text-black dark:text-white">${{ cartStore.productsTotal.toFixed(2) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600 dark:text-gray-400">{{ t('Shipping') }}</span>
|
|
||||||
<span class="text-black dark:text-white">
|
|
||||||
{{ cartStore.shippingCost > 0 ? `$${cartStore.shippingCost.toFixed(2)}` : t('Free') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-gray-600 dark:text-gray-400">{{ t('VAT') }} ({{ (cartStore.vatRate * 100).toFixed(0)
|
|
||||||
}}%)</span>
|
|
||||||
<span class="text-black dark:text-white">${{ cartStore.vatAmount.toFixed(2) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between mb-6">
|
|
||||||
<span class="text-black dark:text-white font-semibold text-lg">{{ t('Total') }}</span>
|
|
||||||
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-bold text-lg">${{
|
|
||||||
cartStore.orderTotal.toFixed(2) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<UButton block color="primary" @click="placeOrder" :disabled="!canPlaceOrder"
|
|
||||||
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white hover:bg-(--accent-blue-dark) dark:hover:bg-(--accent-blue-light) disabled:opacity-50 disabled:cursor-not-allowed">
|
|
||||||
{{ t('Place Order') }}
|
|
||||||
</UButton>
|
|
||||||
<UButton block variant="outline" color="neutral" @click="cancelOrder"
|
|
||||||
class="text-black dark:text-white border-(--border-light) dark:border-(--border-dark) hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
||||||
{{ t('Cancel') }}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 md:gap-10">
|
|
||||||
<div class="flex-1">
|
<UModal v-model:open="showCreateModal">
|
||||||
<div
|
<template #header>
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6">
|
<h3 class="text-lg font-semibold text-black dark:text-white">{{ t('Create New Cart') }}</h3>
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Select Delivery Address') }}</h2>
|
</template>
|
||||||
<div class="mb-4">
|
<template #body>
|
||||||
<UInput v-model="addressSearchQuery" type="text" :placeholder="t('Search address')"
|
<div class="flex flex-col gap-4">
|
||||||
|
<UInput v-model="newCartName" :placeholder="t('Cart name')"
|
||||||
class="w-full bg-white dark:bg-gray-800 text-black dark:text-white" />
|
class="w-full bg-white dark:bg-gray-800 text-black dark:text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="addressStore.filteredAddresses.length > 0" class="space-y-3">
|
</template>
|
||||||
<label v-for="address in addressStore.filteredAddresses" :key="address.id"
|
<template #footer>
|
||||||
class="flex items-start gap-3 p-4 border rounded-lg cursor-pointer transition-colors" :class="cartStore.selectedAddressId === address.id
|
<div class="flex justify-end gap-2">
|
||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
<UButton variant="outline" color="neutral" @click="showCreateModal = false">
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
{{ t('Cancel') }}
|
||||||
<input type="radio" :value="address.id" v-model="selectedAddress"
|
</UButton>
|
||||||
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
<UButton color="primary" @click="createCart" :disabled="!newCartName.trim()"
|
||||||
<div class="flex-1">
|
class="bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}</p>
|
{{ t('Create') }}
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }}, {{ address.city }}</p>
|
</UButton>
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.country }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</template>
|
||||||
</div>
|
</UModal>
|
||||||
<div v-else class="text-center py-6">
|
|
||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
|
||||||
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
|
||||||
{{ t('Add Address') }}
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div
|
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-6">
|
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4">{{ t('Delivery Method') }}</h2>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<label v-for="method in cartStore.deliveryMethods" :key="method.id"
|
|
||||||
class="flex items-center gap-3 p-4 border rounded-lg cursor-pointer transition-colors" :class="cartStore.selectedDeliveryMethodId === method.id
|
|
||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
|
||||||
<input type="radio" :value="method.id" v-model="selectedDeliveryMethod"
|
|
||||||
class="w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-black dark:text-white font-medium">{{ method.name }}</span>
|
|
||||||
<span class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) font-medium">
|
|
||||||
{{ method.price > 0 ? `$${method.price.toFixed(2)}` : t('Free') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-gray-500 dark:text-gray-400 text-sm">{{ method.description }}</p>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</component>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useCartStore } from '@/stores/cart'
|
import { useCartStore } from '@/stores/customer/cart'
|
||||||
import { useAddressStore } from '@/stores/address'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import Default from '@/layouts/default.vue'
|
import Default from '@/layouts/default.vue'
|
||||||
const cartStore = useCartStore()
|
import { useRouter } from 'vue-router'
|
||||||
const addressStore = useAddressStore()
|
|
||||||
const { t } = useI18n()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const selectedAddress = ref<number | null>(cartStore.selectedAddressId)
|
const cartStore = useCartStore()
|
||||||
const selectedDeliveryMethod = ref<number | null>(cartStore.selectedDeliveryMethodId)
|
const { t } = useI18n()
|
||||||
const addressSearchQuery = ref('')
|
|
||||||
|
|
||||||
watch(addressSearchQuery, (val) => {
|
const showCreateModal = ref(false)
|
||||||
addressStore.setSearchQuery(val)
|
const newCartName = ref('')
|
||||||
})
|
|
||||||
|
|
||||||
watch(selectedAddress, (newValue) => {
|
async function createCart() {
|
||||||
cartStore.setSelectedAddress(newValue)
|
await cartStore.addNewCart(newCartName.value)
|
||||||
})
|
newCartName.value = ''
|
||||||
|
showCreateModal.value = false
|
||||||
watch(selectedDeliveryMethod, (newValue) => {
|
|
||||||
if (newValue) {
|
|
||||||
cartStore.setDeliveryMethod(newValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const canPlaceOrder = computed(() => {
|
|
||||||
return cartStore.items.length > 0 &&
|
|
||||||
cartStore.selectedAddressId !== null &&
|
|
||||||
cartStore.selectedDeliveryMethodId !== null
|
|
||||||
})
|
|
||||||
function removeItem(itemId: number) {
|
|
||||||
cartStore.removeItem(itemId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeOrder() {
|
|
||||||
if (canPlaceOrder.value) {
|
onMounted(() => {
|
||||||
console.log('Placing order...')
|
cartStore.fetchCarts()
|
||||||
alert(t('Order placed successfully!'))
|
})
|
||||||
cartStore.clearCart()
|
|
||||||
router.push({ name: 'home' })
|
|
||||||
}
|
function openCart(cart) {
|
||||||
|
router.push({ name: 'customer-cart', params: { id: cart.cart_id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelOrder() {
|
function toggleCart(cartId: number) {
|
||||||
router.back()
|
if (cartStore.activeCartId === cartId) {
|
||||||
|
cartStore.setActiveCart(null)
|
||||||
|
} else {
|
||||||
|
cartStore.setActiveCart(cartId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
|
||||||
Orders page
|
Orders page
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
</script>
|
</script>
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="">
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="flex md:flex-row flex-col justify-between gap-8 my-6">
|
<div class="flex md:flex-row flex-col justify-between gap-8 my-6">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-8 flex items-center justify-center min-h-[300px] ">
|
<div
|
||||||
|
class="bg-gray-100 dark:bg-gray-800 rounded-lg p-8 flex items-center justify-center min-h-[300px] ">
|
||||||
<img :src="selectedColor?.image || productData.image" :alt="productData.name"
|
<img :src="selectedColor?.image || productData.image" :alt="productData.name"
|
||||||
class="max-w-full h-auto object-contain" />
|
class="max-w-full h-auto object-contain" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 flex flex-col gap-4">
|
<div class="flex-1 flex flex-col gap-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
{{ productData.name }}
|
{{ productData.name }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
<UIcon name="material-symbols:favorite"
|
||||||
|
class="cursor-pointer text-2xl transition hover:scale-110"
|
||||||
|
:class="productData.is_favorite ? 'text-red-500' : 'text-gray-400'"
|
||||||
|
@click="toggleFavorite" />
|
||||||
|
</div>
|
||||||
<p class="text-gray-600 dark:text-gray-300">
|
<p class="text-gray-600 dark:text-gray-300">
|
||||||
{{ productData.description }}
|
{{ productData.description }}
|
||||||
</p>
|
</p>
|
||||||
<div class="text-3xl font-bold text-(--accent-blue-light) dark:text-(--accent-blue-dark)">
|
<div class="text-3xl font-bold text-(--text-sky-light) dark:text-(--text-sky-dark)">
|
||||||
{{ productData.price }}
|
{{ productData.price }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@@ -32,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
|
<div class="flex md:flex-row flex-col justify-between md:items-end items-start gap-5 md:gap-0 md:mb-8 mb-4">
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<span class="text-sm text-(--accent-blue-light) dark:text-(--accent-blue-dark) ">Colors:</span>
|
<span class="text-sm text-(--text-sky-light) dark:text-(--text-sky-dark) ">Colors:</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button v-for="color in productData.colors" :key="color.id" @click="selectedColor = color"
|
<button v-for="color in productData.colors" :key="color.id" @click="selectedColor = color"
|
||||||
class="w-10 h-10 border-2 transition-all" :class="selectedColor?.id === color.id
|
class="w-10 h-10 border-2 transition-all" :class="selectedColor?.id === color.id
|
||||||
@@ -43,7 +50,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex gap-5 items-end">
|
<div class="flex gap-5 items-end">
|
||||||
<UInputNumber v-model="value" />
|
<UInputNumber v-model="value" />
|
||||||
<UButton color="primary" class="px-14! bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white">
|
<UButton color="primary"
|
||||||
|
class="px-14! bg-(--accent-blue-light) dark:bg-(--accent-blue-dark) text-white">
|
||||||
Add to Cart
|
Add to Cart
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,14 +78,14 @@
|
|||||||
<hr class="border-t border-(--border-light) dark:border-(--border-dark) mb-8" />
|
<hr class="border-t border-(--border-light) dark:border-(--border-dark) mb-8" />
|
||||||
<ProductVariants />
|
<ProductVariants />
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import ProductCustomization from './components/ProductCustomization.vue'
|
import ProductCustomization from './components/ProductCustomization.vue'
|
||||||
import ProductVariants from './components/ProductVariants.vue'
|
import ProductVariants from './components/ProductVariants.vue'
|
||||||
import Default from '@/layouts/default.vue'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
interface Color {
|
interface Color {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -97,6 +105,7 @@ interface ProductData {
|
|||||||
howToUseText: string
|
howToUseText: string
|
||||||
productDetailsText: string
|
productDetailsText: string
|
||||||
documentsText: string
|
documentsText: string
|
||||||
|
is_favorite: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeTab = ref('description')
|
const activeTab = ref('description')
|
||||||
@@ -157,6 +166,24 @@ const activeTabContent = computed(() => {
|
|||||||
if (productData.colors.length > 0) {
|
if (productData.colors.length > 0) {
|
||||||
selectedColor.value = productData.colors[0] as Color
|
selectedColor.value = productData.colors[0] as Color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// async function toggleFavorite() {
|
||||||
|
// const url = `/api/v1/restricted/product/favorite/${route.params.product_id}`
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// if (!productData.is_favorite) {
|
||||||
|
// await useFetchJson(url, { method: 'POST' })
|
||||||
|
// } else {
|
||||||
|
// await useFetchJson(url, { method: 'DELETE' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// productData.is_favorite = !productData.is_favorite
|
||||||
|
// } catch (e: unknown) {
|
||||||
|
// console.error(e)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<suspense>
|
<suspense>
|
||||||
<component :is="Default || 'div'">
|
<div class="">
|
||||||
<div class="container mx-auto">
|
|
||||||
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
||||||
<template #item="{ item, active }">
|
<template #item="{ item, active }">
|
||||||
<div class="flex items-center gap-2 px-3 py-2">
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
@@ -11,51 +10,49 @@
|
|||||||
</template>
|
</template>
|
||||||
</UNavigationMenu> -->
|
</UNavigationMenu> -->
|
||||||
<h1 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">Products</h1>
|
<h1 class="text-2xl font-bold mb-6 text-gray-900 dark:text-white">Products</h1>
|
||||||
<div v-if="loading" class="text-center py-8">
|
<div v-if="customerProductStore.loading" class="text-center py-8">
|
||||||
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
|
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
<div v-else-if="customerProductStore.error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
||||||
{{ error }}
|
{{ customerProductStore.error }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="overflow-x-auto">
|
<div v-else class="overflow-x-auto">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<CategoryMenu />
|
<CategoryMenu />
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
<UTable :data="customerProductStore.productsList" :columns="columns" class="flex-1">
|
||||||
<template #expanded="{ row }">
|
<template #expanded="{ row }">
|
||||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
<UTable :data="customerProductStore.productsList.slice(0, 3)" :columns="columnsChild"
|
||||||
|
:ui="{
|
||||||
thead: 'hidden'
|
thead: 'hidden'
|
||||||
}" />
|
}" />
|
||||||
</template>
|
</template>
|
||||||
</UTable>
|
</UTable>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center items-center py-8">
|
<div class="flex justify-center items-center py-8">
|
||||||
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
|
<UPagination v-model:page="page" :total="customerProductStore.total"
|
||||||
|
:page-size="customerProductStore.perPage" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
<div v-if="customerProductStore.productsList.length === 0"
|
||||||
|
class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||||
No products found
|
No products found
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
|
||||||
</suspense>
|
</suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, h, resolveComponent, computed } from 'vue'
|
import { ref, watch, h, resolveComponent, computed } from 'vue'
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
import CategoryMenu from '../inner/categoryMenu.vue'
|
import CategoryMenu from '../inner/CategoryMenu.vue'
|
||||||
|
import { useCustomerProductStore } from '@/stores/customer/customer-product'
|
||||||
|
import type { Product } from '@/stores/customer/customer-product'
|
||||||
|
import { useCartStore } from '@/stores/customer/cart'
|
||||||
|
import { useToast } from '@nuxt/ui/runtime/composables/useToast.js'
|
||||||
|
|
||||||
interface Product {
|
|
||||||
reference: number
|
|
||||||
product_id: number
|
|
||||||
name: string
|
|
||||||
image_link: string
|
|
||||||
link_rewrite: string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const customerProductStore = useCustomerProductStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -101,18 +98,6 @@ const sortField = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const perPage = ref(15)
|
|
||||||
const total = ref(0)
|
|
||||||
|
|
||||||
interface ApiResponse {
|
|
||||||
message: string
|
|
||||||
items: Product[]
|
|
||||||
count: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const productsList = ref<Product[]>([])
|
|
||||||
const loading = ref(true)
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
const filters = computed<Record<string, string>>({
|
const filters = computed<Record<string, string>>({
|
||||||
get: () => {
|
get: () => {
|
||||||
const q = { ...route.query }
|
const q = { ...route.query }
|
||||||
@@ -157,36 +142,16 @@ const updateFilter = debounce((columnId: string, val: string) => {
|
|||||||
filters.value = newFilters
|
filters.value = newFilters
|
||||||
}, 400)
|
}, 400)
|
||||||
|
|
||||||
async function fetchProductList() {
|
|
||||||
loading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
|
|
||||||
Object.entries(route.query).forEach(([key, value]) => {
|
|
||||||
if (value) params.append(key, String(value))
|
|
||||||
})
|
|
||||||
|
|
||||||
const url = `/api/v1/restricted/list/list-products?${params}`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await useFetchJson<ApiResponse>(url)
|
|
||||||
productsList.value = response.items || []
|
|
||||||
total.value = response.count || 0
|
|
||||||
} catch (e: unknown) {
|
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToProduct(productId: number) {
|
function goToProduct(productId: number) {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'product-detail',
|
name: 'customer-product-details',
|
||||||
params: { id: productId }
|
params: { product_id: productId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const selectedCount = ref({
|
const selectedCount = ref({
|
||||||
product_id: null,
|
product_id: null,
|
||||||
count: 0
|
count: 0
|
||||||
@@ -205,7 +170,7 @@ const UInput = resolveComponent('UInput')
|
|||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UIcon = resolveComponent('UIcon')
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
|
||||||
const columns: TableColumn<Payment>[] = [
|
const columns: TableColumn<Product>[] = [
|
||||||
{
|
{
|
||||||
id: 'expand',
|
id: 'expand',
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) =>
|
||||||
@@ -344,17 +309,45 @@ const columns: TableColumn<Payment>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h(UButton, {
|
return h(UButton, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('Clicked', row.original)
|
addToCart(row.original.product_id)
|
||||||
},
|
},
|
||||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
|
||||||
variant: 'solid'
|
variant: 'solid'
|
||||||
}, 'Add to cart')
|
}, 'Add to cart')
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
onClick: () => {
|
||||||
|
goToProduct(row.original.product_id)
|
||||||
|
},
|
||||||
|
class: 'cursor-pointer',
|
||||||
|
color: 'info',
|
||||||
|
variant: 'soft'
|
||||||
|
}, () => 'Show product')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'counta',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UIcon, {
|
||||||
|
onClick: () => customerProductStore.toggleFavorite(row.original),
|
||||||
|
class: [
|
||||||
|
'cursor-pointer text-[20px] transition-transform duration-200 hover:scale-125',
|
||||||
|
row.original.is_favorite ? 'text-red-500' : 'text-blue-500'
|
||||||
|
],
|
||||||
|
name: 'material-symbols:favorite',
|
||||||
|
variant: 'soft',
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const columnsChild: TableColumn<Payment>[] = [
|
const columnsChild: TableColumn<Product>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'product_id',
|
accessorKey: 'product_id',
|
||||||
header: '',
|
header: '',
|
||||||
@@ -410,20 +403,72 @@ const columnsChild: TableColumn<Payment>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h(UButton, {
|
return h(UButton, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('Clicked', row.original)
|
addToCart(row.original.product_id)
|
||||||
},
|
},
|
||||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||||
variant: 'solid'
|
variant: 'solid'
|
||||||
}, 'Add to cart')
|
}, 'Add to cart')
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
onClick: () => {
|
||||||
|
goToProduct(row.original.product_id)
|
||||||
|
},
|
||||||
|
class: 'cursor-pointer',
|
||||||
|
color: 'info',
|
||||||
|
variant: 'soft'
|
||||||
|
}, () => 'Show product')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'counta',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UIcon, {
|
||||||
|
onClick: () => customerProductStore.toggleFavorite(row.original),
|
||||||
|
class: [
|
||||||
|
'cursor-pointer text-[20px] transition-transform duration-200 hover:scale-125',
|
||||||
|
row.original.is_favorite ? 'text-red-500' : 'text-blue-500'
|
||||||
|
],
|
||||||
|
name: 'material-symbols:favorite',
|
||||||
|
variant: 'soft',
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const cartStore = useCartStore()
|
||||||
|
const toast = useToast()
|
||||||
|
async function addToCart(product_id: number) {
|
||||||
|
if (!cartStore.activeCartId) {
|
||||||
|
toast.add({
|
||||||
|
title: "No cart selected",
|
||||||
|
description: "Please select a cart before adding products",
|
||||||
|
icon: "i-heroicons-exclamation-triangle",
|
||||||
|
duration: 5000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = selectedCount.value.count || 1
|
||||||
|
await cartStore.addProduct(product_id, count)
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: "Product added to cart",
|
||||||
|
description: `Quantity: ${count}`,
|
||||||
|
icon: "i-heroicons-check-circle",
|
||||||
|
duration: 5000
|
||||||
|
})
|
||||||
|
}
|
||||||
watch(
|
watch(
|
||||||
() => route.query,
|
() => route.query,
|
||||||
() => {
|
() => {
|
||||||
fetchProductList()
|
customerProductStore.fetchProductList()
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="">
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Customer Data') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Customer Data') }}</h1>
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@
|
|||||||
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
class="bg-(--second-light) dark:bg-(--main-dark) rounded-lg border border-(--border-light) dark:border-(--border-dark) p-4">
|
||||||
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
<h2 class="text-xl font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:domain"
|
<UIcon name="mdi:domain"
|
||||||
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[24px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 gap-10">
|
<div class="grid grid-cols-1 gap-10">
|
||||||
@@ -65,7 +64,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
|
class="text-xl font-semibold text-black dark:text-white mb-2 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[24px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[24px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Addresses') }}
|
{{ t('Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
@@ -88,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<UButton color="primary" variant="outline"
|
<UButton color="primary" variant="outline"
|
||||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"
|
class="text-(--text-sky-light) dark:text-(--text-sky-dark) border-(--accent-blue-light) dark:border-(--accent-blue-dark)"
|
||||||
@click="goToCreateAccount">
|
@click="goToCreateAccount">
|
||||||
<UIcon name="ic:sharp-edit" />
|
<UIcon name="ic:sharp-edit" />
|
||||||
{{ t('Edit Account') }}
|
{{ t('Edit Account') }}
|
||||||
@@ -97,16 +96,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useCustomerStore } from '@/stores/customer'
|
import { useCustomerStore } from '@/stores/customer'
|
||||||
import { useAddressStore } from '@/stores/address'
|
import { useAddressStore } from '@/stores/customer/address'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const customerStore = useCustomerStore()
|
const customerStore = useCustomerStore()
|
||||||
const addressStore = useAddressStore()
|
const addressStore = useAddressStore()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="">
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="max-w-2xl mx-auto">
|
<div class="max-w-2xl mx-auto">
|
||||||
<div class="flex flex-col gap-5 mb-6">
|
<div class="flex flex-col gap-5 mb-6">
|
||||||
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Create Account') }}</h1>
|
<h1 class="text-2xl font-bold text-black dark:text-white">{{ t('Create Account') }}</h1>
|
||||||
@@ -11,7 +10,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:domain"
|
<UIcon name="mdi:domain"
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
@@ -54,7 +53,7 @@
|
|||||||
<h2
|
<h2
|
||||||
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
||||||
<UIcon name="mdi:map-marker"
|
<UIcon name="mdi:map-marker"
|
||||||
class="text-[20px] text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="text-[20px] text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
{{ t('Select Addresses') }}
|
{{ t('Select Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
@@ -70,7 +69,7 @@
|
|||||||
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
? 'border-(--accent-blue-light) dark:border-(--accent-blue-dark) bg-blue-50 dark:bg-blue-900/20'
|
||||||
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
: 'border-(--border-light) dark:border-(--border-dark) hover:border-gray-400'">
|
||||||
<input type="radio" :value="address.id" v-model="selectedAddress"
|
<input type="radio" :value="address.id" v-model="selectedAddress"
|
||||||
class="mt-1 w-4 h-4 text-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
class="mt-1 w-4 h-4 text-(--text-sky-light) dark:text-(--text-sky-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
||||||
</p>
|
</p>
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
<UIcon name="mdi:map-marker-outline" class="text-4xl text-gray-400 mb-2" />
|
||||||
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
<p class="text-gray-500 dark:text-gray-400">{{ t('No addresses found') }}</p>
|
||||||
<RouterLink :to="{ name: 'addresses' }"
|
<RouterLink :to="{ name: 'addresses' }"
|
||||||
class="inline-block mt-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) hover:underline">
|
class="inline-block mt-2 text-(--text-sky-light) dark:text-(--text-sky-dark) hover:underline">
|
||||||
{{ t('Add Address') }}
|
{{ t('Add Address') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,17 +108,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useCustomerStore } from '@/stores/customer'
|
import { useCustomerStore } from '@/stores/customer'
|
||||||
import { useAddressStore } from '@/stores/address'
|
import { useAddressStore } from '@/stores/customer/address'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useCartStore } from '@/stores/cart'
|
import { useCartStore } from '@/stores/customer/cart'
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const customerStore = useCustomerStore()
|
const customerStore = useCustomerStore()
|
||||||
const addressStore = useAddressStore()
|
const addressStore = useAddressStore()
|
||||||
|
|||||||
180
bo/src/components/customer/PageSearchProducts.vue
Normal file
180
bo/src/components/customer/PageSearchProducts.vue
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default">
|
||||||
|
<div class="pt-70! flex flex-col items-center justify-center bg-gray-50 dark:bg-(--main-dark)">
|
||||||
|
<h1 class="text-6xl font-bold text-black dark:text-white mb-14">Search Products</h1>
|
||||||
|
|
||||||
|
<div class="w-full max-w-4xl">
|
||||||
|
<UInput icon="i-lucide-search" type="text" placeholder="Type product name or ID..."
|
||||||
|
v-model="searchQuery" class="w-full!" :ui="{ base: 'py-4! rounded-full!' }" />
|
||||||
|
</div>
|
||||||
|
<div v-if="products.length" class="mt-6">
|
||||||
|
<UTable :data="products" :columns="columns" class="flex-1 w-full" :ui="{
|
||||||
|
root: 'max-w-100wv overflow-auto!'
|
||||||
|
}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-else-if="searchQuery">
|
||||||
|
No products found
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson';
|
||||||
|
import Default from '@/layouts/default.vue';
|
||||||
|
import { watch } from 'vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const products = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
async function fetchProducts() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = searchQuery.value
|
||||||
|
? `name=~${searchQuery.value}`
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const result = await useFetchJson(
|
||||||
|
`/api/v1/restricted/list-products/get-listing?${query}`
|
||||||
|
)
|
||||||
|
|
||||||
|
products.value = result.items || result
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'Failed to load products'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
watch(searchQuery, () => {
|
||||||
|
fetchProducts()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
import errorImg from '@/assets/error.svg'
|
||||||
|
import type { TableColumn } from '@nuxt/ui';
|
||||||
|
import type { Product } from '@/types/product';
|
||||||
|
// const columns: TableColumn<Product>[] = [
|
||||||
|
// {
|
||||||
|
// accessorKey: 'product_id',
|
||||||
|
// header: ({ column }) => {
|
||||||
|
// return h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
// h('div', {
|
||||||
|
// class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
// onClick: () => {
|
||||||
|
// sortField.value = ['product_id', 'asc']
|
||||||
|
// }
|
||||||
|
// }, [
|
||||||
|
// h('span', 'ID'),
|
||||||
|
// h(UIcon, {
|
||||||
|
// name: getIcon('product_id')
|
||||||
|
// })
|
||||||
|
// ]),
|
||||||
|
|
||||||
|
// h(UInput, {
|
||||||
|
// placeholder: 'Search...',
|
||||||
|
// modelValue: filters.value[column.id] ?? '',
|
||||||
|
// 'onUpdate:modelValue': (val: string) => {
|
||||||
|
// updateFilter(column.id, val)
|
||||||
|
// },
|
||||||
|
// size: 'xs'
|
||||||
|
// })
|
||||||
|
// ])
|
||||||
|
// },
|
||||||
|
// // header: '#',
|
||||||
|
// cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// accessorKey: 'image_link',
|
||||||
|
// header: 'Image',
|
||||||
|
// cell: ({ row }) => {
|
||||||
|
// return h('img', {
|
||||||
|
// src: row.getValue('image_link') as string,
|
||||||
|
// style: 'width:40px;height:40px;object-fit:cover;',
|
||||||
|
// onError: (e: Event) => {
|
||||||
|
// const target = e.target as HTMLImageElement
|
||||||
|
// target.src = errorImg
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// accessorKey: 'name',
|
||||||
|
// header: ({ column }) => {
|
||||||
|
// return h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
// h('div', {
|
||||||
|
// class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
// onClick: () => {
|
||||||
|
// sortField.value = ['name', 'asc']
|
||||||
|
// }
|
||||||
|
// }, [
|
||||||
|
// h('span', 'Name'),
|
||||||
|
// h(UIcon, {
|
||||||
|
// name: getIcon('name')
|
||||||
|
// })
|
||||||
|
// ]),
|
||||||
|
|
||||||
|
// h(UInput, {
|
||||||
|
// placeholder: 'Search...',
|
||||||
|
// modelValue: filters.value[column.id] ?? '',
|
||||||
|
// 'onUpdate:modelValue': (val: string) => {
|
||||||
|
// updateFilter(column.id, val)
|
||||||
|
// },
|
||||||
|
// size: 'xs'
|
||||||
|
// })
|
||||||
|
// ])
|
||||||
|
// },
|
||||||
|
// cell: ({ row }) => row.getValue('name') as string,
|
||||||
|
// filterFn: (row, columnId, value) => {
|
||||||
|
// const name = row.getValue(columnId) as string
|
||||||
|
// return name.toLowerCase().includes(value.toLowerCase())
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// accessorKey: 'quantity',
|
||||||
|
// header: ({ }) => {
|
||||||
|
// return h('div', { class: 'flex flex-col gap-1' }, [
|
||||||
|
// h('div', {
|
||||||
|
// class: 'flex items-center gap-2 cursor-pointer',
|
||||||
|
// onClick: () => {
|
||||||
|
// sortField.value = ['quantity', 'asc']
|
||||||
|
// }
|
||||||
|
// }, [
|
||||||
|
// h('span', 'In stock'),
|
||||||
|
// h(UIcon, {
|
||||||
|
// name: getIcon('quantity')
|
||||||
|
// })
|
||||||
|
// ]),
|
||||||
|
// ])
|
||||||
|
// },
|
||||||
|
// cell: ({ row }) => row.getValue('quantity') as number
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// accessorKey: 'count',
|
||||||
|
// header: '',
|
||||||
|
// cell: ({ row }) => {
|
||||||
|
// return h(UButton, {
|
||||||
|
// onClick: () => {
|
||||||
|
// goToProduct(row.original.product_id, row.original.link_rewrite)
|
||||||
|
// },
|
||||||
|
// class: 'cursor-pointer',
|
||||||
|
// color: 'info',
|
||||||
|
// variant: 'soft'
|
||||||
|
// }, () => 'Show product')
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
|
||||||
Statistic page
|
Statistic page
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
</script>
|
</script>
|
||||||
158
bo/src/components/customer/StorageFileBrowser.vue
Normal file
158
bo/src/components/customer/StorageFileBrowser.vue
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-4">
|
||||||
|
<div v-if="loading" class="flex justify-center py-8">
|
||||||
|
<ULoader />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error" class="text-red-500">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<UTree v-if="showTree" :items="treeItems" v-model:expanded="expandedFolders" :key="treeKey" @toggle="onToggle" :get-key="item => item.value">
|
||||||
|
<template #item-wrapper="{ item }">
|
||||||
|
<div class="flex items-start cursor-pointer" @click.stop="!item.isFolder">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<UIcon :name="item.icon" :size="30" />
|
||||||
|
<div class="flex gap-1 items-center">
|
||||||
|
<span class="text-[15px] font-medium">
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
<UButton v-if="!item.isFolder && item.fileName" size="xxs" color="neutral"
|
||||||
|
variant="outline" icon="i-lucide-download"
|
||||||
|
@click.stop="downloadFile(item.path, item.fileName)" :ui="{ base: 'ring-0!' }" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UTree>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
|
||||||
|
interface FileItemRaw {
|
||||||
|
Name: string
|
||||||
|
IsFolder: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileItem {
|
||||||
|
name: string
|
||||||
|
type: 'file' | 'folder'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeItem {
|
||||||
|
label: string
|
||||||
|
icon: string
|
||||||
|
children?: TreeItem[]
|
||||||
|
isFolder: boolean
|
||||||
|
path: string
|
||||||
|
value: string
|
||||||
|
fileName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{ initialPath?: string }>()
|
||||||
|
|
||||||
|
const currentPath = ref(props.initialPath || '')
|
||||||
|
|
||||||
|
const allData = ref<Record<string, FileItem[]>>({})
|
||||||
|
const expandedFolders = ref<string[]>([])
|
||||||
|
const treeKey = ref(0)
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
const showTree = computed(() => !error.value)
|
||||||
|
|
||||||
|
async function fetchFolderContents(path: string): Promise<FileItem[]> {
|
||||||
|
const url = `/api/v1/restricted/storage/list-content/${path}`
|
||||||
|
const data = await useFetchJson<FileItemRaw[]>(url)
|
||||||
|
|
||||||
|
return (data.items || []).map(i => ({
|
||||||
|
name: i.Name,
|
||||||
|
type: i.IsFolder ? 'folder' : 'file'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFolder(path: string) {
|
||||||
|
if (allData.value[path]) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const items = await fetchFolderContents(path)
|
||||||
|
allData.value[path] = items
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to load folder contents'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTree(path: string): TreeItem[] {
|
||||||
|
const items = allData.value[path] || []
|
||||||
|
|
||||||
|
return items.map(item => {
|
||||||
|
const itemPath = path ? `${path}/${item.name}` : item.name
|
||||||
|
const isFolder = item.type === 'folder'
|
||||||
|
|
||||||
|
const isExpanded = expandedFolders.value.includes(itemPath)
|
||||||
|
const isLoaded = !!allData.value[itemPath]
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: item.name,
|
||||||
|
icon: isFolder ? 'fxemoji:folder' : 'flat-color-icons:file',
|
||||||
|
isFolder,
|
||||||
|
path: isFolder ? itemPath : path,
|
||||||
|
value: itemPath,
|
||||||
|
fileName: isFolder ? undefined : item.name,
|
||||||
|
|
||||||
|
children: isFolder && isExpanded && isLoaded
|
||||||
|
? buildTree(itemPath)
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeItems = computed(() => buildTree(currentPath.value))
|
||||||
|
|
||||||
|
async function toggleFolder(item: TreeItem) {
|
||||||
|
if (!item.isFolder) return
|
||||||
|
|
||||||
|
const isOpen = expandedFolders.value.includes(item.value)
|
||||||
|
|
||||||
|
if (!isOpen) {
|
||||||
|
await loadFolder(item.value)
|
||||||
|
treeKey.value++
|
||||||
|
} else {
|
||||||
|
treeKey.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggle(_event: unknown, item: TreeItem) {
|
||||||
|
console.log('Toggle:', item)
|
||||||
|
if (item.isFolder) {
|
||||||
|
toggleFolder(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadFile(path: string, fileName: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/v1/restricted/storage/download-file/${path}/${fileName}`)
|
||||||
|
if (!response.ok) throw new Error('Download failed')
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = fileName
|
||||||
|
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
a.remove()
|
||||||
|
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
alert('Download failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFolder(currentPath.value)
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" />
|
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" :ui="{
|
||||||
|
root: ''
|
||||||
|
}" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -37,9 +39,10 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
|||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
item.open = path && path.includes(item.category_id) ? true : openAll.value
|
item.open = path && path.includes(item.category_id) ? true : openAll.value
|
||||||
adaptMenu(item.children);
|
adaptMenu(item.children);
|
||||||
|
|
||||||
item.children.unshift({
|
item.children.unshift({
|
||||||
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
||||||
name: 'customer-products-category', params: {
|
name: item.params.to, params: {
|
||||||
category_id: item.params.category_id,
|
category_id: item.params.category_id,
|
||||||
link_rewrite: item.params.link_rewrite
|
link_rewrite: item.params.link_rewrite
|
||||||
}
|
}
|
||||||
@@ -47,7 +50,7 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
item.to = {
|
item.to = {
|
||||||
name: 'customer-products-category', params: {
|
name: item.params.to, params: {
|
||||||
category_id: item.params.category_id,
|
category_id: item.params.category_id,
|
||||||
link_rewrite: item.params.link_rewrite
|
link_rewrite: item.params.link_rewrite
|
||||||
}
|
}
|
||||||
53
bo/src/components/inner/CountryCurrencySwitch.vue
Normal file
53
bo/src/components/inner/CountryCurrencySwitch.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<USelectMenu v-model="country" :items="countries" value-key="id"
|
||||||
|
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
||||||
|
<template #default>
|
||||||
|
<div class="flex flex-col items-start leading-tight">
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
Country/Currency
|
||||||
|
</span>
|
||||||
|
<span v-if="country" class="font-medium dark:text-white text-black">
|
||||||
|
{{ country?.name || 'Country' }} / {{ country?.ps_currency || 'Currency' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item-leading="{ item }">
|
||||||
|
<div class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<span class="text-lg">{{ item.flag }}</span>
|
||||||
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { countries, currentCountry, switchLocalization } from '@/router/langs'
|
||||||
|
import { useCookie } from '@/composable/useCookie'
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
|
const cookie = useCookie()
|
||||||
|
const country = computed({
|
||||||
|
get() {
|
||||||
|
return currentCountry.value
|
||||||
|
},
|
||||||
|
set(value: string) {
|
||||||
|
currentCountry.value = countries.find((x) => x.id == Number(value))
|
||||||
|
cookie.setCookie('country_id', `${countries.find((x) => x.id == Number(value))?.id}`, { days: 60, secure: true, sameSite: 'Lax' })
|
||||||
|
|
||||||
|
switchLocalization()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => country,
|
||||||
|
(newCountry) => {
|
||||||
|
if (newCountry) {
|
||||||
|
currentCountry.value = countries.find((x) => x.id == Number(newCountry.value?.id))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
</script>
|
||||||
@@ -1,34 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<USelectMenu v-model="locale" :items="langs"
|
<USelectMenu v-model="locale" :items="langs" value-key="iso_code"
|
||||||
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="iso_code"
|
class="w-48 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm" :searchInput="false">
|
||||||
:searchInput="false">
|
<template #default>
|
||||||
<template #default="{ modelValue }">
|
<div class="flex items-center gap-2">
|
||||||
<div class="flex items-center gap-1">
|
<!-- <span class="text-lg">{{ selectedLang?.flag }}</span> -->
|
||||||
<!-- <span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.flag}}</span> -->
|
|
||||||
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.name}}</span>
|
<div class="flex flex-col leading-tight items-start">
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
Language
|
||||||
|
</span>
|
||||||
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ selectedLang?.name || 'Select language' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item-leading="{ item }">
|
<template #item-leading="{ item }">
|
||||||
<div class="flex items-center rounded-md cursor-pointer transition-colors">
|
<div class="flex items-center gap-2">
|
||||||
<!-- <span class="text-md ">{{ item.flag }}</span> -->
|
<span class="text-lg">{{ item.flag }}</span>
|
||||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
<span class="font-medium dark:text-white text-black">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { langs, currentLang } from '@/router/langs'
|
import { langs, currentLang, switchLocalization } from '@/router/langs'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useCookie } from '@/composable/useCookie'
|
import { useCookie } from '@/composable/useCookie'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const cookie = useCookie()
|
const selectedLang = computed(() =>
|
||||||
|
langs.find(item => item.iso_code === locale.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
const cookie = useCookie()
|
||||||
const locale = computed({
|
const locale = computed({
|
||||||
get() {
|
get() {
|
||||||
return currentLang.value?.iso_code || i18n.locale.value
|
return currentLang.value?.iso_code || i18n.locale.value
|
||||||
@@ -41,7 +54,6 @@ const locale = computed({
|
|||||||
const pathParts = currentPath.split('/').filter(Boolean)
|
const pathParts = currentPath.split('/').filter(Boolean)
|
||||||
|
|
||||||
cookie.setCookie('lang_id', `${langs.find((x) => x.iso_code == value)?.id}`, { days: 60, secure: true, sameSite: 'Lax' })
|
cookie.setCookie('lang_id', `${langs.find((x) => x.iso_code == value)?.id}`, { days: 60, secure: true, sameSite: 'Lax' })
|
||||||
|
|
||||||
if (pathParts.length > 0) {
|
if (pathParts.length > 0) {
|
||||||
const isLocale = langs.some((l) => l.lang_code === pathParts[0])
|
const isLocale = langs.some((l) => l.lang_code === pathParts[0])
|
||||||
if (isLocale) {
|
if (isLocale) {
|
||||||
@@ -52,21 +64,10 @@ const locale = computed({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeLang()
|
switchLocalization()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function changeLang() {
|
|
||||||
try {
|
|
||||||
const { items } = await useFetchJson('/api/v1/public/auth/update-choice', {
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params.locale,
|
() => route.params.locale,
|
||||||
(newLocale) => {
|
(newLocale) => {
|
||||||
160
bo/src/components/inner/ProductEditor.vue
Normal file
160
bo/src/components/inner/ProductEditor.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<UEditor v-slot="{ editor }" v-model="localValue" content-type="html"
|
||||||
|
:ui="{ base: 'p-8 sm:px-16', root: 'p-2' }"
|
||||||
|
class="min-w-full border rounded-md bg-white! border-(--border-light)" placeholder="Write there ...">
|
||||||
|
<UEditorToolbar :editor="editor" :items="toolbarItems" class="sm:px-8 flex-wrap!">
|
||||||
|
<template #link>
|
||||||
|
<EditorLinkPopover :editor="editor" auto-open />
|
||||||
|
</template>
|
||||||
|
</UEditorToolbar>
|
||||||
|
</UEditor>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { EditorToolbarItem } from '@nuxt/ui';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const localValue = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (value: string) => emit('update:modelValue', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolbarItems: EditorToolbarItem[][] = [
|
||||||
|
// History controls
|
||||||
|
[{
|
||||||
|
kind: 'undo',
|
||||||
|
icon: 'i-lucide-undo',
|
||||||
|
tooltip: { text: 'Undo' }
|
||||||
|
}, {
|
||||||
|
kind: 'redo',
|
||||||
|
icon: 'i-lucide-redo',
|
||||||
|
tooltip: { text: 'Redo' }
|
||||||
|
}],
|
||||||
|
// Block types
|
||||||
|
[{
|
||||||
|
icon: 'i-lucide-heading',
|
||||||
|
tooltip: { text: 'Headings' },
|
||||||
|
content: {
|
||||||
|
align: 'start'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'heading',
|
||||||
|
level: 1,
|
||||||
|
icon: 'i-lucide-heading-1',
|
||||||
|
label: 'Heading 1'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 2,
|
||||||
|
icon: 'i-lucide-heading-2',
|
||||||
|
label: 'Heading 2'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 3,
|
||||||
|
icon: 'i-lucide-heading-3',
|
||||||
|
label: 'Heading 3'
|
||||||
|
}, {
|
||||||
|
kind: 'heading',
|
||||||
|
level: 4,
|
||||||
|
icon: 'i-lucide-heading-4',
|
||||||
|
label: 'Heading 4'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
icon: 'i-lucide-list',
|
||||||
|
tooltip: { text: 'Lists' },
|
||||||
|
content: {
|
||||||
|
align: 'start'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'bulletList',
|
||||||
|
icon: 'i-lucide-list',
|
||||||
|
label: 'Bullet List'
|
||||||
|
}, {
|
||||||
|
kind: 'orderedList',
|
||||||
|
icon: 'i-lucide-list-ordered',
|
||||||
|
label: 'Ordered List'
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
kind: 'blockquote',
|
||||||
|
icon: 'i-lucide-text-quote',
|
||||||
|
tooltip: { text: 'Blockquote' }
|
||||||
|
}, {
|
||||||
|
kind: 'codeBlock',
|
||||||
|
icon: 'i-lucide-square-code',
|
||||||
|
tooltip: { text: 'Code Block' }
|
||||||
|
}, {
|
||||||
|
kind: 'horizontalRule',
|
||||||
|
icon: 'i-lucide-separator-horizontal',
|
||||||
|
tooltip: { text: 'Horizontal Rule' }
|
||||||
|
}],
|
||||||
|
// Text formatting
|
||||||
|
[{
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'bold',
|
||||||
|
icon: 'i-lucide-bold',
|
||||||
|
tooltip: { text: 'Bold' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'italic',
|
||||||
|
icon: 'i-lucide-italic',
|
||||||
|
tooltip: { text: 'Italic' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'underline',
|
||||||
|
icon: 'i-lucide-underline',
|
||||||
|
tooltip: { text: 'Underline' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'strike',
|
||||||
|
icon: 'i-lucide-strikethrough',
|
||||||
|
tooltip: { text: 'Strikethrough' }
|
||||||
|
}, {
|
||||||
|
kind: 'mark',
|
||||||
|
mark: 'code',
|
||||||
|
icon: 'i-lucide-code',
|
||||||
|
tooltip: { text: 'Code' }
|
||||||
|
}],
|
||||||
|
// Link
|
||||||
|
[{
|
||||||
|
kind: 'link',
|
||||||
|
icon: 'i-lucide-link',
|
||||||
|
tooltip: { text: 'Link' }
|
||||||
|
}],
|
||||||
|
// Text alignment
|
||||||
|
[{
|
||||||
|
icon: 'i-lucide-align-justify',
|
||||||
|
tooltip: { text: 'Text Align' },
|
||||||
|
content: {
|
||||||
|
align: 'end'
|
||||||
|
},
|
||||||
|
items: [{
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'left',
|
||||||
|
icon: 'i-lucide-align-left',
|
||||||
|
label: 'Align Left'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'center',
|
||||||
|
icon: 'i-lucide-align-center',
|
||||||
|
label: 'Align Center'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'right',
|
||||||
|
icon: 'i-lucide-align-right',
|
||||||
|
label: 'Align Right'
|
||||||
|
}, {
|
||||||
|
kind: 'textAlign',
|
||||||
|
align: 'justify',
|
||||||
|
icon: 'i-lucide-align-justify',
|
||||||
|
label: 'Align Justify'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
</script>
|
||||||
37
bo/src/components/inner/ThemeSwitch.vue
Normal file
37
bo/src/components/inner/ThemeSwitch.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<UButton variant="outline" size="sm" color="info" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
<!-- <UButton variant="solid" size="sm" color="info" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
<UButton variant="soft" size="sm" color="info" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
<UButton variant="subtle" size="sm" color="info" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
<UButton variant="ghost" size="sm" color="info" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
<UButton variant="link" size="sm" color="info" @click="themeStorage.setTheme()">
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
||||||
|
</span>
|
||||||
|
</UButton> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useThemeStore } from '@/stores/admin/theme'
|
||||||
|
const themeStorage = useThemeStore()
|
||||||
|
</script>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<template>
|
|
||||||
<UButton variant="ghost" size="sm" @click="themeStorage.setTheme()">
|
|
||||||
<span class="hidden sm:inline">
|
|
||||||
<UIcon class="size-5" :name="themeStorage.themeIcon" />
|
|
||||||
</span>
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useThemeStore } from '@/stores/theme'
|
|
||||||
const themeStorage = useThemeStore()
|
|
||||||
</script>
|
|
||||||
@@ -29,7 +29,7 @@ export async function useFetchJson<T = unknown>(url: string, opt?: RequestInit):
|
|||||||
|
|
||||||
// Handle 401 — access token expired; try to refresh via the HTTPOnly refresh_token cookie
|
// Handle 401 — access token expired; try to refresh via the HTTPOnly refresh_token cookie
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
const { useAuthStore } = await import('@/stores/auth')
|
const { useAuthStore } = await import('../stores/customer/auth')
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const refreshed = await authStore.refreshAccessToken()
|
const refreshed = await authStore.refreshAccessToken()
|
||||||
|
|||||||
@@ -1,14 +1,330 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import TopBar from '@/components/TopBar.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
<div class="flex flex-1 overflow-x-hidden h-svh">
|
||||||
<main class="pt-20 pb-10">
|
<USidebar v-model:open="open" collapsible="icon" rail :ui="{
|
||||||
<TopBar />
|
container: 'h-full z-80',
|
||||||
<div class="container mx-auto px-4">
|
inner: 'bg-elevated/25 divide-transparent',
|
||||||
|
body: 'py-0'
|
||||||
|
}">
|
||||||
|
<template #header>
|
||||||
|
<UDropdownMenu :items="teamsItems" :content="{ align: 'start', collisionPadding: 12 }"
|
||||||
|
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
|
||||||
|
<UButton v-bind="selectedTeam" trailing-icon="i-lucide-chevrons-up-down" color="neutral" variant="ghost"
|
||||||
|
square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
|
||||||
|
trailingIcon: 'text-dimmed ms-auto'
|
||||||
|
}" />
|
||||||
|
</UDropdownMenu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default="{ state }">
|
||||||
|
<UNavigationMenu :key="state" :items="menuItems" orientation="vertical"
|
||||||
|
:ui="{ link: 'p-1.5 overflow-hidden' }" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UDropdownMenu :items="userItems" :content="{ align: 'center', collisionPadding: 12 }"
|
||||||
|
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
|
||||||
|
<UButton v-bind="userStore.user" :label="userStore.user?.email" trailing-icon="i-lucide-chevrons-up-down"
|
||||||
|
color="neutral" variant="ghost" square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
|
||||||
|
trailingIcon: 'text-dimmed ms-auto'
|
||||||
|
}" />
|
||||||
|
</UDropdownMenu>
|
||||||
|
<!-- first_name: '', last_name: '' -->
|
||||||
|
</template>
|
||||||
|
</USidebar>
|
||||||
|
|
||||||
|
<div class="flex-1 flex flex-col">
|
||||||
|
<div class="flex h-(--ui-header-height) shrink-0 items-center justify-between px-4 border-b border-default">
|
||||||
|
<div class="flex items-center gap-5">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UButton icon="i-lucide-panel-left" color="neutral" variant="ghost" aria-label="Toggle sidebar"
|
||||||
|
@click="open = !open" />
|
||||||
|
<span class="text-[20px] font-medium">{{ pageTitle }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div v-if="cartStore.activeCart"
|
||||||
|
class="flex items-center gap-2 p-1 rounded-md bg-gray-100 dark:bg-gray-800">
|
||||||
|
<UIcon name="i-lucide-shopping-cart" />
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p class="text-sm font-medium">
|
||||||
|
{{ cartStore.activeCart.name }}-
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
ID: {{ cartStore.activeCart.cart_id }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span v-else class="text-sm text-red-400 flex gap-1 items-center">
|
||||||
|
<UIcon name="i-lucide-shopping-cart" />
|
||||||
|
No cart selected
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden md:flex items-center gap-12">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<CountryCurrencySwitch />
|
||||||
|
<LangSwitch />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ThemeSwitch />
|
||||||
|
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
|
||||||
|
{{ $t('general.logout') }}
|
||||||
|
<UIcon name="ic:baseline-logout" class="text-red-700 dark:text-red-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 p-4 bg-slate-50 dark:bg-(--main-dark)">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useColorMode } from '@vueuse/core'
|
||||||
|
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
||||||
|
import { defineShortcuts, extractShortcuts } from '@nuxt/ui/runtime/composables/defineShortcuts.js'
|
||||||
|
import { useAuthStore } from '../stores/customer/auth'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const pageTitle = computed(() => route.meta.name ?? 'Default Page')
|
||||||
|
await userStore.getUser()
|
||||||
|
|
||||||
|
const open = ref(true)
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
|
const teams = ref([
|
||||||
|
{
|
||||||
|
label: 'Nuxt',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/nuxt.png',
|
||||||
|
alt: 'Nuxt'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Vue',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/vuejs.png',
|
||||||
|
alt: 'Vue'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'UnJS',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/unjs.png',
|
||||||
|
alt: 'UnJS'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
const selectedTeam = ref(teams.value[0])
|
||||||
|
|
||||||
|
const teamsItems = computed<DropdownMenuItem[][]>(() => {
|
||||||
|
return [
|
||||||
|
teams.value.map((team, index) => ({
|
||||||
|
...team,
|
||||||
|
kbds: ['meta', String(index + 1)],
|
||||||
|
onSelect() {
|
||||||
|
selectedTeam.value = team
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Create team',
|
||||||
|
icon: 'i-lucide-circle-plus'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
function getItems(state: 'collapsed' | 'expanded') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Inbox',
|
||||||
|
icon: 'i-lucide-inbox',
|
||||||
|
badge: '4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Issues',
|
||||||
|
icon: 'i-lucide-square-dot'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Activity',
|
||||||
|
icon: 'i-lucide-square-activity'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
defaultOpen: true,
|
||||||
|
children:
|
||||||
|
state === 'expanded'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'General',
|
||||||
|
icon: 'i-lucide-house'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Team',
|
||||||
|
icon: 'i-lucide-users'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'i-lucide-credit-card'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
] satisfies NavigationMenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { currentLang } from '@/router/langs'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import CountryCurrencySwitch from '@/components/inner/CountryCurrencySwitch.vue'
|
||||||
|
import LangSwitch from '@/components/inner/LangSwitch.vue'
|
||||||
|
import ThemeSwitch from '@/components/inner/ThemeSwitch.vue'
|
||||||
|
import type { LabelTrans, TopMenuItem } from '@/types'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useCartStore } from '@/stores/customer/cart'
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const menu = ref<TopMenuItem[] | null>(null)
|
||||||
|
|
||||||
|
async function getTopMenu() {
|
||||||
|
try {
|
||||||
|
const { items } = await useFetchJson<TopMenuItem[]>('/api/v1/restricted/menu/get-top-menu')
|
||||||
|
menu.value = items[0]?.children || []
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTopMenu()
|
||||||
|
})
|
||||||
|
|
||||||
|
const menuItems = computed(() => {
|
||||||
|
if (!menu.value?.length) return []
|
||||||
|
|
||||||
|
return transformMenu(
|
||||||
|
menu.value || [],
|
||||||
|
currentLang.value?.iso_code
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function transformMenu(
|
||||||
|
items: TopMenuItem[],
|
||||||
|
locale: string | undefined
|
||||||
|
): NavigationMenuItem[] {
|
||||||
|
return items.map((item) => {
|
||||||
|
const route: NavigationMenuItem = {
|
||||||
|
icon: item.label.icon || 'i-lucide-house',
|
||||||
|
label:
|
||||||
|
item.label.trans?.[locale as keyof LabelTrans]?.label ||
|
||||||
|
item.label.trans?.en?.label ||
|
||||||
|
'—',
|
||||||
|
children: item.children
|
||||||
|
? transformMenu(item.children, locale)
|
||||||
|
: undefined,
|
||||||
|
onSelect: () => {
|
||||||
|
router.push({
|
||||||
|
name: item.params.route.name,
|
||||||
|
params: {
|
||||||
|
...(item.params.route.params || {}),
|
||||||
|
locale: currentLang.value?.iso_code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const userItems = computed<DropdownMenuItem[][]>(() => [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Profile',
|
||||||
|
icon: 'i-lucide-user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'i-lucide-credit-card'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
to: '/settings'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Appearance',
|
||||||
|
icon: 'i-lucide-sun-moon',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'Light',
|
||||||
|
icon: 'i-lucide-sun',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: colorMode.value === 'light',
|
||||||
|
onUpdateChecked(checked: boolean) {
|
||||||
|
if (checked) {
|
||||||
|
colorMode.preference = 'light'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dark',
|
||||||
|
icon: 'i-lucide-moon',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: colorMode.value === 'dark',
|
||||||
|
onUpdateChecked(checked: boolean) {
|
||||||
|
if (checked) {
|
||||||
|
colorMode.preference = 'dark'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'GitHub',
|
||||||
|
icon: 'i-simple-icons-github',
|
||||||
|
to: 'https://github.com/nuxt/ui',
|
||||||
|
target: '_blank'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Log out',
|
||||||
|
icon: 'i-lucide-log-out'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
const cartStore = useCartStore()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
cartStore.initCart()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineShortcuts(extractShortcuts(teamsItems.value))
|
||||||
|
</script>
|
||||||
|
|||||||
309
bo/src/layouts/management.vue
Normal file
309
bo/src/layouts/management.vue
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-1 overflow-x-hidden h-svh">
|
||||||
|
<USidebar v-model:open="open" collapsible="icon" rail :ui="{
|
||||||
|
container: 'h-full z-80',
|
||||||
|
inner: 'bg-elevated/25 divide-transparent',
|
||||||
|
body: 'py-0'
|
||||||
|
}">
|
||||||
|
<template #header>
|
||||||
|
<UDropdownMenu :items="teamsItems" :content="{ align: 'start', collisionPadding: 12 }"
|
||||||
|
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
|
||||||
|
<UButton v-bind="selectedTeam" trailing-icon="i-lucide-chevrons-up-down" color="neutral"
|
||||||
|
variant="ghost" square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
|
||||||
|
trailingIcon: 'text-dimmed ms-auto'
|
||||||
|
}" />
|
||||||
|
</UDropdownMenu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #default="{ state }">
|
||||||
|
<UNavigationMenu :key="state" :items="menuItems" orientation="vertical"
|
||||||
|
:ui="{ link: 'p-1.5 overflow-hidden' }" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UDropdownMenu :items="userItems" :content="{ align: 'center', collisionPadding: 12 }"
|
||||||
|
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
|
||||||
|
<UButton v-bind="userStore.user" :label="userStore.user?.email"
|
||||||
|
trailing-icon="i-lucide-chevrons-up-down" color="neutral" variant="ghost" square
|
||||||
|
class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
|
||||||
|
trailingIcon: 'text-dimmed ms-auto'
|
||||||
|
}" />
|
||||||
|
</UDropdownMenu>
|
||||||
|
<!-- first_name: '', last_name: '' -->
|
||||||
|
</template>
|
||||||
|
</USidebar>
|
||||||
|
|
||||||
|
<div class="flex-1 flex flex-col">
|
||||||
|
<div class="flex h-(--ui-header-height) shrink-0 items-center justify-between px-4 border-b border-default">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UButton icon="i-lucide-panel-left" color="neutral" variant="ghost" aria-label="Toggle sidebar"
|
||||||
|
@click="open = !open" />
|
||||||
|
<p class="font-bold text-xl">Customer-Management: <span class="text-[20px] font-medium">{{ pageTitle
|
||||||
|
}}</span></p>
|
||||||
|
</div>
|
||||||
|
<div class="hidden md:flex items-center gap-12">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<CountryCurrencySwitch />
|
||||||
|
<LangSwitch />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ThemeSwitch />
|
||||||
|
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
||||||
|
class="flex gap-2 items-center px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
|
||||||
|
{{ $t('general.logout') }}
|
||||||
|
<UIcon name="ic:baseline-logout" class="text-red-700 dark:text-red-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 p-4 bg-slate-50 dark:bg-(--main-dark)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useColorMode } from '@vueuse/core'
|
||||||
|
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
||||||
|
import { defineShortcuts, extractShortcuts } from '@nuxt/ui/runtime/composables/defineShortcuts.js'
|
||||||
|
import { useAuthStore } from '../stores/customer/auth'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const pageTitle = computed(() => route.meta.name ?? 'Default Page')
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const open = ref(true)
|
||||||
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
|
const teams = ref([
|
||||||
|
{
|
||||||
|
label: 'Nuxt',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/nuxt.png',
|
||||||
|
alt: 'Nuxt'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Vue',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/vuejs.png',
|
||||||
|
alt: 'Vue'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'UnJS',
|
||||||
|
avatar: {
|
||||||
|
src: 'https://github.com/unjs.png',
|
||||||
|
alt: 'UnJS'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
const selectedTeam = ref(teams.value[0])
|
||||||
|
|
||||||
|
const teamsItems = computed<DropdownMenuItem[][]>(() => {
|
||||||
|
return [
|
||||||
|
teams.value.map((team, index) => ({
|
||||||
|
...team,
|
||||||
|
kbds: ['meta', String(index + 1)],
|
||||||
|
onSelect() {
|
||||||
|
selectedTeam.value = team
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Create team',
|
||||||
|
icon: 'i-lucide-circle-plus'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
function getItems(state: 'collapsed' | 'expanded') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Inbox',
|
||||||
|
icon: 'i-lucide-inbox',
|
||||||
|
badge: '4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Issues',
|
||||||
|
icon: 'i-lucide-square-dot'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Activity',
|
||||||
|
icon: 'i-lucide-square-activity'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
defaultOpen: true,
|
||||||
|
children:
|
||||||
|
state === 'expanded'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'General',
|
||||||
|
icon: 'i-lucide-house'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Team',
|
||||||
|
icon: 'i-lucide-users'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'i-lucide-credit-card'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
] satisfies NavigationMenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { currentLang } from '@/router/langs'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import CountryCurrencySwitch from '@/components/inner/CountryCurrencySwitch.vue'
|
||||||
|
import LangSwitch from '@/components/inner/LangSwitch.vue'
|
||||||
|
import ThemeSwitch from '@/components/inner/ThemeSwitch.vue'
|
||||||
|
import type { LabelTrans, TopMenuItem } from '@/types'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const menu = ref<TopMenuItem[] | null>(null)
|
||||||
|
|
||||||
|
const Id = Number(route.params.user_id)
|
||||||
|
async function cmGetTopMenu() {
|
||||||
|
try {
|
||||||
|
const { items } = await useFetchJson<TopMenuItem[]>(`/api/v1/restricted/menu/get-top-menu?target_user_id=${Id}`)
|
||||||
|
|
||||||
|
menu.value = items
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.user_id,
|
||||||
|
() => {
|
||||||
|
if (route.params.user_id) {
|
||||||
|
cmGetTopMenu()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const menuItems = computed(() => {
|
||||||
|
if (!menu.value?.length) return []
|
||||||
|
|
||||||
|
return transformMenu(
|
||||||
|
menu.value || [],
|
||||||
|
currentLang.value?.iso_code
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function transformMenu(
|
||||||
|
items: TopMenuItem[],
|
||||||
|
locale: string | undefined
|
||||||
|
): NavigationMenuItem[] {
|
||||||
|
return items.map((item) => {
|
||||||
|
const route: NavigationMenuItem = {
|
||||||
|
icon: item.label.icon || 'i-lucide-house',
|
||||||
|
label:
|
||||||
|
item.label.trans?.[locale as keyof LabelTrans]?.label ||
|
||||||
|
item.label.trans?.en?.label ||
|
||||||
|
'—',
|
||||||
|
children: item.children
|
||||||
|
? transformMenu(item.children, locale)
|
||||||
|
: undefined,
|
||||||
|
onSelect: () => {
|
||||||
|
router.push({
|
||||||
|
name: item.params.route.name,
|
||||||
|
params: {
|
||||||
|
...(item.params.route.params || {}),
|
||||||
|
locale: currentLang.value?.iso_code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const userItems = computed<DropdownMenuItem[][]>(() => [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Profile',
|
||||||
|
icon: 'i-lucide-user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Billing',
|
||||||
|
icon: 'i-lucide-credit-card'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'i-lucide-settings',
|
||||||
|
to: '/settings'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'Appearance',
|
||||||
|
icon: 'i-lucide-sun-moon',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: 'Light',
|
||||||
|
icon: 'i-lucide-sun',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: colorMode.value === 'light',
|
||||||
|
onUpdateChecked(checked: boolean) {
|
||||||
|
if (checked) {
|
||||||
|
colorMode.preference = 'light'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Dark',
|
||||||
|
icon: 'i-lucide-moon',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: colorMode.value === 'dark',
|
||||||
|
onUpdateChecked(checked: boolean) {
|
||||||
|
if (checked) {
|
||||||
|
colorMode.preference = 'dark'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: 'GitHub',
|
||||||
|
icon: 'i-simple-icons-github',
|
||||||
|
to: 'https://github.com/nuxt/ui',
|
||||||
|
target: '_blank'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Log out',
|
||||||
|
icon: 'i-lucide-log-out'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
defineShortcuts(extractShortcuts(teamsItems.value))
|
||||||
|
</script>
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
|
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import { initLangs, langs } from '@/router/langs'
|
import { initCountryCurrency, initLangs, langs } from '@/router/langs'
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
import { createI18n, type PathValue } from 'vue-i18n'
|
import { createI18n, type PathValue } from 'vue-i18n'
|
||||||
|
|
||||||
// const x =
|
// const x =
|
||||||
await initLangs()
|
await initLangs()
|
||||||
|
await initCountryCurrency()
|
||||||
export const i18ninstall = createI18n({
|
export const i18ninstall = createI18n({
|
||||||
legacy: false, // you must set `false`, to use Composition API
|
legacy: false, // you must set `false`, to use Composition API
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
|
|||||||
@@ -1,24 +1,42 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { currentLang, langs } from './langs'
|
import { currentLang, langs, switchLocalization } from './langs'
|
||||||
import { getSettings } from './settings'
|
import { getSettings } from './settings'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { getRoutes } from './menu'
|
import { getRoutes } from './menu'
|
||||||
|
|
||||||
function isAuthenticated(): boolean {
|
function isAuthenticated(): boolean {
|
||||||
if (typeof document === 'undefined') return false
|
if (typeof document === 'undefined') return false
|
||||||
return document.cookie.split('; ').some((c) => c === 'is_authenticated=1')
|
return document.cookie.split('; ').some((c) => c === 'is_authenticated=1')
|
||||||
}
|
}
|
||||||
|
await switchLocalization()
|
||||||
await getSettings()
|
await getSettings()
|
||||||
|
|
||||||
const routes = await getRoutes()
|
const routes = await getRoutes()
|
||||||
let newRoutes = []
|
let newRoutes = []
|
||||||
|
|
||||||
|
function getLayoutFromComponent(path: string) {
|
||||||
|
const emptyLayouts = [
|
||||||
|
'LoginView.vue',
|
||||||
|
'RegisterView.vue',
|
||||||
|
'PasswordRecoveryView.vue',
|
||||||
|
'VerifyEmailView.vue',
|
||||||
|
'ResetPasswordForm.vue'
|
||||||
|
]
|
||||||
|
return emptyLayouts.some((name) => path.includes(name)) ? 'empty' : 'default'
|
||||||
|
}
|
||||||
|
|
||||||
for (let r of routes) {
|
for (let r of routes) {
|
||||||
const component = () => import(/* @vite-ignore */ `..${r.component}`)
|
const component = () => import(/* @vite-ignore */ `..${r.component}`)
|
||||||
|
const parsedMeta = r.meta ? JSON.parse(r.meta) : {}
|
||||||
|
const layout = parsedMeta.layout ?? getLayoutFromComponent(r.component)
|
||||||
newRoutes.push({
|
newRoutes.push({
|
||||||
path: r.path,
|
path: r.path,
|
||||||
component,
|
component,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
meta: r.meta ? JSON.parse(r.meta) : {},
|
meta: {
|
||||||
|
...parsedMeta,
|
||||||
|
layout,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,14 +90,20 @@ async function setRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const importedComponent = (await importer()).default
|
const importedComponent = (await importer()).default
|
||||||
|
const parsedMeta = item.meta ? JSON.parse(item.meta) : {}
|
||||||
|
const layout = parsedMeta.layout ?? getLayoutFromComponent(item.component)
|
||||||
|
|
||||||
router.addRoute('locale', {
|
router.addRoute('locale', {
|
||||||
path: item.path,
|
path: item.path,
|
||||||
component: importedComponent,
|
component: importedComponent,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
meta: item.meta ? JSON.parse(item.meta) : {}
|
meta: {
|
||||||
|
...parsedMeta,
|
||||||
|
layout,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// await router.replace(router.currentRoute.value.fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
await setRoutes()
|
await setRoutes()
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { useCookie } from "@/composable/useCookie"
|
import { useCookie } from "@/composable/useCookie"
|
||||||
import { useFetchJson } from "@/composable/useFetchJson"
|
import { useFetchJson } from "@/composable/useFetchJson"
|
||||||
import type { Language } from "@/types"
|
import type { Country, Language } from "@/types"
|
||||||
import { reactive, ref } from "vue"
|
import { reactive, ref } from "vue"
|
||||||
|
|
||||||
export const langs = reactive([] as Language[])
|
export const langs = reactive([] as Language[])
|
||||||
export const currentLang = ref<Language>()
|
export const currentLang = ref<Language>()
|
||||||
|
|
||||||
const deflang = ref<Language>()
|
export const countries = reactive([] as Country[])
|
||||||
const cookie = useCookie()
|
export const currentCountry = ref<Country>()
|
||||||
// Get available language codes for route matching
|
|
||||||
// export const availableLocales = computed(() => langs.map((l) => l.lang_code))
|
const defLang = ref<Language>()
|
||||||
|
const defCountry = ref<Country>()
|
||||||
|
const cookie = useCookie()
|
||||||
|
|
||||||
// Initialize languages from API
|
|
||||||
export async function initLangs() {
|
export async function initLangs() {
|
||||||
try {
|
try {
|
||||||
const { items } = await useFetchJson<Language[]>('/api/v1/langs')
|
const { items } = await useFetchJson<Language[]>('/api/v1/langs')
|
||||||
@@ -22,9 +23,36 @@ export async function initLangs() {
|
|||||||
if (cc) {
|
if (cc) {
|
||||||
idfromcookie = langs.find((x) => x.id == parseInt(cc))
|
idfromcookie = langs.find((x) => x.id == parseInt(cc))
|
||||||
}
|
}
|
||||||
deflang.value = items.find((x) => x.is_default == true)
|
defLang.value = items.find((x) => x.is_default == true)
|
||||||
currentLang.value = idfromcookie ?? deflang.value
|
currentLang.value = idfromcookie ?? defLang.value
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch languages:', error)
|
console.error('Failed to fetch languages:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initCountryCurrency() {
|
||||||
|
try {
|
||||||
|
const { items } = await useFetchJson<Country[]>('/api/v1/restricted/langs-and-countries/get-countries')
|
||||||
|
countries.push(...items)
|
||||||
|
|
||||||
|
let idfromcookie = null
|
||||||
|
const cc = cookie.getCookie('country_id')
|
||||||
|
if (cc) {
|
||||||
|
idfromcookie = countries.find((x) => x.id == parseInt(cc))
|
||||||
|
}
|
||||||
|
defCountry.value = items.find((x) => x.id === defLang.value?.id)
|
||||||
|
currentCountry.value = idfromcookie ?? defCountry.value
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch languages:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function switchLocalization() {
|
||||||
|
try {
|
||||||
|
await useFetchJson('/api/v1/public/auth/update-choice', {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
|
|
||||||
export interface AddressFormData {
|
|
||||||
street: string
|
|
||||||
zipCode: string
|
|
||||||
city: string
|
|
||||||
country: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Address {
|
|
||||||
id: number
|
|
||||||
street: string
|
|
||||||
zipCode: string
|
|
||||||
city: string
|
|
||||||
country: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAddressStore = defineStore('address', () => {
|
|
||||||
const addresses = ref<Address[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
|
|
||||||
const currentPage = ref(1)
|
|
||||||
const pageSize = 20
|
|
||||||
|
|
||||||
const searchQuery = ref('')
|
|
||||||
|
|
||||||
function initMockData() {
|
|
||||||
addresses.value = [
|
|
||||||
{ id: 1, street: 'Main Street 123', zipCode: '10-001', city: 'New York', country: 'United States' },
|
|
||||||
{ id: 2, street: 'Oak Avenue 123', zipCode: '90-001', city: 'Los Angeles', country: 'United States' },
|
|
||||||
{ id: 3, street: 'Pine Road 123', zipCode: '60-601', city: 'Chicago', country: 'United States' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredAddresses = computed(() => {
|
|
||||||
if (!searchQuery.value) return addresses.value
|
|
||||||
|
|
||||||
const query = searchQuery.value.toLowerCase()
|
|
||||||
|
|
||||||
return addresses.value.filter(addr =>
|
|
||||||
addr.street.toLowerCase().includes(query) ||
|
|
||||||
addr.city.toLowerCase().includes(query) ||
|
|
||||||
addr.country.toLowerCase().includes(query) ||
|
|
||||||
addr.zipCode.toLowerCase().includes(query)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalItems = computed(() => filteredAddresses.value.length)
|
|
||||||
const totalPages = computed(() => Math.ceil(totalItems.value / pageSize))
|
|
||||||
|
|
||||||
const paginatedAddresses = computed(() => {
|
|
||||||
const start = (currentPage.value - 1) * pageSize
|
|
||||||
return filteredAddresses.value.slice(start, start + pageSize)
|
|
||||||
})
|
|
||||||
|
|
||||||
function getAddressById(id: number) {
|
|
||||||
return addresses.value.find(addr => addr.id === id)
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalize(data: AddressFormData): AddressFormData {
|
|
||||||
return {
|
|
||||||
street: data.street.trim(),
|
|
||||||
zipCode: data.zipCode.trim(),
|
|
||||||
city: data.city.trim(),
|
|
||||||
country: data.country.trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateId(): number {
|
|
||||||
return Math.max(0, ...addresses.value.map(a => a.id)) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function addAddress(formData: AddressFormData): Address {
|
|
||||||
const newAddress: Address = {
|
|
||||||
id: generateId(),
|
|
||||||
...normalize(formData)
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses.value.unshift(newAddress)
|
|
||||||
resetPagination()
|
|
||||||
|
|
||||||
return newAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAddress(id: number, formData: AddressFormData): boolean {
|
|
||||||
const index = addresses.value.findIndex(a => a.id === id)
|
|
||||||
if (index === -1) return false
|
|
||||||
|
|
||||||
const existing = addresses.value[index]
|
|
||||||
if (!existing) return false
|
|
||||||
|
|
||||||
addresses.value[index] = {
|
|
||||||
id: existing.id,
|
|
||||||
...normalize(formData)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
function deleteAddress(id: number): boolean {
|
|
||||||
const index = addresses.value.findIndex(a => a.id === id)
|
|
||||||
if (index === -1) return false
|
|
||||||
|
|
||||||
addresses.value.splice(index, 1)
|
|
||||||
resetPagination()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPage(page: number) {
|
|
||||||
currentPage.value = page
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSearchQuery(query: string) {
|
|
||||||
searchQuery.value = query
|
|
||||||
currentPage.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetPagination() {
|
|
||||||
currentPage.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
initMockData()
|
|
||||||
|
|
||||||
return {
|
|
||||||
addresses,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
currentPage,
|
|
||||||
pageSize,
|
|
||||||
totalItems,
|
|
||||||
totalPages,
|
|
||||||
searchQuery,
|
|
||||||
filteredAddresses,
|
|
||||||
paginatedAddresses,
|
|
||||||
getAddressById,
|
|
||||||
addAddress,
|
|
||||||
updateAddress,
|
|
||||||
deleteAddress,
|
|
||||||
setPage,
|
|
||||||
setSearchQuery,
|
|
||||||
resetPagination
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import type { Address } from './address'
|
|
||||||
|
|
||||||
export interface CustomerData {
|
export interface CustomerData {
|
||||||
companyName: string
|
companyName: string
|
||||||
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
import type { ProductDescription } from '@/types/product'
|
import type { ProductDescription } from '@/types/product'
|
||||||
|
import { useSettingsStore } from './settings'
|
||||||
|
|
||||||
export interface Product {
|
export interface Product {
|
||||||
id: number
|
id: number
|
||||||
@@ -23,21 +24,21 @@ export interface ProductResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useProductStore = defineStore('product', () => {
|
export const useProductStore = defineStore('product', () => {
|
||||||
const productDescription = ref()
|
|
||||||
const currentProduct = ref<Product | null>(null)
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
const productDescription = ref()
|
||||||
|
const copyProductDescription = ref()
|
||||||
|
|
||||||
async function getProductDescription(langId = 1, productID: number) {
|
async function getProductDescription(langId: number | null, productID: number) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ProductDescription>(
|
const response = await useFetchJson<ProductDescription>(
|
||||||
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId}`
|
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId ? langId : settingStore.shopDefaultLanguage}`
|
||||||
)
|
)
|
||||||
productDescription.value = response.items
|
productDescription.value = structuredClone(response.items)
|
||||||
|
copyProductDescription.value = structuredClone(response.items)
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to load product description'
|
error.value = e instanceof Error ? e.message : 'Failed to load product description'
|
||||||
} finally {
|
} finally {
|
||||||
@@ -45,35 +46,13 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveProductDescription(productID?: number) {
|
const settingStore = useSettingsStore()
|
||||||
const id = productID || 1
|
async function translateProductDescription(productID: number, toLangId: number, model: string = 'Google') {
|
||||||
try {
|
|
||||||
const data = await useFetchJson(
|
|
||||||
`/api/v1/restricted/product-description/save-product-description?productID=${id}&productShopID=1&productLangID=1`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(
|
|
||||||
{
|
|
||||||
description: productDescription.value.description,
|
|
||||||
description_short: productDescription.value.description_short,
|
|
||||||
meta_description: productDescription.value.meta_description,
|
|
||||||
available_now: productDescription.value.available_now,
|
|
||||||
usage: productDescription.value.usage
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function translateProductDescription(productID: number, fromLangId: number, toLangId: number) {
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ProductDescription>(`/api/v1/restricted/product-description/translate-product-description?productID=${productID}&productShopID=1&productFromLangID=${fromLangId}&productToLangID=${toLangId}&model=OpenAI`)
|
const response = await useFetchJson<ProductDescription>(`/api/v1/restricted/product-translation/translate-product-description?productID=${productID}&productFromLangID=${settingStore.shopDefaultLanguage}&productToLangID=${toLangId}&model=${model}`)
|
||||||
productDescription.value = response.items
|
productDescription.value = response.items
|
||||||
return response.items
|
return response.items
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -84,18 +63,46 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearCurrentProduct() {
|
async function saveProductDescription(productID?: number, langId?: number | null) {
|
||||||
currentProduct.value = null
|
const id = productID || 1
|
||||||
|
const lang = langId || 1
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await useFetchJson(
|
||||||
|
`/api/v1/restricted/product-translation/save-product-description?productID=${id}&productLangID=${lang}`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: productDescription.value?.name || '',
|
||||||
|
description: productDescription.value?.description || '',
|
||||||
|
description_short: productDescription.value?.description_short || '',
|
||||||
|
meta_title: productDescription.value?.meta_title || '',
|
||||||
|
meta_description: productDescription.value?.meta_description || '',
|
||||||
|
available_now: productDescription.value?.available_now || '',
|
||||||
|
available_later: productDescription.value?.available_later || '',
|
||||||
|
usage: productDescription.value?.usage || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await getProductDescription(langId, productID)
|
||||||
|
return data
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
productDescription,
|
productDescription,
|
||||||
currentProduct,
|
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
getProductDescription,
|
copyProductDescription,
|
||||||
clearCurrentProduct,
|
|
||||||
saveProductDescription,
|
|
||||||
translateProductDescription,
|
translateProductDescription,
|
||||||
|
getProductDescription,
|
||||||
|
saveProductDescription
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
21
bo/src/stores/admin/settings.ts
Normal file
21
bo/src/stores/admin/settings.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import type { Resp } from '@/types'
|
||||||
|
import type { Settings } from '@/types/settings'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
export const useSettingsStore = defineStore('settings', () => {
|
||||||
|
const settings = ref<Settings | null>(null)
|
||||||
|
const loaded = ref(false)
|
||||||
|
const shopDefaultLanguage = computed(() => settings.value?.app?.shop_default_language ?? 1)
|
||||||
|
|
||||||
|
async function getSettings(): Promise<Settings | null> {
|
||||||
|
if (loaded.value && settings.value) return settings.value
|
||||||
|
const resp = await useFetchJson<Settings>('/api/v1/settings')
|
||||||
|
settings.value = resp.items
|
||||||
|
loaded.value = true
|
||||||
|
return resp.items
|
||||||
|
}
|
||||||
|
|
||||||
|
return { settings, loaded, shopDefaultLanguage, getSettings }
|
||||||
|
})
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
|
|
||||||
export interface CartItem {
|
|
||||||
id: number
|
|
||||||
productId: number
|
|
||||||
name: string
|
|
||||||
image: string
|
|
||||||
price: number
|
|
||||||
quantity: number
|
|
||||||
product_number: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeliveryMethod {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
price: number
|
|
||||||
description: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCartStore = defineStore('cart', () => {
|
|
||||||
const items = ref<CartItem[]>([])
|
|
||||||
const selectedAddressId = ref<number | null>(null)
|
|
||||||
const selectedDeliveryMethodId = ref<number | null>(null)
|
|
||||||
const shippingCost = ref(0)
|
|
||||||
const vatRate = ref(0.23) // 23% VAT
|
|
||||||
const currentPage = ref(1)
|
|
||||||
const deliveryMethods = ref<DeliveryMethod[]>([
|
|
||||||
{ id: 1, name: 'Standard Delivery', price: 0, description: '5-7 business days' },
|
|
||||||
{ id: 2, name: 'Express Delivery', price: 15, description: '2-3 business days' },
|
|
||||||
{ id: 3, name: 'Priority Delivery', price: 30, description: 'Next business day' }
|
|
||||||
])
|
|
||||||
|
|
||||||
function initMockData() {
|
|
||||||
items.value = [
|
|
||||||
{ id: 1, productId: 101, name: 'Premium Widget Pro', product_number: 'NC209/7000', image: '/img/product-1.jpg', price: 129.99, quantity: 2 },
|
|
||||||
{ id: 2, productId: 102, name: 'Ultra Gadget X', product_number: 'NC234/6453', image: '/img/product-2.jpg', price: 89.50, quantity: 1 },
|
|
||||||
{ id: 3, productId: 103, name: 'Mega Tool Set', product_number: 'NC324/9030', image: '/img/product-3.jpg', price: 249.00, quantity: 3 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const productsTotal = computed(() => {
|
|
||||||
return items.value.reduce((sum, item) => sum + (item.price * item.quantity), 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const vatAmount = computed(() => {
|
|
||||||
return productsTotal.value * vatRate.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const orderTotal = computed(() => {
|
|
||||||
return productsTotal.value + shippingCost.value + vatAmount.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const itemCount = computed(() => {
|
|
||||||
return items.value.reduce((sum, item) => sum + item.quantity, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
function updateQuantity(itemId: number, quantity: number) {
|
|
||||||
const item = items.value.find(i => i.id === itemId)
|
|
||||||
if (item) {
|
|
||||||
if (quantity <= 0) {
|
|
||||||
removeItem(itemId)
|
|
||||||
} else {
|
|
||||||
item.quantity = quantity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteProduct(id: number): boolean {
|
|
||||||
const index = items.value.findIndex(a => a.id === id)
|
|
||||||
if (index === -1) return false
|
|
||||||
|
|
||||||
items.value.splice(index, 1)
|
|
||||||
resetProductPagination()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetProductPagination() {
|
|
||||||
currentPage.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeItem(itemId: number) {
|
|
||||||
const index = items.value.findIndex(i => i.id === itemId)
|
|
||||||
if (index !== -1) {
|
|
||||||
items.value.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearCart() {
|
|
||||||
items.value = []
|
|
||||||
selectedAddressId.value = null
|
|
||||||
selectedDeliveryMethodId.value = null
|
|
||||||
shippingCost.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSelectedAddress(addressId: number | null) {
|
|
||||||
selectedAddressId.value = addressId
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDeliveryMethod(methodId: number) {
|
|
||||||
selectedDeliveryMethodId.value = methodId
|
|
||||||
const method = deliveryMethods.value.find(m => m.id === methodId)
|
|
||||||
if (method) {
|
|
||||||
shippingCost.value = method.price
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initMockData()
|
|
||||||
|
|
||||||
return {
|
|
||||||
items,
|
|
||||||
selectedAddressId,
|
|
||||||
selectedDeliveryMethodId,
|
|
||||||
shippingCost,
|
|
||||||
vatRate,
|
|
||||||
deliveryMethods,
|
|
||||||
productsTotal,
|
|
||||||
vatAmount,
|
|
||||||
orderTotal,
|
|
||||||
itemCount,
|
|
||||||
deleteProduct,
|
|
||||||
updateQuantity,
|
|
||||||
removeItem,
|
|
||||||
clearCart,
|
|
||||||
setSelectedAddress,
|
|
||||||
setDeliveryMethod
|
|
||||||
}
|
|
||||||
})
|
|
||||||
58
bo/src/stores/customer/address.ts
Normal file
58
bo/src/stores/customer/address.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
id: number
|
||||||
|
country_id: number
|
||||||
|
address_unparsed: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAddressStore = defineStore('address', () => {
|
||||||
|
const addresses = ref<Address[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
async function fetchAddresses() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
try {
|
||||||
|
const res = await useFetchJson<Address[]>('/api/v1/restricted/addresses/retrieve-addresses')
|
||||||
|
addresses.value = res.items ?? []
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to load addresses'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAddress(id: number) {
|
||||||
|
await useFetchJson(`/api/v1/restricted/addresses/delete-address?address_id=${id}`, { method: 'DELETE' })
|
||||||
|
addresses.value = addresses.value.filter((a) => a.id !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTemplate(countryId: number): Promise<Record<string, string>> {
|
||||||
|
const res = await useFetchJson<Record<string, string>>(
|
||||||
|
`/api/v1/restricted/addresses/get-template?country_id=${countryId}`
|
||||||
|
)
|
||||||
|
return res.items ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAddress(countryId: number, data: Record<string, string>) {
|
||||||
|
await useFetchJson(`/api/v1/restricted/addresses/add-new-address?country_id=${countryId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
await fetchAddresses()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateAddress(id: number, countryId: number, data: Record<string, string>) {
|
||||||
|
await useFetchJson(`/api/v1/restricted/addresses/modify-address?country_id=${countryId}&address_id=${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
await fetchAddresses()
|
||||||
|
}
|
||||||
|
|
||||||
|
return { addresses, loading, error, fetchAddresses, deleteAddress, getTemplate, createAddress, updateAddress }
|
||||||
|
})
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import { useUserStore } from '../user'
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string
|
id: string
|
||||||
@@ -41,6 +42,8 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
_isAuthenticated.value = readIsAuthenticatedCookie()
|
_isAuthenticated.value = readIsAuthenticatedCookie()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const auth = useAuthStore()
|
||||||
|
// const userStore = useUserStore()
|
||||||
async function login(email: string, password: string) {
|
async function login(email: string, password: string) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
@@ -60,6 +63,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
|
|
||||||
user.value = response.user
|
user.value = response.user
|
||||||
_syncAuthState()
|
_syncAuthState()
|
||||||
|
// await userStore.getUser()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
115
bo/src/stores/customer/cart.ts
Normal file
115
bo/src/stores/customer/cart.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
export interface Cart {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
items: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
id: number
|
||||||
|
country_id: number
|
||||||
|
customer_id: number
|
||||||
|
address_info: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddressTemplate = Record<string, string>
|
||||||
|
|
||||||
|
export const useCartStore = defineStore('cart', () => {
|
||||||
|
const carts = ref<Cart[]>([])
|
||||||
|
const activeCartId = ref<number | null>(null)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
async function fetchCarts() {
|
||||||
|
try {
|
||||||
|
const res = await useFetchJson<ApiResponse>(
|
||||||
|
`/api/v1/restricted/carts/retrieve-carts-info`
|
||||||
|
)
|
||||||
|
|
||||||
|
carts.value = res.items
|
||||||
|
} catch (e: any) {
|
||||||
|
error.value = e?.message ?? 'Error loading carts'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addNewCart(name: string) {
|
||||||
|
try {
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
const url = `/api/v1/restricted/carts/add-new-cart`
|
||||||
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
|
|
||||||
|
const newCart: Cart = {
|
||||||
|
id: response.items.cart_id,
|
||||||
|
name: response.items.name,
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
|
||||||
|
carts.value.push(newCart)
|
||||||
|
activeCartId.value = newCart.id
|
||||||
|
|
||||||
|
return newCart
|
||||||
|
} catch (e: any) {
|
||||||
|
error.value = e?.message ?? 'Error creating cart'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const amount = ref<number>(1);
|
||||||
|
const errorMessage = ref('');
|
||||||
|
|
||||||
|
async function addProduct(product_id: number, count: number) {
|
||||||
|
if (!activeCartId.value) {
|
||||||
|
errorMessage.value = 'No active cart selected'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await useFetchJson<ApiResponse>(
|
||||||
|
`/api/v1/restricted/carts/add-product-to-cart?cart_id=${activeCartId.value}&product_id=${product_id}&amount=${count}`
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('fsdfsdfdsfdsfs', res)
|
||||||
|
} catch (e: any) {
|
||||||
|
errorMessage.value = e?.message ?? 'Error adding product'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveCart(id: number | null) {
|
||||||
|
activeCartId.value = id
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
localStorage.setItem('activeCartId', String(id))
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('activeCartId')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCart() {
|
||||||
|
const saved = localStorage.getItem('activeCartId')
|
||||||
|
|
||||||
|
if (saved) {
|
||||||
|
activeCartId.value = Number(saved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeCart = computed(() => {
|
||||||
|
return carts.value.find(c => c.cart_id === activeCartId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
carts,
|
||||||
|
activeCartId,
|
||||||
|
error,
|
||||||
|
errorMessage,
|
||||||
|
activeCart,
|
||||||
|
setActiveCart,
|
||||||
|
addProduct,
|
||||||
|
fetchCarts,
|
||||||
|
addNewCart,
|
||||||
|
initCart,
|
||||||
|
}
|
||||||
|
})
|
||||||
101
bo/src/stores/customer/customer-product.ts
Normal file
101
bo/src/stores/customer/customer-product.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
id: number
|
||||||
|
image: string
|
||||||
|
name: string
|
||||||
|
productDetails?: string
|
||||||
|
product_id: number
|
||||||
|
is_favorite?: boolean
|
||||||
|
quantity: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductResponse {
|
||||||
|
items: Product[]
|
||||||
|
items_count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse {
|
||||||
|
message: string
|
||||||
|
items: Product[]
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCustomerProductStore = defineStore('customer-product', () => {
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const route = useRoute()
|
||||||
|
const productsList = ref<Product[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const perPage = ref(15)
|
||||||
|
|
||||||
|
async function fetchProductList() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
|
Object.entries(route.query).forEach(([key, value]) => {
|
||||||
|
if (value) params.append(key, String(value))
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = `/api/v1/restricted/product/list?${params}`
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
|
productsList.value = response.items || []
|
||||||
|
total.value = response.count || 0
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFavoriteState(product_id: number, value: boolean) {
|
||||||
|
const p = productsList.value.find(p => p.product_id === product_id)
|
||||||
|
if (p) p.is_favorite = value
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleFavorite(product: Product) {
|
||||||
|
const productId = product.product_id
|
||||||
|
const isFavorite = product.is_favorite
|
||||||
|
|
||||||
|
const url = `/api/v1/restricted/product/favorite/${productId}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isFavorite) {
|
||||||
|
await useFetchJson(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: productId
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await useFetchJson(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
product.is_favorite = !product.is_favorite
|
||||||
|
} catch (e: unknown) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to update favorite'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchProductList,
|
||||||
|
toggleFavorite,
|
||||||
|
updateFavoriteState,
|
||||||
|
productsList,
|
||||||
|
total,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
perPage
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { useFetchJson } from '@/composable/useFetchJson'
|
|
||||||
import type { Resp } from '@/types'
|
|
||||||
import type { Settings } from '@/types/settings'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useSettingsStore = defineStore('settings', () => {
|
|
||||||
async function getSettings() {
|
|
||||||
const { items } = await useFetchJson<Resp<Settings>>('/api/v1/settings',)
|
|
||||||
console.log(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSettings()
|
|
||||||
return {}
|
|
||||||
})
|
|
||||||
32
bo/src/stores/user.ts
Normal file
32
bo/src/stores/user.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { User } from '@/types/user'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useFetchJson } from '@/composable/useFetchJson'
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', () => {
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const user = ref<User | null>(null)
|
||||||
|
|
||||||
|
async function getUser() {
|
||||||
|
error.value = null
|
||||||
|
try {
|
||||||
|
const data = await useFetchJson<User>(`/api/v1/restricted/customer`)
|
||||||
|
console.log('getUser API response:', data)
|
||||||
|
|
||||||
|
const response: User = (data as any).items ?? data
|
||||||
|
console.log('User response:', response)
|
||||||
|
user.value = response
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (err: any) {
|
||||||
|
error.value = err?.message ?? 'Unknown error'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
user,
|
||||||
|
getUser
|
||||||
|
}
|
||||||
|
})
|
||||||
17
bo/src/types/lang.d.ts
vendored
17
bo/src/types/lang.d.ts
vendored
@@ -12,3 +12,20 @@ export interface Language {
|
|||||||
active: boolean
|
active: boolean
|
||||||
flag: string
|
flag: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Country {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
flag: string
|
||||||
|
currency_id: string
|
||||||
|
ps_currency: {
|
||||||
|
id_currency: string
|
||||||
|
name: string
|
||||||
|
iso_code: string
|
||||||
|
numeric_iso_code: string
|
||||||
|
precision: number
|
||||||
|
conversion_rate: number
|
||||||
|
deleted: boolean
|
||||||
|
active: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
3
bo/src/types/product.d.ts
vendored
3
bo/src/types/product.d.ts
vendored
@@ -5,7 +5,10 @@ export interface ProductDescription {
|
|||||||
description_short: string
|
description_short: string
|
||||||
meta_description: string
|
meta_description: string
|
||||||
available_now: string
|
available_now: string
|
||||||
|
delivery_in_stock?: string
|
||||||
usage: string
|
usage: string
|
||||||
|
image_link: string
|
||||||
|
link_rewrite: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Product {
|
export interface Product {
|
||||||
|
|||||||
1
bo/src/types/settings.d.ts
vendored
1
bo/src/types/settings.d.ts
vendored
@@ -12,6 +12,7 @@ export interface App {
|
|||||||
environment: string
|
environment: string
|
||||||
base_url: string
|
base_url: string
|
||||||
password_regex: string
|
password_regex: string
|
||||||
|
shop_default_language: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Server {
|
export interface Server {
|
||||||
|
|||||||
23
bo/src/types/user.d.ts
vendored
Normal file
23
bo/src/types/user.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
name: string
|
||||||
|
first_name?: string
|
||||||
|
last_name?: string
|
||||||
|
company_name?: string
|
||||||
|
company_email?: string
|
||||||
|
company_address?: Address
|
||||||
|
billing_address?: Address
|
||||||
|
regon?: string
|
||||||
|
nip?: string
|
||||||
|
vat?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface Customer {
|
||||||
|
user_id: number
|
||||||
|
email: string
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<component :is="Default || 'div'">
|
|
||||||
<div class="container mt-24">
|
|
||||||
<div class="row">
|
|
||||||
<!-- <div class="col-12">
|
|
||||||
<h2 class="text-2xl">Category ID: {{ $route.params.category_id }}</h2>
|
|
||||||
<div v-for="(p, i) in products" :key="i">
|
|
||||||
<p>
|
|
||||||
<span class="border-b-1 bg-red-100 px-4">{{ p.name }}</span>
|
|
||||||
<span class="border-b-1 bg-red-100 px-4">{{ p.price }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// import { useRoute } from 'vue-router';
|
|
||||||
import Default from '@/layouts/default.vue';
|
|
||||||
import { useCategoryStore } from '@/stores/category';
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
// const route = useRoute()
|
|
||||||
// console.log(route);
|
|
||||||
|
|
||||||
const categoryStore = useCategoryStore()
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const products = ref([])
|
|
||||||
|
|
||||||
watch(() => route.params, async (n) => {
|
|
||||||
categoryStore.setCategoryID(parseInt(n.category_id as string))
|
|
||||||
const res = await categoryStore.getCategoryProducts()
|
|
||||||
// products.value = res
|
|
||||||
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
|
||||||
home View
|
home View
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Default from '@/layouts/default.vue';
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, defineAsyncComponent, ref } from 'vue'
|
import { computed, defineAsyncComponent, ref } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
@@ -107,7 +107,7 @@ const PrivacyComponent = computed(() =>
|
|||||||
|
|
||||||
<div class="flex items-center justify-between w-full dark:text-white text-black">
|
<div class="flex items-center justify-between w-full dark:text-white text-black">
|
||||||
<button variant="link" size="sm" @click="goToPasswordRecovery"
|
<button variant="link" size="sm" @click="goToPasswordRecovery"
|
||||||
class="text-[15px] w-full flex justify-end text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">
|
class="text-[15px] w-full flex justify-end text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">
|
||||||
{{ $t('general.forgot_password') }}?
|
{{ $t('general.forgot_password') }}?
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,18 +145,18 @@ const PrivacyComponent = computed(() =>
|
|||||||
<p class="dark:text-white text-black">
|
<p class="dark:text-white text-black">
|
||||||
{{ $t('general.dont_have_an_account') }}?
|
{{ $t('general.dont_have_an_account') }}?
|
||||||
<button variant="link" size="sm"
|
<button variant="link" size="sm"
|
||||||
class="text-[15px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer"
|
class="text-[15px] text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer"
|
||||||
@click="goToRegister">{{ $t('general.create_account_now') }}</button>
|
@click="goToRegister">{{ $t('general.create_account_now') }}</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-8 text-center text-xs dark:text-white text-black">
|
<p class="mt-8 text-center text-xs dark:text-white text-black">
|
||||||
{{ $t('general.by_signing_in_you_agree_to_our') }}
|
{{ $t('general.by_signing_in_you_agree_to_our') }}
|
||||||
<span @click="showTherms = !showTherms"
|
<span @click="showTherms = !showTherms"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">{{
|
||||||
$t('general.terms_of_service') }}</span>
|
$t('general.terms_of_service') }}</span>
|
||||||
{{ $t('general.and') }}
|
{{ $t('general.and') }}
|
||||||
<span @click="showPrivacy = !showPrivacy"
|
<span @click="showPrivacy = !showPrivacy"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">{{
|
||||||
$t('general.privacy_policy') }}</span>
|
$t('general.privacy_policy') }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
@@ -92,13 +92,13 @@ function validate(): FormError[] {
|
|||||||
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
||||||
@click="goToLogin">
|
@click="goToLogin">
|
||||||
<UIcon name="mingcute:arrow-left-line"
|
<UIcon name="mingcute:arrow-left-line"
|
||||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[16px]" />
|
class="text-(--text-sky-light) dark:text-(--text-sky-dark) text-[16px]" />
|
||||||
{{ $t('general.back_to_sign_in') }}
|
{{ $t('general.back_to_sign_in') }}
|
||||||
</button>
|
</button>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ $t('general.dont_have_an_account') }}
|
{{ $t('general.dont_have_an_account') }}
|
||||||
<button variant="link" size="sm" @click="goToRegister"
|
<button variant="link" size="sm" @click="goToRegister"
|
||||||
class=" text-[15px] text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">{{
|
class=" text-[15px] text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">{{
|
||||||
$t('general.create_account_now') }}
|
$t('general.create_account_now') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -83,12 +83,12 @@
|
|||||||
<span class="dark:text-white text-black">
|
<span class="dark:text-white text-black">
|
||||||
{{ $t('general.i_agree_to_the') }}
|
{{ $t('general.i_agree_to_the') }}
|
||||||
<span @click="showTherms = !showTherms"
|
<span @click="showTherms = !showTherms"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark)">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark)">{{
|
||||||
$t('general.terms_of_service')
|
$t('general.terms_of_service')
|
||||||
}}</span>
|
}}</span>
|
||||||
{{ $t('general.and') }}
|
{{ $t('general.and') }}
|
||||||
<span @click="showPrivacy = !showPrivacy"
|
<span @click="showPrivacy = !showPrivacy"
|
||||||
class="cursor-pointer underline text-(--accent-blue-light) dark:text-(--accent-blue-dark)">{{
|
class="cursor-pointer underline text-(--text-sky-light) dark:text-(--text-sky-dark)">{{
|
||||||
$t('general.privacy_policy')
|
$t('general.privacy_policy')
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, defineAsyncComponent } from 'vue'
|
import { ref, computed, defineAsyncComponent } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import {
|
|||||||
LinearScale,
|
LinearScale,
|
||||||
} from 'chart.js'
|
} from 'chart.js'
|
||||||
import { getYears, getQuarters, getIssues, type QuarterData, type IssueTimeSummary } from '@/composable/useRepoApi'
|
import { getYears, getQuarters, getIssues, type QuarterData, type IssueTimeSummary } from '@/composable/useRepoApi'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
import type { TableColumn } from '@nuxt/ui'
|
import type { TableColumn } from '@nuxt/ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Default from '@/layouts/default.vue'
|
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
||||||
|
|
||||||
@@ -182,8 +181,7 @@ const columns = computed<TableColumn<IssueTimeSummary>[]>(() => [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<div class="">
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="p-6 bg-(--main-light) dark:bg-(--black) font-sans">
|
<div class="p-6 bg-(--main-light) dark:bg-(--black) font-sans">
|
||||||
<h1 class="text-2xl font-bold mb-6 text-black dark:text-white">{{ $t('repo_chart.repository_work_chart') }}
|
<h1 class="text-2xl font-bold mb-6 text-black dark:text-white">{{ $t('repo_chart.repository_work_chart') }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -256,5 +254,4 @@ const columns = computed<TableColumn<IssueTimeSummary>[]>(() => [
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</template>
|
||||||
</template>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/customer/auth'
|
||||||
import { useValidation } from '@/composable/useValidation'
|
import { useValidation } from '@/composable/useValidation'
|
||||||
import type { FormError } from '@nuxt/ui'
|
import type { FormError } from '@nuxt/ui'
|
||||||
import { i18n } from '@/plugins/02_i18n'
|
import { i18n } from '@/plugins/02_i18n'
|
||||||
@@ -120,7 +120,7 @@ function validate(): FormError[] {
|
|||||||
|
|
||||||
<div class="text-center border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
<div class="text-center border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
||||||
<button color="neutral" variant="ghost" @click="goToLogin"
|
<button color="neutral" variant="ghost" @click="goToLogin"
|
||||||
class="text-[15px] flex items-center gap-2 text-(--accent-blue-light) dark:text-(--accent-blue-dark) cursor-pointer">
|
class="text-[15px] flex items-center gap-2 text-(--text-sky-light) dark:text-(--text-sky-dark) cursor-pointer">
|
||||||
<UIcon name="mingcute:arrow-left-line" />
|
<UIcon name="mingcute:arrow-left-line" />
|
||||||
{{ $t('general.back_to_sign_in') }}
|
{{ $t('general.back_to_sign_in') }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
11
bo/src/views/StorageView.vue
Normal file
11
bo/src/views/StorageView.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<Default>
|
||||||
|
<div class="h-full">
|
||||||
|
<StorageFileBrowser initial-path="dest/src" />
|
||||||
|
</div>
|
||||||
|
</Default>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import StorageFileBrowser from '@/components/customer/StorageFileBrowser.vue'
|
||||||
|
</script>
|
||||||
@@ -135,7 +135,7 @@ function goToLogin() {
|
|||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ $t('verify_email.already_registered') }}
|
{{ $t('verify_email.already_registered') }}
|
||||||
<button variant="link" size="sm" @click="goToLogin"
|
<button variant="link" size="sm" @click="goToLogin"
|
||||||
class="cursor-pointer text-(--accent-blue-light) dark:text-(--accent-blue-dark)"> {{
|
class="cursor-pointer text-(--text-sky-light) dark:text-(--text-sky-dark)"> {{
|
||||||
$t('general.sign_in')
|
$t('general.sign_in')
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
22
bruno/api_v1/customer/Set is_no_vat.yml
Normal file
22
bruno/api_v1/customer/Set is_no_vat.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: Set is_no_vat
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: PATCH
|
||||||
|
url: "{{bas_url}}/restricted/customer/no-vat"
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"customer_id":1,
|
||||||
|
"is_no_vat": false
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
@@ -5,7 +5,7 @@ info:
|
|||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: "{{bas_url}}/restricted/product/list?p=1&elems=30&reference=~NC100"
|
url: "{{bas_url}}/restricted/product/list?p=1&elems=30&reference=~NC100&is_new_eq=0&is_favorite_eq=false&is_oem_eq=FALSE"
|
||||||
params:
|
params:
|
||||||
- name: p
|
- name: p
|
||||||
value: "1"
|
value: "1"
|
||||||
@@ -27,11 +27,12 @@ http:
|
|||||||
- name: is_new_eq
|
- name: is_new_eq
|
||||||
value: "0"
|
value: "0"
|
||||||
type: query
|
type: query
|
||||||
disabled: true
|
|
||||||
- name: is_favorite_eq
|
- name: is_favorite_eq
|
||||||
value: "false"
|
value: "false"
|
||||||
type: query
|
type: query
|
||||||
disabled: true
|
- name: is_oem_eq
|
||||||
|
value: "FALSE"
|
||||||
|
type: query
|
||||||
body:
|
body:
|
||||||
type: json
|
type: json
|
||||||
data: ""
|
data: ""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: addresses
|
name: addresses
|
||||||
type: folder
|
type: folder
|
||||||
seq: 10
|
seq: 9
|
||||||
|
|
||||||
request:
|
request:
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ info:
|
|||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/carts/add-new-cart
|
url: http://localhost:3000/api/v1/restricted/carts/add-new-cart?name=carttt
|
||||||
|
params:
|
||||||
|
- name: name
|
||||||
|
value: carttt
|
||||||
|
type: query
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
info:
|
info:
|
||||||
name: add-product-to-cart (1)
|
name: add-product-to-cart (1)
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 2
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1
|
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1&set_amount=false
|
||||||
params:
|
params:
|
||||||
- name: cart_id
|
- name: cart_id
|
||||||
value: "1"
|
value: "1"
|
||||||
@@ -16,6 +16,9 @@ http:
|
|||||||
- name: amount
|
- name: amount
|
||||||
value: "1"
|
value: "1"
|
||||||
type: query
|
type: query
|
||||||
|
- name: set_amount
|
||||||
|
value: "false"
|
||||||
|
type: query
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
info:
|
info:
|
||||||
name: add-product-to-cart
|
name: add-product-to-cart
|
||||||
type: http
|
type: http
|
||||||
seq: 14
|
seq: 6
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=1
|
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&product_attribute_id=1115&amount=1&set_amount=true
|
||||||
params:
|
params:
|
||||||
- name: cart_id
|
- name: cart_id
|
||||||
value: "1"
|
value: "1"
|
||||||
@@ -19,6 +19,9 @@ http:
|
|||||||
- name: amount
|
- name: amount
|
||||||
value: "1"
|
value: "1"
|
||||||
type: query
|
type: query
|
||||||
|
- name: set_amount
|
||||||
|
value: "true"
|
||||||
|
type: query
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: change-cart-name
|
name: change-cart-name
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 3
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: retrieve-cart
|
name: retrieve-cart
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 4
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: retrieve-carts-info
|
name: retrieve-carts-info
|
||||||
type: http
|
type: http
|
||||||
seq: 1
|
seq: 5
|
||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
|
|||||||
1
bruno/b2b_daniel/environments/dev.yml
Normal file
1
bruno/b2b_daniel/environments/dev.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
name: dev
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
info:
|
|
||||||
name: list
|
|
||||||
type: folder
|
|
||||||
seq: 3
|
|
||||||
|
|
||||||
request:
|
|
||||||
auth: inherit
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
info:
|
|
||||||
name: list-products
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
|
|
||||||
http:
|
|
||||||
method: GET
|
|
||||||
url: http://localhost:3000/api/v1/restricted/list/list-products?p=1&elems=10&target_user_id=2
|
|
||||||
params:
|
|
||||||
- name: p
|
|
||||||
value: "1"
|
|
||||||
type: query
|
|
||||||
- name: elems
|
|
||||||
value: "10"
|
|
||||||
type: query
|
|
||||||
- name: target_user_id
|
|
||||||
value: "2"
|
|
||||||
type: query
|
|
||||||
|
|
||||||
settings:
|
|
||||||
encodeUrl: true
|
|
||||||
timeout: 0
|
|
||||||
followRedirects: true
|
|
||||||
maxRedirects: 5
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
info:
|
|
||||||
name: list-users
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
|
|
||||||
http:
|
|
||||||
method: GET
|
|
||||||
url: http://localhost:3000/api/v1/restricted/list/list-users?p=1&elems=10
|
|
||||||
params:
|
|
||||||
- name: p
|
|
||||||
value: "1"
|
|
||||||
type: query
|
|
||||||
- name: elems
|
|
||||||
value: "10"
|
|
||||||
type: query
|
|
||||||
|
|
||||||
settings:
|
|
||||||
encodeUrl: true
|
|
||||||
timeout: 0
|
|
||||||
followRedirects: true
|
|
||||||
maxRedirects: 5
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: orders
|
name: orders
|
||||||
type: folder
|
type: folder
|
||||||
seq: 11
|
seq: 10
|
||||||
|
|
||||||
request:
|
request:
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: product-translation
|
name: product-translation
|
||||||
type: folder
|
type: folder
|
||||||
seq: 2
|
seq: 3
|
||||||
|
|
||||||
request:
|
request:
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
info:
|
info:
|
||||||
name: storage-old
|
name: storage-old
|
||||||
type: folder
|
type: folder
|
||||||
seq: 1
|
seq: 2
|
||||||
|
|
||||||
request:
|
request:
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user