update empty string to null

This commit is contained in:
2025-07-28 18:17:52 +03:00
parent 6d71c60550
commit 462ed2b671
33 changed files with 369 additions and 242 deletions

Binary file not shown.

View File

@@ -17,11 +17,9 @@
<!-- <!--
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>"> <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
--> -->
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png"> <link rel="icon" href="icons/favicon.svg" type="image/svg+xml">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png"> <link rel="icon" type="image/ico" href="icons/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png"> <link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
</head> </head>
<body> <body>
<!-- quasar:entry-point --> <!-- quasar:entry-point -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/icons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

67
public/icons/favicon.svg Normal file
View File

@@ -0,0 +1,67 @@
<svg
viewBox="0 0 8.4666662 8.4666662"
width="32"
height="32"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
>
<defs id="defs1" />
<g id="layer1">
<rect
style="fill: #27A7E7 ;stroke-width:0.233149"
id="rect5"
width="6.9885192"
height="0.35581663"
x="3.114475"
y="0.86827624"
transform="matrix(0.77578367,0.63099897,-0.77578367,0.63099897,0,0)"
/>
<rect
style="fill: #27A7E7 ;stroke-width:0.24961"
id="rect5-7"
width="7.4819207"
height="0.3809379"
x="-3.9267058"
y="5.7988153"
transform="matrix(-0.70756824,0.70664502,0.70756824,0.70664502,0,0)"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.134869"
id="path5-8"
cx="1.5875"
cy="6.8791666"
r="1.0583333"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.168586"
id="path5-8-5"
cx="7.1437502"
cy="7.1437502"
r="1.3229166"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.118011"
id="path5-8-5-1"
cx="1.4552083"
cy="2.5135417"
r="0.92604166"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.101152"
id="path5-8-5-1-7"
cx="7.1437502"
cy="1.3229166"
r="0.79374999"
/>
<circle
style="fill: #F36D3A; stroke-width:0.23602"
id="path5"
cx="3.96875"
cy="4.4979167"
r="1.8520833"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -1,7 +1,13 @@
import { defineBoot } from '#q-app/wrappers' import { defineBoot } from '#q-app/wrappers'
import axios, { type AxiosError } from 'axios' import axios, { type AxiosError, type AxiosRequestConfig } from 'axios'
import { Notify } from 'quasar' import { Notify } from 'quasar'
declare module 'axios' {
export interface AxiosRequestConfig {
suppressNotify?: boolean
}
}
class ServerError extends Error { class ServerError extends Error {
constructor( constructor(
public code: string, public code: string,
@@ -31,12 +37,15 @@ api.interceptors.response.use(
errorData.message errorData.message
) )
if (!error.config?.suppressNotify) {
Notify.create({ Notify.create({
type: 'negative', type: 'negative',
message: errorData.code + ': ' + errorData.message, message: errorData.code + ': ' + errorData.message,
icon: 'mdi-alert-outline', icon: 'mdi-alert-outline',
position: 'bottom' position: 'bottom'
}) })
}
return Promise.reject(serverError) return Promise.reject(serverError)
} }
) )

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex row items-center"> <div class="flex row items-center no-wrap">
<svg <svg
class="iconcolor q-mr-sm" class="iconcolor q-mr-xs"
viewBox="0 0 8.4666662 8.4666662" viewBox="0 0 8.4666662 8.4666662"
width="32" width="32"
height="32" height="32"
@@ -68,12 +68,17 @@
</g> </g>
</svg> </svg>
<span class="text-h4 q-pa-0" style="color: var(--logo-color-bg-white);"> <span
projects class="text-h4 text-brand"
style="
color: var(--logo-color-bg-white);
margin-right: 2px;"
>
tg
</span> </span>
<span class="text-h4 text-brand text-bold q-pa-0"> <span class="text-h4 text-brand2 text-bold q-pa-0">
Node Crew
</span> </span>
</div> </div>
@@ -99,16 +104,16 @@
} }
.iconcolor { .iconcolor {
--icon-color: var(--logo-color-bg-white); --icon-color: #27A7E7;
} }
@keyframes blink { @keyframes blink {
100%, 100%,
0% { 0% {
fill: $light-green-14; fill: #fa9e7a;
} }
60% { 60% {
fill: $green-14; fill: #F36D3A;
} }
} }

