1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-30 19:52:20 +01:00

Store settings + update text editor options

This commit is contained in:
vincent 2022-09-01 18:02:22 +02:00
parent 27a3bdc308
commit a5a45ee1ce
9 changed files with 144 additions and 26 deletions

View File

@ -12,11 +12,14 @@ import { MenuBar } from './menu-bar';
import { WarningOctagon } from 'phosphor-react'; import { WarningOctagon } from 'phosphor-react';
interface FabTextEditorProps { interface FabTextEditorProps {
paragraphTools?: boolean, heading?: boolean,
content?: string, bulletList?: boolean,
limit?: number, blockquote?: boolean,
link?: boolean,
video?: boolean, video?: boolean,
image?: boolean, image?: boolean,
content?: string,
limit?: number,
onChange?: (content: string) => void, onChange?: (content: string) => void,
placeholder?: string, placeholder?: string,
error?: string, error?: string,
@ -30,7 +33,7 @@ export interface FabTextEditorRef {
/** /**
* This component is a WYSIWYG text editor * This component is a WYSIWYG text editor
*/ */
export const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, FabTextEditorProps> = ({ paragraphTools, content, limit = 400, video, image, onChange, placeholder, error, disabled = false }, ref: RefObject<FabTextEditorRef>) => { export const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, FabTextEditorProps> = ({ heading, bulletList, blockquote, content, limit = 400, video, image, link, onChange, placeholder, error, disabled = false }, ref: RefObject<FabTextEditorRef>) => {
const { t } = useTranslation('shared'); const { t } = useTranslation('shared');
const placeholderText = placeholder || t('app.shared.text_editor.fab_text_editor.text_placeholder'); const placeholderText = placeholder || t('app.shared.text_editor.fab_text_editor.text_placeholder');
// TODO: Add ctrl+click on link to visit // TODO: Add ctrl+click on link to visit
@ -86,7 +89,7 @@ export const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, Fab
return ( return (
<div className={`fab-text-editor ${disabled && 'is-disabled'}`}> <div className={`fab-text-editor ${disabled && 'is-disabled'}`}>
<MenuBar editor={editor} paragraphTools={paragraphTools} video={video} image={image} disabled={disabled} /> <MenuBar editor={editor} heading={heading} bulletList={bulletList} blockquote={blockquote} video={video} image={image} link={link} disabled={disabled} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
<div className="fab-text-editor-character-count"> <div className="fab-text-editor-character-count">
{editor?.storage.characterCount.characters()} / {limit} {editor?.storage.characterCount.characters()} / {limit}

View File

@ -6,7 +6,10 @@ import { TextAa, TextBolder, TextItalic, TextUnderline, LinkSimpleHorizontal, Li
interface MenuBarProps { interface MenuBarProps {
editor?: Editor, editor?: Editor,
paragraphTools?: boolean, heading?: boolean,
bulletList?: boolean,
blockquote?: boolean,
link?: boolean,
video?: boolean, video?: boolean,
image?: boolean, image?: boolean,
disabled?: boolean, disabled?: boolean,
@ -15,7 +18,7 @@ interface MenuBarProps {
/** /**
* This component is the menu bar for the WYSIWYG text editor * This component is the menu bar for the WYSIWYG text editor
*/ */
export const MenuBar: React.FC<MenuBarProps> = ({ editor, paragraphTools, video, image, disabled = false }) => { export const MenuBar: React.FC<MenuBarProps> = ({ editor, heading, bulletList, blockquote, link, video, image, disabled = false }) => {
const { t } = useTranslation('shared'); const { t } = useTranslation('shared');
const [submenu, setSubmenu] = useState(''); const [submenu, setSubmenu] = useState('');
@ -142,8 +145,7 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, paragraphTools, video,
return ( return (
<> <>
<div className={`fab-text-editor-menu ${disabled ? 'fab-text-editor-menu--disabled' : ''}`}> <div className={`fab-text-editor-menu ${disabled ? 'fab-text-editor-menu--disabled' : ''}`}>
{ paragraphTools && {heading &&
(<>
<button <button
type='button' type='button'
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
@ -152,6 +154,8 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, paragraphTools, video,
> >
<TextAa size={24} /> <TextAa size={24} />
</button> </button>
}
{bulletList &&
<button <button
type='button' type='button'
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
@ -160,6 +164,8 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, paragraphTools, video,
> >
<ListBullets size={24} /> <ListBullets size={24} />
</button> </button>
}
{blockquote &&
<button <button
type='button' type='button'
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
@ -168,9 +174,8 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, paragraphTools, video,
> >
<Quotes size={24} /> <Quotes size={24} />
</button> </button>
<span className='menu-divider'></span>
</>)
} }
{ (heading || bulletList || blockquote) && <span className='menu-divider'></span> }
<button <button
type='button' type='button'
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
@ -195,14 +200,16 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, paragraphTools, video,
> >
<TextUnderline size={24} /> <TextUnderline size={24} />
</button> </button>
<button {link &&
type='button' <button
onClick={() => toggleSubmenu('link')} type='button'
disabled={disabled} onClick={() => toggleSubmenu('link')}
className={`ignore-onclickoutside ${editor.isActive('link') ? 'is-active' : ''}`} disabled={disabled}
> className={`ignore-onclickoutside ${editor.isActive('link') ? 'is-active' : ''}`}
<LinkSimpleHorizontal size={24} /> >
</button> <LinkSimpleHorizontal size={24} />
</button>
}
{ (video || image) && <span className='menu-divider'></span> } { (video || image) && <span className='menu-divider'></span> }
{ video && { video &&
(<> (<>

View File

@ -10,15 +10,18 @@ import { FieldPathValue, UnpackNestedValue } from 'react-hook-form/dist/types';
interface FormRichTextProps<TFieldValues, TContext extends object> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> { interface FormRichTextProps<TFieldValues, TContext extends object> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
valueDefault?: string, valueDefault?: string,
limit?: number, limit?: number,
paragraphTools?: boolean, heading?: boolean,
bulletList?: boolean,
blockquote?: boolean,
link?: boolean,
video?: boolean, video?: boolean,
image?: boolean, image?: boolean
} }
/** /**
* This component is a rich-text editor to use with react-hook-form. * This component is a rich-text editor to use with react-hook-form.
*/ */
export const FormRichText = <TFieldValues extends FieldValues, TContext extends object>({ id, label, tooltip, className, control, valueDefault, error, warning, rules, disabled = false, formState, limit, paragraphTools, video, image }: FormRichTextProps<TFieldValues, TContext>) => { export const FormRichText = <TFieldValues extends FieldValues, TContext extends object>({ id, label, tooltip, className, control, valueDefault, error, warning, rules, disabled = false, formState, limit, heading, bulletList, blockquote, video, image, link }: FormRichTextProps<TFieldValues, TContext>) => {
const textEditorRef = React.useRef<FabTextEditorRef>(); const textEditorRef = React.useRef<FabTextEditorRef>();
const [isDisabled, setIsDisabled] = React.useState<boolean>(false); const [isDisabled, setIsDisabled] = React.useState<boolean>(false);
@ -53,9 +56,12 @@ export const FormRichText = <TFieldValues extends FieldValues, TContext extends
<FabTextEditor onChange={onChange} <FabTextEditor onChange={onChange}
content={value} content={value}
limit={limit} limit={limit}
paragraphTools={paragraphTools} heading={heading}
bulletList={bulletList}
blockquote={blockquote}
video={video} video={video}
image={image} image={image}
link={link}
disabled={isDisabled} disabled={isDisabled}
ref={textEditorRef} /> ref={textEditorRef} />
} /> } />

View File

@ -362,7 +362,10 @@ export const ProductForm: React.FC<ProductFormProps> = ({ product, title, onSucc
<HtmlTranslate trKey="app.admin.store.product_form.product_description_info" /> <HtmlTranslate trKey="app.admin.store.product_form.product_description_info" />
</FabAlert> </FabAlert>
<FormRichText control={control} <FormRichText control={control}
paragraphTools={true} heading
bulletList
blockquote
link
limit={6000} limit={6000}
id="description" /> id="description" />
</div> </div>

View File

@ -0,0 +1,67 @@
import React from 'react';
import { react2angular } from 'react2angular';
import { Loader } from '../base/loader';
import { IApplication } from '../../models/application';
import { useTranslation } from 'react-i18next';
import { HtmlTranslate } from '../base/html-translate';
import { useForm, SubmitHandler } from 'react-hook-form';
import { FabAlert } from '../base/fab-alert';
import { FormRichText } from '../form/form-rich-text';
import { FabButton } from '../base/fab-button';
declare const Application: IApplication;
interface StoreSettingsProps {
onError: (message: string) => void,
onSuccess: (message: string) => void
}
interface Settings {
withdrawal: string
}
/**
* Shows store settings
*/
export const StoreSettings: React.FC<StoreSettingsProps> = (onError, onSuccess) => {
const { t } = useTranslation('admin');
const { control, handleSubmit } = useForm<Settings>();
/**
* Callback triggered when the form is submitted: process with the product creation or update.
*/
const onSubmit: SubmitHandler<Settings> = (data) => {
console.log(data);
};
return (
<div className='store-settings'>
<header>
<h2>{t('app.admin.store_settings.title')}</h2>
</header>
<form onSubmit={handleSubmit(onSubmit)}>
<p>{t('app.admin.store_settings.withdrawal_instructions')}</p>
<FabAlert level="warning">
<HtmlTranslate trKey="app.admin.store_settings.withdrawal_info" />
</FabAlert>
<FormRichText control={control}
heading
bulletList
link
limit={400}
id="withdrawal" />
<FabButton type='submit' className='save-btn'>{t('app.admin.store_settings.save')}</FabButton>
</form>
</div>
);
};
const StoreSettingsWrapper: React.FC<StoreSettingsProps> = (props) => {
return (
<Loader>
<StoreSettings {...props} />
</Loader>
);
};
Application.Components.component('storeSettings', react2angular(StoreSettingsWrapper, ['onError', 'onSuccess']));

View File

@ -101,6 +101,7 @@
@import "modules/store/store-filters"; @import "modules/store/store-filters";
@import "modules/store/store-list-header"; @import "modules/store/store-list-header";
@import "modules/store/store-list"; @import "modules/store/store-list";
@import "modules/store/store-settings";
@import "modules/store/store"; @import "modules/store/store";
@import "modules/subscriptions/free-extend-modal"; @import "modules/subscriptions/free-extend-modal";
@import "modules/subscriptions/renew-modal"; @import "modules/subscriptions/renew-modal";

View File

@ -0,0 +1,27 @@
.store-settings {
max-width: 1600px;
margin: 0 auto;
padding-bottom: 6rem;
@include grid-col(12);
gap: 3.2rem;
align-items: flex-start;
header {
@include header();
padding-bottom: 0;
grid-column: 2 / -2;
}
form {
grid-column: 2 / 7;
p { @include title-base; }
.save-btn {
background-color: var(--main);
color: var(--gray-soft-lightest);
border: none;
&:hover {
background-color: var(--main);
color: var(--gray-soft-lightest);
opacity: 0.75;
}
}
}
}

View File

@ -1 +1 @@
<h2>Settings page</h2> <store-settings on-success="onSuccess" on-error="onError"/>

View File

@ -2026,4 +2026,8 @@ en:
gift_total: "Discount total" gift_total: "Discount total"
coupon: "Coupon" coupon: "Coupon"
cart_total: "Cart total" cart_total: "Cart total"
store_settings:
title: 'Settings'
withdrawal_instructions: 'Product withdrawal instructions'
withdrawal_info: "This text is displayed on the checkout page to inform the client about the products withdrawal method"
save: "Save"