mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 16:23:06 +10:00
## Description Adds support for customising the theme and CSS for the embedding components which is restricted to platform customers and above. Additionally adds proper support for the platform plan which will let us update our stripe products. <img width="1040" alt="image" src="https://github.com/user-attachments/assets/f694cd1e-ac93-4dc0-9f78-92fa813f6404"> <img width="1015" alt="image" src="https://github.com/user-attachments/assets/4209972a-b2bd-40c9-9049-0367382a4de5"> <img width="1065" alt="image" src="https://github.com/user-attachments/assets/fdbaaaa5-a028-4b1d-a58a-ea6224e21abe"> ## Related Issue N/A ## Changes Made - Added support for using CSS Vars and CSS within the embedding route - Added a guard for platform and enterprise plans to activate the custom css - Added support for the platform plan ## Testing Performed Yes
174 lines
6.0 KiB
TypeScript
174 lines
6.0 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { Trans } from '@lingui/macro';
|
|
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
|
|
|
|
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
|
|
import { type TRadioFieldMeta as RadioFieldMeta } from '@documenso/lib/types/field-meta';
|
|
import { Button } from '@documenso/ui/primitives/button';
|
|
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
|
import { Input } from '@documenso/ui/primitives/input';
|
|
import { Label } from '@documenso/ui/primitives/label';
|
|
import { Switch } from '@documenso/ui/primitives/switch';
|
|
|
|
export type RadioFieldAdvancedSettingsProps = {
|
|
fieldState: RadioFieldMeta;
|
|
handleFieldChange: (
|
|
key: keyof RadioFieldMeta,
|
|
value: string | { checked: boolean; value: string }[] | boolean,
|
|
) => void;
|
|
handleErrors: (errors: string[]) => void;
|
|
};
|
|
|
|
export const RadioFieldAdvancedSettings = ({
|
|
fieldState,
|
|
handleFieldChange,
|
|
handleErrors,
|
|
}: RadioFieldAdvancedSettingsProps) => {
|
|
const [showValidation, setShowValidation] = useState(false);
|
|
const [values, setValues] = useState(
|
|
fieldState.values ?? [{ id: 1, checked: false, value: 'Default value' }],
|
|
);
|
|
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
|
|
const [required, setRequired] = useState(fieldState.required ?? false);
|
|
|
|
const addValue = () => {
|
|
const newId = values.length > 0 ? Math.max(...values.map((val) => val.id)) + 1 : 1;
|
|
const newValue = { id: newId, checked: false, value: '' };
|
|
const updatedValues = [...values, newValue];
|
|
|
|
setValues(updatedValues);
|
|
handleFieldChange('values', updatedValues);
|
|
};
|
|
|
|
const removeValue = (id: number) => {
|
|
if (values.length === 1) return;
|
|
|
|
const newValues = values.filter((val) => val.id !== id);
|
|
setValues(newValues);
|
|
handleFieldChange('values', newValues);
|
|
};
|
|
|
|
const handleCheckedChange = (checked: boolean, id: number) => {
|
|
const newValues = values.map((val) => {
|
|
if (val.id === id) {
|
|
return { ...val, checked: Boolean(checked) };
|
|
} else {
|
|
return { ...val, checked: false };
|
|
}
|
|
});
|
|
|
|
setValues(newValues);
|
|
handleFieldChange('values', newValues);
|
|
};
|
|
|
|
const handleToggleChange = (field: keyof RadioFieldMeta, value: string | boolean) => {
|
|
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
|
|
const required = field === 'required' ? Boolean(value) : Boolean(fieldState.required);
|
|
setReadOnly(readOnly);
|
|
setRequired(required);
|
|
|
|
const errors = validateRadioField(String(value), { readOnly, required, values, type: 'radio' });
|
|
handleErrors(errors);
|
|
|
|
handleFieldChange(field, value);
|
|
};
|
|
|
|
const handleInputChange = (value: string, id: number) => {
|
|
const newValues = values.map((val) => {
|
|
if (val.id === id) {
|
|
return { ...val, value };
|
|
}
|
|
return val;
|
|
});
|
|
|
|
setValues(newValues);
|
|
handleFieldChange('values', newValues);
|
|
|
|
return newValues;
|
|
};
|
|
|
|
useEffect(() => {
|
|
setValues(fieldState.values ?? [{ id: 1, checked: false, value: 'Default value' }]);
|
|
}, [fieldState.values]);
|
|
|
|
useEffect(() => {
|
|
const errors = validateRadioField(undefined, { readOnly, required, values, type: 'radio' });
|
|
handleErrors(errors);
|
|
}, [values]);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex flex-row items-center gap-2">
|
|
<Switch
|
|
className="bg-background"
|
|
checked={fieldState.required}
|
|
onCheckedChange={(checked) => handleToggleChange('required', checked)}
|
|
/>
|
|
<Label>
|
|
<Trans>Required field</Trans>
|
|
</Label>
|
|
</div>
|
|
<div className="flex flex-row items-center gap-2">
|
|
<Switch
|
|
className="bg-background"
|
|
checked={fieldState.readOnly}
|
|
onCheckedChange={(checked) => handleToggleChange('readOnly', checked)}
|
|
/>
|
|
<Label>
|
|
<Trans>Read only</Trans>
|
|
</Label>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
|
|
variant="outline"
|
|
onClick={() => setShowValidation((prev) => !prev)}
|
|
>
|
|
<span className="flex w-full flex-row justify-between">
|
|
<span className="flex items-center">
|
|
<Trans>Radio values</Trans>
|
|
</span>
|
|
{showValidation ? <ChevronUp /> : <ChevronDown />}
|
|
</span>
|
|
</Button>
|
|
|
|
{showValidation && (
|
|
<div>
|
|
{values.map((value) => (
|
|
<div key={value.id} className="mt-2 flex items-center gap-4">
|
|
<Checkbox
|
|
className="data-[state=checked]:bg-documenso border-foreground/30 data-[state=checked]:ring-primary dark:data-[state=checked]:ring-offset-background h-5 w-5 rounded-full data-[state=checked]:ring-1 data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-white"
|
|
checked={value.checked}
|
|
onCheckedChange={(checked) => handleCheckedChange(Boolean(checked), value.id)}
|
|
/>
|
|
<Input
|
|
className="w-1/2"
|
|
value={value.value}
|
|
onChange={(e) => handleInputChange(e.target.value, value.id)}
|
|
/>
|
|
<button
|
|
type="button"
|
|
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50 dark:text-white"
|
|
onClick={() => removeValue(value.id)}
|
|
>
|
|
<Trash className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
<Button
|
|
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 ml-9 mt-4 border"
|
|
variant="outline"
|
|
onClick={addValue}
|
|
>
|
|
<Trans>Add another value</Trans>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|