View File

@@ -126,7 +126,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import type { AxiosError } from 'axios' import type { AxiosError } from 'axios'
import { useQuasar } from 'quasar'
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { QInput } from 'quasar' import { QInput } from 'quasar'
import { useAuthStore, type AuthFlowType } from 'stores/auth' import { useAuthStore, type AuthFlowType } from 'stores/auth'
@@ -141,7 +140,6 @@
: 'changeMethod' : 'changeMethod'
}) })
const $q = useQuasar()
const { t } = useI18n() const { t } = useI18n()
const authStore = useAuthStore() const authStore = useAuthStore()
@@ -190,23 +188,6 @@
await authStore.loginWithCredentials(login.value, password.value) await authStore.loginWithCredentials(login.value, password.value)
} }
} }
}
const handleError = (err: AxiosError) => {
const error = err as AxiosError<{ error?: { message?: string } }>
const message = error.response?.data?.error?.message || t('unknown_error')
$q.notify({
message: `${t('error')}: ${message}`,
type: 'negative',
position: 'bottom',
timeout: 2500
})
if (step.value > 1) {
code.value = ''
password.value = ''
}
} }
const handleSubmit = async () => { const handleSubmit = async () => {
@@ -218,7 +199,7 @@
showSuccessOverlay.value = true showSuccessOverlay.value = true
} }
} catch (error) { } catch (error) {
handleError(error as AxiosError) console.error(error as AxiosError)
} }
} }

View File

@@ -7,8 +7,8 @@
<q-btn <q-btn
rounded color="primary" rounded color="primary"
class="w100 q-mt-md q-mb-xs" class="w100 q-mt-md q-mb-xs"
:disable="!(isFormValid && (isDirty(initialCompany, modelValue)))" :disable="!isFormValid"
@click = "emit('update')" @click = "onSubmit"
> >
{{ $t(btnText) }} {{ $t(btnText) }}
</q-btn> </q-btn>
@@ -57,10 +57,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, computed, ref } from 'vue' import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { isDirty } from 'helpers/helpers'
import type { CompanyParams } from 'types/Company' import type { CompanyParams } from 'types/Company'
import { convertEmptyStringsToNull } from 'helpers/helpers'
const { t }= useI18n() const { t }= useI18n()
@@ -108,12 +108,10 @@
return Object.values(validations).every(Boolean) return Object.values(validations).every(Boolean)
}) })
const initialCompany = ref({} as CompanyParams) function onSubmit() {
const cleanedData = convertEmptyStringsToNull(modelValue.value)
onMounted(() => { emit('update', cleanedData)
console.log(111, modelValue.value) }
initialCompany.value = { ...modelValue.value }
})
</script> </script>

View File

