Merge branch 'master' into fix_all_customer_load

This commit is contained in:
Mohit Panjwani
2022-02-15 10:45:50 +05:30
150 changed files with 815 additions and 503 deletions

View File

@ -41,7 +41,7 @@
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/fonts/Poppins-Semibold.ttf") format("truetype");
src: url("/fonts/Poppins-SemiBold.ttf") format("truetype");
}
// Default Theme

View File

@ -28,7 +28,16 @@
"
>
<div class="w-full">
<MainLogo class="block w-48 h-auto max-w-full mb-32 text-primary-500" />
<MainLogo
v-if="!loginPageLogo"
class="block w-48 h-auto max-w-full mb-32 text-primary-500"
/>
<img
v-else
:src="loginPageLogo"
class="block w-48 h-auto max-w-full mb-32 text-primary-500"
/>
<router-view />
@ -76,7 +85,7 @@
<LoginBackgroundOverlay class="absolute h-full w-full right-[7.5%]" />
<div class="pl-20 xl:pl-0 relative z-50">
<div class="md:pl-10 xl:pl-0 relative z-50 w-7/12 xl:w-5/12 xl:w-5/12">
<h1
class="
hidden
@ -89,9 +98,7 @@
lg:block
"
>
<b class="font-bold">Simple Invoicing</b> <br />
for Individuals & <br />
Small Businesses <br />
{{ pageHeading }}
</h1>
<p
class="
@ -106,9 +113,7 @@
lg:block
"
>
Crater helps you track expenses, record payments & generate beautiful
<br />
invoices & estimates. <br />
{{ pageDescription }}
</p>
</div>
@ -136,6 +141,31 @@ import LoginBackground from '@/scripts/components/svg/LoginBackground.vue'
import LoginPlanetCrater from '@/scripts/components/svg/LoginPlanetCrater.vue'
import LoginBottomVector from '@/scripts/components/svg/LoginBottomVector.vue'
import LoginBackgroundOverlay from '@/scripts/components/svg/LoginBackgroundOverlay.vue'
import { computed, ref } from 'vue'
const pageHeading = computed(() => {
if (window.login_page_heading) {
return window.login_page_heading
}
return 'Simple Invoicing for Individuals Small Businesses'
})
const pageDescription = computed(() => {
if (window.login_page_description) {
return window.login_page_description
}
return 'Crater helps you track expenses, record payments & generate beautiful invoices & estimates.'
})
const loginPageLogo = computed(() => {
if (window.login_page_logo) {
return window.login_page_logo
}
return false
})
</script>
<style lang="scss" scoped>

View File

