Compare commits
3 Commits
012058b998
...
img_button
Author | SHA1 | Date | |
---|---|---|---|
c18497b3fa | |||
bf317d2e34 | |||
fd4b122936 |
2
app.vue
2
app.vue
@ -10,5 +10,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// await userStore.checkIsLogged()
|
await userStore.checkIsLogged()
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* ===== Base toast style (applies to all toasts) ===== */
|
||||||
.Toastify__toast {
|
.Toastify__toast {
|
||||||
font-family: var(--font-inter);
|
font-family: var(--font-inter);
|
||||||
background-color: var(--color-bg-light);
|
background-color: var(--color-bg-light);
|
||||||
@ -7,29 +8,44 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Base toast style for dark mode ===== */
|
||||||
.dark .Toastify__toast {
|
.dark .Toastify__toast {
|
||||||
background-color: var(--color-bg-dark);
|
background-color: var(--color-bg-dark);
|
||||||
border: 1px solid var(--color-block);
|
border: 1px solid var(--color-block);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Error toast: red background and white text ===== */
|
||||||
.Toastify__toast--error {
|
.Toastify__toast--error {
|
||||||
background-color: #dc3545;
|
background-color: var(--color-bg-light);
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Error toast in dark mode: darker red background ===== */
|
||||||
.dark .Toastify__toast--error {
|
.dark .Toastify__toast--error {
|
||||||
background-color: #c82333;
|
background-color: var(--color-bg-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Default progress bar color (used for success and others) ===== */
|
||||||
.Toastify__progress-bar {
|
.Toastify__progress-bar {
|
||||||
background-color: var(--color-accent-green-dark);
|
background-color: var(--color-accent-green-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Error toast: custom progress bar color ===== */
|
||||||
|
.Toastify__toast--error .Toastify__progress-bar {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Success toast: icon color ===== */
|
||||||
.Toastify__toast--success .Toastify__toast-icon svg {
|
.Toastify__toast--success .Toastify__toast-icon svg {
|
||||||
fill: var(--color-accent-green-dark);
|
fill: var(--color-accent-green-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Error toast: icon color ===== */
|
||||||
|
.Toastify__toast--error .Toastify__toast-icon svg {
|
||||||
|
fill: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Close button color in dark mode ===== */
|
||||||
.dark .Toastify__close-button {
|
.dark .Toastify__close-button {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
@ -1,82 +0,0 @@
|
|||||||
export class TurnStilleCaptcha {
|
|
||||||
sitekey = "{{.SiteKey}}";
|
|
||||||
/**
|
|
||||||
* Initializes the TurnStilleCaptcha instance.
|
|
||||||
* Creates a container for the captcha and appends it to the target element.
|
|
||||||
* If the Cloudflare Turnstile script is already loaded, it runs the captcha.
|
|
||||||
* Otherwise, it loads the script and initializes the captcha with the given properties.
|
|
||||||
* @param {HTMLElement} target - The element to attach the captcha container to.
|
|
||||||
* @param {Object} [props={}] - Optional properties for captcha initialization, such as theme.
|
|
||||||
*/
|
|
||||||
|
|
||||||
constructor(target, props = {}) {
|
|
||||||
// create holder
|
|
||||||
this.holder = document.createElement("div");
|
|
||||||
this.holder.id = "turnstile-container";
|
|
||||||
this.theme = props.theme || "auto";
|
|
||||||
target.appendChild(this.holder);
|
|
||||||
|
|
||||||
|
|
||||||
// execute code
|
|
||||||
if (window.turnstile) {
|
|
||||||
this.runCaptcha();
|
|
||||||
} else {
|
|
||||||
this.loadCloudflareScript();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runCaptcha() {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (globalThis.turnstileInstance) {
|
|
||||||
window.turnstile.remove(globalThis.turnstileInstance);
|
|
||||||
}
|
|
||||||
globalThis.turnstileInstance = window.turnstile.render(this.holder, {
|
|
||||||
sitekey: this.sitekey,
|
|
||||||
theme: this.theme,
|
|
||||||
callback: (token) => {
|
|
||||||
if (token) {
|
|
||||||
const event = new CustomEvent("token", {
|
|
||||||
detail: token,
|
|
||||||
bubbles: true,
|
|
||||||
});
|
|
||||||
this.holder.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
const event = new CustomEvent("failure", {
|
|
||||||
detail: error,
|
|
||||||
bubbles: true,
|
|
||||||
});
|
|
||||||
this.holder.dispatchEvent(event);
|
|
||||||
window.turnstile.reset(globalThis.turnstileInstance);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadCloudflareScript() {
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.id = "turnstile-script";
|
|
||||||
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js";
|
|
||||||
// script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
||||||
script.async = true;
|
|
||||||
script.defer = true;
|
|
||||||
|
|
||||||
script.onload = () => {
|
|
||||||
const event = new CustomEvent("loaded", {
|
|
||||||
detail: "Turnstile script loaded",
|
|
||||||
bubbles: true,
|
|
||||||
});
|
|
||||||
this.holder.dispatchEvent(event);
|
|
||||||
|
|
||||||
this.runCaptcha();
|
|
||||||
};
|
|
||||||
script.onerror = () => {
|
|
||||||
const event = new CustomEvent("failure", {
|
|
||||||
detail: "Failed to load Turnstile script",
|
|
||||||
bubbles: true,
|
|
||||||
});
|
|
||||||
this.holder.dispatchEvent(event);
|
|
||||||
};
|
|
||||||
document.head.appendChild(script);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
var VerifyUrl = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
|
||||||
|
|
||||||
type TurnstileResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
ErrorCodes []string `json:"error-codes,omitempty"`
|
|
||||||
ChallengeTS string `json:"challenge_ts,omitempty"`
|
|
||||||
Hostname string `json:"hostname,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TurnStilleCaptcha struct {
|
|
||||||
SiteKey string
|
|
||||||
SecretKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeTurnstilleCaptchaJS(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/api/email/script.js", func(e *core.RequestEvent) error {
|
|
||||||
// www.abrasive.ma-al.pl
|
|
||||||
// siteKey: "0x4AAAAAABdgeAdu4Pxxovj3"
|
|
||||||
// secretKey: "0x4AAAAAABdgeHJDjMwmeX5aXaXGh6HWZbw"
|
|
||||||
|
|
||||||
settings, err := GetSettings(app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := JS.ReadFile("TurnStilleCaptcha.js")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
templ, err := template.New("test").Parse(string(file))
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
templ.Execute(&buf, map[string]interface{}{
|
|
||||||
"SiteKey": settings.SiteKey,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Response.Header().Set("Content-Type", "application/javascript")
|
|
||||||
return e.String(http.StatusOK, buf.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSettings(app *pocketbase.PocketBase) (*TurnStilleCaptcha, error) {
|
|
||||||
record, err := app.FindFirstRecordByFilter("settings", "key='turnstile'", nil)
|
|
||||||
|
|
||||||
settings := TurnStilleCaptcha{}
|
|
||||||
json.Unmarshal([]byte(record.GetString("value")), &settings)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &settings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func VerifyTurnstile(app *pocketbase.PocketBase, token, ip string) error {
|
|
||||||
conf, err := GetSettings(app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]string{"secret": conf.SecretKey, "response": token, "remoteip": ip}
|
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
resp, err := http.Post(VerifyUrl, "application/json", bytes.NewBuffer(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
var turnstileResp TurnstileResponse
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
json.Unmarshal(body, &turnstileResp)
|
|
||||||
|
|
||||||
if !turnstileResp.Success {
|
|
||||||
return errors.New(turnstileResp.ChallengeTS + ": " + strings.Join(turnstileResp.ErrorCodes, " "))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed TurnStilleCaptcha.js
|
|
||||||
var JS embed.FS
|
|
@ -1,61 +0,0 @@
|
|||||||
package custom
|
|
||||||
|
|
||||||
import (
|
|
||||||
"pocketbase/custom/cloudflare"
|
|
||||||
"pocketbase/custom/gtm"
|
|
||||||
"pocketbase/custom/mail"
|
|
||||||
"pocketbase/custom/manifest"
|
|
||||||
"pocketbase/custom/proxy"
|
|
||||||
"pocketbase/custom/seo"
|
|
||||||
"pocketbase/custom/supervise"
|
|
||||||
"pocketbase/custom/version"
|
|
||||||
webpconverter "pocketbase/custom/webpConverter"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadCustomCode(app *pocketbase.PocketBase, se *core.ServeEvent) error {
|
|
||||||
|
|
||||||
// include supervised subprocess if command provided
|
|
||||||
supervise.ServeSubprocessSupervisor(app, se)
|
|
||||||
|
|
||||||
// include serving js code from cloudflare
|
|
||||||
cloudflare.ServeTurnstilleCaptchaJS(app, se)
|
|
||||||
|
|
||||||
// include sending emails service
|
|
||||||
mail.ServeMailSender(app, se)
|
|
||||||
|
|
||||||
// include robots.txt endpoint
|
|
||||||
seo.ServeRobotsTxt(app, se)
|
|
||||||
|
|
||||||
// include feeds endpoint
|
|
||||||
seo.ServeFeeds(app, se)
|
|
||||||
seo.ServeFeedsIndex(app, se)
|
|
||||||
|
|
||||||
// include proxy to serve nuxt app
|
|
||||||
proxy.ServeProxyPassingToNuxt(app, se)
|
|
||||||
|
|
||||||
// create endpoint to serve version information
|
|
||||||
version.ServeVersionInfo(app, se)
|
|
||||||
|
|
||||||
// include endpoint to server GTM script
|
|
||||||
gtm.ServeTagMangerJS(app, se)
|
|
||||||
|
|
||||||
// include endpoint serving manifest
|
|
||||||
manifest.ServeManifst(app, se)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtendApp(app *pocketbase.PocketBase) *pocketbase.PocketBase {
|
|
||||||
app.RootCmd.PersistentFlags().String("proxy", "", "inner proxy target")
|
|
||||||
app.RootCmd.PersistentFlags().String("subcommand", "", "provide command with params like cli that will be executed and supervised by main process")
|
|
||||||
|
|
||||||
// include webp converter
|
|
||||||
app.OnRecordCreate().Bind(webpconverter.CreateEventHandler(app))
|
|
||||||
app.OnRecordUpdate().Bind(webpconverter.CreateEventHandler(app))
|
|
||||||
app.OnFileDownloadRequest().Bind(webpconverter.ThumbEventHandler(app))
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package gtm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ServeTagMangerJS(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/api/gtm/script.js", func(e *core.RequestEvent) error {
|
|
||||||
|
|
||||||
settings, err := GetSettings(app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := JS.ReadFile("gtm.js")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
templ, err := template.New("test").Parse(string(file))
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
templ.Execute(&buf, map[string]interface{}{
|
|
||||||
"GtagID": settings.GtagID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Response.Header().Set("Content-Type", "application/javascript")
|
|
||||||
return e.String(http.StatusOK, buf.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type GTagSettings struct {
|
|
||||||
GtagID string `json:"gtagID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSettings(app *pocketbase.PocketBase) (*GTagSettings, error) {
|
|
||||||
record, err := app.FindFirstRecordByFilter("settings", "key='gtagID'", nil)
|
|
||||||
|
|
||||||
settings := GTagSettings{}
|
|
||||||
json.Unmarshal([]byte(record.GetString("value")), &settings)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &settings, nil
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
export class GTM {
|
|
||||||
gtagID = "{{.GtagID}}";
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.insertScript(window, document, 'script', 'dataLayer', this.gtagID);
|
|
||||||
this.insertNoScript();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
insertScript(w, d, s, l, i) {
|
|
||||||
w[l] = w[l] || []; w[l].push({
|
|
||||||
'gtm.start':
|
|
||||||
new Date().getTime(), event: 'gtm.js'
|
|
||||||
}); var f = d.getElementsByTagName(s)[0],
|
|
||||||
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src =
|
|
||||||
'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
insertNoScript() {
|
|
||||||
const noscript = document.createElement("noscript");
|
|
||||||
const iframe = document.createElement("iframe");
|
|
||||||
iframe.src = "https://www.googletagmanager.com/ns.html?id=" + this.gtagID;
|
|
||||||
iframe.height = 0;
|
|
||||||
iframe.width = 0;
|
|
||||||
iframe.style = "display:none;visibility:hidden";
|
|
||||||
noscript.appendChild(iframe);
|
|
||||||
document.body.appendChild(noscript);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package gtm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed gtm.js
|
|
||||||
var JS embed.FS
|
|
@ -1,175 +0,0 @@
|
|||||||
package mail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/mail"
|
|
||||||
"pocketbase/custom/cloudflare"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EmailData struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Phone string `json:"phone"`
|
|
||||||
LangIso string `json:"lang_iso"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MailsSettings struct {
|
|
||||||
ReceiverMail string `json:"receiver_mail"`
|
|
||||||
ReceiverName string `json:"receiver_name"`
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeMailSender(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.POST("/api/email/send", func(e *core.RequestEvent) error {
|
|
||||||
// name := e.Request.PathValue("name")
|
|
||||||
|
|
||||||
data := EmailData{}
|
|
||||||
if err := e.BindBody(&data); err != nil {
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
app.Logger().Error(err.Error(), "type", "mail")
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
if pass, err := validateMX(data.Email); !pass && err != nil {
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
app.Logger().Error("Invalid email address.", "type", "mail", "error", err.Error())
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid email address."})
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Printf("e.Request.Body: %v IP: %s\n", data, e.RealIP())
|
|
||||||
|
|
||||||
err := cloudflare.VerifyTurnstile(app, data.Token, e.RealIP())
|
|
||||||
if err != nil {
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
app.Logger().Error("Captcha verification failed.", "type", "mail", "error", err.Error())
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": "Captcha verification failed."})
|
|
||||||
}
|
|
||||||
|
|
||||||
eamil_body, err := app.FindFirstRecordByFilter("email_template", fmt.Sprintf("name='contact_form'&&id_lang='%s'", data.LangIso), nil)
|
|
||||||
if err != nil {
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
app.Logger().Error("Template not available", "type", "mail", "error", err.Error())
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": "Template not available"})
|
|
||||||
}
|
|
||||||
templ, err := template.New("test").Parse(eamil_body.GetString("template"))
|
|
||||||
if err != nil {
|
|
||||||
app.Logger().Error("Template parsing error.", "type", "mail", "error", err.Error())
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": "Template parsing error."})
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
templ.Execute(&buf, map[string]interface{}{
|
|
||||||
"Data": time.Now().Local().Format("2006-01-02 15:04:05"),
|
|
||||||
"Name": data.Name,
|
|
||||||
"Email": data.Email,
|
|
||||||
"Message": strings.ReplaceAll(data.Message, "\n", "<br>"),
|
|
||||||
"Phone": data.Phone,
|
|
||||||
})
|
|
||||||
|
|
||||||
mailsSettings, err := getSettings(app)
|
|
||||||
if err != nil {
|
|
||||||
app.Logger().Error("Mails settings corrupted", "type", "mail", "error", err.Error())
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": "Mails settings corrupted"})
|
|
||||||
}
|
|
||||||
|
|
||||||
cc := []mail.Address{{Address: data.Email, Name: data.Name}}
|
|
||||||
to := []mail.Address{{Address: mailsSettings.ReceiverMail, Name: mailsSettings.ReceiverName}}
|
|
||||||
|
|
||||||
message := &mailer.Message{
|
|
||||||
From: mail.Address{
|
|
||||||
Address: e.App.Settings().Meta.SenderAddress,
|
|
||||||
Name: e.App.Settings().Meta.SenderName,
|
|
||||||
},
|
|
||||||
Cc: cc,
|
|
||||||
To: to,
|
|
||||||
Subject: mailsSettings.Subject,
|
|
||||||
HTML: buf.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = e.App.NewMailClient().Send(message)
|
|
||||||
if err != nil {
|
|
||||||
app.Logger().Error("Mails sending error", "type", "mail", "error", err.Error())
|
|
||||||
e.Response.Header().Set("Content-Type", "application/json")
|
|
||||||
return e.JSON(http.StatusBadRequest, map[string]string{"error": "Mails sending error"})
|
|
||||||
}
|
|
||||||
|
|
||||||
receivers := formReceiversList(to, cc, []mail.Address{})
|
|
||||||
app.Logger().Info("Mail Sent", "type", "mail", "receivers", receivers)
|
|
||||||
return e.JSON(http.StatusOK, map[string]string{"status": "success", "message": "Email sent successfully. to " + receivers})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func formReceiversList(to []mail.Address, cc []mail.Address, bcc []mail.Address) string {
|
|
||||||
|
|
||||||
res := ""
|
|
||||||
|
|
||||||
for _, a := range to {
|
|
||||||
res = fmt.Sprintf("%s %s<%s>", res, a.Name, a.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range cc {
|
|
||||||
res = fmt.Sprintf("%s %s<%s>", res, a.Name, a.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range bcc {
|
|
||||||
res = fmt.Sprintf("%s %s<%s>", res, a.Name, a.Address)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
func getSettings(app *pocketbase.PocketBase) (*MailsSettings, error) {
|
|
||||||
record, err := app.FindFirstRecordByFilter("settings", "key='contact_page'", nil)
|
|
||||||
|
|
||||||
settings := MailsSettings{}
|
|
||||||
json.Unmarshal([]byte(record.GetString("value")), &settings)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &settings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractDomain(email string) string {
|
|
||||||
parts := strings.Split(email, "@")
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(parts[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateMX checks if the domain has valid MX records or A records as a fallback
|
|
||||||
func validateMX(email string) (bool, error) {
|
|
||||||
// Check MX records
|
|
||||||
|
|
||||||
domain := extractDomain(email)
|
|
||||||
mxRecords, err := net.LookupMX(domain)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("'MX' records for %s not found", domain)
|
|
||||||
} else if len(mxRecords) > 0 {
|
|
||||||
// At least one MX record exists
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: Check for A records (some domains accept mail via A records)
|
|
||||||
aRecords, err := net.LookupHost(domain)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("'A' record for %s not found", domain)
|
|
||||||
}
|
|
||||||
return len(aRecords) > 0, nil
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package manifest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Manifest struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
ShortName string `json:"short_name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Icons []Icon `json:"icons"`
|
|
||||||
StartURL string `json:"start_url"`
|
|
||||||
Display string `json:"display"`
|
|
||||||
BackgroundColor string `json:"background_color"`
|
|
||||||
ThemeColor string `json:"theme_color"`
|
|
||||||
Lang string `json:"lang"`
|
|
||||||
Author string `json:"author"`
|
|
||||||
OgHost string `json:"ogHost"`
|
|
||||||
Orientation string `json:"orientation"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Icon struct {
|
|
||||||
Src string `json:"src"`
|
|
||||||
Sizes string `json:"sizes"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeManifst(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/api/manifest.json", func(e *core.RequestEvent) error {
|
|
||||||
manifest, err := GetSettings(app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.Response.Header().Add("content-type", "application/json")
|
|
||||||
return e.String(http.StatusOK, manifest)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSettings(app *pocketbase.PocketBase) (string, error) {
|
|
||||||
record, err := app.FindFirstRecordByFilter("settings", "key='manifest'", nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return record.GetString("value"), nil
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
package proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Logger *slog.Logger
|
|
||||||
|
|
||||||
func ServeProxyPassingToNuxt(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
Logger = app.Logger()
|
|
||||||
|
|
||||||
proxyUrl, _ := app.RootCmd.Flags().GetString("proxy")
|
|
||||||
|
|
||||||
if len(proxyUrl) > 0 {
|
|
||||||
target, _ := url.Parse(proxyUrl) // Node.js app
|
|
||||||
|
|
||||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
|
||||||
|
|
||||||
originalDirector := proxy.Director
|
|
||||||
proxy.Director = func(req *http.Request) {
|
|
||||||
originalDirector(req)
|
|
||||||
req.Host = target.Host
|
|
||||||
}
|
|
||||||
proxy.Transport = &loggingTransport{http.DefaultTransport}
|
|
||||||
|
|
||||||
proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.Logger().Error(fmt.Sprintf("Proxy error: %v for %s %s", err, req.Method, req.URL.Path), "type", "proxy")
|
|
||||||
http.Error(rw, "Proxy error", http.StatusBadGateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
return se.Router.Any("/", func(e *core.RequestEvent) error {
|
|
||||||
// Ping the backend with a HEAD request (or TCP dial)
|
|
||||||
backendUp := isBackendAlive(target)
|
|
||||||
if !backendUp {
|
|
||||||
app.Logger().Error(fmt.Sprintf("Backend %s is unreachable, sending 502", target), "type", "proxy", "path", e.Request.URL.Path, "remoteIP", e.Request.RemoteAddr)
|
|
||||||
e.Response.WriteHeader(http.StatusBadGateway)
|
|
||||||
e.Response.Write([]byte("502 Backend is unavailable"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward to Node.js
|
|
||||||
proxy.ServeHTTP(e.Response, e.Request)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBackendAlive(target *url.URL) bool {
|
|
||||||
conn, err := net.DialTimeout("tcp", target.Host, 500*time.Millisecond)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
conn.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type loggingTransport struct {
|
|
||||||
rt http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
// Do the actual request
|
|
||||||
resp, err := t.rt.RoundTrip(req)
|
|
||||||
duration := time.Since(start)
|
|
||||||
|
|
||||||
// Prepare fields
|
|
||||||
remoteAddr := req.RemoteAddr
|
|
||||||
if ip := req.Header.Get("X-Real-IP"); len(ip) > 0 {
|
|
||||||
remoteAddr = ip
|
|
||||||
}
|
|
||||||
method := req.Method
|
|
||||||
uri := req.URL.RequestURI()
|
|
||||||
// proto := req.Proto
|
|
||||||
status := 0
|
|
||||||
size := 0
|
|
||||||
|
|
||||||
if resp != nil {
|
|
||||||
status = resp.StatusCode
|
|
||||||
if resp.ContentLength > 0 {
|
|
||||||
size = int(resp.ContentLength)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
referer := req.Referer()
|
|
||||||
ua := req.UserAgent()
|
|
||||||
// timestamp := time.Now().Format("02/Jan/2006:15:04:05 -0700")
|
|
||||||
|
|
||||||
Logger.Info(
|
|
||||||
fmt.Sprintf("%s %s", method, uri),
|
|
||||||
"type", "proxy",
|
|
||||||
// "method", method,
|
|
||||||
// "url", uri,
|
|
||||||
"referer", referer,
|
|
||||||
"remoteIP", remoteAddr,
|
|
||||||
"userAgent", ua,
|
|
||||||
"execTime", fmt.Sprintf("%.4fms", float64(duration.Milliseconds())),
|
|
||||||
"status", status,
|
|
||||||
"size", size,
|
|
||||||
)
|
|
||||||
|
|
||||||
return resp, err
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
package seo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MenuRecord struct {
|
|
||||||
Active bool `json:"active"`
|
|
||||||
CollectionID string `json:"collectionId"`
|
|
||||||
CollectionName string `json:"collectionName"`
|
|
||||||
Created string `json:"created"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
IDLang string `json:"id_lang"`
|
|
||||||
IDPage string `json:"id_page"`
|
|
||||||
IDParent string `json:"id_parent"`
|
|
||||||
IsDefault bool `json:"is_default"`
|
|
||||||
IsRoot bool `json:"is_root"`
|
|
||||||
LinkRewrite string `json:"link_rewrite"`
|
|
||||||
LinkTitle string `json:"link_title"`
|
|
||||||
MetaDescription string `json:"meta_description"`
|
|
||||||
MetaTitle string `json:"meta_title"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
PageName string `json:"page_name"`
|
|
||||||
PositionID int `json:"position_id"`
|
|
||||||
Updated string `json:"updated"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UrlSet struct {
|
|
||||||
XMLName xml.Name `xml:"urlset"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Urls []Url `xml:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Url struct {
|
|
||||||
Loc string `xml:"loc"`
|
|
||||||
LastMod string `xml:"lastmod,omitempty"`
|
|
||||||
ChangeFreq string `xml:"changefreq,omitempty"`
|
|
||||||
Priority string `xml:"priority,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeFeeds(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/feeds/{lang}/sitemap.xml", func(e *core.RequestEvent) error {
|
|
||||||
|
|
||||||
lang := e.Request.PathValue("lang")
|
|
||||||
|
|
||||||
urls, err := getLocations(app, lang)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
xx := UrlSet{
|
|
||||||
Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
|
||||||
Urls: urls,
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := xml.MarshalIndent(xx, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Response.Header().Add("content-type", "text/xml")
|
|
||||||
return e.String(http.StatusOK, xml.Header+string(bytes))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLocations(app *pocketbase.PocketBase, lang string) ([]Url, error) {
|
|
||||||
records, err := app.FindRecordsByFilter("menu_view", fmt.Sprintf("id_lang='%s'&&active=true", lang), "position_id", 200, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUrl, err := getBaseUrl(app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
locations := []Url{}
|
|
||||||
lastMod := time.Now().Add(time.Hour * 24 * 7 * -1).Format("2006-01-02")
|
|
||||||
|
|
||||||
for _, r := range records {
|
|
||||||
rec := MenuRecord{}
|
|
||||||
x, _ := r.MarshalJSON()
|
|
||||||
json.Unmarshal(x, &rec)
|
|
||||||
if rec.IsRoot {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(rec.Url) > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if rec.IsDefault {
|
|
||||||
|
|
||||||
locations = append(locations, Url{
|
|
||||||
Loc: baseUrl + path.Join(lang),
|
|
||||||
LastMod: lastMod,
|
|
||||||
ChangeFreq: "weekly",
|
|
||||||
Priority: "1.0",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
locations = append(locations, Url{
|
|
||||||
Loc: baseUrl + path.Join(lang, rec.IDPage, rec.LinkRewrite),
|
|
||||||
LastMod: lastMod,
|
|
||||||
ChangeFreq: "weekly",
|
|
||||||
Priority: "1.0",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return locations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseUrl struct {
|
|
||||||
BaseURL string `json:"baseUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBaseUrl(app *pocketbase.PocketBase) (string, error) {
|
|
||||||
record, err := app.FindFirstRecordByFilter("settings", "key='baseUrl'", nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := BaseUrl{}
|
|
||||||
json.Unmarshal([]byte(record.GetString("value")), &settings)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.BaseURL, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SitemapIndex struct {
|
|
||||||
XMLName xml.Name `xml:"sitemapindex"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Sitemaps []Sitemap `xml:"sitemap"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sitemap represents each <sitemap> entry
|
|
||||||
type Sitemap struct {
|
|
||||||
Loc string `xml:"loc"`
|
|
||||||
LastMod string `xml:"lastmod"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeFeedsIndex(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/feeds/index.xml", func(e *core.RequestEvent) error {
|
|
||||||
index, err := makeSiteMapIndex(app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := xml.MarshalIndent(index, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Response.Header().Add("content-type", "text/xml")
|
|
||||||
return e.String(http.StatusOK, xml.Header+string(bytes))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSiteMapIndex(app *pocketbase.PocketBase) (*SitemapIndex, error) {
|
|
||||||
index := &SitemapIndex{
|
|
||||||
Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
|
|
||||||
}
|
|
||||||
|
|
||||||
bseUrl, err := getBaseUrl(app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
langs, err := app.FindRecordsByFilter("lang", "active=true", "", 200, 0)
|
|
||||||
if err != nil {
|
|
||||||
return index, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lastMod := time.Now().Add(time.Hour * 24 * 7 * -1).Format("2006-01-02")
|
|
||||||
|
|
||||||
for _, l := range langs {
|
|
||||||
index.Sitemaps = append(index.Sitemaps, Sitemap{
|
|
||||||
Loc: bseUrl + path.Join("feeds/", l.Id, "sitemap.xml"),
|
|
||||||
LastMod: lastMod,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return index, nil
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package seo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Robots struct {
|
|
||||||
Robots []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeRobotsTxt(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/robots.txt", func(e *core.RequestEvent) error {
|
|
||||||
|
|
||||||
text, err := getRobots(app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return e.String(http.StatusOK, text)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRobots(app *pocketbase.PocketBase) (string, error) {
|
|
||||||
record, err := app.FindFirstRecordByFilter("settings", "key='robots_txt'", nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := Robots{}
|
|
||||||
json.Unmarshal([]byte(record.GetString("value")), &settings)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUrl, err := getBaseUrl(app)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Robots = append(settings.Robots, fmt.Sprintf("\n\nSitemap: %s%s", baseUrl, "feeds/index.xml"))
|
|
||||||
|
|
||||||
return strings.Join(settings.Robots, "\n"), nil
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package supervise
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ServeSubprocessSupervisor(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
|
|
||||||
command, _ := app.RootCmd.PersistentFlags().GetString("subcommand")
|
|
||||||
|
|
||||||
if len(command) > 0 {
|
|
||||||
cmdsub := strings.Split(command, " ")
|
|
||||||
if len(cmdsub) > 0 {
|
|
||||||
startNodeProcessSupervised(cmdsub[0], strings.Join(cmdsub[1:], " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func startNodeProcessSupervised(command string, args ...string) {
|
|
||||||
const maxRetries = 3
|
|
||||||
const retryDelay = 30 * time.Second
|
|
||||||
|
|
||||||
var (
|
|
||||||
cmdMu sync.Mutex
|
|
||||||
cmd *exec.Cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
stopChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(stopChan, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
retries := 0
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stopChan:
|
|
||||||
log.Println("Received shutdown signal. Terminating subprocess...")
|
|
||||||
cmdMu.Lock()
|
|
||||||
if cmd != nil && cmd.Process != nil {
|
|
||||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
}
|
|
||||||
cmdMu.Unlock()
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cmdMu.Lock()
|
|
||||||
cmd = exec.CommandContext(ctx, command, args...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmdMu.Unlock()
|
|
||||||
|
|
||||||
log.Printf("Starting Process: %s %v\n", command, args)
|
|
||||||
err := cmd.Run()
|
|
||||||
cancel() // cancel the context when done
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Process exited with error: %v\n", err)
|
|
||||||
retries++
|
|
||||||
if retries >= maxRetries {
|
|
||||||
log.Printf("Process failed %d times. Shutting down application...", retries)
|
|
||||||
// _ = app.ResetBootstrapState()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
log.Printf("Retrying in %s (%d/%d)...", retryDelay, retries, maxRetries)
|
|
||||||
time.Sleep(retryDelay)
|
|
||||||
} else {
|
|
||||||
log.Printf("Process exited normally. Resetting retry count.")
|
|
||||||
retries = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package version
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Version string
|
|
||||||
var BuildDate string
|
|
||||||
var Company string
|
|
||||||
var CompanyUrl string
|
|
||||||
|
|
||||||
func ServeVersionInfo(app *pocketbase.PocketBase, se *core.ServeEvent) *router.Route[*core.RequestEvent] {
|
|
||||||
return se.Router.GET("/api/version/send", func(e *core.RequestEvent) error {
|
|
||||||
|
|
||||||
return e.JSON(http.StatusOK, map[string]string{
|
|
||||||
"version": Version,
|
|
||||||
"build_date": BuildDate,
|
|
||||||
"company": Company,
|
|
||||||
"company_url": CompanyUrl,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package webpconverter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type memFileReader struct {
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memFileReader) Open() (io.ReadSeekCloser, error) {
|
|
||||||
return readSeekCloser{bytes.NewReader(m.data)}, nil
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package webpconverter
|
|
||||||
|
|
||||||
import "bytes"
|
|
||||||
|
|
||||||
type readSeekCloser struct {
|
|
||||||
*bytes.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r readSeekCloser) Close() error { return nil }
|
|
@ -1,144 +0,0 @@
|
|||||||
package webpconverter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/chai2010/webp"
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/hook"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateEventHandler(app *pocketbase.PocketBase) *hook.Handler[*core.RecordEvent] {
|
|
||||||
return &hook.Handler[*core.RecordEvent]{
|
|
||||||
Func: func(e *core.RecordEvent) error {
|
|
||||||
fieldsData := e.Record.FieldsData()
|
|
||||||
for _, v := range fieldsData {
|
|
||||||
if files, ok := v.([]interface{}); ok {
|
|
||||||
for _, f := range files {
|
|
||||||
if file, ok := f.(*filesystem.File); ok {
|
|
||||||
if shouldBeReplacedWithWebp(file.Name) {
|
|
||||||
convertToWebp(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f, ok := v.(interface{}); ok {
|
|
||||||
if file, ok := f.(*filesystem.File); ok {
|
|
||||||
if shouldBeReplacedWithWebp(file.Name) {
|
|
||||||
convertToWebp(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return e.Next()
|
|
||||||
},
|
|
||||||
Priority: 100,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ThumbEventHandler(app *pocketbase.PocketBase) *hook.Handler[*core.FileDownloadRequestEvent] {
|
|
||||||
return &hook.Handler[*core.FileDownloadRequestEvent]{
|
|
||||||
Func: func(fdre *core.FileDownloadRequestEvent) error {
|
|
||||||
if filepath.Ext(fdre.ServedName) == ".webp" {
|
|
||||||
|
|
||||||
filename := fdre.Request.PathValue("filename")
|
|
||||||
baseFilesPath := fdre.Record.BaseFilesPath()
|
|
||||||
|
|
||||||
fs, err := app.App.NewFilesystem()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fs.Close()
|
|
||||||
|
|
||||||
blob, err := fs.GetReader(baseFilesPath + "/thumbs_" + filename + "/" + fdre.ServedName)
|
|
||||||
if err != nil {
|
|
||||||
blob, err = fs.GetReader(baseFilesPath + "/" + fdre.ServedName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer blob.Close()
|
|
||||||
|
|
||||||
img, err := png.Decode(blob)
|
|
||||||
if err != nil {
|
|
||||||
return fdre.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
err = webp.Encode(&buf, img, &webp.Options{Quality: 80})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = fs.Upload(buf.Bytes(), baseFilesPath+"/thumbs_"+filename+"/"+fdre.ServedName)
|
|
||||||
if err != nil {
|
|
||||||
_, err := fdre.Response.Write(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fdre.Next()
|
|
||||||
}
|
|
||||||
return fdre.Next()
|
|
||||||
},
|
|
||||||
Priority: 99,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToWebp(file *filesystem.File) error {
|
|
||||||
file.Name = replaceExtWithWebp(file.Name)
|
|
||||||
file.OriginalName = replaceExtWithWebp(file.OriginalName)
|
|
||||||
ff, err := file.Reader.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ff.Close()
|
|
||||||
fff, err := io.ReadAll(ff)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
img, _, err := image.Decode(bytes.NewReader(fff))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.Buffer{}
|
|
||||||
err = webp.Encode(&buf, img, &webp.Options{Quality: 80})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file.Reader = &memFileReader{data: buf.Bytes()}
|
|
||||||
file.Size = int64(buf.Len())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceExtWithWebp(path string) string {
|
|
||||||
// List of image extensions to convert
|
|
||||||
exts := []string{".png", ".jpg", ".jpeg", ".bmp", ".tiff"}
|
|
||||||
|
|
||||||
for _, ext := range exts {
|
|
||||||
if strings.HasSuffix(strings.ToLower(path), ext) {
|
|
||||||
return strings.TrimSuffix(path, ext) + ".webp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldBeReplacedWithWebp(path string) bool {
|
|
||||||
// List of image extensions to convert
|
|
||||||
exts := []string{".png", ".jpg", ".jpeg", ".bmp", ".tiff"}
|
|
||||||
|
|
||||||
for _, ext := range exts {
|
|
||||||
if strings.HasSuffix(strings.ToLower(path), ext) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
module pocketbase
|
|
||||||
|
|
||||||
go 1.23.2
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/chai2010/webp v1.4.0
|
|
||||||
github.com/pocketbase/pocketbase v0.28.2
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
|
||||||
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
|
|
||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
|
||||||
github.com/pocketbase/dbx v1.11.0 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
|
||||||
github.com/spf13/cast v1.8.0 // indirect
|
|
||||||
github.com/spf13/cobra v1.9.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
|
||||||
golang.org/x/crypto v0.38.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
|
||||||
golang.org/x/image v0.27.0 // indirect
|
|
||||||
golang.org/x/net v0.40.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.30.0 // indirect
|
|
||||||
golang.org/x/sync v0.14.0 // indirect
|
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
|
||||||
golang.org/x/text v0.25.0 // indirect
|
|
||||||
modernc.org/libc v1.65.7 // indirect
|
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
|
||||||
modernc.org/memory v1.11.0 // indirect
|
|
||||||
modernc.org/sqlite v1.37.1 // indirect
|
|
||||||
)
|
|
127
backend/go.sum
127
backend/go.sum
@ -1,127 +0,0 @@
|
|||||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
|
||||||
github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko=
|
|
||||||
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
|
|
||||||
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
|
||||||
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
|
|
||||||
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
|
||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
|
||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
|
||||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
|
|
||||||
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
|
||||||
github.com/pocketbase/pocketbase v0.28.2 h1:b6cfUfr5d4whvUFGFhI8oHRzx/eB76GCUQGftqgv9lM=
|
|
||||||
github.com/pocketbase/pocketbase v0.28.2/go.mod h1:ElwIYS1b5xS9w0U7AK7tsm6FuC0lzw57H8p/118Cu7g=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
|
|
||||||
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
|
||||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
|
||||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
|
||||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
|
||||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
|
||||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
|
||||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
|
||||||
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
|
|
||||||
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
|
||||||
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
|
|
||||||
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
|
|
||||||
modernc.org/libc v1.65.8 h1:7PXRJai0TXZ8uNA3srsmYzmTyrLoHImV5QxHeni108Q=
|
|
||||||
modernc.org/libc v1.65.8/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
|
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
|
||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
|
||||||
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
|
|
||||||
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
|
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"pocketbase/custom"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := pocketbase.New()
|
|
||||||
|
|
||||||
app = custom.ExtendApp(app)
|
|
||||||
|
|
||||||
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
|
|
||||||
|
|
||||||
custom.LoadCustomCode(app, se)
|
|
||||||
|
|
||||||
return se.Next()
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := app.Start(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -65,9 +65,11 @@
|
|||||||
{{ productStore.cart.total_value }}
|
{{ productStore.cart.total_value }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<UiButtonArrow @click="menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 12))"
|
<UiButtonArrow @click="() => {
|
||||||
class="w-full" type="fill" :arrow="true" :full="true">{{
|
menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 12))
|
||||||
$t('to_checkout') }}
|
openCart = false
|
||||||
|
}" class="w-full" type="fill" :arrow="true" :full="true">{{
|
||||||
|
$t('to_checkout') }}
|
||||||
</UiButtonArrow>
|
</UiButtonArrow>
|
||||||
</div>
|
</div>
|
||||||
<div v-else
|
<div v-else
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<p>{{ $t("country") }}</p>
|
<p>{{ $t("country") }}</p>
|
||||||
<div
|
<div
|
||||||
class="bg-inherit w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1">
|
class="w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1">
|
||||||
<div class="p-0" @click="openDrop('country')">
|
<div class="p-0" @click="openDrop('country')">
|
||||||
<div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
|
<div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
|
||||||
{{ $session.currentCountryIso }} <span> <i
|
{{ $session.currentCountryIso }} <span> <i
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="dropCountry"
|
<div v-if="dropCountry"
|
||||||
class="rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 bg-inherit ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
|
class="bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
|
||||||
<div class="overflow-auto h-[200px] w-full">
|
<div class="overflow-auto h-[200px] w-full">
|
||||||
<p @click="() => { $session.setCountry(item.iso_code); $session.loadSession(); dropCountry = false }"
|
<p @click="() => { $session.setCountry(item.iso_code); $session.loadSession(); dropCountry = false }"
|
||||||
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
|
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<div class="flex flex-col items-start gap-[6px]">
|
<div class="flex flex-col items-start gap-[6px]">
|
||||||
<p>{{ $t("currency") }}</p>
|
<p>{{ $t("currency") }}</p>
|
||||||
<div
|
<div
|
||||||
class="bg-inherit w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1">
|
class="w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1">
|
||||||
<div class="p-0" @click="openDrop()">
|
<div class="p-0" @click="openDrop()">
|
||||||
<div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
|
<div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
|
||||||
{{ $session.currentCurrencyIso }}<span> <i
|
{{ $session.currentCurrencyIso }}<span> <i
|
||||||
@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="dropCurrency"
|
<div v-if="dropCurrency"
|
||||||
class="rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 bg-inherit ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
|
class="bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
|
||||||
<div class="overflow-auto w-full">
|
<div class="overflow-auto w-full">
|
||||||
<p @click="() => { $session.setCurrency(item.iso_code); $session.loadSession(); dropCurrency = false }"
|
<p @click="() => { $session.setCurrency(item.iso_code); $session.loadSession(); dropCurrency = false }"
|
||||||
class="w-full hover:bg-block dark:hover:bg-button pl-1 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
|
class="w-full hover:bg-block dark:hover:bg-button pl-1 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
|
||||||
|
@ -18,8 +18,15 @@
|
|||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
<div class="w-full flex items-center justify-between">
|
<div class="w-full flex items-center justify-between">
|
||||||
<div class="flex items-center gap-[30px]">
|
<div class="flex items-center gap-[30px]">
|
||||||
<i @click="menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 11))"
|
<div>
|
||||||
class="uil uil-user text-[31px] cursor-pointer"></i>
|
<i v-if="!userStore.isLogged"
|
||||||
|
@click="menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 11))"
|
||||||
|
class="uil uil-user text-[31px] cursor-pointer"></i>
|
||||||
|
<div v-else class="py-[6px] px-3 border border-block rounded-sm">
|
||||||
|
<!-- {{ userStore.user }} -->
|
||||||
|
Arina Yakovenko
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<CartPopup />
|
<CartPopup />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@ -74,7 +81,7 @@
|
|||||||
{{ item.front_menu_lang[0].name }}
|
{{ item.front_menu_lang[0].name }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
|
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
|
||||||
<svg class="" width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
|
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
|
||||||
fill="currentColor" />
|
fill="currentColor" />
|
||||||
@ -119,7 +126,7 @@
|
|||||||
{{ item.front_menu_lang[0].name }}
|
{{ item.front_menu_lang[0].name }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
|
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
|
||||||
<svg class="" width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
|
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
|
||||||
fill="currentColor" />
|
fill="currentColor" />
|
||||||
@ -179,7 +186,7 @@
|
|||||||
{{ item.front_menu_lang[0].name }}
|
{{ item.front_menu_lang[0].name }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
|
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
|
||||||
<svg class="" width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
|
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
|
||||||
fill="currentColor" />
|
fill="currentColor" />
|
||||||
@ -216,6 +223,7 @@ import CountryCurrencySelector from "./CountryCurrencySelector.vue";
|
|||||||
import LangSwitcher from "./LangSwitcher.vue";
|
import LangSwitcher from "./LangSwitcher.vue";
|
||||||
|
|
||||||
const menuStore = useMenuStore();
|
const menuStore = useMenuStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const productStore = useProductStore();
|
const productStore = useProductStore();
|
||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
const colorMode = useColorMode();
|
const colorMode = useColorMode();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<UiContainer>
|
<UiContainer>
|
||||||
<form class="w-[85%] mx-auto" @submit.prevent="checkoutStore.sendForm">
|
<div class="w-[85%] mx-auto">
|
||||||
<div v-if="userStore.isLogged" class="space-25-55">
|
<div v-if="userStore.isLogged" class="space-25-55">
|
||||||
|
|
||||||
<div class="w-full flex items-center justify-center">
|
<div class="w-full flex items-center justify-center">
|
||||||
@ -29,21 +29,21 @@
|
|||||||
<div class="flex flex-col gap-[30px] xl:flex-row">
|
<div class="flex flex-col gap-[30px] xl:flex-row">
|
||||||
<div class="flex flex-col w-1/2 gap-[30px]">
|
<div class="flex flex-col w-1/2 gap-[30px]">
|
||||||
<CheckoutInput v-model="checkoutStore.userName" :id="1" disabled>{{ $t("first_name")
|
<CheckoutInput v-model="checkoutStore.userName" :id="1" disabled>{{ $t("first_name")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
<CheckoutInput v-model="checkoutStore.lastName" :id="2" disabled>{{ $t("surname")
|
<CheckoutInput v-model="checkoutStore.lastName" :id="2" disabled>{{ $t("surname")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
<CheckoutInput v-model="checkoutStore.address" :id="3" disabled>{{ $t("address")
|
<CheckoutInput v-model="checkoutStore.address" :id="3" disabled>{{ $t("address")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
<CheckoutInput v-model="checkoutStore.postCode" :id="4" disabled>{{ $t("post_code")
|
<CheckoutInput v-model="checkoutStore.postCode" :id="4" disabled>{{ $t("post_code")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-1/2 gap-[30px]">
|
<div class="flex flex-col w-1/2 gap-[30px]">
|
||||||
<CheckoutInput v-model="checkoutStore.city" :id="5" disabled>{{ $t("city")
|
<CheckoutInput v-model="checkoutStore.city" :id="5" disabled>{{ $t("city")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
<CheckoutInput v-model="checkoutStore.country" :id="6" disabled>{{ $t("country")
|
<CheckoutInput v-model="checkoutStore.country" :id="6" disabled>{{ $t("country")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
<CheckoutInput v-model="checkoutStore.phoneNumber" :id="7" disabled>{{ $t("phone")
|
<CheckoutInput v-model="checkoutStore.accountPhoneNumber" :id="7" disabled>{{ $t("phone")
|
||||||
}} </CheckoutInput>
|
}} </CheckoutInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -51,14 +51,52 @@
|
|||||||
<h2 class="h2-bold-bounded">
|
<h2 class="h2-bold-bounded">
|
||||||
{{ $t("Shipping details") }}
|
{{ $t("Shipping details") }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="relative">
|
<div class="relative border border-block rounded-lg px-6 h-[67px] w-full">
|
||||||
<input v-model="checkoutStore.accountPhoneNumber" type="text"
|
<div class="flex items-center gap-[10px]">
|
||||||
class="border border-block placeholder:text-bg-dark dark:placeholder:text-bg-light rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2 text-button" />
|
<div class="flex items-center gap-[25px]" v-if="!checkoutStore.vUseAccountPhoneNumber">
|
||||||
|
<div class="flex flex-col items-start gap-[25px]">
|
||||||
|
<div ref="dropdownIsoRef"
|
||||||
|
class="pl-[25px] relative w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0">
|
||||||
|
<div class="p-0" @click="dropIso = !dropIso">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
|
||||||
|
{{ checkoutStore.selectedIso.name }} <span> <i
|
||||||
|
class="uil uil-angle-down text-2xl font-light cursor-pointer"></i></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dropIso"
|
||||||
|
class="absolute w-full mt-2 left-0 bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
|
||||||
|
<div class="overflow-auto h-[200px] w-full">
|
||||||
|
<p @click="() => { checkoutStore.selectedIso = item; dropIso = false; checkoutStore.changePrefix(item.call_prefix as string) }"
|
||||||
|
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
|
||||||
|
v-for="item in menuStore.countries" :key="item.iso_code">
|
||||||
|
{{ item?.name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-xl">{{ checkoutStore.currentPrefix }}</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
:value="checkoutStore.vUseAccountPhoneNumber ? checkoutStore.accountPhoneNumber : checkoutStore.phoneNumber"
|
||||||
|
:disabled="checkoutStore.vUseAccountPhoneNumber" @input="(e) => {
|
||||||
|
if (!checkoutStore.vUseAccountPhoneNumber) {
|
||||||
|
checkoutStore.phoneNumber = (e.target as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
}" type="tel" placeholder="123 xxxx xxx"
|
||||||
|
class="placeholder:text-xl placeholder:text-gray placeholder:uppercase dark:placeholder:text-bg-light rounded-lg h-[67px] w-full focus:outline-none focus:ring-0 focus:border-0" />
|
||||||
|
</div>
|
||||||
|
<p v-if="checkoutStore.phoneValidation === false && !checkoutStore.vUseAccountPhoneNumber"
|
||||||
|
class="text-red-500">Invalid phone number</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input @change="(event: Event) => {
|
<input @change="(event: Event) => {
|
||||||
const target = event.target as HTMLInputElement
|
const target = event.target as HTMLInputElement
|
||||||
target.checked ? checkoutStore.vUseAccountPhoneNumber = true : checkoutStore.vUseAccountPhoneNumber = false
|
target.checked ? checkoutStore.vUseAccountPhoneNumber = true : checkoutStore.vUseAccountPhoneNumber = false
|
||||||
|
checkoutStore.phoneValidation = null
|
||||||
}" type="checkbox" class="border border-button !bg-inherit" />
|
}" type="checkbox" class="border border-button !bg-inherit" />
|
||||||
<p>{{ $t('use_account_phone') }}</p>
|
<p>{{ $t('use_account_phone') }}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -119,30 +157,55 @@
|
|||||||
<CheckoutInput v-model="checkoutStore.vNewAddressSurname" :placeholder="$t('surname')"
|
<CheckoutInput v-model="checkoutStore.vNewAddressSurname" :placeholder="$t('surname')"
|
||||||
:id="11">{{ $t("surname") }}
|
:id="11">{{ $t("surname") }}
|
||||||
</CheckoutInput>
|
</CheckoutInput>
|
||||||
<CheckoutInput v-model="checkoutStore.vNewAddressCountry" :placeholder="$t('country')"
|
<div class="space-y-[15px]">
|
||||||
:id="12">{{ $t("country") }}
|
<p class="pl-6">
|
||||||
</CheckoutInput>
|
{{ $t("country") }}
|
||||||
|
</p>
|
||||||
|
<div ref="dropdownCountryRef"
|
||||||
|
class="relative w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0">
|
||||||
|
<div class="border border-block placeholder:text-gray dark:placeholder:text-button-disabled rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2 flex items-center justify-start"
|
||||||
|
@click="dropCountry = !dropCountry">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
|
||||||
|
{{ checkoutStore.vNewAddressCountry ?
|
||||||
|
checkoutStore.vNewAddressCountry.name : '-' }} <span> <i
|
||||||
|
class="uil uil-angle-down text-2xl font-light cursor-pointer"></i></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dropCountry"
|
||||||
|
class="absolute z-50 w-full mt-2 left-0 bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
|
||||||
|
<div class="overflow-auto h-[200px] w-full">
|
||||||
|
<p @click="() => { checkoutStore.vNewAddressCountry = item; dropCountry = false }"
|
||||||
|
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
|
||||||
|
v-for="item in menuStore.countries" :key="item.iso_code">
|
||||||
|
{{ item?.name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<CheckoutInput v-model="checkoutStore.vNewAddressCode" :placeholder="$t('post_code')"
|
<CheckoutInput v-model="checkoutStore.vNewAddressCode" :placeholder="$t('post_code')"
|
||||||
:id="13">{{ $t("post_code") }}
|
:id="13">{{ $t("post_code") }}
|
||||||
</CheckoutInput>
|
</CheckoutInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="checkoutStore.uploadAddress()">
|
<div>
|
||||||
<span v-if="addressValidation === false" class="text-red"> {{
|
<span v-if="addressValidation === false" class="text-red"> {{
|
||||||
$t("Remember to select a shipping address") }}</span>
|
$t("Remember to select a shipping address") }}</span>
|
||||||
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
|
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
|
||||||
<button type="submit"
|
<button @click="checkoutStore.uploadAddress()"
|
||||||
:class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px] bg-button text-text-dark group-hover:bg-button-hover']">
|
:class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px] bg-button text-text-dark group-hover:bg-button-hover']">
|
||||||
{{ $t("add_new_address") }}
|
{{ $t("add_new_address") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
|
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
|
||||||
<button
|
<button @click="checkoutStore.sendForm()" :disabled="!checkoutStore.activeAddress"
|
||||||
:class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px]', checkoutStore.activeAddress ? 'bg-button text-text-dark group-hover:bg-button-hover' : ' bg-button-disabled text-gray']">
|
:class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px]', checkoutStore.activeAddress ? 'bg-button text-text-dark group-hover:bg-button-hover' : ' bg-button-disabled text-gray']">
|
||||||
{{ $t("continue") }}
|
{{ $t("continue") }}
|
||||||
</button>
|
</button>
|
||||||
@ -158,39 +221,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</UiContainer>
|
</UiContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { LazyColorScheme } from '#components';
|
||||||
import CheckoutInput from '../ui/CheckoutInput.vue';
|
import CheckoutInput from '../ui/CheckoutInput.vue';
|
||||||
|
import { onClickOutside } from "@vueuse/core";
|
||||||
|
|
||||||
const checkoutStore = useCheckoutStore();
|
const checkoutStore = useCheckoutStore();
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const router = useRoute();
|
const menuStore = useMenuStore()
|
||||||
|
const dropIso = ref(false)
|
||||||
|
const dropCountry = ref(false)
|
||||||
|
|
||||||
const addressValidation = ref<null | boolean>(null);
|
const addressValidation = ref<null | boolean>(null);
|
||||||
|
|
||||||
watch(
|
const dropdownIsoRef = ref(null);
|
||||||
() => checkoutStore.vUseAccountPhoneNumber,
|
const dropdownCountryRef = ref(null);
|
||||||
(newValue) => {
|
onClickOutside(dropdownIsoRef, () => {
|
||||||
if (newValue == true) {
|
dropIso.value = false
|
||||||
checkoutStore.accountPhoneNumber = checkoutStore.phoneNumber;
|
});
|
||||||
} else {
|
|
||||||
checkoutStore.accountPhoneNumber = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
onClickOutside(dropdownCountryRef, () => {
|
||||||
|
dropCountry.value = false
|
||||||
|
});
|
||||||
|
|
||||||
// fetchWithCookie(useRequestEvent(), `restricted/cart/checkout`, {
|
checkoutStore.getCheckout()
|
||||||
// method: "PUT",
|
checkoutStore.getAddressList()
|
||||||
// });
|
checkoutStore.getUserData()
|
||||||
|
|
||||||
// if change needs to be applied then provide it in second argument of function below
|
|
||||||
// $setSeoMetaFromTranslation(useStore(), {});
|
|
||||||
|
|
||||||
|
|
||||||
checkoutStore.restrictedAddress()
|
|
||||||
checkoutStore.restrictedAddressOfficial()
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -41,59 +41,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</UiContainer>
|
</UiContainer>
|
||||||
|
|
||||||
<UiContainer v-if="!userStore.vCodeVerify" class="flex py-20 sm:py-14">
|
<UiContainer v-if="userStore.vCodeVerify" class="flex py-20 sm:py-14">
|
||||||
<div class="hidden xl:block rounded-2xl min-w-[50%] h-[830px]" :style="{
|
<div class="hidden xl:block rounded-2xl min-w-[60%] h-[830px]" :style="{
|
||||||
backgroundImage: `url('/api/public/file/${component.img[0]}_l.webp')`,
|
backgroundImage: `url('/api/public/file/${component.img[0]}_l.webp')`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
}" />
|
}" />
|
||||||
<div class="w-full sm:w-[80%] mx-auto my-auto xl:w-full xl:px-12 ">
|
<div class="w-full sm:w-[80%] mx-auto my-auto xl:w-full xl:px-12 ">
|
||||||
<div class="space-25-55">
|
<div class="space-25-55">
|
||||||
<div class="space-y-[15px]">
|
<div class="flex flex-wrap-reverse gap-y-4 justify-between">
|
||||||
<div class="flex flex-wrap-reverse gap-y-4 justify-between">
|
<h2 class="h2-bold-bounded">{{ $t('verify_login') }}</h2>
|
||||||
<h2 class="h2-bold-bounded">{{ $t('verify_login') }}</h2>
|
<button @click="menuStore.navigateToItem()"
|
||||||
<button @click="menuStore.navigateToItem()"
|
class="h-[40px] sm:h-[43px] px-[10px] sm:px-[17px] border border-gray dark:border-button-disabled text-gray dark:text-button-disabled hover:bg-gray transition-all hover:text-white rounded-[8px] cursor-pointer">{{
|
||||||
class="h-[40px] sm:h-[43px] px-[10px] sm:px-[17px] border border-gray dark:border-button-disabled text-gray dark:text-button-disabled hover:bg-gray transition-all hover:text-white rounded-[8px] cursor-pointer">{{
|
$t('back_to_home') }}</button>
|
||||||
$t('back_to_home') }}</button>
|
</div>
|
||||||
</div>
|
<div class="space-y-[30px]">
|
||||||
<p>{{ $t('send_code') }} <span class="text-button font-semibold">test@ma-al.com</span> {{
|
<p>{{ $t('send_code') }} <span class="text-button font-semibold">{{ userStore.email }}</span> {{
|
||||||
$t('by_email') }}
|
$t('by_email') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<div class="space-y-[15px]">
|
||||||
<div class="space-y-[15px]">
|
<p class="pl-6">{{ $t('code') }}</p>
|
||||||
<p class="pl-6">{{ $t('code') }}</p>
|
<input v-model="userStore.vCode" :placeholder="$t('code')" type="text"
|
||||||
<input v-model="userStore.vCode" :placeholder="$t('code')" type="text"
|
class="border-2 border-block placeholder:text-gray dark:placeholder:text-button-disabled text-bg-dark dark:text-bg-light rounded-lg px-6 h-[50px] sm:h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2" />
|
||||||
class="border-2 border-block placeholder:text-gray dark:placeholder:text-button-disabled text-bg-dark dark:text-bg-light rounded-lg px-6 h-[50px] sm:h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-[25px] sm:py-12 flex justify-center w-full">
|
<div class="py-[25px] sm:py-12 flex justify-center w-full">
|
||||||
<UiButtonArrow @click="userStore.logIn()" type="fill" :arrow="true">{{ $t('confirm') }}</UiButtonArrow>
|
<UiButtonArrow @click="userStore.sendFormCode(true)" type="fill" :arrow="true">{{ $t('confirm') }}
|
||||||
|
</UiButtonArrow>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UiContainer>
|
</UiContainer>
|
||||||
|
|
||||||
<!-- v-if="store.vCodeVerify" -->
|
|
||||||
<div class="max-w-[590px] w-full">
|
|
||||||
<h3 class="mb-1 text-2xl font-bold">
|
|
||||||
{{ $t("FrontTranslations", "Verify your login") }}
|
|
||||||
</h3>
|
|
||||||
<span class="text-sm font-bold text-black">
|
|
||||||
{{ $t("FrontTranslations", "We send code to") }} <span class="text-yellow"> {{ userStore.vEmail }}</span>
|
|
||||||
{{ $t("FrontTranslations", "by") }} e-mail.</span>
|
|
||||||
<!-- <form
|
|
||||||
@submit.prevent="useUserStore().sendFormCode(`${vCode}`, vEmail, useRoute().query.to ? (useRoute().query.to as string) : undefined)">
|
|
||||||
<div class="flex flex-col w-full gap-10 mt-6">
|
|
||||||
<BaseInput v-model="vCode" :id="1"> {{ $t("FrontTranslations", "Code") }}</BaseInput>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex">
|
|
||||||
<BaseButtonLogin id="2" type="submit">
|
|
||||||
{{ $t("FrontTranslations", "Confirm") }}
|
|
||||||
</BaseButtonLogin>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -113,7 +91,6 @@ defineProps<{
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
}>();
|
}>();
|
||||||
const store = useStore()
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const menuStore = useMenuStore()
|
const menuStore = useMenuStore()
|
||||||
</script>
|
</script>
|
@ -9,11 +9,11 @@
|
|||||||
:placeholder="placeholder" :disabled="disabled"
|
:placeholder="placeholder" :disabled="disabled"
|
||||||
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
||||||
@focus="$emit('focus')" @blur="$emit('blur')"
|
@focus="$emit('focus')" @blur="$emit('blur')"
|
||||||
class="border border-block placeholder:text-gray dark:placeholder:text-button-disabled rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2 text-button" />
|
class="border border-block placeholder:text-gray dark:placeholder:text-button-disabled rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2" />
|
||||||
<i v-if="disabled"
|
<i v-if="disabled"
|
||||||
class="uil uil-lock-alt text-[22px] absolute right-6 top-1/2 -translate-y-1/2 text-gray" />
|
class="uil uil-lock-alt text-[22px] absolute right-6 top-1/2 -translate-y-1/2 text-gray" />
|
||||||
|
|
||||||
<div v-if="type === 'password'" class="order-2 ml-1.5 cursor-pointer" :title="!isPasswordVisible ? $t('Panel.Component.InputDefault', 'Show password') : $t('Panel.Component.InputDefault', 'Hide password')
|
<div v-if="type === 'password'" class="order-2 ml-1.5 cursor-pointer" :title="!isPasswordVisible ? $t('show_password') : $t('hide_password')
|
||||||
" @click="isPasswordVisible = !isPasswordVisible">
|
" @click="isPasswordVisible = !isPasswordVisible">
|
||||||
<FaceObserver class="ml-4 text-xl leading-6" :isPasswordVisible="isPasswordVisible" />
|
<FaceObserver class="ml-4 text-xl leading-6" :isPasswordVisible="isPasswordVisible" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,18 +3,6 @@ import tailwindcss from "@tailwindcss/vite";
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: "2024-11-01",
|
compatibilityDate: "2024-11-01",
|
||||||
devtools: { enabled: false },
|
devtools: { enabled: false },
|
||||||
// app: {
|
|
||||||
// pageTransition: { name: "page", mode: "out-in" },
|
|
||||||
// },
|
|
||||||
// nitro: {
|
|
||||||
// routeRules: {
|
|
||||||
// "/api/**": {
|
|
||||||
// proxy: {
|
|
||||||
// to: `${process.env.POCKETBASE_URL || "http://127.0.0.1:8090"}/api/**`,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
"@pinia/nuxt",
|
"@pinia/nuxt",
|
||||||
|
@ -1,47 +1,32 @@
|
|||||||
import type { GenericResponse } from "~/types";
|
import type { GenericResponse } from "~/types";
|
||||||
import type { AddressesList } from "~/types/checkout";
|
import type { AddressesList, UserAddressOfficial } from "~/types/checkout";
|
||||||
import { validation } from "../utils/validation";
|
import { validation } from "../utils/validation";
|
||||||
import { REGEX_PHONE } from "../utils/regex";
|
import { REGEX_PHONE } from "../utils/regex";
|
||||||
|
|
||||||
export const useCheckoutStore = defineStore("checkoutStore", () => {
|
export const useCheckoutStore = defineStore("checkoutStore", () => {
|
||||||
const { $toast } = useNuxtApp();
|
const { $toast } = useNuxtApp();
|
||||||
|
const menuStore = useMenuStore();
|
||||||
|
const selectedIso = ref(menuStore.selectedCountry);
|
||||||
|
|
||||||
|
// get address list
|
||||||
const addressesList = ref<AddressesList[]>();
|
const addressesList = ref<AddressesList[]>();
|
||||||
const activeAddress = ref<AddressesList | null>();
|
const activeAddress = ref<AddressesList | null>();
|
||||||
async function restrictedAddress() {
|
async function getAddressList() {
|
||||||
try {
|
try {
|
||||||
// const { data } = await useMyFetch<GenericResponse<object>>(
|
const { data } = await useMyFetch<GenericResponse<AddressesList[]>>(
|
||||||
// `/api/restricted/user/addresses`,
|
`/api/restricted/user/addresses`,
|
||||||
// {
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// },
|
|
||||||
// onErrorOccured: async (_, status) => {
|
|
||||||
// throw createError({
|
|
||||||
// statusCode: status,
|
|
||||||
// statusMessage: `HTTP error: ${status}`,
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
{
|
||||||
address: {
|
headers: {
|
||||||
city: "Bochnia",
|
"Content-Type": "application/json",
|
||||||
country_iso: "pl",
|
|
||||||
name: "John",
|
|
||||||
postcode: "32-700",
|
|
||||||
street: "Karosek",
|
|
||||||
surname: "Kornelsky",
|
|
||||||
},
|
},
|
||||||
address_id: 2,
|
onErrorOccured: async (_, status) => {
|
||||||
alias: "home",
|
throw createError({
|
||||||
customer_id: 4,
|
statusCode: status,
|
||||||
is_default: true,
|
statusMessage: `HTTP error: ${status}`,
|
||||||
is_official: false,
|
});
|
||||||
},
|
},
|
||||||
];
|
}
|
||||||
|
);
|
||||||
|
|
||||||
addressesList.value = data;
|
addressesList.value = data;
|
||||||
activeAddress.value = addressesList.value[0];
|
activeAddress.value = addressesList.value[0];
|
||||||
@ -50,6 +35,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get user data
|
||||||
const userName = ref("");
|
const userName = ref("");
|
||||||
const lastName = ref("");
|
const lastName = ref("");
|
||||||
const address = ref("");
|
const address = ref("");
|
||||||
@ -58,65 +44,47 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
const country = ref("");
|
const country = ref("");
|
||||||
const phoneNumber = ref("");
|
const phoneNumber = ref("");
|
||||||
const accountPhoneNumber = ref("");
|
const accountPhoneNumber = ref("");
|
||||||
async function restrictedAddressOfficial() {
|
async function getUserData() {
|
||||||
try {
|
try {
|
||||||
// const { data } = await useMyFetch<GenericResponse<object>>(
|
const { data } = await useMyFetch<GenericResponse<UserAddressOfficial>>(
|
||||||
// `/api/restricted/user/address/official`,
|
`/api/restricted/user/address/official`,
|
||||||
// {
|
{
|
||||||
// headers: {
|
headers: {
|
||||||
// "Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
// },
|
},
|
||||||
// onErrorOccured: async (_, status) => {
|
onErrorOccured: async (_, status) => {
|
||||||
// throw createError({
|
throw createError({
|
||||||
// statusCode: status,
|
statusCode: status,
|
||||||
// statusMessage: `HTTP error: ${status}`,
|
statusMessage: `HTTP error: ${status}`,
|
||||||
// });
|
});
|
||||||
// },
|
},
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
|
|
||||||
const data = {
|
|
||||||
address: {
|
|
||||||
city: "Bochnia",
|
|
||||||
country_iso: "pl",
|
|
||||||
name: "John",
|
|
||||||
postcode: "32-700",
|
|
||||||
street: "Karosek",
|
|
||||||
surname: "Kornelsky",
|
|
||||||
},
|
|
||||||
address_id: 2,
|
|
||||||
alias: "home",
|
|
||||||
customer_id: 4,
|
|
||||||
is_default: true,
|
|
||||||
is_official: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
userName.value = data.address.name;
|
userName.value = data.address.name;
|
||||||
lastName.value = data.address.surname;
|
lastName.value = data.address.surname;
|
||||||
address.value = data.address.street;
|
address.value = data.address.street;
|
||||||
postCode.value = data.address.postcode;
|
postCode.value = data.address.postcode;
|
||||||
city.value = data.address.city;
|
city.value = data.address.city;
|
||||||
country.value = data.address.country_iso;
|
country.value = data.address.country.country_lang[0].name;
|
||||||
// resolve this
|
|
||||||
// accountPhoneNumber.value = useUserStore().fullUserData.phone_number;
|
|
||||||
phoneNumber.value = "+36 789 3773 737";
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("restrictedAddressOfficial error:", error);
|
console.error("getUserData error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// upload new address
|
||||||
const vNewAddressName = ref("");
|
const vNewAddressName = ref("");
|
||||||
const vNewAddressSurname = ref("");
|
const vNewAddressSurname = ref("");
|
||||||
const vNewAddressAddress = ref("");
|
const vNewAddressAddress = ref("");
|
||||||
const vNewAddressCode = ref("");
|
const vNewAddressCode = ref("");
|
||||||
const vNewAddressCity = ref("");
|
const vNewAddressCity = ref("");
|
||||||
const vNewAddressCountry = ref("");
|
const vNewAddressCountry = ref();
|
||||||
const vUseAccountPhoneNumber = ref(false);
|
const vUseAccountPhoneNumber = ref(false);
|
||||||
const isOpen = ref<boolean>(false);
|
const isOpen = ref<boolean>(false);
|
||||||
async function uploadAddress() {
|
async function uploadAddress() {
|
||||||
try {
|
try {
|
||||||
const res = await useMyFetch<GenericResponse<object>>(
|
const res = await useMyFetch<GenericResponse<object>>(
|
||||||
`/api/restricted/user/address/official`,
|
`/api/restricted/user/address`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@ -125,7 +93,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
address: {
|
address: {
|
||||||
city: vNewAddressCity.value,
|
city: vNewAddressCity.value,
|
||||||
// country_iso: vNewAddressCountry.value?.iso_code,
|
country_iso: vNewAddressCountry.value.iso_code,
|
||||||
name: vNewAddressName.value,
|
name: vNewAddressName.value,
|
||||||
postcode: vNewAddressCode.value,
|
postcode: vNewAddressCode.value,
|
||||||
street: vNewAddressAddress.value,
|
street: vNewAddressAddress.value,
|
||||||
@ -147,7 +115,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
dangerouslyHTMLString: true,
|
dangerouslyHTMLString: true,
|
||||||
});
|
});
|
||||||
isOpen.value = false;
|
isOpen.value = false;
|
||||||
restrictedAddress();
|
getAddressList();
|
||||||
} else {
|
} else {
|
||||||
$toast.error("Failed to add address. Please try again.", {
|
$toast.error("Failed to add address. Please try again.", {
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
@ -159,23 +127,32 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPrefix = ref<string | number>("+43");
|
const currentPrefix = ref<string | number>(
|
||||||
const changePrefix = (item: any) => {
|
menuStore.selectedCountry.call_prefix
|
||||||
|
);
|
||||||
|
const changePrefix = (item: string) => {
|
||||||
currentPrefix.value = item;
|
currentPrefix.value = item;
|
||||||
};
|
};
|
||||||
const phoneValidation = ref<boolean | null>(null);
|
const phoneValidation = ref<boolean | null>(null);
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
// send form
|
||||||
async function sendForm() {
|
async function sendForm() {
|
||||||
let phoneNum = `${currentPrefix.value}${phoneNumber.value}`
|
let phoneNum = vUseAccountPhoneNumber.value
|
||||||
.replaceAll(" ", "")
|
? accountPhoneNumber.value
|
||||||
.trim();
|
: `${currentPrefix.value}${phoneNumber.value}`.replaceAll(" ", "").trim();
|
||||||
// if (vUseAccountPhoneNumber.value) {
|
// if (vUseAccountPhoneNumber.value) {
|
||||||
// phoneNum = phoneNumber.value;
|
// phoneNum = phoneNumber.value;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
phoneValidation.value = validation(phoneNum, 1, 49, REGEX_PHONE);
|
phoneValidation.value = validation(phoneNum, 1, 49, REGEX_PHONE);
|
||||||
|
if (!phoneValidation.value && !vUseAccountPhoneNumber.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await useMyFetch<GenericResponse<object>>(
|
const res = await useMyFetch<GenericResponse<object>>(
|
||||||
`restricted/cart/checkout/delivery`,
|
`/api/restricted/cart/checkout/delivery`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
@ -191,7 +168,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
surname: activeAddress.value?.address.surname,
|
surname: activeAddress.value?.address.surname,
|
||||||
},
|
},
|
||||||
phone_number: phoneNum,
|
phone_number: phoneNum,
|
||||||
// email: useUserStore().fullUserData.email,
|
email: userStore.fullUserData?.email,
|
||||||
}),
|
}),
|
||||||
onErrorOccured: async (_, status) => {
|
onErrorOccured: async (_, status) => {
|
||||||
throw createError({
|
throw createError({
|
||||||
@ -223,10 +200,34 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
activeAddress.value = item;
|
activeAddress.value = item;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function getCheckout() {
|
||||||
|
try {
|
||||||
|
const res = await useMyFetch<GenericResponse<object>>(
|
||||||
|
`/api/restricted/cart/checkout`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
onErrorOccured: async (_, status) => {
|
||||||
|
throw createError({
|
||||||
|
statusCode: status,
|
||||||
|
statusMessage: `HTTP error: ${status}`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("uploadAddress error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addressesList,
|
addressesList,
|
||||||
activeAddress,
|
activeAddress,
|
||||||
isOpen,
|
isOpen,
|
||||||
|
selectedIso,
|
||||||
|
phoneValidation,
|
||||||
|
|
||||||
userName,
|
userName,
|
||||||
lastName,
|
lastName,
|
||||||
@ -246,8 +247,10 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
|
|||||||
vNewAddressCity,
|
vNewAddressCity,
|
||||||
vNewAddressCountry,
|
vNewAddressCountry,
|
||||||
|
|
||||||
restrictedAddress,
|
changePrefix,
|
||||||
restrictedAddressOfficial,
|
getCheckout,
|
||||||
|
getAddressList,
|
||||||
|
getUserData,
|
||||||
changeActive,
|
changeActive,
|
||||||
uploadAddress,
|
uploadAddress,
|
||||||
sendForm,
|
sendForm,
|
||||||
|
@ -3,6 +3,10 @@ import type { Customer } from "~/types/user";
|
|||||||
|
|
||||||
export const useUserStore = defineStore("userStore", () => {
|
export const useUserStore = defineStore("userStore", () => {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const menuStore = useMenuStore();
|
||||||
|
const checkoutStore = useCheckoutStore();
|
||||||
|
|
||||||
|
const { $toast } = useNuxtApp();
|
||||||
|
|
||||||
const fullUserData = ref<Customer | null>(null);
|
const fullUserData = ref<Customer | null>(null);
|
||||||
const isLogged = ref<boolean>(true);
|
const isLogged = ref<boolean>(true);
|
||||||
@ -10,28 +14,26 @@ export const useUserStore = defineStore("userStore", () => {
|
|||||||
|
|
||||||
async function checkIsLogged() {
|
async function checkIsLogged() {
|
||||||
try {
|
try {
|
||||||
const { data } = await useMyFetch<
|
const { data } = await useMyFetch<GenericResponse<Customer>>(
|
||||||
GenericResponse<{ loggedin: boolean } | Customer>
|
`/api/public/user`,
|
||||||
>(`/api/public/user`, {
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
onErrorOccured: async (_, status) => {
|
onErrorOccured: async (_, status) => {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: status,
|
statusCode: status,
|
||||||
statusMessage: `HTTP error: ${status}`,
|
statusMessage: `HTTP error: ${status}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if ("loggedin" in data && data.loggedin === true) {
|
if ("loggedin" in data && data.loggedin === true) {
|
||||||
isLogged.value = true;
|
isLogged.value = true;
|
||||||
user.value = null;
|
user.value = `${data.first_name} ${data.last_name}` as any;
|
||||||
fullUserData.value = null;
|
fullUserData.value = data;
|
||||||
} else if ("first_name" in data && "last_name" in data) {
|
checkoutStore.accountPhoneNumber = fullUserData.value.phone_number;
|
||||||
isLogged.value = true;
|
|
||||||
user.value = `${data.first_name} ${data.last_name}`;
|
|
||||||
fullUserData.value = data as Customer;
|
|
||||||
} else {
|
} else {
|
||||||
isLogged.value = false;
|
isLogged.value = false;
|
||||||
user.value = null;
|
user.value = null;
|
||||||
@ -48,7 +50,6 @@ export const useUserStore = defineStore("userStore", () => {
|
|||||||
const vLogin = ref<boolean>(true);
|
const vLogin = ref<boolean>(true);
|
||||||
const vCodeVerify = ref<boolean>(false);
|
const vCodeVerify = ref<boolean>(false);
|
||||||
const vCode = ref<number | null>(null);
|
const vCode = ref<number | null>(null);
|
||||||
const vEmail = ref<string>("");
|
|
||||||
async function logIn() {
|
async function logIn() {
|
||||||
try {
|
try {
|
||||||
const data = await useMyFetch<GenericResponse<object>>(
|
const data = await useMyFetch<GenericResponse<object>>(
|
||||||
@ -71,35 +72,77 @@ export const useUserStore = defineStore("userStore", () => {
|
|||||||
if (data.status === 200 || data.status === 201) {
|
if (data.status === 200 || data.status === 201) {
|
||||||
console.log(vCodeVerify.value);
|
console.log(vCodeVerify.value);
|
||||||
|
|
||||||
// $toast.success("Address successfully added", {
|
$toast.success("Code successfully sent to your email", {
|
||||||
// autoClose: 5000,
|
autoClose: 5000,
|
||||||
// dangerouslyHTMLString: true,
|
dangerouslyHTMLString: true,
|
||||||
// });
|
});
|
||||||
vLogin.value = false;
|
vLogin.value = false;
|
||||||
vCodeVerify.value = true;
|
vCodeVerify.value = true;
|
||||||
|
} else {
|
||||||
|
$toast.error("Failed to sent code to your email. Please try again.", {
|
||||||
|
autoClose: 5000,
|
||||||
|
dangerouslyHTMLString: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// else {
|
|
||||||
// $toast.error("Failed to add address. Please try again.", {
|
|
||||||
// autoClose: 5000,
|
|
||||||
// dangerouslyHTMLString: true,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
store.minValue = data;
|
store.minValue = data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("getList error:", error);
|
console.error("getList error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendFormCode = async (redirect?: boolean) => {
|
||||||
|
try {
|
||||||
|
const data = await useMyFetch<GenericResponse<object>>(
|
||||||
|
`/api/public/user/session/confirm`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
code: vCode.value,
|
||||||
|
mail: email.value,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
onErrorOccured: (_, status) => {
|
||||||
|
throw new Error(`HTTP error: ${status}`);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await checkIsLogged();
|
||||||
|
|
||||||
|
if (isLogged.value) {
|
||||||
|
if (redirect) {
|
||||||
|
console.log(isLogged.value);
|
||||||
|
menuStore.navigateToItem();
|
||||||
|
} else {
|
||||||
|
// window.location.href = atob(redirect);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
useNuxtApp().$toast.error(`Error occurred: Failed to confirm code`, {
|
||||||
|
autoClose: 5000,
|
||||||
|
dangerouslyHTMLString: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
useNuxtApp().$toast.error(`Invalid code provided`, {
|
||||||
|
autoClose: 5000,
|
||||||
|
dangerouslyHTMLString: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLogged,
|
isLogged,
|
||||||
user,
|
user,
|
||||||
fullUserData,
|
fullUserData,
|
||||||
vCodeVerify,
|
vCodeVerify,
|
||||||
vCode,
|
vCode,
|
||||||
vEmail,
|
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
logIn,
|
logIn,
|
||||||
checkIsLogged,
|
checkIsLogged,
|
||||||
|
sendFormCode,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -13,3 +13,20 @@ export interface AddressesList {
|
|||||||
is_default: boolean;
|
is_default: boolean;
|
||||||
is_official: boolean;
|
is_official: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserAddressOfficial {
|
||||||
|
address: {
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
street: string;
|
||||||
|
postcode: string;
|
||||||
|
city: string;
|
||||||
|
country: {
|
||||||
|
country_lang: [
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user