@@ -7,7 +7,7 @@
<q-btn <q-btn
rounded color="primary" rounded color="primary"
class="w100 q-mt-md q-mb-xs" class="w100 q-mt-md q-mb-xs"
:disable="!(isFormValid && (isDirty(initialProject, modelValue)))" :disable="!isFormValid"
@click = "emit('update')" @click = "emit('update')"
> >
{{ $t(btnText) }} {{ $t(btnText) }}
@@ -64,7 +64,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, computed, ref } from 'vue' import { onMounted, computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { isDirty } from 'helpers/helpers'
import type { ProjectParams } from 'types/Project' import type { ProjectParams } from 'types/Project'
const { t } = useI18n() const { t } = useI18n()

View File

@@ -7,8 +7,7 @@
<q-btn <q-btn
rounded color="primary" rounded color="primary"
class="w100 q-mt-md q-mb-xs" class="w100 q-mt-md q-mb-xs"
:disable="!(isDirty(initialUser, modelValue))" @click = "onSubmit"
@click = "emit('update')"
> >
{{ $t(btnText) }} {{ $t(btnText) }}
</q-btn> </q-btn>
@@ -108,12 +107,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, computed, ref } from 'vue' import { computed } from 'vue'
import { useCompaniesStore } from 'stores/companies' import { useCompaniesStore } from 'stores/companies'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { isDirty } from 'helpers/helpers' import { convertEmptyStringsToNull } from 'src/helpers/helpers'
import type { User } from 'types/Users' import type { User } from 'types/Users'
const { t } = useI18n() const { t } = useI18n()
const modelValue = defineModel<User>({ const modelValue = defineModel<User>({
@@ -127,11 +127,10 @@
const emit = defineEmits(['update']) const emit = defineEmits(['update'])
const initialUser = ref({} as User) function onSubmit() {
const cleanedData = convertEmptyStringsToNull(modelValue.value)
onMounted(() => { emit('update', cleanedData)
initialUser.value = { ...modelValue.value } }
})
const companiesStore = useCompaniesStore() const companiesStore = useCompaniesStore()
const companies = computed(() => companiesStore.companies) const companies = computed(() => companiesStore.companies)

View File

@@ -1,10 +1,18 @@
// app global css in SCSS form // app global css in SCSS form
.text-brand { .text-brand {
color: $green-14 !important; color: #27A7E7 !important;
} }
.bg-brand { .bg-brand {
background: $green-14 !important; background: #27A7E7 !important;
}
.text-brand2 {
color: #F36D3A !important;
}
.bg-brand2 {
background: #F36D3A !important;
} }
$base-width: 100; $base-width: 100;
@@ -65,3 +73,12 @@ body {
border-bottom: 1px solid grey; border-bottom: 1px solid grey;
margin: auto; margin: auto;
} }
@font-face {
font-family: 'myFont';
src: url(./fonts/OpenSans-Regular.woff2);
}
button[disabled] {
background-color: $grey-5 !important;
}

Binary file not shown.

View File

@@ -12,7 +12,7 @@
// to match your app's branding. // to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2; $primary : #27A7E7;
$secondary : #26A69A; $secondary : #26A69A;
$accent : #9C27B0; $accent : #9C27B0;
@@ -26,4 +26,16 @@ $warning : #F2C037;
$lightgrey : #DCDCDC; $lightgrey : #DCDCDC;
$body-font-size: var(--dynamic-font-size) $body-font-size: var(--dynamic-font-size);
$typography-font-family: 'myFont', Roboto !default;
body, html, #q-app {
font-family: $typography-font-family;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
font-family: inherit;
}

View File

@@ -92,7 +92,22 @@ function parseIntString (s: string | string[] | undefined) :number | null {
return regex.test(s) ? Number(s) : null return regex.test(s) ? Number(s) : null
} }
// Функция для преобразования пустых строк в null
function convertEmptyStringsToNull<T extends Record<string, unknown>>(obj: T): T {
const result = { ...obj } as Record<string, unknown>
Object.keys(result).forEach(key => {
if (result[key] === '') {
result[key] = null
}
})
return result as T
}
export { export {
isDirty, isDirty,
parseIntString parseIntString,
convertEmptyStringsToNull
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -192,12 +192,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import type { AxiosError } from 'axios' import type { AxiosError } from 'axios'
import { useQuasar } from 'quasar'
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { QInput } from 'quasar' import { QInput } from 'quasar'
import { useAuthStore } from 'stores/auth' import { useAuthStore } from 'stores/auth'
const $q = useQuasar()
const { t } = useI18n() const { t } = useI18n()
const authStore = useAuthStore() const authStore = useAuthStore()
@@ -248,23 +246,6 @@
await authStore.setNewEmailPassword(code.value, newCode.value, newLogin.value, password.value) await authStore.setNewEmailPassword(code.value, newCode.value, newLogin.value, password.value)
await authStore.loginWithCredentials(newLogin.value, password.value) await authStore.loginWithCredentials(newLogin.value, password.value)
} }
}
const handleError = (err: AxiosError) => {
const error = err as AxiosError<{ error?: { message?: string } }>
const message = error.response?.data?.error?.message || t('unknown_error')
$q.notify({
message: `${t('error')}: ${message}`,
type: 'negative',
position: 'bottom',
timeout: 2500
})
if (step.value > 1) {
code.value = ''
password.value = ''
}
} }
const handleSubmit = async () => { const handleSubmit = async () => {
@@ -276,7 +257,7 @@
showSuccessOverlay.value = true showSuccessOverlay.value = true
} }
} catch (error) { } catch (error) {
handleError(error as AxiosError) console.error(error as AxiosError)
} }
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<pn-page-card> <pn-page-card>
<template #title> <template #title>
{{$t('login__register')}} {{$t('login__register_title')}}
</template> </template>
<pn-scroll-list> <pn-scroll-list>
<account-helper :type :email/> <account-helper :type :email/>