@ -33,7 +33,8 @@
md:block
"
>
<MainLogo class="h-6" light-color="white" dark-color="white" />
<img v-if="adminLogo" :src="adminLogo" class="h-6" />
<MainLogo v-else class="h-6" light-color="white" dark-color="white" />
</router-link>
<!-- toggle button-->
@ -195,6 +196,14 @@ const previewAvatar = computed(() => {
: getDefaultAvatar()
})
const adminLogo = computed(() => {
if (globalStore.globalSettings.admin_portal_logo) {
return '/storage/' + globalStore.globalSettings.admin_portal_logo
}
return false
})
function getDefaultAvatar() {
const imgUrl = new URL('/img/default-avatar.jpg', import.meta.url)
return imgUrl

View File

@ -46,7 +46,7 @@ export const useAuthStore = (useWindow = false) => {
logout() {
return new Promise((resolve, reject) => {
axios
.get('/auth/logout')
.post('/auth/logout')
.then((response) => {
const notificationStore = useNotificationStore()
notificationStore.showNotification({
@ -55,7 +55,7 @@ export const useAuthStore = (useWindow = false) => {
})
window.router.push('/login')
// resetStore.clearPinia()
// resetStore.clearPinia()
resolve(response)
})
.catch((err) => {
@ -67,4 +67,4 @@ export const useAuthStore = (useWindow = false) => {
},
},
})()
}
}

View File

@ -2,11 +2,10 @@
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-9 xl:gap-8">
<!-- Amount Due -->
<DashboardStatsItem
v-if="userStore.hasAbilities(abilities.VIEW_INVOICE)"
:icon-component="DollarIcon"
:loading="!dashboardStore.isDashboardDataLoaded"
:route="
userStore.hasAbilities(abilities.VIEW_INVOICE) ? `/admin/invoices` : ''
"
route="/admin/invoices"
:large="true"
:label="$t('dashboard.cards.due_amount')"
>
@ -18,13 +17,10 @@
<!-- Customers -->
<DashboardStatsItem
v-if="userStore.hasAbilities(abilities.VIEW_CUSTOMER)"
:icon-component="CustomerIcon"
:loading="!dashboardStore.isDashboardDataLoaded"
:route="
userStore.hasAbilities(abilities.VIEW_CUSTOMER)
? `/admin/customers`
: ''
"
route="/admin/customers"
:label="$t('dashboard.cards.customers')"
>
{{ dashboardStore.stats.totalCustomerCount }}
@ -32,11 +28,10 @@
<!-- Invoices -->
<DashboardStatsItem
v-if="userStore.hasAbilities(abilities.VIEW_INVOICE)"
:icon-component="InvoiceIcon"
:loading="!dashboardStore.isDashboardDataLoaded"
:route="
userStore.hasAbilities(abilities.VIEW_INVOICE) ? `/admin/invoices` : ''
"
route="/admin/invoices"
:label="$t('dashboard.cards.invoices')"
>
{{ dashboardStore.stats.totalInvoiceCount }}
@ -44,13 +39,10 @@
<!-- Estimates -->
<DashboardStatsItem
v-if="userStore.hasAbilities(abilities.VIEW_ESTIMATE)"
:icon-component="EstimateIcon"
:loading="!dashboardStore.isDashboardDataLoaded"
:route="
userStore.hasAbilities(abilities.VIEW_ESTIMATE)
? `/admin/estimates`
: ''
"
route="/admin/estimates"
:label="$t('dashboard.cards.estimates')"
>
{{ dashboardStore.stats.totalEstimateCount }}

View File

@ -2,7 +2,10 @@
<div>
<div class="grid grid-cols-1 gap-6 mt-10 xl:grid-cols-2">
<!-- Due Invoices -->
<div class="due-invoices">
<div
v-if="userStore.hasAbilities(abilities.VIEW_INVOICE)"
class="due-invoices"
>
<div class="relative z-10 flex items-center justify-between mb-3">
<h6 class="mb-0 text-xl font-semibold leading-normal">
{{ $t('dashboard.recent_invoices_card.title') }}
@ -49,7 +52,10 @@
</div>
<!-- Recent Estimates -->
<div class="recent-estimates">
<div
v-if="userStore.hasAbilities(abilities.VIEW_ESTIMATE)"
class="recent-estimates"
>
<div class="relative z-10 flex items-center justify-between mb-3">
<h6 class="mb-0 text-xl font-semibold leading-normal">
{{ $t('dashboard.recent_estimate_card.title') }}

View File

@ -50,6 +50,7 @@ import { useUserStore } from '@/scripts/admin/stores/user'
import { useRecurringInvoiceStore } from '@/scripts/admin/stores/recurring-invoice'
import abilities from '@/scripts/admin/stub/abilities'
import InvoiceDropdown from '@/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue'
import SendInvoiceModal from '@/scripts/admin/components/modal-components/SendInvoiceModal.vue'
const recurringInvoiceStore = useRecurringInvoiceStore()

View File

@ -48,14 +48,14 @@
</dd>
</div>
<div
v-if="invoice.notes"
v-if="invoice.formatted_notes"
class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-500">
{{ $t('invoices.notes') }}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<span v-html="invoice.notes"></span>
<span v-html="invoice.formatted_notes"></span>
</dd>
</div>
</dl>

View File

@ -196,7 +196,6 @@ async function getFields() {
{ label: 'Due Date', value: 'INVOICE_DUE_DATE' },
{ label: 'Number', value: 'INVOICE_NUMBER' },
{ label: 'Ref Number', value: 'INVOICE_REF_NUMBER' },
{ label: 'Invoice Link', value: 'INVOICE_LINK' },
...invoiceFields.value.map((i) => ({
label: i.label,
value: i.slug,
@ -213,7 +212,6 @@ async function getFields() {
{ label: 'Expiry Date', value: 'ESTIMATE_EXPIRY_DATE' },
{ label: 'Number', value: 'ESTIMATE_NUMBER' },
{ label: 'Ref Number', value: 'ESTIMATE_REF_NUMBER' },
{ label: 'Estimate Link', value: 'ESTIMATE_LINK' },
...estimateFields.value.map((i) => ({
label: i.label,
value: i.slug,
@ -230,7 +228,6 @@ async function getFields() {
{ label: 'Number', value: 'PAYMENT_NUMBER' },
{ label: 'Mode', value: 'PAYMENT_MODE' },
{ label: 'Amount', value: 'PAYMENT_AMOUNT' },
{ label: 'Payment Link', value: 'PAYMENT_LINK' },
...paymentFields.value.map((i) => ({
label: i.label,
value: i.slug,

View File

@ -1,8 +1,8 @@
<template>
<div class="flex flex-col">
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="-my-2 overflow-x-auto lg:overflow-visible sm:-mx-6 lg:-mx-8">
<div class="py-2 align-middle inline-block min-w-full sm:px-4 lg:px-6">
<div class="overflow-hidden sm:px-2 lg:p-2">
<div class="overflow-hidden lg:overflow-visible sm:px-2 lg:p-2">
<slot />
</div>
</div>

View File

@ -504,6 +504,72 @@
>
<redo-icon class="h-3 cursor-pointer fill-current" />
</span>
<span
class="
flex
items-center
justify-center
w-6
h-6
rounded-sm
cursor-pointer
hover:bg-gray-100
"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'left' }) }"
@click="editor.chain().focus().setTextAlign('left').run()"
>
<menu-alt2-icon class="h-5 cursor-pointer fill-current" />
</span>
<span
class="
flex
items-center
justify-center
w-6
h-6
rounded-sm
cursor-pointer
hover:bg-gray-100
"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'right' }) }"
@click="editor.chain().focus().setTextAlign('right').run()"
>
<menu-alt3-icon class="h-5 cursor-pointer fill-current" />
</span>
<span
class="
flex
items-center
justify-center
w-6
h-6
rounded-sm
cursor-pointer
hover:bg-gray-100
"
:class="{
'bg-gray-200': editor.isActive({ textAlign: 'justify' }),
}"
@click="editor.chain().focus().setTextAlign('justify').run()"
>
<menu-icon class="h-5 cursor-pointer fill-current" />
</span>
<span
class="
flex
items-center
justify-center
w-6
h-6
rounded-sm
cursor-pointer
hover:bg-gray-100
"
:class="{ 'bg-gray-200': editor.isActive({ textAlign: 'center' }) }"
@click="editor.chain().focus().setTextAlign('center').run()"
>
<menu-center-icon class="h-5 cursor-pointer fill-current" />
</span>
</div>
</div>
<editor-content
@ -526,7 +592,13 @@
import { onUnmounted, watch } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { DotsVerticalIcon } from '@heroicons/vue/outline'
import {
DotsVerticalIcon,
MenuAlt2Icon,
MenuAlt3Icon,
MenuIcon,
} from '@heroicons/vue/outline'
import TextAlign from '@tiptap/extension-text-align'
import {
BoldIcon,
@ -540,6 +612,7 @@ import {
UndoIcon,
RedoIcon,
CodeBlockIcon,
MenuCenterIcon,
} from './icons/index.js'
export default {
@ -557,6 +630,10 @@ export default {
RedoIcon,
CodeBlockIcon,
DotsVerticalIcon,
MenuCenterIcon,
MenuAlt2Icon,
MenuAlt3Icon,
MenuIcon,
},
props: {
@ -573,7 +650,13 @@ export default {
setup(props, { emit }) {
const editor = useEditor({
content: props.modelValue,
extensions: [StarterKit],
extensions: [
StarterKit,
TextAlign.configure({
types: ['heading', 'paragraph'],
alignments: ['left', 'right', 'center', 'justify'],
}),
],
onUpdate: () => {
emit('update:modelValue', editor.value.getHTML())

View File

@ -0,0 +1,11 @@
<template>
<svg viewBox="0 0 24 24">
<path
fill="currentColor"
fill-rule="evenodd"
d="M3.75 5.25h16.5a.75.75 0 1 1 0 1.5H3.75a.75.75 0 0 1 0-1.5zm4 4h8.5a.75.75 0 1 1 0 1.5h-8.5a.75.75 0 1 1 0-1.5zm-4 4h16.5a.75.75 0 1 1 0 1.5H3.75a.75.75 0 1 1 0-1.5zm4 4h8.5a.75.75 0 1 1 0 1.5h-8.5a.75.75 0 1 1 0-1.5z"
></path>
</svg>
</template>

View File

@ -10,6 +10,7 @@ import StrikethroughIcon from './StrikethroughIcon.vue'
import UndoIcon from './UndoIcon.vue'
import RedoIcon from './RedoIcon.vue'
import CodeBlockIcon from './CodeBlockIcon.vue'
import MenuCenterIcon from './MenuCenterIcon.vue'
export {
UnderlineIcon,
@ -23,5 +24,6 @@ export {
StrikethroughIcon,
UndoIcon,
RedoIcon,
CodeBlockIcon
CodeBlockIcon,
MenuCenterIcon
}

View File

@ -50,7 +50,7 @@ export const useAuthStore = defineStore({
axios
.post(`/api/v1/${data.company}/customer/auth/password/email`, data)
.then((response) => {
.then((response) => {
if (response.data) {
notificationStore.showNotification({
type: 'success',
@ -78,7 +78,7 @@ export const useAuthStore = defineStore({
axios
.post(`/api/v1/${company}/customer/auth/reset/password`, data)
.then((response) => {
.then((response) => {
if (response.data) {
const notificationStore = useNotificationStore(true)
notificationStore.showNotification({
@ -103,7 +103,7 @@ export const useAuthStore = defineStore({
logout(data) {
return new Promise((resolve, reject) => {
axios
.get(`${data}/customer/logout`)
.post(`${data}/customer/logout`)
.then((response) => {
const notificationStore = useNotificationStore()
notificationStore.showNotification({
@ -120,4 +120,4 @@ export const useAuthStore = defineStore({
})
},
},
})
})

View File

@ -85,21 +85,21 @@
"select_state": "Sélectionnez l'état",
"select_country": "Choisissez le pays",
"select_city": "Sélectionnez une ville",
"street_1": "Rue 1",
"street_2": "Rue # 2",
"street_1": "Rue, voie, boite postale",
"street_2": "Bâtiment, étage, lieu-dit, complément,...",
"action_failed": "Action : échoué",
"retry": "Réessayez",
"choose_note": "Choisissez une note de bas de page",
"no_note_found": "Aucune note de bas de page trouvée",
"insert_note": "Insérer une note",
"copied_pdf_url_clipboard": "L'adresse du PDF a été copiée.",
"copied_url_clipboard": "Copied url to clipboard!",
"copied_url_clipboard": "URL copiée vers le presse-papier!",
"docs": "Documents",
"do_you_wish_to_continue": "Voulez-vous continuer ?",
"note": "Note de bas de page",
"pay_invoice": "Pay Invoice",
"login_successfully": "Logged in successfully!",
"logged_out_successfully": "Logged out successfully"
"pay_invoice": "Payer facture",
"login_successfully": "Identifié avec succès!",
"logged_out_successfully": "Déconnecté avec succès"
},
"dashboard": {
"select_year": "Sélectionnez l'année",
@ -141,7 +141,7 @@
"name": "Nom",
"description": "Description",
"percent": "Pourcentage",
"compound_tax": "Taxe empilée"
"compound_tax": "Taxe composée"
},
"global_search": {
"search": "Rechercher",
@ -207,10 +207,10 @@
"new_customer": "Nouveau client",
"edit_customer": "Modifier le client",
"basic_info": "Informations de base",
"portal_access": "Portal Access",
"portal_access_text": "Would you like to allow this customer to login to the Customer Portal?",
"portal_access_url": "Customer Portal Login URL",
"portal_access_url_help": "Please copy & forward the above given URL to your customer for providing access.",
"portal_access": "Accès Portail",
"portal_access_text": "Souhaitez vous autoriser ce client à se connecter au Portail Client ?",
"portal_access_url": "URL de connexion Portail Client",
"portal_access_url_help": "Veuillez copiez et envoyez le lien ci-dessus au client pour lui fournir l'accès au portail.",
"billing_address": "Adresse de facturation",
"shipping_address": "Adresse de livraison",
"copy_billing_address": "Copier depuis l'adresse de facturation",
@ -230,7 +230,7 @@
"confirm_delete": "Vous ne pourrez pas récupérer ce client et les devis, factures et paiements associés. | Vous ne serez pas en mesure de récupérer ces clients et les devis, factures et paiements associés.",
"created_message": "Client créé",
"updated_message": "Client mis à jour",
"address_updated_message": "Address Information Updated succesfully",
"address_updated_message": "Adresse mise à jour avec succès",
"deleted_message": "Client supprimé | Clients supprimés",
"edit_currency_not_allowed": "Impossible de changer de devise une fois les transactions créées."
},
@ -264,8 +264,8 @@
},
"estimates": {
"title": "Devis",
"accept_estimate": "Accept Estimate",
"reject_estimate": "Reject Estimate",
"accept_estimate": "Accepter devis",
"reject_estimate": "Rejeter devis",
"estimate": "Devis | Devis",
"estimates_list": "Liste des devis",
"days": "{days} jours",
@ -357,10 +357,10 @@
},
"invoices": {
"title": "Factures",
"download": "Download",
"pay_invoice": "Pay Invoice",
"download": "Télécharger",
"pay_invoice": "Payer facture",
"invoices_list": "Liste des factures",
"invoice_information": "Invoice Information",
"invoice_information": "Informations sur la facture",
"days": "{days} jours",
"months": "{months} mois",
"years": "{years} années",
@ -806,7 +806,7 @@
"address_information": "Address Information"
},
"address_information": {
"section_description": " You can update Your Address information using form below."
"section_description": " Vous pouvez mettre à jour vos informations d'adresse via le formulaire ci dessous."
},
"title": "Paramètres",
"setting": "Paramètres | Paramètres",
@ -1173,8 +1173,8 @@
"discount_setting": "Réglage de remise",
"discount_per_item": "Remise par article",
"discount_setting_description": "Activez cette option si vous souhaitez détailler les remises par article. Par défaut, les remises sont ajoutées au sous-total.",
"expire_public_links": "Automatically Expire Public Links",
"expire_setting_description": "Specify whether you would like to expire all the links sent by application to view invoices, estimates & payments, etc after a specified duration.",
"expire_public_links": "Expiration automatique des liens publics",
"expire_setting_description": "Spécifiez si vous souhaitez faire expirer tous les liens publiques envoyés par l'application pour consulter les factures, devis, paiements,... après une durée spécifique.",
"save": "Enregistrer",
"preference": "Préférence | Préférences",
"general_settings": "Modifiez ici les paramètres globaux de Crater.",
@ -1296,16 +1296,16 @@
"invalid_disk_credentials": "Informations d'identification non valides du stockage sélectionné"
},
"taxations": {
"add_billing_address": "Enter Billing Address",
"add_shipping_address": "Enter Shipping Address",
"add_company_address": "Enter Company Address",
"modal_description": "The information below is required in order to fetch sales tax.",
"add_address": "Add Address for fetching sales tax.",
"address_placeholder": "Example: 123, My Street",
"city_placeholder": "Example: Los Angeles",
"state_placeholder": "Example: CA",
"zip_placeholder": "Example: 90024",
"invalid_address": "Please provide valid address details."
"add_billing_address": "Entrez l'adresse de facturation",
"add_shipping_address": "Entrez l'adresse de livraison",
"add_company_address": "Entrez l'adresse de la société",
"modal_description": "Les informations ci-dessous sont requises afin de récupérer les taxes de vente.",
"add_address": "Ajoutez une adresse pour récupérer les taxes de vente.",
"address_placeholder": "Exemple: 123, My Street",
"city_placeholder": "Exemple: Los Angeles",
"state_placeholder": "Exemple: CA",
"zip_placeholder": "Exemple: 90024",
"invalid_address": "Veuillez fournir une adresse valide."
}
},
"wizard": {
@ -1465,17 +1465,17 @@
"not_allowed": "Non autorisé",
"login_invalid_credentials": "Ces identifiants ne correspondent pas à nos enregistrements.",
"enter_valid_cron_format": "Veuillez entrer une tâche Cron valide",
"email_could_not_be_sent": "Email could not be sent to this email address.",
"invalid_address": "Please enter a valid address.",
"invalid_key": "Please enter valid key.",
"invalid_state": "Please enter a valid state.",
"invalid_city": "Please enter a valid city.",
"invalid_postal_code": "Please enter a valid zip.",
"invalid_format": "Please enter valid query string format.",
"api_error": "Server not responding.",
"feature_not_enabled": "Feature not enabled.",
"request_limit_met": "Api request limit exceeded.",
"address_incomplete": "Incomplete Address"
"email_could_not_be_sent": "L'Email n'a pas pu être envoyé vers cette adresse email.",
"invalid_address": "Veuillez spécifier une adresse valide.",
"invalid_key": "Veuillez spécifier une clé valide.",
"invalid_state": "Veuillez spécifier un état valide.",
"invalid_city": "Veuillez spécifier une ville valide.",
"invalid_postal_code": "Veuillez spécifier un code postal valide.",
"invalid_format": "Veuillez spécifier un format de requête valide.",
"api_error": "Le serveur ne répond plus.",
"feature_not_enabled": "Fonctionnalité inactive.",
"request_limit_met": "Limite de requêtes API dépassée.",
"address_incomplete": "Adresse incomplète"
},
"pdf_estimate_label": "Devis",
"pdf_estimate_number": "N°",

View File

@ -1,3 +1,4 @@
import cs from './cs.json'
import en from './en.json'
import fr from './fr.json'
import es from './es.json'
@ -18,6 +19,7 @@ import el from './el.json'
import hr from './hr.json'
export default {
cs,
en,
fr,
es,

View File

@ -42,6 +42,21 @@
window.customer_logo = "/storage/{{$customer_logo}}"
@endif
@if(isset($login_page_logo))
window.login_page_logo = "/storage/{{$login_page_logo}}"
@endif
@if(isset($login_page_heading))
window.login_page_heading = "{{$login_page_heading}}"
@endif
@if(isset($login_page_description))
window.login_page_description = "{{$login_page_description}}"
@endif
window.Crater.start()
</script>