1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-20 14:54:15 +01:00

(feat) store withdrawal instructions

This commit is contained in:
Sylvain 2022-09-28 17:45:48 +02:00
parent a30eac10de
commit a260f88555
13 changed files with 106 additions and 32 deletions

View File

@ -1,6 +1,13 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Setting, SettingBulkResult, SettingError, SettingName, SettingValue } from '../models/setting';
import {
Setting,
SettingBulkArray,
SettingBulkResult,
SettingError,
SettingName,
SettingValue
} from '../models/setting';
export default class SettingAPI {
static async get (name: SettingName): Promise<Setting> {
@ -60,7 +67,7 @@ export default class SettingAPI {
return map;
}
private static toObjectArray (data: Map<SettingName, SettingValue>): Array<Record<string, SettingValue>> {
private static toObjectArray (data: Map<SettingName, SettingValue>): SettingBulkArray {
const array = [];
data.forEach((value, key) => {
array.push({

View File

@ -83,6 +83,12 @@ export const FabTextEditor: React.ForwardRefRenderFunction<FabTextEditorRef, Fab
editor?.setEditable(!disabled);
}, [disabled]);
useEffect(() => {
if (editor?.getHTML() !== content) {
editor?.commands.setContent(content);
}
}, [content]);
// bind the editor to the ref, once it is ready
if (!editor) return null;
editorRef.current = editor;

View File

@ -79,12 +79,6 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, heading, bulletList, b
}
};
// prevent form submition propagation to parent forms
const handleSubmit = (event) => {
event.preventDefault();
event.stopPropagation();
};
// Update the selected link
const setLink = useCallback((closeLinkMenu?: boolean) => {
if (url.href === '') {
@ -241,7 +235,7 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, heading, bulletList, b
}
</div>
<form ref={ref} className={`fab-text-editor-subMenu ${submenu ? 'is-active' : ''}`} onSubmit={handleSubmit}>
<div ref={ref} className={`fab-text-editor-subMenu ${submenu ? 'is-active' : ''}`}>
{ submenu === 'link' &&
(<>
<h6>{t('app.shared.text_editor.menu_bar.add_link')}</h6>
@ -290,7 +284,7 @@ export const MenuBar: React.FC<MenuBarProps> = ({ editor, heading, bulletList, b
</div>
</>)
}
</form>
</div>
</>
);
};

View File

@ -18,6 +18,8 @@ import noImage from '../../../../images/no_image.png';
import Switch from 'react-switch';
import OrderLib from '../../lib/order';
import { CaretDown, CaretUp } from 'phosphor-react';
import SettingAPI from '../../api/setting';
import { SettingName } from '../../models/setting';
declare const Application: IApplication;
@ -35,8 +37,15 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
const { t } = useTranslation('public');
const { cart, setCart } = useCart(currentUser);
const [itemsQuantity, setItemsQuantity] = useState<{ id: number; quantity: number; }[]>();
const [itemsQuantity, setItemsQuantity] = useState<{ id: number; quantity: number; }[]>([]);
const [paymentModal, setPaymentModal] = useState<boolean>(false);
const [settings, setSettings] = useState<Map<SettingName, string>>(null);
useEffect(() => {
SettingAPI.query(['store_withdrawal_instructions', 'fablab_name'])
.then(res => setSettings(res))
.catch(onError);
}, []);
useEffect(() => {
const quantities = cart?.order_items_attributes.map(i => {
@ -153,6 +162,17 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
}
};
/**
* Text instructions for the customer
*/
const withdrawalInstructions = (): string => {
const instructions = settings?.get('store_withdrawal_instructions');
if (instructions) {
return instructions;
}
return t('app.public.store_cart.please_contact_FABLAB', { FABLAB: settings?.get('fablab_name') });
};
return (
<div className='store-cart'>
<div className="store-cart-list">
@ -160,7 +180,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
{cart && cart.order_items_attributes.map(item => (
<article key={item.id} className='store-cart-list-item'>
<div className='picture'>
<img alt=''src={item.orderable_main_image_url || noImage} />
<img alt='' src={item.orderable_main_image_url || noImage} />
</div>
<div className="ref">
<span>{t('app.public.store_cart.reference_short')} {item.orderable_ref || ''}</span>
@ -179,7 +199,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
onChange={e => changeProductQuantity(e, item)}
min={item.quantity_min}
max={item.orderable_external_stock}
value={itemsQuantity?.find(i => i.id === item.id).quantity}
value={itemsQuantity?.find(i => i.id === item.id)?.quantity || 1}
/>
<button onClick={() => handleInputNumber(item, 'up')}><CaretUp size={12} weight="fill" /></button>
<button onClick={() => handleInputNumber(item, 'down')}><CaretDown size={12} weight="fill" /></button>
@ -197,7 +217,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
<label>
<span>{t('app.public.store_cart.offer_product')}</span>
<Switch
checked={item.is_offered}
checked={item.is_offered || false}
onChange={toggleProductOffer(item)}
width={40}
height={19}
@ -214,7 +234,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
<div className="group">
<div className='store-cart-info'>
<h3>{t('app.public.store_cart.pickup')}</h3>
<p>[TODO: texte venant des paramètres de la boutique]</p>
<p dangerouslySetInnerHTML={{ __html: withdrawalInstructions() }} />
</div>
{cart && !cartIsEmpty() &&

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { react2angular } from 'react2angular';
import { Loader } from '../base/loader';
import { IApplication } from '../../models/application';
@ -8,6 +8,9 @@ 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';
import SettingAPI from '../../api/setting';
import SettingLib from '../../lib/setting';
import { SettingName, SettingValue, storeSettings } from '../../models/setting';
declare const Application: IApplication;
@ -15,25 +18,32 @@ interface StoreSettingsProps {
onError: (message: string) => void,
onSuccess: (message: string) => void
}
interface Settings {
withdrawal: string
}
/**
* Shows store settings
* Store settings display and edition
*/
// TODO: delete next eslint disable
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const StoreSettings: React.FC<StoreSettingsProps> = (onError, onSuccess) => {
export const StoreSettings: React.FC<StoreSettingsProps> = ({ onError, onSuccess }) => {
const { t } = useTranslation('admin');
const { control, handleSubmit, reset } = useForm<Record<SettingName, SettingValue>>();
const { control, handleSubmit } = useForm<Settings>();
useEffect(() => {
SettingAPI.query(storeSettings)
.then(settings => {
const data = SettingLib.mapToBulkObject(settings);
reset(data);
})
.catch(onError);
}, []);
/**
* Callback triggered when the form is submitted: process with the product creation or update.
* Callback triggered when the form is submitted: save the settings
*/
const onSubmit: SubmitHandler<Settings> = (data) => {
console.log(data);
const onSubmit: SubmitHandler<Record<SettingName, SettingValue>> = (data) => {
SettingAPI.bulkUpdate(SettingLib.bulkObjectToMap(data)).then(() => {
onSuccess(t('app.admin.store_settings.update_success'));
}, reason => {
onError(reason);
});
};
return (
@ -51,7 +61,7 @@ export const StoreSettings: React.FC<StoreSettingsProps> = (onError, onSuccess)
bulletList
link
limit={400}
id="withdrawal" />
id="store_withdrawal_instructions" />
<FabButton type='submit' className='save-btn'>{t('app.admin.store_settings.save')}</FabButton>
</form>
</div>

View File

@ -150,11 +150,12 @@ export default class ProductLib {
const value = ParsingLib.parse(params[key]) || initialFilters[key];
switch (key) {
case 'category':
case 'category': {
const parents = categories?.filter(c => (value as Array<string>)?.includes(c.slug));
// we may also add to the selection children categories
res.categories = [...parents, ...categories?.filter(c => parents.map(c => c.id).includes(c.parent_id))];
break;
}
case 'categories':
res.categories = [...categories?.filter(c => (value as Array<string>)?.includes(c.slug))];
break;

View File

@ -0,0 +1,25 @@
import { SettingName, SettingValue } from '../models/setting';
export default class SettingLib {
/**
* Convert the provided data to a map, as expected by BulkUpdate
*/
static bulkObjectToMap = (data: Record<SettingName, SettingValue>): Map<SettingName, SettingValue> => {
const res = new Map<SettingName, SettingValue>();
for (const key in data) {
res.set(key as SettingName, data[key]);
}
return res;
};
/**
* Convert the provided map to a simple javascript object
*/
static mapToBulkObject = (data: Map<SettingName, SettingValue>): Record<SettingName, SettingValue> => {
const res = {} as Record<SettingName, SettingValue>;
data.forEach((value, key) => {
res[key] = value;
});
return res;
};
}

View File

@ -213,6 +213,10 @@ export const displaySettings = [
'email_from'
] as const;
export const storeSettings = [
'store_withdrawal_instructions'
] as const;
export const allSettings = [
...homePageSettings,
...privacyPolicySettings,
@ -237,7 +241,8 @@ export const allSettings = [
...adminSettings,
...pricingSettings,
...poymentSettings,
...displaySettings
...displaySettings,
...storeSettings
] as const;
export type SettingName = typeof allSettings[number];
@ -264,3 +269,5 @@ export interface SettingBulkResult {
error?: string,
localized?: string,
}
export type SettingBulkArray = Array<{ name: SettingName, value: SettingValue }>;

View File

@ -153,7 +153,8 @@ class Setting < ApplicationRecord
user_validation_required
user_validation_required_list
show_username_in_admin_list
store_module] }
store_module
store_withdrawal_instructions] }
# WARNING: when adding a new key, you may also want to add it in:
# - config/locales/en.yml#settings
# - app/frontend/src/javascript/models/setting.ts#SettingName

View File

@ -42,7 +42,7 @@ class SettingPolicy < ApplicationPolicy
payment_gateway payzen_endpoint payzen_public_key public_agenda_module renew_pack_threshold statistics_module
pack_only_for_subscription overlapping_categories public_registrations facebook twitter viadeo linkedin instagram
youtube vimeo dailymotion github echosciences pinterest lastfm flickr machines_module user_change_group
user_validation_required user_validation_required_list store_module]
user_validation_required user_validation_required_list store_module store_withdrawal_instructions]
end
##

View File

@ -2095,3 +2095,4 @@ en:
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"
update_success: "The settings were successfully updated"

View File

@ -442,6 +442,7 @@ en:
checkout_error: "An unexpected error occurred. Please contact the administrator."
checkout_success: "Purchase confirmed. Thanks!"
select_user: "Please select a user before continuing."
please_contact_FABLAB: "Please contact {FABLAB, select, undefined{us} other{{FABLAB}}} for withdrawal instructions."
orders_dashboard:
heading: "My orders"
sort:

View File

@ -609,3 +609,4 @@ en:
user_change_group: "Allow users to change their group"
show_username_in_admin_list: "Show the username in the admin's members list"
store_module: "Store module"
store_withdrawal_instructions: "Withdrawal instructions"