Compare commits
10 Commits
03f04b2f53
...
translate_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7264a11ba6 | ||
| 04e238fd66 | |||
|
|
e0c53c97ba | ||
| 09a77c14c9 | |||
|
|
c7533a8deb | ||
| cf4d14a3cb | |||
| 30eb82ba53 | |||
| a2a2c35ab3 | |||
| 5feaa9e15c | |||
| fb4f7048ab |
@@ -20,6 +20,7 @@ type ProductDescription struct {
|
|||||||
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"`
|
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"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"
|
||||||
@@ -36,6 +37,27 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, 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 errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -52,10 +74,10 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
|||||||
|
|
||||||
// If it doesn't exist, returns an error.
|
// If it doesn't exist, returns an error.
|
||||||
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_lang uint) error {
|
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_lang uint) error {
|
||||||
record := model.ProductDescription{
|
record := dbmodel.PsProductLang{
|
||||||
ProductID: productID,
|
IDProduct: int32(productID),
|
||||||
ShopID: constdata.SHOP_ID,
|
IDShop: int32(constdata.SHOP_ID),
|
||||||
LangID: productid_lang,
|
IDLang: int32(productid_lang),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.Get().
|
err := db.Get().
|
||||||
|
|||||||
@@ -89,13 +89,24 @@ func (s *ProductTranslationService) GetProductDescription(userID uint, productID
|
|||||||
// Updates relevant fields with the "updates" map
|
// Updates relevant fields with the "updates" map
|
||||||
func (s *ProductTranslationService) SaveProductDescription(userID uint, productID uint, productLangID uint, updates map[string]string) error {
|
func (s *ProductTranslationService) SaveProductDescription(userID uint, productID uint, productLangID uint, updates map[string]string) error {
|
||||||
// only some fields can be affected
|
// only some fields can be affected
|
||||||
allowedFields := []string{"description", "description_short", "meta_description", "meta_title", "name", "available_now", "available_later", "usage"}
|
allowedFields := []string{"description", "description_short", "link_rewrite", "meta_description", "meta_keywords", "meta_title", "name",
|
||||||
|
"available_now", "available_later", "delivery_in_stock", "delivery_out_stock", "usage"}
|
||||||
for key := range updates {
|
for key := range updates {
|
||||||
if !slices.Contains(allowedFields, key) {
|
if !slices.Contains(allowedFields, key) {
|
||||||
return responseErrors.ErrBadField
|
return responseErrors.ErrBadField
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if text, exists := updates["link_rewrite"]; exists {
|
||||||
|
// sanitize and check that link_rewrite is a valid url slug
|
||||||
|
sanitized := SanitizeSlug(text)
|
||||||
|
if !IsValidSlug(sanitized) {
|
||||||
|
return responseErrors.ErrInvalidURLSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
updates["link_rewrite"] = sanitized
|
||||||
|
}
|
||||||
|
|
||||||
// check that fields description, description_short and usage, if they exist, have a valid html format
|
// check that fields description, description_short and usage, if they exist, have a valid html format
|
||||||
mustBeHTML := []string{"description", "description_short", "usage"}
|
mustBeHTML := []string{"description", "description_short", "usage"}
|
||||||
for i := 0; i < len(mustBeHTML); i++ {
|
for i := 0; i < len(mustBeHTML); i++ {
|
||||||
@@ -136,20 +147,28 @@ func (s *ProductTranslationService) TranslateProductDescription(userID uint, pro
|
|||||||
|
|
||||||
fields := []*string{&productDescription.Description,
|
fields := []*string{&productDescription.Description,
|
||||||
&productDescription.DescriptionShort,
|
&productDescription.DescriptionShort,
|
||||||
|
&productDescription.LinkRewrite,
|
||||||
&productDescription.MetaDescription,
|
&productDescription.MetaDescription,
|
||||||
|
&productDescription.MetaKeywords,
|
||||||
&productDescription.MetaTitle,
|
&productDescription.MetaTitle,
|
||||||
&productDescription.Name,
|
&productDescription.Name,
|
||||||
&productDescription.AvailableNow,
|
&productDescription.AvailableNow,
|
||||||
&productDescription.AvailableLater,
|
&productDescription.AvailableLater,
|
||||||
|
&productDescription.DeliveryInStock,
|
||||||
|
&productDescription.DeliveryOutStock,
|
||||||
&productDescription.Usage,
|
&productDescription.Usage,
|
||||||
}
|
}
|
||||||
keys := []string{"translation_of_product_description",
|
keys := []string{"translation_of_product_description",
|
||||||
"translation_of_product_short_description",
|
"translation_of_product_short_description",
|
||||||
|
"translation_of_product_url_link",
|
||||||
"translation_of_product_meta_description",
|
"translation_of_product_meta_description",
|
||||||
|
"translation_of_product_meta_keywords",
|
||||||
"translation_of_product_meta_title",
|
"translation_of_product_meta_title",
|
||||||
"translation_of_product_name",
|
"translation_of_product_name",
|
||||||
"translation_of_product_available_now",
|
"translation_of_product_available_now_message",
|
||||||
"translation_of_product_available_later",
|
"translation_of_product_available_later_message",
|
||||||
|
"translation_of_product_delivery_in_stock_message",
|
||||||
|
"translation_of_product_delivery_out_stock_message",
|
||||||
"translation_of_product_usage",
|
"translation_of_product_usage",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
69
app/service/productTranslationService/sanitizeURLSlug.go
Normal file
69
app/service/productTranslationService/sanitizeURLSlug.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package productTranslationService
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||||
|
"github.com/dlclark/regexp2"
|
||||||
|
"golang.org/x/text/runes"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsValidSlug(s string) bool {
|
||||||
|
var slug_regex2 = regexp2.MustCompile(constdata.SLUG_REGEX, regexp2.None)
|
||||||
|
|
||||||
|
ok, _ := slug_regex2.MatchString(s)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func SanitizeSlug(s string) string {
|
||||||
|
s = strings.TrimSpace(strings.ToLower(s))
|
||||||
|
|
||||||
|
// First apply explicit transliteration for language-specific letters.
|
||||||
|
s = transliterateWithTable(s)
|
||||||
|
|
||||||
|
// Then normalize and strip any remaining combining marks.
|
||||||
|
s = removeDiacritics(s)
|
||||||
|
|
||||||
|
// Replace all non-alphanumeric runs with "-"
|
||||||
|
var non_alphanum_regex2 = regexp2.MustCompile(constdata.NON_ALNUM_REGEX, regexp2.None)
|
||||||
|
s, _ = non_alphanum_regex2.Replace(s, "-", -1, -1)
|
||||||
|
|
||||||
|
// Collapse repeated "-" and trim edges
|
||||||
|
var multi_dash_regex2 = regexp2.MustCompile(constdata.MULTI_DASH_REGEX, regexp2.None)
|
||||||
|
s, _ = multi_dash_regex2.Replace(s, "-", -1, -1)
|
||||||
|
|
||||||
|
s = strings.Trim(s, "-")
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func transliterateWithTable(s string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.Grow(len(s))
|
||||||
|
|
||||||
|
for _, r := range s {
|
||||||
|
if repl, ok := constdata.TRANSLITERATION_TABLE[r]; ok {
|
||||||
|
b.WriteString(repl)
|
||||||
|
} else {
|
||||||
|
b.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDiacritics(s string) string {
|
||||||
|
t := transform.Chain(
|
||||||
|
norm.NFD,
|
||||||
|
runes.Remove(runes.In(unicode.Mn)),
|
||||||
|
norm.NFC,
|
||||||
|
)
|
||||||
|
out, _, err := transform.String(t, s)
|
||||||
|
if err != nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -12,3 +12,28 @@ const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
|||||||
const DEFAULT_NEW_CART_NAME = "new cart"
|
const DEFAULT_NEW_CART_NAME = "new cart"
|
||||||
|
|
||||||
const USER_LOCALE = "user"
|
const USER_LOCALE = "user"
|
||||||
|
|
||||||
|
// Slug sanitization
|
||||||
|
const NON_ALNUM_REGEX = `[^a-z0-9]+`
|
||||||
|
const MULTI_DASH_REGEX = `-+`
|
||||||
|
const SLUG_REGEX = `^[a-z0-9]+(?:-[a-z0-9]+)*$`
|
||||||
|
|
||||||
|
// Currently supports only German+Polish specific cases
|
||||||
|
var TRANSLITERATION_TABLE = map[rune]string{
|
||||||
|
// German
|
||||||
|
'ä': "ae",
|
||||||
|
'ö': "oe",
|
||||||
|
'ü': "ue",
|
||||||
|
'ß': "ss",
|
||||||
|
|
||||||
|
// Polish
|
||||||
|
'ą': "a",
|
||||||
|
'ć': "c",
|
||||||
|
'ę': "e",
|
||||||
|
'ł': "l",
|
||||||
|
'ń': "n",
|
||||||
|
'ó': "o",
|
||||||
|
'ś': "s",
|
||||||
|
'ż': "z",
|
||||||
|
'ź': "z",
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ var (
|
|||||||
// Typed errors for product description handler
|
// Typed errors for product description handler
|
||||||
ErrBadAttribute = errors.New("bad or missing attribute value in header")
|
ErrBadAttribute = errors.New("bad or missing attribute value in header")
|
||||||
ErrBadField = errors.New("this field can not be updated")
|
ErrBadField = errors.New("this field can not be updated")
|
||||||
|
ErrInvalidURLSlug = errors.New("URL slug does not obey the industry standard")
|
||||||
ErrInvalidXHTML = errors.New("text is not in xhtml format")
|
ErrInvalidXHTML = errors.New("text is not in xhtml format")
|
||||||
ErrAIResponseFail = errors.New("AI responded with failure")
|
ErrAIResponseFail = errors.New("AI responded with failure")
|
||||||
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
||||||
@@ -136,6 +137,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
|||||||
return i18n.T_(c, "error.err_bad_attribute")
|
return i18n.T_(c, "error.err_bad_attribute")
|
||||||
case errors.Is(err, ErrBadField):
|
case errors.Is(err, ErrBadField):
|
||||||
return i18n.T_(c, "error.err_bad_field")
|
return i18n.T_(c, "error.err_bad_field")
|
||||||
|
case errors.Is(err, ErrInvalidURLSlug):
|
||||||
|
return i18n.T_(c, "error.invalid_url_slug")
|
||||||
case errors.Is(err, ErrInvalidXHTML):
|
case errors.Is(err, ErrInvalidXHTML):
|
||||||
return i18n.T_(c, "error.err_invalid_html")
|
return i18n.T_(c, "error.err_invalid_html")
|
||||||
case errors.Is(err, ErrAIResponseFail):
|
case errors.Is(err, ErrAIResponseFail):
|
||||||
@@ -195,6 +198,7 @@ func GetErrorStatus(err error) int {
|
|||||||
errors.Is(err, ErrInvalidPassword),
|
errors.Is(err, ErrInvalidPassword),
|
||||||
errors.Is(err, ErrBadAttribute),
|
errors.Is(err, ErrBadAttribute),
|
||||||
errors.Is(err, ErrBadField),
|
errors.Is(err, ErrBadField),
|
||||||
|
errors.Is(err, ErrInvalidURLSlug),
|
||||||
errors.Is(err, ErrInvalidXHTML),
|
errors.Is(err, ErrInvalidXHTML),
|
||||||
errors.Is(err, ErrBadPaging),
|
errors.Is(err, ErrBadPaging),
|
||||||
errors.Is(err, ErrNoRootFound),
|
errors.Is(err, ErrNoRootFound),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="overflow-x-auto">
|
<div v-else class="overflow-x-auto">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<CategoryMenuListing />
|
<CategoryMenu />
|
||||||
<UTable :data="productsList" :columns="columns" class="flex-1">
|
<UTable :data="productsList" :columns="columns" class="flex-1">
|
||||||
<template #expanded="{ row }">
|
<template #expanded="{ row }">
|
||||||
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
<UTable :data="productsList.slice(0, 3)" :columns="columnsChild" :ui="{
|
||||||
@@ -46,7 +46,7 @@ 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'
|
||||||
|
|
||||||
interface Product {
|
interface Product {
|
||||||
reference: number
|
reference: number
|
||||||
@@ -167,7 +167,7 @@ async function fetchProductList() {
|
|||||||
if (value) params.append(key, String(value))
|
if (value) params.append(key, String(value))
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = `/api/v1/restricted/list-products/get-listing?${params}`
|
const url = `/api/v1/restricted/list/list-products?${params}`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await useFetchJson<ApiResponse>(url)
|
const response = await useFetchJson<ApiResponse>(url)
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -59,7 +59,6 @@ async function setRoutes() {
|
|||||||
const componentName = item.component
|
const componentName = item.component
|
||||||
const [, folder] = componentName.split('/')
|
const [, folder] = componentName.split('/')
|
||||||
const componentPath = `/src${componentName}`
|
const componentPath = `/src${componentName}`
|
||||||
console.log(componentPath);
|
|
||||||
|
|
||||||
|
|
||||||
let modules =
|
let modules =
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
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');
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
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
|
||||||
|
|||||||
39
bruno/b2b-daniel/save-product-description.yml
Normal file
39
bruno/b2b-daniel/save-product-description.yml
Normal file
File diff suppressed because one or more lines are too long
28
bruno/b2b-daniel/translate-product-description.yml
Normal file
28
bruno/b2b-daniel/translate-product-description.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
info:
|
||||||
|
name: translate-product-description
|
||||||
|
type: http
|
||||||
|
seq: 20
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: http://localhost:3000/api/v1/restricted/product-translation/translate-product-description?productID=51&productFromLangID=1&productToLangID=3&model=Google
|
||||||
|
params:
|
||||||
|
- name: productID
|
||||||
|
value: "51"
|
||||||
|
type: query
|
||||||
|
- name: productFromLangID
|
||||||
|
value: "1"
|
||||||
|
type: query
|
||||||
|
- name: productToLangID
|
||||||
|
value: "3"
|
||||||
|
type: query
|
||||||
|
- name: model
|
||||||
|
value: Google
|
||||||
|
type: query
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
1
storage/folder/a.txt
Normal file
1
storage/folder/a.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is a test.
|
||||||
Reference in New Issue
Block a user