mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
feat: hook up preferences branding form
This commit is contained in:
@@ -118,8 +118,8 @@ export default class OrganizationController extends BaseController {
|
|||||||
check('address.phone').optional().isString().trim(),
|
check('address.phone').optional().isString().trim(),
|
||||||
|
|
||||||
// # Branding
|
// # Branding
|
||||||
check('primary_color').optional().isHexColor().trim(),
|
check('primary_color').optional({ nullable: true }).isHexColor().trim(),
|
||||||
check('logo_key').optional().isString().trim(),
|
check('logo_key').optional({ nullable: true }).isString().trim(),
|
||||||
|
|
||||||
check('tax_number').optional({ nullable: true }).isString().trim(),
|
check('tax_number').optional({ nullable: true }).isString().trim(),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -41,4 +41,18 @@ export default class TenantMetadata extends BaseModel {
|
|||||||
static get tableName() {
|
static get tableName() {
|
||||||
return 'tenants_metadata';
|
return 'tenants_metadata';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
*/
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['logoUri'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public get logoUri() {
|
||||||
|
return this.logoKey ? `https://bigcapital.sfo3.digitaloceanspaces.com/${this.logoKey}` : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ export default [
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/preferences/general',
|
href: '/preferences/general',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Branding',
|
||||||
|
disabled: false,
|
||||||
|
href: '/preferences/branding',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Billing',
|
text: 'Billing',
|
||||||
href: '/preferences/billing',
|
href: '/preferences/billing',
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import { useCurrentOrganization } from '@/hooks/query';
|
||||||
import React, { createContext, useContext, ReactNode } from 'react';
|
import React, { createContext, useContext, ReactNode } from 'react';
|
||||||
|
|
||||||
interface PreferencesBrandingContextType {}
|
interface PreferencesBrandingContextType {
|
||||||
|
isOrganizationLoading: boolean;
|
||||||
|
organization: any;
|
||||||
|
}
|
||||||
|
|
||||||
const PreferencesBrandingContext =
|
const PreferencesBrandingContext =
|
||||||
createContext<PreferencesBrandingContextType>(
|
createContext<PreferencesBrandingContextType>(
|
||||||
@@ -14,7 +18,14 @@ interface PreferencesBrandingProviderProps {
|
|||||||
export const PreferencesBrandingBoot: React.FC<
|
export const PreferencesBrandingBoot: React.FC<
|
||||||
PreferencesBrandingProviderProps
|
PreferencesBrandingProviderProps
|
||||||
> = ({ children }) => {
|
> = ({ children }) => {
|
||||||
const contextValue: PreferencesBrandingContextType = {};
|
// Fetches current organization information.
|
||||||
|
const { isLoading: isOrganizationLoading, data: organization } =
|
||||||
|
useCurrentOrganization({});
|
||||||
|
|
||||||
|
const contextValue: PreferencesBrandingContextType = {
|
||||||
|
isOrganizationLoading,
|
||||||
|
organization,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesBrandingContext.Provider value={contextValue}>
|
<PreferencesBrandingContext.Provider value={contextValue}>
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
import { Formik, Form, FormikHelpers } from 'formik';
|
import { Formik, Form, FormikHelpers } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
import { omit } from 'lodash';
|
||||||
import { PreferencesBrandingFormValues } from './_types';
|
import { PreferencesBrandingFormValues } from './_types';
|
||||||
import { useUploadAttachments } from '@/hooks/query/attachments';
|
import { useUploadAttachments } from '@/hooks/query/attachments';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { excludePrivateProps } from '@/utils';
|
import {
|
||||||
|
excludePrivateProps,
|
||||||
|
transformToCamelCase,
|
||||||
|
transformToForm,
|
||||||
|
transfromToSnakeCase,
|
||||||
|
} from '@/utils';
|
||||||
|
import { useUpdateOrganization } from '@/hooks/query';
|
||||||
|
import { usePreferencesBrandingBoot } from './PreferencesBrandingBoot';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
logoKey: '',
|
logoKey: '',
|
||||||
@@ -28,7 +36,19 @@ export const PreferencesBrandingForm = ({
|
|||||||
}: PreferencesBrandingFormProps) => {
|
}: PreferencesBrandingFormProps) => {
|
||||||
// Uploads the attachments.
|
// Uploads the attachments.
|
||||||
const { mutateAsync: uploadAttachments } = useUploadAttachments({});
|
const { mutateAsync: uploadAttachments } = useUploadAttachments({});
|
||||||
|
// Mutate organization information.
|
||||||
|
const { mutateAsync: updateOrganization } = useUpdateOrganization();
|
||||||
|
|
||||||
|
const { organization } = usePreferencesBrandingBoot();
|
||||||
|
|
||||||
|
const formInitialValues = {
|
||||||
|
...transformToForm(
|
||||||
|
transformToCamelCase(organization?.metadata),
|
||||||
|
initialValues,
|
||||||
|
),
|
||||||
|
} as PreferencesBrandingFormValues;
|
||||||
|
|
||||||
|
// Handle the form submitting.
|
||||||
const handleSubmit = async (
|
const handleSubmit = async (
|
||||||
values: PreferencesBrandingFormValues,
|
values: PreferencesBrandingFormValues,
|
||||||
{ setSubmitting }: FormikHelpers<PreferencesBrandingFormValues>,
|
{ setSubmitting }: FormikHelpers<PreferencesBrandingFormValues>,
|
||||||
@@ -53,20 +73,32 @@ export const PreferencesBrandingForm = ({
|
|||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
||||||
// Adds the attachment key to the values after finishing upload.
|
// Adds the attachment key to the values after finishing upload.
|
||||||
_values['_logoFile'] = uploadedAttachmentRes?.key;
|
_values['logoKey'] = uploadedAttachmentRes?.key;
|
||||||
} catch {
|
} catch {
|
||||||
handleError('An error occurred while uploading company logo.');
|
handleError('An error occurred while uploading company logo.');
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Exclude all the private props that starts with _.
|
|
||||||
const excludedPrivateValues = excludePrivateProps(_values);
|
|
||||||
}
|
}
|
||||||
|
// Exclude all the private props that starts with _.
|
||||||
|
const excludedPrivateValues = excludePrivateProps(_values);
|
||||||
|
|
||||||
|
const __values = transfromToSnakeCase(
|
||||||
|
omit(excludedPrivateValues, ['logoUri']),
|
||||||
|
);
|
||||||
|
// Update organization branding.
|
||||||
|
// @ts-expect-error
|
||||||
|
await updateOrganization({ ...__values });
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Organization branding has been updated.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={initialValues}
|
initialValues={formInitialValues}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export function BrandingCompanyLogoUpload() {
|
|||||||
|
|
||||||
setFieldValue('_logoFile', file);
|
setFieldValue('_logoFile', file);
|
||||||
setFieldValue('logoUri', imageUrl);
|
setFieldValue('logoUri', imageUrl);
|
||||||
|
setFieldValue('logoKey', '');
|
||||||
}}
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
root: styles.fileUploadRoot,
|
root: styles.fileUploadRoot,
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ export default function PreferencesGeneralForm({ isSubmitting }) {
|
|||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'address'}
|
name={'address'}
|
||||||
label={'Organization Address'}
|
label={'Organization Address'}
|
||||||
|
|
||||||
inline
|
inline
|
||||||
fastField
|
fastField
|
||||||
>
|
>
|
||||||
@@ -120,7 +119,6 @@ export default function PreferencesGeneralForm({ isSubmitting }) {
|
|||||||
placeholder={'Address 2'}
|
placeholder={'Address 2'}
|
||||||
fastField
|
fastField
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Group spacing={15}>
|
<Group spacing={15}>
|
||||||
<FInputGroup name={'address.city'} placeholder={'City'} fastField />
|
<FInputGroup name={'address.city'} placeholder={'City'} fastField />
|
||||||
<FInputGroup
|
<FInputGroup
|
||||||
@@ -129,7 +127,6 @@ export default function PreferencesGeneralForm({ isSubmitting }) {
|
|||||||
fastField
|
fastField
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group spacing={15}>
|
<Group spacing={15}>
|
||||||
<FInputGroup
|
<FInputGroup
|
||||||
name={'address.state_province'}
|
name={'address.state_province'}
|
||||||
|
|||||||
@@ -77,12 +77,12 @@ export function useOrganizationSetup() {
|
|||||||
/**
|
/**
|
||||||
* Saves the settings.
|
* Saves the settings.
|
||||||
*/
|
*/
|
||||||
export function useUpdateOrganization(props) {
|
export function useUpdateOrganization(props = {}) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const apiRequest = useApiRequest();
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
(information) => apiRequest.put('organization', information),
|
(information: any) => apiRequest.put('organization', information),
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
|
queryClient.invalidateQueries(t.ORGANIZATION_CURRENT);
|
||||||
|
|||||||
Reference in New Issue
Block a user