91
src/pages/ChatPage.vue Normal file
View File

@@ -0,0 +1,91 @@
<template>
<pn-page-card>
<template #title>
<pn-account-block-name/>
<q-btn
@click="logout()"
flat
round
icon="mdi-logout"
/>
</template>
<pn-scroll-list>
<q-list separator>
<q-item
v-for="item in displayItems"
:key="item.id"
@click="goTo(item.pathName)"
clickable
v-ripple
>
<q-item-section avatar>
<q-avatar
:icon="item.icon"
:color="item.iconColor ? item.iconColor: 'brand'"
text-color="white"
rounded
font-size ="26px"
/>
</q-item-section>
<q-item-section>
<q-item-label>
{{ $t(item.name) }}
</q-item-label>
<q-item-label class="text-caption" v-if="$te(item.description)">
{{ $t(item.description) }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</pn-scroll-list>
</pn-page-card>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from 'stores/auth'
const router = useRouter()
const authStore = useAuthStore()
interface ItemList {
id: number
name: string
description?: string
icon: string
iconColor?: string
pathName: string
display?: boolean
}
const items = computed(() => ([
{ id: 1, name: 'account__subscribe', description: 'account__subscribe_description', icon: 'mdi-crown-circle-outline', iconColor: 'orange', pathName: 'subscribe' },
{ id: 2, name: 'account__auth_change_method', description: 'account__auth_change_method_description', icon: 'mdi-account-sync-outline', iconColor: 'primary', pathName: 'change_account_auth_method', display: !authStore.customer?.email },
{ id: 3, name: 'account__auth_change_password', description: 'account__auth_change_password_description', icon: 'mdi-account-key-outline', iconColor: 'primary', pathName: 'change_account_password', display: !!authStore.customer?.email },
{ id: 4, name: 'account__auth_change_account', description: 'account__auth_change_account_description', icon: 'mdi-account-switch-outline', iconColor: 'primary', pathName: 'change_account_email', display: !!authStore.customer?.email },
{ id: 5, name: 'account__company_data', icon: 'mdi-account-group-outline', description: 'account__company_data_description', pathName: 'your_company' },
{ id: 6, name: 'account__settings', icon: 'mdi-cog-outline', description: 'account__settings_description', iconColor: 'info', pathName: 'settings' },
{ id: 7, name: 'account__support', icon: 'mdi-lifebuoy', description: 'account__support_description', iconColor: 'info', pathName: 'support' },
{ id: 9, name: 'account__terms_of_use', icon: 'mdi-book-open-variant-outline', description: '', iconColor: 'grey', pathName: 'terms' },
{ id: 10, name: 'account__privacy', icon: 'mdi-lock-outline', description: '', iconColor: 'grey', pathName: 'privacy' }
]))
const displayItems = computed(() => (
items.value.filter((item: ItemList) => !('display' in item) || item.display === true)
))
async function goTo (path: string) {
await router.push({ name: path })
}
async function logout () {
await authStore.logout()
await router.push({ name: 'login' })
}
</script>
<style lang="scss">
</style>

View File

@@ -8,7 +8,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { reactive } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import companyBlock from 'components/companyBlock.vue' import companyBlock from 'components/companyBlock.vue'
import { useCompaniesStore } from 'stores/companies' import { useCompaniesStore } from 'stores/companies'
@@ -17,18 +17,10 @@
const router = useRouter() const router = useRouter()
const companiesStore = useCompaniesStore() const companiesStore = useCompaniesStore()
const newCompany = ref(<CompanyParams>{ const newCompany = reactive<CompanyParams>({} as CompanyParams)
name: '',
logo: '',
description: '',
site: '',
address: '',
phone: '',
email: ''
})
async function addCompany () { async function addCompany(companyData: CompanyParams) {
await companiesStore.add(newCompany.value) await companiesStore.add(companyData)
router.go(-1) router.go(-1)
} }

View File

@@ -1,12 +1,12 @@
<template> <template>
<company-block <company-block
v-if="companyMod" v-if="company"
v-model="companyMod" v-model="company"
title="company_edit__title_card" title="company_edit__title_card"
btnText="company_edit__btn" btnText="company_edit__btn"
@update="updateCompany" @update="updateCompany"
> >
<template #myCompany v-if="companyMod.is_own"> <template #myCompany v-if="company.is_own">
<div class="q-mb-md flex w100 justify-center"> <div class="q-mb-md flex w100 justify-center">
<div class="flex items-center text-amber-10"> <div class="flex items-center text-amber-10">
<q-icon name="star" class="q-pr-xs"/> <q-icon name="star" class="q-pr-xs"/>
@@ -32,24 +32,27 @@
const route = useRoute() const route = useRoute()
const companiesStore = useCompaniesStore() const companiesStore = useCompaniesStore()
const companyMod = ref<CompanyParams | null>(null) const company = ref<CompanyParams | null>(null)
const companyId = computed(() => parseIntString(route.params.companyId)) const companyId = computed(() => parseIntString(route.params.companyId))
if (companiesStore.isInit) { function initCompany() {
companyMod.value = companyId.value if (companiesStore.isInit && companyId.value) {
? { ...companiesStore.companyById(companyId.value) } as CompanyParams const foundCompany = companiesStore.companyById(companyId.value)
: null if (foundCompany) {
company.value = foundCompany as CompanyParams
} }
}
}
if (companiesStore.isInit) initCompany()
watch(() => companiesStore.isInit, (isInit) => { watch(() => companiesStore.isInit, (isInit) => {
if (isInit && companyId.value && !companyMod.value) { if (isInit) initCompany()
companyMod.value = { ...companiesStore.companyById(companyId.value) as CompanyParams }
}
}) })
async function updateCompany () { async function updateCompany(companyData: CompanyParams) {
if (companyId.value && companyMod.value) { if (companyId.value) {
await companiesStore.update(companyId.value, companyMod.value) await companiesStore.update(companyId.value, companyData)
router.go(-1) router.go(-1)
} }
} }

View File

@@ -1,18 +1,23 @@
<template> <template>
<q-page class="flex column items-center justify-between"> <q-page
<div :style="{ height: `${blockHeight}px` }" /> class="q-ma-none w100"
>
<q-scroll-area
style="height: 100vh"
>
<div class="flex items-center justify-center" style="height: 100vh">
<q-card <q-card
id="login_block" id="login_block"
flat flat
class="flex column items-center w80 justify-between q-py-md login-card " class="flex column no-wrap items-center justify-between q-px-xl q-py-md login-card"
style="max-width: 400px;"
> >
<login-logo <base-logo
class="col-grow q-pa-md" class="col-grow q-pa-lg"
:style="{ alignItems: 'flex-end' }" :style="{ alignItems: 'flex-end' }"
/> />
<div class="q-ma-md flex column input-login"> <div class="flex column w100">
<q-input <q-input
v-model="login" v-model="login"
autofocus autofocus
@@ -70,26 +75,27 @@
<q-btn <q-btn
@click="sendAuth()" @click="sendAuth()"
color="primary" color="primary"
:disabled="!acceptTermsOfUse || !isEmailValid || !isPasswordValid" class="w100 q-my-md"
:disabled="!isEmailValid || !isPasswordValid"
> >
{{$t('login__sign_in')}} {{$t('login__sign_in')}}
</q-btn> </q-btn>
<div class="q-pt-lg">
<q-btn <q-btn
flat flat
sm sm
no-caps no-caps
class="q-my-md"
color="primary" color="primary"
@click="createAccount" @click="createAccount"
> >
{{$t('login__register')}} {{$t('login__register')}}
</q-btn> </q-btn>
</div>
<div <div
v-if="isTelegramApp" v-if="isTelegramApp"
id="alt_login" id="alt_login"
class="w80 q-flex column items-center q-pt-md" class="flex w100 column items-center "
> >
<div <div
class="orline w100 text-grey" class="orline w100 text-grey"
@@ -101,7 +107,7 @@
sm sm
no-caps no-caps
color="primary" color="primary"
:disabled="!acceptTermsOfUse" class="q-my-md"
@click="handleTelegramLogin" @click="handleTelegramLogin"
> >
<div class="flex items-center text-blue"> <div class="flex items-center text-blue">
@@ -122,45 +128,19 @@
</q-btn> </q-btn>
</div> </div>
</q-card> </q-card>
<div
id="term-of-use"
class="q-pb-md text-white flex justify-center row text-caption"
ref="bottomBlock"
>
<q-resize-observer @resize="syncHeights" />
<q-checkbox
v-model="acceptTermsOfUse"
checked-icon="task_alt"
unchecked-icon="highlight_off"
:color="acceptTermsOfUse ? 'brand' : 'red'"
dense
keep-color
size="sm"
/>
<span class="q-px-xs">
{{ $t('login__accept_terms_of_use') + ' ' }}
</span>
<span
@click="router.push('terms-of-use')"
style="text-decoration: underline;"
>
{{ $t('login__terms_of_use') }}
</span>
</div> </div>
</q-scroll-area>
</q-page> </q-page>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, inject, onUnmounted } from 'vue' import { ref, computed, inject, onUnmounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import loginLogo from 'components/login-page/loginLogo.vue' import baseLogo from 'components/BaseLogo.vue'
import { useI18n } from "vue-i18n" import { useI18n } from "vue-i18n"
import { useAuthStore } from 'stores/auth' import { useAuthStore } from 'stores/auth'
import type { WebApp } from '@twa-dev/types' import type { WebApp } from '@twa-dev/types'
import { QInput } from 'quasar' import { QInput } from 'quasar'
import { useNotify, type ServerError } from 'composables/useNotify'
const { notifyError } = useNotify()
type ValidationRule = (val: string) => boolean | string type ValidationRule = (val: string) => boolean | string
@@ -174,10 +154,6 @@
const login = ref<string>('') const login = ref<string>('')
const password = ref<string>('') const password = ref<string>('')
const isPwd = ref<boolean>(true) const isPwd = ref<boolean>(true)
const acceptTermsOfUse = ref<boolean>(true)
const bottomBlock = ref<HTMLDivElement | null>(null)
const blockHeight = ref<number>(0)
const emailInput = ref<InstanceType<typeof QInput>>() const emailInput = ref<InstanceType<typeof QInput>>()
const passwordInput = ref<InstanceType<typeof QInput>>() const passwordInput = ref<InstanceType<typeof QInput>>()
@@ -223,7 +199,7 @@
async function sendAuth() { async function sendAuth() {
try { void await authStore.loginWithCredentials(login.value, password.value) } try { void await authStore.loginWithCredentials(login.value, password.value) }
catch (error) { catch (error) {
notifyError(error as ServerError) console.error(error)
} }
await router.push({ name: 'projects' }) await router.push({ name: 'projects' })
} }
@@ -251,12 +227,6 @@
await router.push({ name: 'projects' }) await router.push({ name: 'projects' })
} }
function syncHeights() {
if (bottomBlock.value) {
blockHeight.value = bottomBlock.value.offsetHeight
}
}
onUnmounted(() => { onUnmounted(() => {
Object.values(validateTimerId.value).forEach(timer => timer && clearTimeout(timer)) Object.values(validateTimerId.value).forEach(timer => timer && clearTimeout(timer))
}) })
@@ -264,16 +234,6 @@
</script> </script>
<style scoped> <style scoped>
.maxh15 {
max-height: calc(100Vh *0.15);
}
.input-login {
width: calc(100% * 0.8);
max-width: 300px;
}
.login-card { .login-card {
opacity: 0.9 !important; opacity: 0.9 !important;
border-radius: var(--top-raduis); border-radius: var(--top-raduis);

View File

@@ -10,13 +10,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter } from 'vue-router'
import projectBlock from 'components/projectBlock.vue' import projectBlock from 'components/projectBlock.vue'
import { useProjectsStore } from 'stores/projects' import { useProjectsStore } from 'stores/projects'
import type { ProjectParams } from 'types/Project' import type { ProjectParams } from 'types/Project'
const router = useRouter() const router = useRouter()
const route = useRoute()
const projectsStore = useProjectsStore() const projectsStore = useProjectsStore()
const projectId = computed(() => projectsStore.currentProjectId) const projectId = computed(() => projectsStore.currentProjectId)

View File

@@ -1,7 +1,7 @@
<template> <template>
<user-block <user-block
v-if="userMod" v-if="user"
v-model="userMod" v-model="user"
title="user_edit__title_card" title="user_edit__title_card"
btnText="user_edit__btn" btnText="user_edit__btn"
@update="updateUser" @update="updateUser"
@@ -20,24 +20,25 @@
const route = useRoute() const route = useRoute()
const usersStore = useUsersStore() const usersStore = useUsersStore()
const userMod = ref<User | null>(null) const user = ref<User | null>(null)
const userId = computed(() => parseIntString(route.params.userId)) const userId = computed(() => parseIntString(route.params.userId))
if (usersStore.isInit) { function initUser() {
userMod.value = userId.value if (usersStore.isInit && userId.value) {
? { ...usersStore.userById(userId.value) } as User const foundUser = usersStore.userById(userId.value)
: null if (foundUser) {
user.value = { ...foundUser } as User
}
}
} }
watch(() => usersStore.isInit, (isInit) => { if (usersStore.isInit) initUser()
if (isInit && userId.value && !userMod.value) {
userMod.value = { ...usersStore.userById(userId.value) as User }
}
})
async function updateUser () { watch(() => usersStore.isInit, initUser)
if (userId.value && userMod.value) {
await usersStore.update(userId.value, userMod.value) async function updateUser (userData: User) {
if (userId.value) {
await usersStore.update(userId.value, userData)
router.go(-1) router.go(-1)
} }
} }

View File

@@ -40,7 +40,7 @@ export const useAuthStore = defineStore('auth', () => {
const initialize = async () => { const initialize = async () => {
try { try {
const { data } = await api.get('/customer/profile') const { data } = await api.get('/customer/profile', { suppressNotify: true })
customer.value = data.data customer.value = data.data
const socket = new WebSocket("wss://946gp81j-9000.euw.devtunnels.ms/api/admin") const socket = new WebSocket("wss://946gp81j-9000.euw.devtunnels.ms/api/admin")
console.log(socket) console.log(socket)

View File

@@ -1,18 +1,17 @@
interface CompanyParams { interface CompanyParams {
name: string name: string
description: string description: string | null
address: string address: string | null
site: string site: string | null
phone: string phone: string | null
email: string email: string | null
logo: string logo: string | null
[key: string]: string | number [key: string]: string | number | null
} }
interface Company extends CompanyParams { interface Company extends CompanyParams {
id: number id: number
project_id: number project_id: number
[key: string]: string | number
} }
interface CompanyMask { interface CompanyMask {

View File

@@ -1,11 +1,12 @@
interface UserParams { interface UserParams {
fullname: string fullname: string | null
department: string department: string | null
role: string role: string | null
phone: string phone: string | null
email: string email: string | null
is_blocked: boolean is_blocked: boolean
company_id: number | null company_id: number | null
[key: string]: string | boolean | number | null
} }
interface User extends UserParams { interface User extends UserParams {
@@ -17,7 +18,6 @@ interface User extends UserParams {
username: string | null username: string | null
photo: string | null photo: string | null
is_leave: boolean is_leave: boolean
[key: string]: unknown
} }
export type { export type {