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:
parent
a30eac10de
commit
a260f88555
@ -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({
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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() &&
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
25
app/frontend/src/javascript/lib/setting.ts
Normal file
25
app/frontend/src/javascript/lib/setting.ts
Normal 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;
|
||||
};
|
||||
}
|
@ -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 }>;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
##
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user