Compare commits
17 Commits
routing
...
add_image_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7533a8deb | ||
| cf4d14a3cb | |||
| 30eb82ba53 | |||
| a2a2c35ab3 | |||
| 684f910090 | |||
| 5feaa9e15c | |||
|
|
04e2549a66 | ||
| fb4f7048ab | |||
|
|
a3f01eca7c | ||
|
|
1fa6206b75 | ||
|
|
fa89723eb6 | ||
|
|
8665c566ee | ||
| ec5ff123ac | |||
| 17317e778c | |||
| 94291ccc03 | |||
| 91c5de1f67 | |||
|
|
d0ce65c287 |
2
.env
2
.env
@@ -58,3 +58,5 @@ FILE_MAAL_PL_PASSWORD=1FnwqcEgIUjQHjt1
|
|||||||
|
|
||||||
IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta
|
IMAGE_PREFIX=https://www.naluconcept.com # remove prefix to serv them from same host as presta
|
||||||
CORS_ORGIN=https://www.naluconcept.com
|
CORS_ORGIN=https://www.naluconcept.com
|
||||||
|
|
||||||
|
DSN=root:Maal12345678@tcp(localhost:3306)/nalu
|
||||||
@@ -1127,21 +1127,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/restricted/menu/get-menu": {
|
"/api/v1/restricted/menu/get-category-tree": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Menu"],
|
"tags": ["Menu"],
|
||||||
"summary": "Get menu structure",
|
"summary": "Get category tree",
|
||||||
"description": "Returns the menu structure for the current language. Requires authentication.",
|
"description": "Returns the category tree rooted at the given category ID for the current language. Requires authentication.",
|
||||||
"operationId": "getMenu",
|
"operationId": "getCategoryTree",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"CookieAuth": [],
|
"CookieAuth": [],
|
||||||
"BearerAuth": []
|
"BearerAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "root_category_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Root category ID to build the tree from",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Menu retrieved successfully",
|
"description": "Category tree retrieved successfully",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -1151,7 +1162,73 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Invalid request",
|
"description": "Invalid request or root category not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Not authenticated",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/restricted/menu/get-breadcrumb": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Menu"],
|
||||||
|
"summary": "Get breadcrumb",
|
||||||
|
"description": "Returns the breadcrumb path from the root category to the specified category for the current language. Requires authentication.",
|
||||||
|
"operationId": "getBreadcrumb",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CookieAuth": [],
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "root_category_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Root category ID (breadcrumb starting point)",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Target category ID (breadcrumb destination)",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Breadcrumb retrieved successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request, category not found, or root never reached",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -1221,7 +1298,23 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ApiResponse"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/B2BTopMenu"
|
||||||
|
},
|
||||||
|
"description": "Root menu items with nested children"
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of root menu items"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1995,46 +2088,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MenuItem": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Menu item structure",
|
|
||||||
"properties": {
|
|
||||||
"category_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint",
|
|
||||||
"description": "Category ID"
|
|
||||||
},
|
|
||||||
"label": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Menu item label"
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"$ref": "#/components/schemas/MenuItemParams"
|
|
||||||
},
|
|
||||||
"children": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/MenuItem"
|
|
||||||
},
|
|
||||||
"description": "Child menu items"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"MenuItemParams": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"category_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "uint"
|
|
||||||
},
|
|
||||||
"link_rewrite": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"locale": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Route": {
|
"Route": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Application route",
|
"description": "Application route",
|
||||||
@@ -2338,6 +2391,58 @@
|
|||||||
"description": "Build date in RFC3339 format"
|
"description": "Build date in RFC3339 format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"CategoryInBreadcrumb": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A single item in a category breadcrumb path",
|
||||||
|
"properties": {
|
||||||
|
"category_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint",
|
||||||
|
"description": "Category ID"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Category name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"B2BTopMenu": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Top-level menu item for B2B back-office",
|
||||||
|
"properties": {
|
||||||
|
"menu_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Menu item ID"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Menu label as JSON (multilingual, e.g. {\"en\": \"Dashboard\", \"pl\": \"Panel\"})"
|
||||||
|
},
|
||||||
|
"parent_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Parent menu ID (null for root items)"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Menu item parameters as JSON"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Active status (1 = active, 0 = inactive)"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Sort position"
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/B2BTopMenu"
|
||||||
|
},
|
||||||
|
"description": "Child menu items"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {
|
"securitySchemes": {
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ package public
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
"git.ma-al.com/goc_daniel/b2b/app/delivery/middleware"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/authService"
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
@@ -345,9 +349,58 @@ func (h *AuthHandler) CompleteRegistration(c fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusCreated).JSON(response)
|
return c.Status(fiber.StatusCreated).JSON(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates JWT Tokens
|
// Updates JWT Tokens. Requires authentication and updates access token only
|
||||||
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
func (h *AuthHandler) UpdateJWTToken(c fiber.Ctx) error {
|
||||||
return h.authService.UpdateJWTToken(c)
|
userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
|
||||||
|
}
|
||||||
|
|
||||||
|
user := model.Customer{
|
||||||
|
ID: userLocals.UserID,
|
||||||
|
Email: userLocals.Email,
|
||||||
|
Role: userLocals.Role,
|
||||||
|
LangID: userLocals.LangID,
|
||||||
|
CountryID: userLocals.CountryID,
|
||||||
|
IsActive: userLocals.IsActive,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse language and country_id from query params
|
||||||
|
langIDStr := c.Query("lang_id")
|
||||||
|
|
||||||
|
if langIDStr != "" {
|
||||||
|
parsedID, err := strconv.ParseUint(langIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
|
||||||
|
}
|
||||||
|
user.LangID = uint(parsedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
countryIDStr := c.Query("country_id")
|
||||||
|
|
||||||
|
if countryIDStr != "" {
|
||||||
|
parsedID, err := strconv.ParseUint(countryIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
|
||||||
|
}
|
||||||
|
user.CountryID = uint(parsedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
newAccessToken, err := h.authService.UpdateJWTToken(&user)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
||||||
|
"error": responseErrors.GetErrorCode(c, err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not reset refresh token
|
||||||
|
h.setAuthCookies(c, newAccessToken, "")
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&fiber.Map{"token": newAccessToken}, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoogleLogin redirects the user to Google's OAuth2 consent page
|
// GoogleLogin redirects the user to Google's OAuth2 consent page
|
||||||
@@ -414,12 +467,12 @@ func (h *AuthHandler) GoogleCallback(c fiber.Ctx) error {
|
|||||||
|
|
||||||
// Redirect to the locale-prefixed charts page after successful Google login.
|
// Redirect to the locale-prefixed charts page after successful Google login.
|
||||||
// The user's preferred language is stored in the auth response; fall back to "en".
|
// The user's preferred language is stored in the auth response; fall back to "en".
|
||||||
lang, err := h.authService.GetLangISOCode(response.User.LangID)
|
lang_iso_code, err := h.authService.GetLangISOCode(response.User.LangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadLangID)).JSON(fiber.Map{
|
return c.Status(responseErrors.GetErrorStatus(err)).JSON(fiber.Map{
|
||||||
"error": responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID),
|
"error": responseErrors.GetErrorCode(c, err),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Redirect().To(h.config.App.BaseURL + "/" + lang)
|
return c.Redirect().To(h.config.App.BaseURL + "/" + lang_iso_code)
|
||||||
}
|
}
|
||||||
|
|||||||
98
app/delivery/web/api/restricted/list.go
Normal file
98
app/delivery/web/api/restricted/list.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package restricted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/service/listService"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListHandler handles endpoints that list various things (e.g. products or users)
|
||||||
|
type ListHandler struct {
|
||||||
|
listService *listService.ListService
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListHandler creates a new ListHandler instance
|
||||||
|
func NewListHandler() *ListHandler {
|
||||||
|
listService := listService.New()
|
||||||
|
return &ListHandler{
|
||||||
|
listService: listService,
|
||||||
|
config: config.Get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
|
handler := NewListHandler()
|
||||||
|
|
||||||
|
r.Get("/list-products", handler.ListProducts)
|
||||||
|
r.Get("/list-users", handler.ListUsers)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListHandler) ListProducts(c fiber.Ctx) error {
|
||||||
|
paging, filters, err := query_params.ParseFilters[model.Product](c, columnMappingListProducts)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
id_lang, ok := c.Locals("langID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
listing, err := h.listService.ListProducts(id_lang, paging, filters)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnMappingListProducts map[string]string = map[string]string{
|
||||||
|
"product_id": "ps.id_product",
|
||||||
|
"name": "pl.name",
|
||||||
|
"reference": "p.reference",
|
||||||
|
"category_name": "cl.name",
|
||||||
|
"category_id": "cp.id_category",
|
||||||
|
"quantity": "sa.quantity",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ListHandler) ListUsers(c fiber.Ctx) error {
|
||||||
|
paging, filters, err := query_params.ParseFilters[model.Customer](c, columnMappingListUsers)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
id_lang, ok := c.Locals("langID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
listing, err := h.listService.ListUsers(id_lang, paging, filters)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnMappingListUsers map[string]string = map[string]string{
|
||||||
|
"user_id": "users.id",
|
||||||
|
"email": "users.email",
|
||||||
|
"first_name": "users.first_name",
|
||||||
|
"second_name": "users.second_name",
|
||||||
|
"role": "users.role",
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package restricted
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/listProductsService"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/query_params"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
|
||||||
"github.com/gofiber/fiber/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListProductsHandler handles endpoints that receive, save and translate product descriptions.
|
|
||||||
type ListProductsHandler struct {
|
|
||||||
listProductsService *listProductsService.ListProductsService
|
|
||||||
config *config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewListProductsHandler creates a new ListProductsHandler instance
|
|
||||||
func NewListProductsHandler() *ListProductsHandler {
|
|
||||||
listProductsService := listProductsService.New()
|
|
||||||
return &ListProductsHandler{
|
|
||||||
listProductsService: listProductsService,
|
|
||||||
config: config.Get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListProductsHandlerRoutes(r fiber.Router) fiber.Router {
|
|
||||||
handler := NewListProductsHandler()
|
|
||||||
|
|
||||||
r.Get("/get-listing", handler.GetListing)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ListProductsHandler) GetListing(c fiber.Ctx) error {
|
|
||||||
paging, filters, err := query_params.ParseFilters[model.Product](c, columnMappingGetListing)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
id_lang, ok := c.Locals("langID").(uint)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
listing, err := h.listProductsService.GetListing(id_lang, paging, filters)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(response.Make(&listing.Items, int(listing.Count), i18n.T_(c, response.Message_OK)))
|
|
||||||
}
|
|
||||||
|
|
||||||
var columnMappingGetListing map[string]string = map[string]string{
|
|
||||||
"product_id": "ps.id_product",
|
|
||||||
"name": "pl.name",
|
|
||||||
"reference": "p.reference",
|
|
||||||
"category_name": "cl.name",
|
|
||||||
"category_id": "cp.id_category",
|
|
||||||
"quantity": "sa.quantity",
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package restricted
|
package restricted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||||
@@ -23,25 +25,64 @@ func NewMenuHandler() *MenuHandler {
|
|||||||
func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
||||||
handler := NewMenuHandler()
|
handler := NewMenuHandler()
|
||||||
|
|
||||||
r.Get("/get-menu", handler.GetMenu)
|
r.Get("/get-category-tree", handler.GetCategoryTree)
|
||||||
|
r.Get("/get-breadcrumb", handler.GetBreadcrumb)
|
||||||
r.Get("/get-top-menu", handler.GetTopMenu)
|
r.Get("/get-top-menu", handler.GetTopMenu)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetMenu(c fiber.Ctx) error {
|
func (h *MenuHandler) GetCategoryTree(c fiber.Ctx) error {
|
||||||
lang_id, ok := c.Locals("langID").(uint)
|
lang_id, ok := c.Locals("langID").(uint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
}
|
}
|
||||||
menu, err := h.menuService.GetMenu(lang_id)
|
|
||||||
|
root_category_id_attribute := c.Query("root_category_id")
|
||||||
|
root_category_id, err := strconv.Atoi(root_category_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
category_tree, err := h.menuService.GetCategoryTree(uint(root_category_id), lang_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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
return c.JSON(response.Make(&category_tree, 0, i18n.T_(c, response.Message_OK)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MenuHandler) GetBreadcrumb(c fiber.Ctx) error {
|
||||||
|
lang_id, ok := c.Locals("langID").(uint)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
root_category_id_attribute := c.Query("root_category_id")
|
||||||
|
root_category_id, err := strconv.Atoi(root_category_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
category_id_attribute := c.Query("category_id")
|
||||||
|
category_id, err := strconv.Atoi(category_id_attribute)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||||
|
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||||
|
}
|
||||||
|
|
||||||
|
breadcrumb, err := h.menuService.GetBreadcrumb(uint(root_category_id), uint(category_id), lang_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(&breadcrumb, 0, i18n.T_(c, response.Message_OK)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
func (h *MenuHandler) GetTopMenu(c fiber.Ctx) error {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ type AppSettings struct {
|
|||||||
Environment string `json:"environment"`
|
Environment string `json:"environment"`
|
||||||
BaseURL string `json:"base_url"`
|
BaseURL string `json:"base_url"`
|
||||||
PasswordRegex string `json:"password_regex"`
|
PasswordRegex string `json:"password_regex"`
|
||||||
|
CategoryTreeRootID uint `json:"category_tree_root_id"`
|
||||||
|
ShopDefaultLanguage uint `json:"shop_default_language"`
|
||||||
// Config config.Config `json:"config"`
|
// Config config.Config `json:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +71,8 @@ func (h *SettingsHandler) GetSettings(cfg *config.Config) fiber.Handler {
|
|||||||
Environment: cfg.App.Environment,
|
Environment: cfg.App.Environment,
|
||||||
BaseURL: cfg.App.BaseURL,
|
BaseURL: cfg.App.BaseURL,
|
||||||
PasswordRegex: constdata.PASSWORD_VALIDATION_REGEX,
|
PasswordRegex: constdata.PASSWORD_VALIDATION_REGEX,
|
||||||
|
CategoryTreeRootID: constdata.CATEGORY_TREE_ROOT_ID,
|
||||||
|
ShopDefaultLanguage: constdata.SHOP_DEFAULT_LANGUAGE,
|
||||||
// Config: *config.Get(),
|
// Config: *config.Get(),
|
||||||
},
|
},
|
||||||
Server: ServerSettings{
|
Server: ServerSettings{
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ func (s *Server) Setup() error {
|
|||||||
productTranslation := s.restricted.Group("/product-translation")
|
productTranslation := s.restricted.Group("/product-translation")
|
||||||
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
restricted.ProductTranslationHandlerRoutes(productTranslation)
|
||||||
|
|
||||||
// listing products routes (restricted)
|
// lists of things routes (restricted)
|
||||||
listProducts := s.restricted.Group("/list-products")
|
list := s.restricted.Group("/list")
|
||||||
restricted.ListProductsHandlerRoutes(listProducts)
|
restricted.ListHandlerRoutes(list)
|
||||||
|
|
||||||
// locale selector (restricted)
|
// locale selector (restricted)
|
||||||
// this is basically for changing user's selected language and country
|
// this is basically for changing user's selected language and country
|
||||||
|
|||||||
33
app/model/category.go
Normal file
33
app/model/category.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ScannedCategory struct {
|
||||||
|
CategoryID uint `gorm:"column:category_id;primaryKey"`
|
||||||
|
Name string `gorm:"column:name"`
|
||||||
|
Active uint `gorm:"column:active"`
|
||||||
|
Position uint `gorm:"column:position"`
|
||||||
|
ParentID uint `gorm:"column:id_parent"`
|
||||||
|
IsRoot uint `gorm:"column:is_root_category"`
|
||||||
|
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||||
|
IsoCode string `gorm:"column:iso_code"`
|
||||||
|
|
||||||
|
Visited bool //this is for internal backend use only
|
||||||
|
}
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
CategoryID uint `json:"category_id" form:"category_id"`
|
||||||
|
Label string `json:"label" form:"label"`
|
||||||
|
// Active bool `json:"active" form:"active"`
|
||||||
|
Params CategoryParams `json:"params" form:"params"`
|
||||||
|
Children []Category `json:"children" form:"children"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CategoryParams struct {
|
||||||
|
CategoryID uint `json:"category_id" form:"category_id"`
|
||||||
|
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
||||||
|
Locale string `json:"locale" form:"locale"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CategoryInBreadcrumb struct {
|
||||||
|
CategoryID uint `json:"category_id" form:"category_id"`
|
||||||
|
Name string `json:"name" form:"name"`
|
||||||
|
}
|
||||||
@@ -1,31 +1,17 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||||
|
|
||||||
// Represents a country together with its associated currency
|
// Represents a country together with its associated currency
|
||||||
type Country struct {
|
type Country struct {
|
||||||
ID uint `gorm:"primaryKey;column:id" json:"id"`
|
ID uint `gorm:"primaryKey;column:id" json:"id"`
|
||||||
Name string `gorm:"column:name" json:"name"`
|
Name string `gorm:"column:name" json:"name"`
|
||||||
Flag string `gorm:"size:16;not null;column:flag" json:"flag"`
|
Flag string `gorm:"size:16;not null;column:flag" json:"flag"`
|
||||||
CurrencyID uint `gorm:"column:id_currency" json:"currency_id"`
|
|
||||||
CurrencyISOCode string `gorm:"column:iso_code" json:"currency_iso_code"`
|
PSCurrencyID uint `gorm:"column:currency_id" json:"currency_id"`
|
||||||
CurrencyName string `gorm:"column:name" json:"currency_name"`
|
PSCurrency *dbmodel.PsCurrency `gorm:"foreignKey:PSCurrencyID;references:IDCurrency" json:"ps_currency"`
|
||||||
// PSCountryID int `gorm:"column:id_country" json:"ps_country_id"`
|
|
||||||
// PSCountry *PSCountry `gorm:"foreignKey:PSCountryID;references:ID" json:"ps_country"`
|
|
||||||
PSCurrencyID uint `gorm:"column:currency" json:"currency"`
|
|
||||||
PSCurrency *PSCurrency `gorm:"foreignKey:PSCurrencyID;references:currency_id" json:"ps_currency"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Country) TableName() string {
|
func (Country) TableName() string {
|
||||||
return "b2b_countries"
|
return "b2b_countries"
|
||||||
}
|
}
|
||||||
|
|
||||||
type PSCountry struct {
|
|
||||||
CurrencyID uint `gorm:"column:id_currency" json:"currency_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (PSCountry) TableName() string {
|
|
||||||
return "ps_country"
|
|
||||||
}
|
|
||||||
|
|
||||||
type PSCurrency struct {
|
|
||||||
Currency int `gorm:"column:currency" json:"currency"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -144,3 +144,11 @@ type RefreshToken struct {
|
|||||||
func (RefreshToken) TableName() string {
|
func (RefreshToken) TableName() string {
|
||||||
return "b2b_refresh_tokens"
|
return "b2b_refresh_tokens"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserInList struct {
|
||||||
|
UserID uint `gorm:"primaryKey;column:id" json:"user_id"`
|
||||||
|
Email string `gorm:"column:email" json:"email"`
|
||||||
|
FirstName string `gorm:"column:first_name" json:"first_name"`
|
||||||
|
LastName string `gorm:"column:last_name" json:"last_name"`
|
||||||
|
Role string `gorm:"column:role" json:"role"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,28 +84,4 @@ type ProductFilters struct {
|
|||||||
InStock uint `query:"stock,omitempty"`
|
InStock uint `query:"stock,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScannedCategory struct {
|
|
||||||
CategoryID uint `gorm:"column:category_id;primaryKey"`
|
|
||||||
Name string `gorm:"column:name"`
|
|
||||||
Active uint `gorm:"column:active"`
|
|
||||||
Position uint `gorm:"column:position"`
|
|
||||||
ParentID uint `gorm:"column:id_parent"`
|
|
||||||
IsRoot uint `gorm:"column:is_root_category"`
|
|
||||||
LinkRewrite string `gorm:"column:link_rewrite"`
|
|
||||||
IsoCode string `gorm:"column:iso_code"`
|
|
||||||
}
|
|
||||||
type Category struct {
|
|
||||||
CategoryID uint `json:"category_id" form:"category_id"`
|
|
||||||
Label string `json:"label" form:"label"`
|
|
||||||
// Active bool `json:"active" form:"active"`
|
|
||||||
Params CategpryParams `json:"params" form:"params"`
|
|
||||||
Children []Category `json:"children" form:"children"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CategpryParams struct {
|
|
||||||
CategoryID uint `json:"category_id" form:"category_id"`
|
|
||||||
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
|
||||||
Locale string `json:"locale" form:"locale"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeatVal = map[uint][]uint
|
type FeatVal = map[uint][]uint
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ type ProductDescription struct {
|
|||||||
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
|
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
|
||||||
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
|
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
|
||||||
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
|
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
|
||||||
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
|
Usage string `gorm:"column:_usage_;type:text" json:"usage" form:"usage"`
|
||||||
|
|
||||||
|
ImageLink string `gorm:"column:image_link" json:"image_link"`
|
||||||
|
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductRow struct {
|
type ProductRow struct {
|
||||||
|
|||||||
@@ -37,12 +37,11 @@ func (r *CategoriesRepo) GetAllCategories(idLang uint) ([]model.ScannedCategory,
|
|||||||
ps_category_lang.link_rewrite AS link_rewrite,
|
ps_category_lang.link_rewrite AS link_rewrite,
|
||||||
ps_lang.iso_code AS iso_code
|
ps_lang.iso_code AS iso_code
|
||||||
`).
|
`).
|
||||||
Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ? AND ??.id_lang = ?`,
|
Joins(`LEFT JOIN `+categoryLangTbl+` ON `+categoryLangTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryLangTbl+`.id_shop = ? AND `+categoryLangTbl+`.id_lang = ?`,
|
||||||
categoryLangTbl, categoryLangTbl, categoryTbl, categoryLangTbl, constdata.SHOP_ID, categoryLangTbl, idLang).
|
constdata.SHOP_ID, idLang).
|
||||||
Joins(`LEFT JOIN ? ON ??.id_category = ??.id_category AND ??.id_shop = ?`,
|
Joins(`LEFT JOIN `+categoryShopTbl+` ON `+categoryShopTbl+`.id_category = `+categoryTbl+`.id_category AND `+categoryShopTbl+`.id_shop = ?`,
|
||||||
categoryShopTbl, categoryShopTbl, categoryTbl, categoryShopTbl, constdata.SHOP_ID).
|
constdata.SHOP_ID).
|
||||||
Joins(`JOIN ? ON ??.id_lang = ??.id_lang`,
|
Joins(`JOIN ` + langTbl + ` ON ` + langTbl + `.id_lang = ` + categoryLangTbl + `.id_lang`).
|
||||||
langTbl, langTbl, categoryLangTbl).
|
|
||||||
Scan(&allCategories).Error
|
Scan(&allCategories).Error
|
||||||
|
|
||||||
return allCategories, err
|
return allCategories, err
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package listProductsRepo
|
package listRepo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
@@ -7,30 +7,32 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
"git.ma-al.com/goc_marek/gormcol"
|
||||||
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIListProductsRepo interface {
|
type UIListRepo interface {
|
||||||
GetListing(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error)
|
ListProducts(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error)
|
||||||
|
ListUsers(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.UserInList], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListProductsRepo struct{}
|
type ListRepo struct{}
|
||||||
|
|
||||||
func New() UIListProductsRepo {
|
func New() UIListRepo {
|
||||||
return &ListProductsRepo{}
|
return &ListRepo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
func (repo *ListRepo) ListProducts(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
||||||
var listing []model.ProductInList
|
var list []model.ProductInList
|
||||||
var total int64
|
var total int64
|
||||||
|
|
||||||
query := db.Get().
|
query := db.Get().
|
||||||
Table("ps_product_shop ps").
|
Table(gormcol.Field.Tab(dbmodel.PsProductShopCols.Active)+" AS ps").
|
||||||
Select(`
|
Select(`
|
||||||
ps.id_product AS product_id,
|
ps.id_product AS product_id,
|
||||||
pl.name AS name,
|
pl.name AS name,
|
||||||
pl.link_rewrite AS link_rewrite,
|
pl.link_rewrite AS link_rewrite,
|
||||||
CONCAT(?, ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
CONCAT(?, '/', ims.id_image, '-small_default/', pl.link_rewrite, '.webp') AS image_link,
|
||||||
cl.name AS category_name,
|
cl.name AS category_name,
|
||||||
p.reference AS reference,
|
p.reference AS reference,
|
||||||
COALESCE(v.variants_number, 0) AS variants_number,
|
COALESCE(v.variants_number, 0) AS variants_number,
|
||||||
@@ -67,13 +69,53 @@ func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filt
|
|||||||
Order("ps.id_product DESC").
|
Order("ps.id_product DESC").
|
||||||
Limit(p.Limit()).
|
Limit(p.Limit()).
|
||||||
Offset(p.Offset()).
|
Offset(p.Offset()).
|
||||||
Find(&listing).Error
|
Find(&list).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return find.Found[model.ProductInList]{}, err
|
return find.Found[model.ProductInList]{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return find.Found[model.ProductInList]{
|
return find.Found[model.ProductInList]{
|
||||||
Items: listing,
|
Items: list,
|
||||||
|
Count: uint(total),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *ListRepo) ListUsers(id_lang uint, p find.Paging, filt *filters.FiltersList) (find.Found[model.UserInList], error) {
|
||||||
|
var list []model.UserInList
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := db.Get().
|
||||||
|
Table("b2b_customers AS users").
|
||||||
|
Select(`
|
||||||
|
users.id AS id,
|
||||||
|
users.email AS email,
|
||||||
|
users.first_name AS first_name,
|
||||||
|
users.last_name AS last_name,
|
||||||
|
users.role AS role
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Apply all filters
|
||||||
|
if filt != nil {
|
||||||
|
filt.ApplyAll(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run counter first as query is without limit and offset
|
||||||
|
err := query.Count(&total).Error
|
||||||
|
if err != nil {
|
||||||
|
return find.Found[model.UserInList]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = query.
|
||||||
|
Order("users.id DESC").
|
||||||
|
Limit(p.Limit()).
|
||||||
|
Offset(p.Offset()).
|
||||||
|
Find(&list).Error
|
||||||
|
if err != nil {
|
||||||
|
return find.Found[model.UserInList]{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return find.Found[model.UserInList]{
|
||||||
|
Items: list,
|
||||||
Count: uint(total),
|
Count: uint(total),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
package productDescriptionRepo
|
package productDescriptionRepo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
"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"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
"git.ma-al.com/goc_daniel/b2b/app/model/dbmodel"
|
||||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
"github.com/WinterYukky/gorm-extra-clause-plugin/exclause"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIProductDescriptionRepo interface {
|
type UIProductDescriptionRepo interface {
|
||||||
@@ -28,14 +31,42 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
|||||||
var ProductDescription model.ProductDescription
|
var ProductDescription model.ProductDescription
|
||||||
|
|
||||||
err := db.Get().
|
err := db.Get().
|
||||||
|
Model(dbmodel.PsProductLang{}).
|
||||||
Where(&dbmodel.PsProductLang{
|
Where(&dbmodel.PsProductLang{
|
||||||
IDProduct: int32(productID),
|
IDProduct: int32(productID),
|
||||||
IDShop: int32(constdata.SHOP_ID),
|
IDShop: int32(constdata.SHOP_ID),
|
||||||
IDLang: int32(productid_lang),
|
IDLang: int32(productid_lang),
|
||||||
}).
|
}).
|
||||||
|
Select(`
|
||||||
|
`+dbmodel.PsProductLangCols.IDProduct.TabCol()+` AS id_product,
|
||||||
|
`+dbmodel.PsProductLangCols.IDShop.TabCol()+` AS id_shop,
|
||||||
|
`+dbmodel.PsProductLangCols.IDLang.TabCol()+` AS id_lang,
|
||||||
|
`+dbmodel.PsProductLangCols.Description.TabCol()+` AS description,
|
||||||
|
`+dbmodel.PsProductLangCols.DescriptionShort.TabCol()+` AS description_short,
|
||||||
|
`+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+` AS link_rewrite,
|
||||||
|
`+dbmodel.PsProductLangCols.MetaDescription.TabCol()+` AS meta_description,
|
||||||
|
`+dbmodel.PsProductLangCols.MetaKeywords.TabCol()+` AS meta_keywords,
|
||||||
|
`+dbmodel.PsProductLangCols.MetaTitle.TabCol()+` AS meta_title,
|
||||||
|
`+dbmodel.PsProductLangCols.Name.TabCol()+` AS name,
|
||||||
|
`+dbmodel.PsProductLangCols.AvailableNow.TabCol()+` AS available_now,
|
||||||
|
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
|
||||||
|
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
|
||||||
|
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
|
||||||
|
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
|
||||||
|
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
|
||||||
|
`, config.Get().Image.ImagePrefix).
|
||||||
|
Joins("JOIN " + dbmodel.TableNamePsImageShop +
|
||||||
|
" ON " + dbmodel.PsImageShopCols.IDProduct.TabCol() + "=" + dbmodel.PsProductLangCols.IDProduct.TabCol() +
|
||||||
|
" AND " + dbmodel.PsImageShopCols.Cover.TabCol() + " = 1").
|
||||||
First(&ProductDescription).Error
|
First(&ProductDescription).Error
|
||||||
if err != nil {
|
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// handle "not found" case only
|
||||||
|
ProductDescription.ExistsInDatabase = false
|
||||||
|
} else if err != nil {
|
||||||
return nil, fmt.Errorf("database error: %w", err)
|
return nil, fmt.Errorf("database error: %w", err)
|
||||||
|
} else {
|
||||||
|
ProductDescription.ExistsInDatabase = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProductDescription, nil
|
return &ProductDescription, nil
|
||||||
@@ -50,6 +81,7 @@ func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := db.Get().
|
err := db.Get().
|
||||||
|
Model(dbmodel.PsProductLang{}).
|
||||||
Where(&dbmodel.PsProductLang{
|
Where(&dbmodel.PsProductLang{
|
||||||
IDProduct: int32(productID),
|
IDProduct: int32(productID),
|
||||||
IDShop: int32(constdata.SHOP_ID),
|
IDShop: int32(constdata.SHOP_ID),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||||
@@ -14,13 +13,9 @@ import (
|
|||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
|
"git.ma-al.com/goc_daniel/b2b/app/service/emailService"
|
||||||
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/i18n"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||||
|
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/gofiber/fiber/v3"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -167,7 +162,7 @@ func (s *AuthService) Register(req *model.RegisterRequest) error {
|
|||||||
baseURL := config.Get().App.BaseURL
|
baseURL := config.Get().App.BaseURL
|
||||||
lang, err := s.GetLangISOCode(req.LangID)
|
lang, err := s.GetLangISOCode(req.LangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return responseErrors.ErrBadLangID
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.email.SendVerificationEmail(user.Email, user.EmailVerificationToken, baseURL, lang); err != nil {
|
if err := s.email.SendVerificationEmail(user.Email, user.EmailVerificationToken, baseURL, lang); err != nil {
|
||||||
@@ -276,7 +271,7 @@ func (s *AuthService) RequestPasswordReset(emailAddr string) error {
|
|||||||
baseURL := config.Get().App.BaseURL
|
baseURL := config.Get().App.BaseURL
|
||||||
lang, err := s.GetLangISOCode(user.LangID)
|
lang, err := s.GetLangISOCode(user.LangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return responseErrors.ErrBadLangID
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.email.SendPasswordResetEmail(user.Email, user.PasswordResetToken, baseURL, lang); err != nil {
|
if err := s.email.SendPasswordResetEmail(user.Email, user.PasswordResetToken, baseURL, lang); err != nil {
|
||||||
@@ -482,7 +477,7 @@ func hashToken(raw string) string {
|
|||||||
func (s *AuthService) generateAccessToken(user *model.Customer) (string, error) {
|
func (s *AuthService) generateAccessToken(user *model.Customer) (string, error) {
|
||||||
_, err := s.GetLangISOCode(user.LangID)
|
_, err := s.GetLangISOCode(user.LangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", responseErrors.ErrBadLangID
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.CheckIfCountryExists(user.CountryID)
|
err = s.CheckIfCountryExists(user.CountryID)
|
||||||
@@ -508,97 +503,19 @@ func (s *AuthService) generateAccessToken(user *model.Customer) (string, error)
|
|||||||
return token.SignedString([]byte(s.config.JWTSecret))
|
return token.SignedString([]byte(s.config.JWTSecret))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthService) UpdateJWTToken(c fiber.Ctx) error {
|
func (s *AuthService) UpdateJWTToken(user *model.Customer) (string, error) {
|
||||||
// Get user ID from JWT claims in context (set by auth middleware)
|
// Update choice and get new access token using AuthService
|
||||||
// claims, ok := c.Locals("jwt_claims").(*JWTClaims)
|
new_access_token, err := s.generateAccessToken(user)
|
||||||
// if !ok || claims == nil {
|
|
||||||
// return c.Status(fiber.StatusUnauthorized).
|
|
||||||
// JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
|
|
||||||
// }
|
|
||||||
// fmt.Printf("claims: %v\n", claims)
|
|
||||||
// var user model.Customer
|
|
||||||
// // Find user by ID
|
|
||||||
// if err := s.db.First(&user, claims.UserID).Error; err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
userLocals, ok := c.Locals(constdata.USER_LOCALES_NAME).(*model.UserSession)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(fiber.StatusUnauthorized).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrNotAuthenticated)))
|
|
||||||
}
|
|
||||||
|
|
||||||
user := model.Customer{
|
|
||||||
ID: userLocals.UserID,
|
|
||||||
Email: userLocals.Email,
|
|
||||||
Role: userLocals.Role,
|
|
||||||
LangID: userLocals.LangID,
|
|
||||||
CountryID: userLocals.CountryID,
|
|
||||||
IsActive: userLocals.IsActive,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse language and country_id from query params
|
|
||||||
langIDStr := c.Query("lang_id")
|
|
||||||
|
|
||||||
var langID uint
|
|
||||||
if langIDStr != "" {
|
|
||||||
parsedID, err := strconv.ParseUint(langIDStr, 10, 32)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).
|
return "", err
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadLangID)))
|
|
||||||
}
|
|
||||||
langID = uint(parsedID)
|
|
||||||
|
|
||||||
_, err = s.GetLangISOCode(langID)
|
|
||||||
if err != nil {
|
|
||||||
return responseErrors.ErrBadLangID
|
|
||||||
} else {
|
|
||||||
user.LangID = langID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
countryIDStr := c.Query("country_id")
|
|
||||||
|
|
||||||
var countryID uint
|
|
||||||
if countryIDStr != "" {
|
|
||||||
parsedID, err := strconv.ParseUint(countryIDStr, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadCountryID)))
|
|
||||||
}
|
|
||||||
countryID = uint(parsedID)
|
|
||||||
|
|
||||||
err = s.CheckIfCountryExists(countryID)
|
|
||||||
if err != nil {
|
|
||||||
return responseErrors.ErrBadCountryID
|
|
||||||
} else {
|
|
||||||
user.CountryID = countryID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update choice and get new token using AuthService
|
|
||||||
newToken, err := s.generateAccessToken(&user)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
|
||||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the updated user
|
// Save the updated user
|
||||||
if err := s.db.Save(&user).Error; err != nil {
|
if err := s.db.Save(user).Error; err != nil {
|
||||||
return fmt.Errorf("database error: %w", err)
|
return "", fmt.Errorf("database error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the new JWT cookie
|
return new_access_token, nil
|
||||||
cookie := new(fiber.Cookie)
|
|
||||||
cookie.Name = "jwt_token"
|
|
||||||
cookie.Value = newToken
|
|
||||||
cookie.HTTPOnly = true
|
|
||||||
cookie.Secure = true
|
|
||||||
cookie.SameSite = fiber.CookieSameSiteLaxMode
|
|
||||||
|
|
||||||
c.Cookie(cookie)
|
|
||||||
|
|
||||||
return c.JSON(response.Make(&fiber.Map{"token": newToken}, 0, i18n.T_(c, response.Message_OK)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateVerificationToken generates a random verification token
|
// generateVerificationToken generates a random verification token
|
||||||
@@ -623,14 +540,20 @@ func validatePassword(password string) error {
|
|||||||
|
|
||||||
func (s *AuthService) GetLangISOCode(langID uint) (string, error) {
|
func (s *AuthService) GetLangISOCode(langID uint) (string, error) {
|
||||||
var lang string
|
var lang string
|
||||||
|
var err error
|
||||||
|
|
||||||
if langID == 0 { // retrieve the default lang
|
if langID == 0 { // retrieve the default lang
|
||||||
err := db.DB.Table("b2b_language").Where("is_default = ?", 1).Select("iso_code").Scan(&lang).Error
|
err = db.DB.Table("b2b_language").Where("is_default = ?", 1).Select("iso_code").Scan(&lang).Error
|
||||||
return lang, err
|
|
||||||
} else {
|
} else {
|
||||||
err := db.DB.Table("b2b_language").Where("id = ?", langID).Where("active = ?", 1).Select("iso_code").Scan(&lang).Error
|
err = db.DB.Table("b2b_language").Where("id = ?", langID).Where("active = ?", 1).Select("iso_code").Scan(&lang).Error
|
||||||
return lang, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return lang, err
|
||||||
|
} else if lang == "" {
|
||||||
|
return lang, responseErrors.ErrBadLangID
|
||||||
|
}
|
||||||
|
return lang, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AuthService) CheckIfCountryExists(countryID uint) error {
|
func (s *AuthService) CheckIfCountryExists(countryID uint) error {
|
||||||
|
|||||||
@@ -153,7 +153,8 @@ func (s *AuthService) findOrCreateGoogleUser(info *view.GoogleUserInfo) (*model.
|
|||||||
Role: model.RoleUser,
|
Role: model.RoleUser,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
EmailVerified: true,
|
EmailVerified: true,
|
||||||
LangID: 2,
|
LangID: 2, // default is english
|
||||||
|
CountryID: 2, // default is England
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.db.Create(&newUser).Error; err != nil {
|
if err := s.db.Create(&newUser).Error; err != nil {
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package listProductsService
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/repos/listProductsRepo"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListProductsService struct {
|
|
||||||
listProductsRepo listProductsRepo.UIListProductsRepo
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *ListProductsService {
|
|
||||||
return &ListProductsService{
|
|
||||||
listProductsRepo: listProductsRepo.New(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ListProductsService) GetListing(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
|
||||||
var products find.Found[model.ProductInList]
|
|
||||||
|
|
||||||
products, err := s.listProductsRepo.GetListing(id_lang, p, filters)
|
|
||||||
if err != nil {
|
|
||||||
return products, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return products, nil
|
|
||||||
}
|
|
||||||
26
app/service/listService/listService.go
Normal file
26
app/service/listService/listService.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package listService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/repos/listRepo"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/filters"
|
||||||
|
"git.ma-al.com/goc_daniel/b2b/app/utils/query/find"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListService struct {
|
||||||
|
listRepo listRepo.UIListRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *ListService {
|
||||||
|
return &ListService{
|
||||||
|
listRepo: listRepo.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ListService) ListProducts(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
||||||
|
return s.listRepo.ListProducts(id_lang, p, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ListService) ListUsers(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.UserInList], error) {
|
||||||
|
return s.listRepo.ListUsers(id_lang, p, filters)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package menuService
|
package menuService
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||||
@@ -21,7 +22,7 @@ func New() *MenuService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
func (s *MenuService) GetCategoryTree(root_category_id uint, id_lang uint) (*model.Category, error) {
|
||||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &model.Category{}, err
|
return &model.Category{}, err
|
||||||
@@ -31,7 +32,7 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
|||||||
root_index := 0
|
root_index := 0
|
||||||
root_found := false
|
root_found := false
|
||||||
for i := 0; i < len(all_categories); i++ {
|
for i := 0; i < len(all_categories); i++ {
|
||||||
if all_categories[i].IsRoot == 1 {
|
if all_categories[i].CategoryID == root_category_id {
|
||||||
root_index = i
|
root_index = i
|
||||||
root_found = true
|
root_found = true
|
||||||
break
|
break
|
||||||
@@ -44,6 +45,7 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
|||||||
// now create the children and reorder them according to position
|
// now create the children and reorder them according to position
|
||||||
id_to_index := make(map[uint]int)
|
id_to_index := make(map[uint]int)
|
||||||
for i := 0; i < len(all_categories); i++ {
|
for i := 0; i < len(all_categories); i++ {
|
||||||
|
all_categories[i].Visited = false
|
||||||
id_to_index[all_categories[i].CategoryID] = i
|
id_to_index[all_categories[i].CategoryID] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,19 +60,32 @@ func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// finally, create the tree
|
// finally, create the tree
|
||||||
tree := s.createTree(root_index, &all_categories, &children_indices)
|
tree, success := s.createTree(root_index, &all_categories, &children_indices)
|
||||||
|
if !success {
|
||||||
|
return &tree, responseErrors.ErrCircularDependency
|
||||||
|
}
|
||||||
|
|
||||||
return &tree, nil
|
return &tree, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) model.Category {
|
func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) (model.Category, bool) {
|
||||||
node := s.scannedToNormalCategory((*all_categories)[index])
|
node := s.scannedToNormalCategory((*all_categories)[index])
|
||||||
|
|
||||||
|
if (*all_categories)[index].Visited {
|
||||||
|
return node, false
|
||||||
|
}
|
||||||
|
(*all_categories)[index].Visited = true
|
||||||
|
|
||||||
for i := 0; i < len((*children_indices)[index]); i++ {
|
for i := 0; i < len((*children_indices)[index]); i++ {
|
||||||
node.Children = append(node.Children, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices))
|
next_child, success := s.createTree((*children_indices)[index][i].Index, all_categories, children_indices)
|
||||||
|
if !success {
|
||||||
|
return node, false
|
||||||
|
}
|
||||||
|
node.Children = append(node.Children, next_child)
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
(*all_categories)[index].Visited = false // just in case we have a "diamond" diagram
|
||||||
|
return node, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) {
|
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) {
|
||||||
@@ -83,7 +98,7 @@ func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) mod
|
|||||||
normal.CategoryID = scanned.CategoryID
|
normal.CategoryID = scanned.CategoryID
|
||||||
normal.Label = scanned.Name
|
normal.Label = scanned.Name
|
||||||
// normal.Active = scanned.Active == 1
|
// normal.Active = scanned.Active == 1
|
||||||
normal.Params = model.CategpryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
normal.Params = model.CategoryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
||||||
normal.Children = []model.Category{}
|
normal.Children = []model.Category{}
|
||||||
return normal
|
return normal
|
||||||
}
|
}
|
||||||
@@ -98,6 +113,69 @@ func (a ByPosition) Len() int { return len(a) }
|
|||||||
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ByPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
func (a ByPosition) Less(i, j int) bool { return a[i].Position < a[j].Position }
|
||||||
|
|
||||||
|
func (s *MenuService) GetBreadcrumb(root_category_id uint, start_category_id uint, id_lang uint) ([]model.CategoryInBreadcrumb, error) {
|
||||||
|
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||||
|
if err != nil {
|
||||||
|
return []model.CategoryInBreadcrumb{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
breadcrumb := []model.CategoryInBreadcrumb{}
|
||||||
|
|
||||||
|
start_index := 0
|
||||||
|
start_found := false
|
||||||
|
for i := 0; i < len(all_categories); i++ {
|
||||||
|
if all_categories[i].CategoryID == start_category_id {
|
||||||
|
start_index = i
|
||||||
|
start_found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !start_found {
|
||||||
|
return []model.CategoryInBreadcrumb{}, responseErrors.ErrStartCategoryNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// map category ids to indices
|
||||||
|
id_to_index := make(map[uint]int)
|
||||||
|
for i := 0; i < len(all_categories); i++ {
|
||||||
|
all_categories[i].Visited = false
|
||||||
|
id_to_index[all_categories[i].CategoryID] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// do a simple graph traversal, always jumping from node to its parent
|
||||||
|
index := start_index
|
||||||
|
success := true
|
||||||
|
for {
|
||||||
|
if all_categories[index].Visited {
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
all_categories[index].Visited = true
|
||||||
|
|
||||||
|
var next_category model.CategoryInBreadcrumb
|
||||||
|
next_category.CategoryID = all_categories[index].CategoryID
|
||||||
|
next_category.Name = all_categories[index].Name
|
||||||
|
breadcrumb = append(breadcrumb, next_category)
|
||||||
|
|
||||||
|
if all_categories[index].CategoryID == root_category_id {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
next_index, ok := id_to_index[all_categories[index].ParentID]
|
||||||
|
if !ok {
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
index = next_index
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Reverse(breadcrumb)
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
return breadcrumb, responseErrors.ErrRootNeverReached
|
||||||
|
}
|
||||||
|
|
||||||
|
return breadcrumb, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
|
func (s *MenuService) GetTopMenu(id uint) ([]*model.B2BTopMenu, error) {
|
||||||
items, err := s.routesRepo.GetTopMenu(id)
|
items, err := s.routesRepo.GetTopMenu(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ package constdata
|
|||||||
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
// PASSWORD_VALIDATION_REGEX is used by the frontend (JavaScript supports lookaheads).
|
||||||
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
const PASSWORD_VALIDATION_REGEX = `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$`
|
||||||
const SHOP_ID = 1
|
const SHOP_ID = 1
|
||||||
|
const SHOP_DEFAULT_LANGUAGE = 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 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"
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ var (
|
|||||||
|
|
||||||
// Typed errors for menu handler
|
// Typed errors for menu handler
|
||||||
ErrNoRootFound = errors.New("no root found in categories table")
|
ErrNoRootFound = errors.New("no root found in categories table")
|
||||||
|
ErrCircularDependency = errors.New("circular dependency structure in tree (could be caused by improper root id)")
|
||||||
|
ErrStartCategoryNotFound = errors.New("the start category has not been found")
|
||||||
|
ErrRootNeverReached = errors.New("the root category is not an ancestor of start category")
|
||||||
|
|
||||||
// Typed errors for carts handler
|
// Typed errors for carts handler
|
||||||
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
ErrMaxAmtOfCartsReached = errors.New("maximal amount of carts reached")
|
||||||
@@ -145,6 +148,12 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
|
|
||||||
case errors.Is(err, ErrNoRootFound):
|
case errors.Is(err, ErrNoRootFound):
|
||||||
return i18n.T_(c, "error.no_root_found")
|
return i18n.T_(c, "error.no_root_found")
|
||||||
|
case errors.Is(err, ErrCircularDependency):
|
||||||
|
return i18n.T_(c, "error.circular_dependency")
|
||||||
|
case errors.Is(err, ErrStartCategoryNotFound):
|
||||||
|
return i18n.T_(c, "error.start_category_not_found")
|
||||||
|
case errors.Is(err, ErrRootNeverReached):
|
||||||
|
return i18n.T_(c, "error.root_never_reached")
|
||||||
|
|
||||||
case errors.Is(err, ErrMaxAmtOfCartsReached):
|
case errors.Is(err, ErrMaxAmtOfCartsReached):
|
||||||
return i18n.T_(c, "error.max_amt_of_carts_reached")
|
return i18n.T_(c, "error.max_amt_of_carts_reached")
|
||||||
@@ -189,6 +198,9 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrInvalidXHTML),
|
errors.Is(err, ErrInvalidXHTML),
|
||||||
errors.Is(err, ErrBadPaging),
|
errors.Is(err, ErrBadPaging),
|
||||||
errors.Is(err, ErrNoRootFound),
|
errors.Is(err, ErrNoRootFound),
|
||||||
|
errors.Is(err, ErrCircularDependency),
|
||||||
|
errors.Is(err, ErrStartCategoryNotFound),
|
||||||
|
errors.Is(err, ErrRootNeverReached),
|
||||||
errors.Is(err, ErrMaxAmtOfCartsReached),
|
errors.Is(err, ErrMaxAmtOfCartsReached),
|
||||||
errors.Is(err, ErrUserHasNoSuchCart),
|
errors.Is(err, ErrUserHasNoSuchCart),
|
||||||
errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
errors.Is(err, ErrProductOrItsVariationDoesNotExist):
|
||||||
|
|||||||
1
bo/.gitignore
vendored
1
bo/.gitignore
vendored
@@ -13,6 +13,7 @@ dist
|
|||||||
dist-ssr
|
dist-ssr
|
||||||
coverage
|
coverage
|
||||||
*.local
|
*.local
|
||||||
|
/bo/components.d.ts
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
13
bo/components.d.ts
vendored
13
bo/components.d.ts
vendored
@@ -11,33 +11,26 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
Cart1: typeof import('./src/components/customer/Cart1.vue')['default']
|
|
||||||
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']
|
||||||
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
|
CategoryMenuListing: typeof import('./src/components/inner/categoryMenuListing.vue')['default']
|
||||||
copy: typeof import('./src/components/inner/categoryMenu copy.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']
|
LangSwitch: typeof import('./src/components/inner/langSwitch.vue')['default']
|
||||||
Page: typeof import('./src/components/customer/Page.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']
|
||||||
PageCreateAccount: typeof import('./src/components/customer/PageCreateAccount.vue')['default']
|
PageOrders: typeof import('./src/components/customer/PageOrders.vue')['default']
|
||||||
PageCustomerData: typeof import('./src/components/customer/PageCustomerData.vue')['default']
|
|
||||||
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
PageProduct: typeof import('./src/components/customer/PageProduct.vue')['default']
|
||||||
PageProductCardFull: typeof import('./src/components/customer/PageProductCardFull.vue')['default']
|
|
||||||
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
PageProducts: typeof import('./src/components/admin/PageProducts.vue')['default']
|
||||||
PageProductsList: typeof import('./src/components/admin/PageProductsList.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']
|
||||||
|
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']
|
||||||
ProductsView: typeof import('./src/components/admin/ProductsView.vue')['default']
|
|
||||||
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.vue')['default']
|
ProductVariants: typeof import('./src/components/customer/components/ProductVariants.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']
|
||||||
@@ -49,13 +42,11 @@ declare module 'vue' {
|
|||||||
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']
|
|
||||||
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']
|
||||||
UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default']
|
UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default']
|
||||||
UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default']
|
UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default']
|
||||||
ULink: typeof import('./node_modules/@nuxt/ui/dist/runtime/vue/overrides/vue-router/Link.vue')['default']
|
|
||||||
UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
|
UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
|
||||||
UNavigationMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue')['default']
|
UNavigationMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue')['default']
|
||||||
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']
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ 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/auth'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, onMounted, 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'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
let menu = ref()
|
let menu = ref()
|
||||||
@@ -19,30 +20,43 @@ async function getTopMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const menuItems = computed(() => transformMenu(menu.value[0].children, currentLang.value?.iso_code))
|
const menuItems = computed(() => transformMenu(menu.value[0].children, currentLang.value?.iso_code))
|
||||||
function transformMenu(items: TopMenuItem[], locale: string | undefined): NavigationMenuItem[] {
|
function transformMenu(items: TopMenuItem[], locale: string | undefined): NavigationMenuItem[] {
|
||||||
|
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
let route = {
|
const route: NavigationMenuItem = {
|
||||||
icon: 'i-lucide-house',
|
icon: item.label.icon ? item.label.icon : 'i-lucide-house',
|
||||||
label: item.label.trans[locale as keyof LabelTrans].label,
|
label: item.label.trans[locale as keyof LabelTrans].label,
|
||||||
children: item.children ? transformMenu(item.children, locale) : undefined,
|
children: item.children
|
||||||
|
? transformMenu(item.children, locale)
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
if (item.params?.route) {
|
|
||||||
route = { ...route, ...{ to: { name: item.params.route.name, params: { locale: locale } } } }
|
route.onSelect = () => {
|
||||||
|
const query = {
|
||||||
|
name: item.params.route.name,
|
||||||
|
params: {
|
||||||
|
...(item.params.route.params || {}),
|
||||||
|
locale: currentLang.value?.iso_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
return route
|
return route
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await getTopMenu()
|
await getTopMenu()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{ menuItems }}
|
<header
|
||||||
<!-- fixed top-0 left-0 right-0 z-50 -->
|
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)">
|
||||||
<header class="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 sm:px-6 lg:px-8">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex items-center justify-between h-14">
|
<div class="flex items-center justify-between h-14">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
<RouterLink :to="{ name: 'home' }" class="flex items-center gap-2">
|
||||||
@@ -52,49 +66,18 @@ await getTopMenu()
|
|||||||
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
<span class="font-semibold text-gray-900 dark:text-white">TimeTracker</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
<UNavigationMenu :items="menuItems" class="w-full" />
|
<UNavigationMenu :type="'trigger'" :ui="{
|
||||||
|
root: 'justify-center',
|
||||||
<!-- {{ router }} -->
|
list: 'gap-4'
|
||||||
<!-- <RouterLink :to="{ name: 'admin-products' }">
|
}" :items="menuItems" class="w-full"></UNavigationMenu>
|
||||||
products list
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'admin-product-detail' }">
|
|
||||||
product detail
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{
|
|
||||||
name: 'customer-product', params: {
|
|
||||||
product_id: '51'
|
|
||||||
}
|
|
||||||
}">
|
|
||||||
Product (51)
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'addresses' }">
|
|
||||||
Addresses
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'profile-details' }">
|
|
||||||
Customer Data
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{ name: 'cart' }">
|
|
||||||
Cart
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink :to="{
|
|
||||||
name: 'cart-details', params: {
|
|
||||||
cart_id: '1'
|
|
||||||
}
|
|
||||||
}">
|
|
||||||
Cart details (1)
|
|
||||||
</RouterLink> -->
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<!-- Language Switcher -->
|
<!-- Language Switcher -->
|
||||||
<LangSwitch />
|
<LangSwitch />
|
||||||
<!-- Theme Switcher -->
|
<!-- Theme Switcher -->
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
<!-- Logout Button (only when authenticated) -->
|
<!-- Logout Button (only when authenticated) -->
|
||||||
<button
|
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
|
||||||
v-if="authStore.isAuthenticated"
|
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)">
|
||||||
@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') }}
|
{{ $t('general.logout') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,39 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<suspense>
|
<suspense>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<div class="flex gap-10">
|
||||||
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
<CategoryMenu />
|
||||||
<template #item="{ item, active }">
|
<div class="w-full flex flex-col items-center gap-4">
|
||||||
<div class="flex items-center gap-2 px-3 py-2">
|
<UTable :data="productsList" :columns="columns" class="flex-1 w-full" />
|
||||||
<UIcon name="i-heroicons-book-open" />
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</UNavigationMenu> -->
|
|
||||||
<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">
|
|
||||||
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
<div v-else class="overflow-x-auto">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<CategoryMenuListing />
|
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
|
||||||
<template #expanded="{ row }">
|
|
||||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
|
||||||
thead: 'hidden'
|
|
||||||
}" />
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center items-center py-8">
|
|
||||||
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
|
|
||||||
</div>
|
|
||||||
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
||||||
No products found
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
@@ -46,26 +18,20 @@ import { useFetchJson } from '@/composable/useFetchJson'
|
|||||||
import Default from '@/layouts/default.vue'
|
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 CategoryMenuListing from '../inner/categoryMenuListing.vue'
|
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||||
|
import type { Product } from '@/types/product'
|
||||||
interface Product {
|
|
||||||
reference: number
|
|
||||||
product_id: number
|
|
||||||
name: string
|
|
||||||
image_link: string
|
|
||||||
link_rewrite: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
const perPage = ref(15)
|
||||||
const page = computed({
|
const page = computed({
|
||||||
get: () => Number(route.query.page) || 1,
|
get: () => Number(route.query.p) || 1,
|
||||||
set: (val: number) => {
|
set: (val: number) => {
|
||||||
router.push({
|
router.push({
|
||||||
query: {
|
query: {
|
||||||
...route.query,
|
...route.query,
|
||||||
page: val
|
p: val
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -101,7 +67,6 @@ const sortField = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const perPage = ref(15)
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
|
|
||||||
interface ApiResponse {
|
interface ApiResponse {
|
||||||
@@ -162,17 +127,25 @@ async function fetchProductList() {
|
|||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
Object.entries(route.query).forEach(([key, value]) => {
|
Object.entries(route.query).forEach(([key, value]) => {
|
||||||
if (value) params.append(key, String(value))
|
if (value === undefined || value === null) return
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(v => params.append(key, String(v)))
|
||||||
|
} else {
|
||||||
|
params.append(key, String(value))
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
if (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()}`
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ApiResponse>(url)
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
productsList.value = response.items || []
|
productsList.value = response.items || []
|
||||||
total.value = response.count || 0
|
total.value = Number(response.count) || 0
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
error.value = e instanceof Error ? e.message : 'Failed to load products'
|
||||||
} finally {
|
} finally {
|
||||||
@@ -182,16 +155,11 @@ async function fetchProductList() {
|
|||||||
|
|
||||||
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({
|
|
||||||
product_id: null,
|
|
||||||
count: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
function getIcon(name: string) {
|
function getIcon(name: string) {
|
||||||
if (sortField.value[0] === name) {
|
if (sortField.value[0] === name) {
|
||||||
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
||||||
@@ -205,25 +173,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',
|
|
||||||
cell: ({ row }) =>
|
|
||||||
h(UButton, {
|
|
||||||
color: 'neutral',
|
|
||||||
variant: 'ghost',
|
|
||||||
icon: 'i-lucide-chevron-down',
|
|
||||||
square: true,
|
|
||||||
'aria-label': 'Expand',
|
|
||||||
ui: {
|
|
||||||
leadingIcon: [
|
|
||||||
'transition-transform',
|
|
||||||
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
|
|
||||||
]
|
|
||||||
},
|
|
||||||
onClick: () => row.toggleExpanded()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'product_id',
|
accessorKey: 'product_id',
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
@@ -314,108 +264,17 @@ const columns: TableColumn<Payment>[] = [
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => row.getValue('quantity') as number
|
cell: ({ row }) => row.getValue('quantity') as number
|
||||||
},
|
},
|
||||||
{
|
|
||||||
accessorKey: 'count',
|
|
||||||
header: 'Count',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h(UInputNumber, {
|
|
||||||
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
|
||||||
'onUpdate:modelValue': (val: number) => {
|
|
||||||
if (val)
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: row.original.product_id,
|
|
||||||
count: val
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: null,
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: row.original.quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'count',
|
accessorKey: 'count',
|
||||||
header: '',
|
header: '',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return h(UButton, {
|
return h(UButton, {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('Clicked', row.original)
|
goToProduct(row.original.product_id)
|
||||||
},
|
},
|
||||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
color: 'primary',
|
||||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
|
||||||
variant: 'solid'
|
variant: 'solid'
|
||||||
}, 'Add to cart')
|
}, () => 'Show product')
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const columnsChild: TableColumn<Payment>[] = [
|
|
||||||
{
|
|
||||||
accessorKey: 'product_id',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'image_link',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h('img', {
|
|
||||||
src: row.getValue('image_link') as string,
|
|
||||||
style: 'width:40px;height:40px;object-fit:cover;'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'name',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => row.getValue('name') as string
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'quantity',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => row.getValue('quantity') as number
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'count',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h(UInputNumber, {
|
|
||||||
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
|
||||||
'onUpdate:modelValue': (val: number) => {
|
|
||||||
if (val)
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: row.original.product_id,
|
|
||||||
count: val
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
selectedCount.value = {
|
|
||||||
product_id: null,
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
max: row.original.quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'count',
|
|
||||||
header: '',
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return h(UButton, {
|
|
||||||
onClick: () => {
|
|
||||||
console.log('Clicked', row.original)
|
|
||||||
},
|
|
||||||
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
|
||||||
disabled: selectedCount.value.product_id !== row.original.product_id,
|
|
||||||
variant: 'solid'
|
|
||||||
}, 'Add to cart')
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
<div
|
<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">
|
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">
|
<div class="flex items-end gap-3">
|
||||||
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!" valueKey="iso_code">
|
<USelect v-model="selectedLanguage" :items="availableLangs" variant="outline" class="w-40!"
|
||||||
|
valueKey="iso_code">
|
||||||
<template #default="{ modelValue }">
|
<template #default="{ modelValue }">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
<span class="text-md">{{availableLangs.find(x => x.iso_code == modelValue)?.flag}}</span>
|
||||||
@@ -93,7 +94,8 @@
|
|||||||
<UButton v-if="isEditing" @click="saveText" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
<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>
|
<p class="dark:text-white text-black">Save the edited text</p>
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
<UButton v-if="isEditing" @click="cancelEdit" color="neutral" variant="outline"
|
||||||
|
class="p-2.5 cursor-pointer">
|
||||||
Cancel
|
Cancel
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,10 +112,12 @@
|
|||||||
<p class="text-white">Change Text</p>
|
<p class="text-white">Change Text</p>
|
||||||
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
<UIcon name="material-symbols-light:stylus-note-sharp" class="text-[30px] text-white!" />
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="saveDescription" color="neutral" variant="outline" class="p-2.5 cursor-pointer">
|
<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>
|
<p class="dark:text-white text-black ">Save the edited text</p>
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral" variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
<UButton v-if="descriptionEdit.isEditing.value" @click="cancelDescriptionEdit" color="neutral"
|
||||||
|
variant="outline" class="p-2.5 cursor-pointer">Cancel</UButton>
|
||||||
</div>
|
</div>
|
||||||
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
<div ref="descriptionRef" v-html="productStore.productDescription.description"
|
||||||
class="flex flex-col justify-center dark:text-white text-black">
|
class="flex flex-col justify-center dark:text-white text-black">
|
||||||
@@ -142,7 +146,7 @@ const isEditing = ref(false)
|
|||||||
|
|
||||||
const availableLangs = computed(() => langs)
|
const availableLangs = computed(() => langs)
|
||||||
|
|
||||||
const selectedLanguage = ref('pl')
|
const selectedLanguage = ref('en')
|
||||||
|
|
||||||
const currentLangId = ref(2)
|
const currentLangId = ref(2)
|
||||||
const productID = ref<number>(0)
|
const productID = ref<number>(0)
|
||||||
@@ -176,7 +180,7 @@ const translateToSelectedLanguage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const id = route.params.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(selectedLanguage.value)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<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') }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<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('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">
|
<div class="flex md:flex-row flex-col justify-between items-start md:items-center gap-5 md:gap-0">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20 flex flex-col gap-5 md:gap-10">
|
<div class="container mx-auto flex flex-col gap-5 md:gap-10">
|
||||||
<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 Cart') }}</h1>
|
||||||
<div class="flex flex-col lg:flex-row gap-5 md:gap-10">
|
<div class="flex flex-col lg:flex-row gap-5 md:gap-10">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
9
bo/src/components/customer/PageOrders.vue
Normal file
9
bo/src/components/customer/PageOrders.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
Orders page
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Default from '@/layouts/default.vue'
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mt-20 mx-auto">
|
<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] ">
|
||||||
|
|||||||
430
bo/src/components/customer/PageProducts.vue
Normal file
430
bo/src/components/customer/PageProducts.vue
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
<template>
|
||||||
|
<suspense>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<!-- <UNavigationMenu orientation="vertical" :items="listing" class="data-[orientation=vertical]:w-48">
|
||||||
|
<template #item="{ item, active }">
|
||||||
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
|
<UIcon name="i-heroicons-book-open" />
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UNavigationMenu> -->
|
||||||
|
<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">
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">Loading products...</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error" class="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="overflow-x-auto">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<CategoryMenu />
|
||||||
|
<UTable :data="productsList" :columns="columns" class="flex-1">
|
||||||
|
<template #expanded="{ row }">
|
||||||
|
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
||||||
|
thead: 'hidden'
|
||||||
|
}" />
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center py-8">
|
||||||
|
<UPagination v-model:page="page" :total="total" :page-size="perPage" />
|
||||||
|
</div>
|
||||||
|
<div v-if="productsList.length === 0" class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||||
|
No products found
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</component>
|
||||||
|
</suspense>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
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 type { TableColumn } from '@nuxt/ui'
|
||||||
|
import CategoryMenu from '../inner/categoryMenu.vue'
|
||||||
|
|
||||||
|
interface Product {
|
||||||
|
reference: number
|
||||||
|
product_id: number
|
||||||
|
name: string
|
||||||
|
image_link: string
|
||||||
|
link_rewrite: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const page = computed({
|
||||||
|
get: () => Number(route.query.page) || 1,
|
||||||
|
set: (val: number) => {
|
||||||
|
router.push({
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
page: 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
|
||||||
|
|
||||||
|
let 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 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>>({
|
||||||
|
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(key => {
|
||||||
|
if (!['page', 'sort', 'direction'].includes(key)) {
|
||||||
|
delete baseQuery[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
router.push({
|
||||||
|
name: 'product-detail',
|
||||||
|
params: { id: productId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedCount = ref({
|
||||||
|
product_id: null,
|
||||||
|
count: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
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 UInputNumber = resolveComponent('UInputNumber')
|
||||||
|
const UInput = resolveComponent('UInput')
|
||||||
|
const UButton = resolveComponent('UButton')
|
||||||
|
const UIcon = resolveComponent('UIcon')
|
||||||
|
|
||||||
|
const columns: TableColumn<Payment>[] = [
|
||||||
|
{
|
||||||
|
id: 'expand',
|
||||||
|
cell: ({ row }) =>
|
||||||
|
h(UButton, {
|
||||||
|
color: 'neutral',
|
||||||
|
variant: 'ghost',
|
||||||
|
icon: 'i-lucide-chevron-down',
|
||||||
|
square: true,
|
||||||
|
'aria-label': 'Expand',
|
||||||
|
ui: {
|
||||||
|
leadingIcon: [
|
||||||
|
'transition-transform',
|
||||||
|
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
|
||||||
|
]
|
||||||
|
},
|
||||||
|
onClick: () => row.toggleExpanded()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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;'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: ({ column }) => {
|
||||||
|
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: 'Count',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UInputNumber, {
|
||||||
|
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
||||||
|
'onUpdate:modelValue': (val: number) => {
|
||||||
|
if (val)
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: row.original.product_id,
|
||||||
|
count: val
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: null,
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
max: row.original.quantity
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
onClick: () => {
|
||||||
|
console.log('Clicked', row.original)
|
||||||
|
},
|
||||||
|
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||||
|
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||||
|
variant: 'solid'
|
||||||
|
}, 'Add to cart')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const columnsChild: TableColumn<Payment>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'product_id',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => `#${row.getValue('product_id') as number}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'image_link',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h('img', {
|
||||||
|
src: row.getValue('image_link') as string,
|
||||||
|
style: 'width:40px;height:40px;object-fit:cover;'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => row.getValue('name') as string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'quantity',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => row.getValue('quantity') as number
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UInputNumber, {
|
||||||
|
modelValue: selectedCount.value.product_id === row.original.product_id ? selectedCount.value.count : 0,
|
||||||
|
'onUpdate:modelValue': (val: number) => {
|
||||||
|
if (val)
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: row.original.product_id,
|
||||||
|
count: val
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedCount.value = {
|
||||||
|
product_id: null,
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
max: row.original.quantity
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'count',
|
||||||
|
header: '',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return h(UButton, {
|
||||||
|
onClick: () => {
|
||||||
|
console.log('Clicked', row.original)
|
||||||
|
},
|
||||||
|
color: selectedCount.value.product_id !== row.original.product_id ? 'info' : 'primary',
|
||||||
|
disabled: selectedCount.value.product_id !== row.original.product_id,
|
||||||
|
variant: 'solid'
|
||||||
|
}, 'Add to cart')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
() => {
|
||||||
|
fetchProductList()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="Default || 'div'">
|
<component :is="Default || 'div'">
|
||||||
<div class="container mx-auto mt-20">
|
<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>
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
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">
|
||||||
<UForm @submit.prevent="saveAccount" :validate="validate" class="space-y-6">
|
<UForm @submit.prevent="saveAccount" :validate="validate" class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
<h2
|
||||||
|
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-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Company Information') }}
|
{{ t('Company Information') }}
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
t('Company Email') }} *</label>
|
t('Company Email') }} *</label>
|
||||||
<UInput v-model="formData.companyEmail" type="email"
|
<UInput v-model="formData.companyEmail" type="email"
|
||||||
:placeholder="t('Enter company email')" name="companyEmail" class="w-full" />
|
:placeholder="t('Enter company email')" name="companyEmail"
|
||||||
|
class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
@@ -33,13 +35,15 @@
|
|||||||
class="w-full" />
|
class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{ t('NIP')
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
|
t('NIP')
|
||||||
}}</label>
|
}}</label>
|
||||||
<UInput v-model="formData.nip" :placeholder="t('Enter NIP')" name="nip"
|
<UInput v-model="formData.nip" :placeholder="t('Enter NIP')" name="nip"
|
||||||
class="w-full" />
|
class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{ t('VAT')
|
<label class="block text-sm font-medium text-black dark:text-white mb-1">{{
|
||||||
|
t('VAT')
|
||||||
}}</label>
|
}}</label>
|
||||||
<UInput v-model="formData.vat" :placeholder="t('Enter VAT')" name="vat"
|
<UInput v-model="formData.vat" :placeholder="t('Enter VAT')" name="vat"
|
||||||
class="w-full" />
|
class="w-full" />
|
||||||
@@ -47,15 +51,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-black dark:text-white mb-4 flex items-center gap-2">
|
<h2
|
||||||
|
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-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
{{ t('Select Addresses') }}
|
{{ t('Select Addresses') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div
|
<div class="bg-(--second-light) dark:bg-(--main-dark)">
|
||||||
class="bg-(--second-light) dark:bg-(--main-dark)">
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<UInput v-model="addressSearchQuery" type="text" :placeholder="t('Search address')"
|
<UInput v-model="addressSearchQuery" type="text"
|
||||||
|
:placeholder="t('Search address')"
|
||||||
class="w-full bg-white dark:bg-(--black) text-black dark:text-white" />
|
class="w-full bg-white dark:bg-(--black) text-black dark:text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="addressStore.filteredAddresses.length > 0" class="space-y-3">
|
<div v-if="addressStore.filteredAddresses.length > 0" class="space-y-3">
|
||||||
@@ -67,10 +72,13 @@
|
|||||||
<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-(--accent-blue-light) dark:text-(--accent-blue-dark)" />
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="text-black dark:text-white font-medium">{{ address.street }}</p>
|
<p class="text-black dark:text-white font-medium">{{ address.street }}
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode }},
|
</p>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.zipCode
|
||||||
|
}},
|
||||||
{{ address.city }}</p>
|
{{ address.city }}</p>
|
||||||
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.country }}
|
<p class="text-gray-600 dark:text-gray-400 text-sm">{{ address.country
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -170,7 +178,7 @@ function saveAccount() {
|
|||||||
vat: formData.value.vat,
|
vat: formData.value.vat,
|
||||||
companyAddressId: formData.value.companyAddressId,
|
companyAddressId: formData.value.companyAddressId,
|
||||||
billingAddressId: formData.value.billingAddressId,
|
billingAddressId: formData.value.billingAddressId,
|
||||||
companyAddress : ''
|
companyAddress: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
router.push({ name: 'profile-details' })
|
router.push({ name: 'profile-details' })
|
||||||
|
|||||||
9
bo/src/components/customer/PageStatistic.vue
Normal file
9
bo/src/components/customer/PageStatistic.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<component :is="Default || 'div'">
|
||||||
|
Statistic page
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Default from '@/layouts/default.vue'
|
||||||
|
</script>
|
||||||
@@ -1,20 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<UNavigationMenu orientation="vertical" type="single" :items="items" class="data-[orientation=vertical]:w-72" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getMenu } from '@/router/menu'
|
import { getMenu } from '@/router/menu'
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
let menu = await getMenu() as NavigationMenuItem[]
|
let menu = await getMenu() as NavigationMenuItem[]
|
||||||
|
|
||||||
const openAll = ref(false)
|
const openAll = ref(false)
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
let categoryId = ref(route.params.category_id)
|
||||||
|
function findPath(tree: NavigationMenuItem[], id: number, path: Array<number> = []): Array<number> | null {
|
||||||
|
for (let item of tree) {
|
||||||
|
let newPath: Array<number> = [...path, item.category_id]
|
||||||
|
if (item.category_id === id) {
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
const result: Array<number> | null = findPath(item.children, id, newPath)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = findPath(menu, Number(categoryId.value))
|
||||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
function adaptMenu(menu: NavigationMenuItem[]) {
|
||||||
for (const item of menu) {
|
for (const item of menu) {
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
console.log(item);
|
item.open = path && path.includes(item.category_id) ? true : openAll.value
|
||||||
adaptMenu(item.children);
|
adaptMenu(item.children);
|
||||||
item.open = openAll.value
|
item.children.unshift({
|
||||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
|
label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: {
|
||||||
|
name: 'customer-products-category', params: {
|
||||||
|
category_id: item.params.category_id,
|
||||||
|
link_rewrite: item.params.link_rewrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
item.to = { name: 'category', params: item.params };
|
item.to = {
|
||||||
|
name: 'customer-products-category', params: {
|
||||||
|
category_id: item.params.category_id,
|
||||||
|
link_rewrite: item.params.link_rewrite
|
||||||
|
}
|
||||||
|
};
|
||||||
item.icon = 'i-lucide-file-text'
|
item.icon = 'i-lucide-file-text'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +59,6 @@ function adaptMenu(menu: NavigationMenuItem[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menu = adaptMenu(menu)
|
menu = adaptMenu(menu)
|
||||||
|
|
||||||
const items = ref<NavigationMenuItem[][]>([
|
const items = ref<NavigationMenuItem[][]>([
|
||||||
[
|
[
|
||||||
...menu as NavigationMenuItem[]
|
...menu as NavigationMenuItem[]
|
||||||
@@ -30,7 +66,3 @@ const items = ref<NavigationMenuItem[][]>([
|
|||||||
|
|
||||||
])
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
|
|
||||||
</template>
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { getMenu } from '@/router/menu'
|
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
let menu = await getMenu() as NavigationMenuItem[]
|
|
||||||
|
|
||||||
const openAll = ref(false)
|
|
||||||
|
|
||||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
|
||||||
for (const item of menu) {
|
|
||||||
if (item.children && item.children.length > 0) {
|
|
||||||
adaptMenu(item.children);
|
|
||||||
item.open = openAll.value
|
|
||||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label, to: { name: 'category', params: item.params } })
|
|
||||||
} else {
|
|
||||||
item.to = { name: 'category', params: item.params };
|
|
||||||
item.icon = 'i-lucide-file-text'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = adaptMenu(menu)
|
|
||||||
|
|
||||||
const items = ref<NavigationMenuItem[][]>([
|
|
||||||
[
|
|
||||||
...menu as NavigationMenuItem[]
|
|
||||||
],
|
|
||||||
|
|
||||||
])
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UNavigationMenu orientation="vertical" type="single" :items="items" class="p-4" />
|
|
||||||
</template>
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
{{ locale }}
|
<USelectMenu v-model="locale" :items="langs"
|
||||||
<USelectMenu v-model="locale" :items="langs" class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!"
|
class="w-40 bg-(--main-light) dark:bg-(--black) rounded-md shadow-sm hover:none!" valueKey="iso_code"
|
||||||
valueKey="iso_code" :searchInput="false">
|
:searchInput="false">
|
||||||
<template #default="{ modelValue }">
|
<template #default="{ modelValue }">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span class="text-md dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.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>
|
<span class="font-medium dark:text-white text-black">{{langs.find(x => x.iso_code == modelValue)?.name}}</span>
|
||||||
</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 rounded-md cursor-pointer transition-colors">
|
||||||
<span class="text-md ">{{ item.flag }}</span>
|
<!-- <span class="text-md ">{{ item.flag }}</span> -->
|
||||||
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
<span class="ml-2 dark:text-white text-black font-medium">{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,6 +23,7 @@ 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()
|
||||||
|
|
||||||
@@ -50,9 +51,22 @@ const locale = computed({
|
|||||||
router.replace({ path: '/' + value + currentPath, query: route.query })
|
router.replace({ path: '/' + value + currentPath, query: route.query })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeLang()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import TopBar from '@/components/TopBar.vue';
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
||||||
<main class="p-10">
|
<main class="pt-20 pb-10">
|
||||||
<TopBar/>
|
<TopBar />
|
||||||
<slot></slot>
|
<div class="container mx-auto px-4">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -49,6 +49,41 @@ const router = createRouter({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const viewModules = import.meta.glob('/src/views/**/*.vue')
|
||||||
|
const componentModules = import.meta.glob('/src/components/**/*.vue')
|
||||||
|
|
||||||
|
async function setRoutes() {
|
||||||
|
const routes = await getRoutes()
|
||||||
|
|
||||||
|
for (const item of routes) {
|
||||||
|
const componentName = item.component
|
||||||
|
const [, folder] = componentName.split('/')
|
||||||
|
const componentPath = `/src${componentName}`
|
||||||
|
|
||||||
|
|
||||||
|
let modules =
|
||||||
|
folder === 'views' ? viewModules : componentModules
|
||||||
|
|
||||||
|
const importer = modules[componentPath]
|
||||||
|
|
||||||
|
if (!importer) {
|
||||||
|
console.error('Component not found:', componentPath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const importedComponent = (await importer()).default
|
||||||
|
|
||||||
|
router.addRoute('locale', {
|
||||||
|
path: item.path,
|
||||||
|
component: importedComponent,
|
||||||
|
name: item.name,
|
||||||
|
meta: item.meta ? JSON.parse(item.meta) : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await setRoutes()
|
||||||
|
|
||||||
router.beforeEach((to, from) => {
|
router.beforeEach((to, from) => {
|
||||||
const locale = to.params.locale as string
|
const locale = to.params.locale as string
|
||||||
const localeLang = langs.find((x) => x.iso_code === locale)
|
const localeLang = langs.find((x) => x.iso_code === locale)
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { useFetchJson } from "@/composable/useFetchJson";
|
import { useFetchJson } from "@/composable/useFetchJson";
|
||||||
import type { MenuItem, Route } from "@/types/menu";
|
import type { MenuItem, Route } from "@/types/menu";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { settings } from "./settings";
|
||||||
|
|
||||||
|
|
||||||
|
const categoryId = ref()
|
||||||
export const getMenu = async () => {
|
export const getMenu = async () => {
|
||||||
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-menu');
|
if(!categoryId.value){
|
||||||
|
categoryId.value = settings['app'].category_tree_root_id
|
||||||
|
}
|
||||||
|
const resp = await useFetchJson<MenuItem>(`/api/v1/restricted/menu/get-category-tree?root_category_id=${categoryId.value}`);
|
||||||
return resp.items.children
|
return resp.items.children
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getRoutes = async () => {
|
export const getRoutes = async () => {
|
||||||
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
const resp = await useFetchJson<Route[]>('/api/v1/public/menu/get-routes');
|
||||||
|
|
||||||
return resp.items
|
return resp.items
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,24 +13,24 @@ const products = [
|
|||||||
// type CategoryProducts = {}
|
// type CategoryProducts = {}
|
||||||
|
|
||||||
export const useCategoryStore = defineStore('category', () => {
|
export const useCategoryStore = defineStore('category', () => {
|
||||||
const id_category = ref(0)
|
const idCategory = ref(0)
|
||||||
const categoryProducts = ref(products)
|
const categoryProducts = ref(products)
|
||||||
|
|
||||||
|
|
||||||
function setCategoryID(id: number) {
|
function setCategoryID(id: number) {
|
||||||
id_category.value = id
|
idCategory.value = id
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCategoryProducts() {
|
async function getCategoryProducts() {
|
||||||
return new Promise<typeof products>((resolve) => {
|
return new Promise<typeof products>((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Fetching products from category id: ', id_category.value);
|
// console.log('Fetching products from category id: ', idCategory.value);
|
||||||
resolve(categoryProducts.value)
|
resolve(categoryProducts.value)
|
||||||
}, 2000 * Math.random())
|
}, 2000 * Math.random())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id_category,
|
idCategory,
|
||||||
getCategoryProducts,
|
getCategoryProducts,
|
||||||
setCategoryID
|
setCategoryID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,8 @@ export const useProductStore = defineStore('product', () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ProductDescription>(
|
const response = await useFetchJson<ProductDescription>(
|
||||||
`/api/v1/restricted/product-description/get-product-description?productID=${productID}&productLangID=${langId}`
|
`/api/v1/restricted/product-translation/get-product-description?productID=${productID}&productLangID=${langId}`
|
||||||
)
|
)
|
||||||
console.log(response, 'dfsfsdf');
|
|
||||||
productDescription.value = response.items
|
productDescription.value = response.items
|
||||||
|
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
9
bo/src/types/menu.d.ts
vendored
9
bo/src/types/menu.d.ts
vendored
@@ -31,12 +31,13 @@ export interface TopMenuItem {
|
|||||||
|
|
||||||
export interface Label {
|
export interface Label {
|
||||||
label: string
|
label: string
|
||||||
trans:LabelTrans
|
trans: LabelTrans
|
||||||
|
icon?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LabelTrans{
|
export interface LabelTrans {
|
||||||
pl:LabelItem
|
pl: LabelItem
|
||||||
en:LabelItem
|
en: LabelItem
|
||||||
de: LabelItem
|
de: LabelItem
|
||||||
}
|
}
|
||||||
export interface LabelItem {
|
export interface LabelItem {
|
||||||
|
|||||||
10
bo/src/types/product.d.ts
vendored
10
bo/src/types/product.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
export interface ProductDescription {
|
export interface ProductDescription {
|
||||||
id?: number
|
id?: number
|
||||||
name?: string
|
name?: string
|
||||||
description: string
|
description: string
|
||||||
@@ -7,3 +7,11 @@
|
|||||||
available_now: string
|
available_now: string
|
||||||
usage: string
|
usage: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
reference: number
|
||||||
|
product_id: number
|
||||||
|
name: string
|
||||||
|
image_link: string
|
||||||
|
link_rewrite: string
|
||||||
|
}
|
||||||
1
bo/src/types/settings.d.ts
vendored
1
bo/src/types/settings.d.ts
vendored
@@ -7,6 +7,7 @@ export interface Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface App {
|
export interface App {
|
||||||
|
category_tree_root_id: number
|
||||||
name: string
|
name: string
|
||||||
environment: string
|
environment: string
|
||||||
base_url: string
|
base_url: string
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ info:
|
|||||||
|
|
||||||
http:
|
http:
|
||||||
method: GET
|
method: GET
|
||||||
url: "{{bas_url}}/restricted/list-products/get-listing?p=1&elems=30&sort=product_id,asc&category_id_in=243&reference=~62"
|
url: "{{bas_url}}/restricted/list/list-products?p=1&elems=30&sort=product_id,asc&category_id_in=243&reference=~62"
|
||||||
params:
|
params:
|
||||||
- name: p
|
- name: p
|
||||||
value: "1"
|
value: "1"
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ http:
|
|||||||
type: json
|
type: json
|
||||||
data: |-
|
data: |-
|
||||||
{
|
{
|
||||||
"q": "kinder",
|
"q": "mat",
|
||||||
"limit": 50,
|
"limit": 50,
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
// "filter": "'attr.10'= 71",
|
"filter": "'category_ids'= 10",
|
||||||
"facets":["category_ids", "price"]
|
// "facets":["category_ids", "price"]
|
||||||
// "facets": ["category_ids", "attr", "feat", "price"]
|
"facets": ["category_ids", "attr", "feat", "price"]
|
||||||
}
|
}
|
||||||
auth:
|
auth:
|
||||||
type: bearer
|
type: bearer
|
||||||
|
|||||||
9
bruno/b2b-daniel/.gitignore
vendored
Normal file
9
bruno/b2b-daniel/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Secrets
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
15
bruno/b2b-daniel/add-new-cart.yml
Normal file
15
bruno/b2b-daniel/add-new-cart.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: add-new-cart
|
||||||
|
type: http
|
||||||
|
seq: 11
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/carts/add-new-cart
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
25
bruno/b2b-daniel/add-product-to-cart (1).yml
Normal file
25
bruno/b2b-daniel/add-product-to-cart (1).yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
info:
|
||||||
|
name: add-product-to-cart (1)
|
||||||
|
type: http
|
||||||
|
seq: 16
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/carts/add-product-to-cart?cart_id=1&product_id=51&amount=1
|
||||||
|
params:
|
||||||
|
- name: cart_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: product_id
|
||||||
|
value: "51"
|
||||||
|
type: query
|
||||||
|
- name: amount
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
28
bruno/b2b-daniel/add-product-to-cart.yml
Normal file
28
bruno/b2b-daniel/add-product-to-cart.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
info:
|
||||||
|
name: add-product-to-cart
|
||||||
|
type: http
|
||||||
|
seq: 15
|
||||||
|
|
||||||
|
http:
|
||||||
|
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
|
||||||
|
params:
|
||||||
|
- name: cart_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: product_id
|
||||||
|
value: "51"
|
||||||
|
type: query
|
||||||
|
- name: product_attribute_id
|
||||||
|
value: "1115"
|
||||||
|
type: query
|
||||||
|
- name: amount
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
bruno/b2b-daniel/change-cart-name.yml
Normal file
22
bruno/b2b-daniel/change-cart-name.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: change-cart-name
|
||||||
|
type: http
|
||||||
|
seq: 12
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/carts/change-cart-name?cart_id=1&new_name=test
|
||||||
|
params:
|
||||||
|
- name: cart_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: new_name
|
||||||
|
value: test
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
15
bruno/b2b-daniel/create-index.yml
Normal file
15
bruno/b2b-daniel/create-index.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: create-index
|
||||||
|
type: http
|
||||||
|
seq: 7
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/meili-search/create-index
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
bruno/b2b-daniel/get-breadcrumb.yml
Normal file
22
bruno/b2b-daniel/get-breadcrumb.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: get-breadcrumb
|
||||||
|
type: http
|
||||||
|
seq: 18
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/menu/get-breadcrumb?root_category_id=10&category_id=13
|
||||||
|
params:
|
||||||
|
- name: root_category_id
|
||||||
|
value: "10"
|
||||||
|
type: query
|
||||||
|
- name: category_id
|
||||||
|
value: "13"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
19
bruno/b2b-daniel/get-category-tree.yml
Normal file
19
bruno/b2b-daniel/get-category-tree.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
info:
|
||||||
|
name: get-category-tree
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/menu/get-category-tree?root_category_id=10
|
||||||
|
params:
|
||||||
|
- name: root_category_id
|
||||||
|
value: "10"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
17
bruno/b2b-daniel/get-indexes.yml
Normal file
17
bruno/b2b-daniel/get-indexes.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
info:
|
||||||
|
name: get-indexes
|
||||||
|
type: http
|
||||||
|
seq: 9
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:7700/indexes
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
bruno/b2b-daniel/get-product-description.yml
Normal file
22
bruno/b2b-daniel/get-product-description.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: get-product-description
|
||||||
|
type: http
|
||||||
|
seq: 17
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/product-translation/get-product-description?productID=51&productLangID=1
|
||||||
|
params:
|
||||||
|
- name: productID
|
||||||
|
value: "51"
|
||||||
|
type: query
|
||||||
|
- name: productLangID
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
15
bruno/b2b-daniel/get_countries.yml
Normal file
15
bruno/b2b-daniel/get_countries.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: get_countries
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/langs-and-countries/get-countries
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
21
bruno/b2b-daniel/list-products.yml
Normal file
21
bruno/b2b-daniel/list-products.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
params:
|
||||||
|
- name: p
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: elems
|
||||||
|
value: "10"
|
||||||
|
type: query
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
21
bruno/b2b-daniel/list-users.yml
Normal file
21
bruno/b2b-daniel/list-users.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
info:
|
||||||
|
name: list-users
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
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
|
||||||
21
bruno/b2b-daniel/opencollection.yml
Normal file
21
bruno/b2b-daniel/opencollection.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
opencollection: 1.0.0
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: b2b-daniel
|
||||||
|
config:
|
||||||
|
proxy:
|
||||||
|
inherit: true
|
||||||
|
config:
|
||||||
|
protocol: http
|
||||||
|
hostname: ""
|
||||||
|
port: ""
|
||||||
|
auth:
|
||||||
|
username: ""
|
||||||
|
password: ""
|
||||||
|
bypassProxy: ""
|
||||||
|
bundled: false
|
||||||
|
extensions:
|
||||||
|
bruno:
|
||||||
|
ignore:
|
||||||
|
- node_modules
|
||||||
|
- .git
|
||||||
17
bruno/b2b-daniel/remove-index.yml
Normal file
17
bruno/b2b-daniel/remove-index.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
info:
|
||||||
|
name: remove-index
|
||||||
|
type: http
|
||||||
|
seq: 8
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: DELETE
|
||||||
|
url: http://localhost:7700/indexes/meili_products_shop1_lang1
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
19
bruno/b2b-daniel/retrieve-cart.yml
Normal file
19
bruno/b2b-daniel/retrieve-cart.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
info:
|
||||||
|
name: retrieve-cart
|
||||||
|
type: http
|
||||||
|
seq: 14
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/carts/retrieve-cart?cart_id=3
|
||||||
|
params:
|
||||||
|
- name: cart_id
|
||||||
|
value: "3"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
15
bruno/b2b-daniel/retrieve-carts-info.yml
Normal file
15
bruno/b2b-daniel/retrieve-carts-info.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: retrieve-carts-info
|
||||||
|
type: http
|
||||||
|
seq: 13
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/carts/retrieve-carts-info
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
31
bruno/b2b-daniel/search.yml
Normal file
31
bruno/b2b-daniel/search.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
info:
|
||||||
|
name: search
|
||||||
|
type: http
|
||||||
|
seq: 10
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/meili-search/search?query=w&limit=4&id_category=0&price_lower_bound=60.0&price_upper_bound=70.0
|
||||||
|
params:
|
||||||
|
- name: query
|
||||||
|
value: w
|
||||||
|
type: query
|
||||||
|
- name: limit
|
||||||
|
value: "4"
|
||||||
|
type: query
|
||||||
|
- name: id_category
|
||||||
|
value: "0"
|
||||||
|
type: query
|
||||||
|
- name: price_lower_bound
|
||||||
|
value: "60.0"
|
||||||
|
type: query
|
||||||
|
- name: price_upper_bound
|
||||||
|
value: "70.0"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
15
bruno/b2b-daniel/test.yml
Normal file
15
bruno/b2b-daniel/test.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
info:
|
||||||
|
name: test
|
||||||
|
type: http
|
||||||
|
seq: 6
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/meili-search/test
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
bruno/b2b-daniel/update-choice.yml
Normal file
22
bruno/b2b-daniel/update-choice.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: update-choice
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: http://localhost:3000/api/v1/public/auth/update-choice?lang_id=0&country_id=1
|
||||||
|
params:
|
||||||
|
- name: lang_id
|
||||||
|
value: "0"
|
||||||
|
type: query
|
||||||
|
- name: country_id
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
4
bruno/folder.yml
Normal file
4
bruno/folder.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
info:
|
||||||
|
name: bruno
|
||||||
|
type: folder
|
||||||
|
seq: 18
|
||||||
2
go.mod
2
go.mod
@@ -5,6 +5,7 @@ go 1.26.0
|
|||||||
require (
|
require (
|
||||||
cloud.google.com/go/auth v0.16.4
|
cloud.google.com/go/auth v0.16.4
|
||||||
cloud.google.com/go/translate v1.12.7
|
cloud.google.com/go/translate v1.12.7
|
||||||
|
git.ma-al.com/goc_marek/gormcol v1.0.3
|
||||||
github.com/WinterYukky/gorm-extra-clause-plugin v0.4.0
|
github.com/WinterYukky/gorm-extra-clause-plugin v0.4.0
|
||||||
github.com/a-h/templ v0.3.1001
|
github.com/a-h/templ v0.3.1001
|
||||||
github.com/alecthomas/chroma v0.10.0
|
github.com/alecthomas/chroma v0.10.0
|
||||||
@@ -28,7 +29,6 @@ require (
|
|||||||
cloud.google.com/go v0.121.6 // indirect
|
cloud.google.com/go v0.121.6 // indirect
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||||
git.ma-al.com/goc_marek/gormcol v1.0.3 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
|||||||
11
go.sum
11
go.sum
@@ -217,17 +217,11 @@ go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2
|
|||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
|
||||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
@@ -244,17 +238,14 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
|
||||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
|
||||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||||
|
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
|
||||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
|
||||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ vue:
|
|||||||
- "**/dist/**"
|
- "**/dist/**"
|
||||||
- "**/.nuxt/**"
|
- "**/.nuxt/**"
|
||||||
- "**/.output/**"
|
- "**/.output/**"
|
||||||
|
tablename_prefix: "b2b_"
|
||||||
# Vue scope and components
|
# Vue scope and components
|
||||||
scope_id: 3
|
scope_id: 3
|
||||||
language_ids:
|
language_ids:
|
||||||
@@ -53,6 +54,7 @@ go:
|
|||||||
- "**/vendor/**"
|
- "**/vendor/**"
|
||||||
- "**/.git/**"
|
- "**/.git/**"
|
||||||
- "**/node_modules/**"
|
- "**/node_modules/**"
|
||||||
|
tablename_prefix: "b2b_"
|
||||||
# Go scope and components
|
# Go scope and components
|
||||||
scope_id: 1
|
scope_id: 1
|
||||||
scope_name: "Backend"
|
scope_name: "Backend"
|
||||||
@@ -73,6 +75,7 @@ go:
|
|||||||
# Legacy configuration (for backward compatibility)
|
# Legacy configuration (for backward compatibility)
|
||||||
# Used when vue/go sections are not present
|
# Used when vue/go sections are not present
|
||||||
save:
|
save:
|
||||||
|
tablename_prefix: "b2b_"
|
||||||
scope_id: 1
|
scope_id: 1
|
||||||
scope_name: "Default"
|
scope_name: "Default"
|
||||||
language_ids:
|
language_ids:
|
||||||
|
|||||||
Reference in New Issue
Block a user