Fix auth translations and admin PWA UI
This commit is contained in:
@@ -60,7 +60,7 @@ vi.mock('@tamagui/select', () => {
|
||||
return { Select };
|
||||
});
|
||||
|
||||
import { MobileSelect } from './FormControls';
|
||||
import { MobileColorInput, MobileDateTimeInput, MobileFileInput, MobileSelect } from './FormControls';
|
||||
|
||||
describe('MobileSelect', () => {
|
||||
it('maps options and forwards selection changes', () => {
|
||||
@@ -83,3 +83,30 @@ describe('MobileSelect', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MobileColorInput', () => {
|
||||
it('renders a color input with default sizing', () => {
|
||||
render(<MobileColorInput value="#ff0000" onChange={vi.fn()} />);
|
||||
|
||||
const input = screen.getByDisplayValue('#ff0000');
|
||||
expect(input).toHaveAttribute('type', 'color');
|
||||
});
|
||||
});
|
||||
|
||||
describe('MobileFileInput', () => {
|
||||
it('renders a hidden file input', () => {
|
||||
render(<MobileFileInput data-testid="file-input" />);
|
||||
|
||||
const input = screen.getByTestId('file-input');
|
||||
expect(input).toHaveAttribute('type', 'file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('MobileDateTimeInput', () => {
|
||||
it('renders a datetime-local input', () => {
|
||||
render(<MobileDateTimeInput value="2024-10-20T14:30" onChange={vi.fn()} />);
|
||||
|
||||
const input = screen.getByDisplayValue('2024-10-20T14:30');
|
||||
expect(input).toHaveAttribute('type', 'datetime-local');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,86 @@ type MobileSelectProps = React.ComponentPropsWithoutRef<'select'> & ControlProps
|
||||
containerStyle?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export const MobileColorInput = React.forwardRef<
|
||||
HTMLInputElement,
|
||||
React.ComponentPropsWithoutRef<'input'> & { size?: number }
|
||||
>(function MobileColorInput({ size = 52, style, ...props }, ref) {
|
||||
const { border, surface } = useAdminTheme();
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
type="color"
|
||||
{...props}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${border}`,
|
||||
background: surface,
|
||||
padding: 0,
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const MobileFileInput = React.forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<'input'>>(
|
||||
function MobileFileInput({ style, ...props }, ref) {
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
type="file"
|
||||
{...props}
|
||||
style={{
|
||||
display: 'none',
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const MobileDateTimeInput = React.forwardRef<
|
||||
HTMLInputElement,
|
||||
React.ComponentPropsWithoutRef<'input'> & ControlProps
|
||||
>(function MobileDateTimeInput({ hasError = false, style, ...props }, ref) {
|
||||
const { border, surface, text, primary, danger } = useAdminTheme();
|
||||
const ringColor = hasError ? withAlpha(danger, 0.18) : withAlpha(primary, 0.18);
|
||||
const borderColor = hasError ? danger : border;
|
||||
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
type="datetime-local"
|
||||
{...props}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 44,
|
||||
padding: '0 12px',
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
borderColor,
|
||||
backgroundColor: surface,
|
||||
color: text,
|
||||
fontSize: 14,
|
||||
outline: 'none',
|
||||
boxShadow: `0 0 0 0 ${ringColor}`,
|
||||
...style,
|
||||
}}
|
||||
onFocus={(event) => {
|
||||
event.currentTarget.style.boxShadow = `0 0 0 3px ${ringColor}`;
|
||||
props.onFocus?.(event);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
event.currentTarget.style.boxShadow = `0 0 0 0 ${ringColor}`;
|
||||
props.onBlur?.(event);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const MobileInput = React.forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<'input'> & ControlProps>(
|
||||
function MobileInput({ hasError = false, compact = false, style, onChange, type, ...props }, ref) {
|
||||
const { border, surface, text, primary, danger } = useAdminTheme();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { YStack } from '@tamagui/stacks';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Checkbox } from '@tamagui/checkbox';
|
||||
import { Check } from 'lucide-react';
|
||||
import { MobileSheet } from './Sheet';
|
||||
import { CTAButton } from './Primitives';
|
||||
import { useAdminTheme } from '../theme';
|
||||
@@ -41,17 +43,6 @@ export function LegalConsentSheet({
|
||||
const [acceptedTerms, setAcceptedTerms] = React.useState(false);
|
||||
const [acceptedWaiver, setAcceptedWaiver] = React.useState(false);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const checkboxStyle = {
|
||||
marginTop: 4,
|
||||
width: 18,
|
||||
height: 18,
|
||||
accentColor: primary,
|
||||
backgroundColor: surface,
|
||||
border: `1px solid ${border}`,
|
||||
borderRadius: 4,
|
||||
appearance: 'auto',
|
||||
WebkitAppearance: 'auto',
|
||||
} as any;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
@@ -111,36 +102,60 @@ export function LegalConsentSheet({
|
||||
{copy?.description ?? t('events.legalConsent.description', 'Please confirm the legal notes before buying an add-on.')}
|
||||
</Text>
|
||||
{requireTerms ? (
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
<XStack space="$3" alignItems="flex-start">
|
||||
<Checkbox
|
||||
id="legal-terms"
|
||||
size="$4"
|
||||
checked={acceptedTerms}
|
||||
onChange={(event) => setAcceptedTerms(event.target.checked)}
|
||||
style={checkboxStyle}
|
||||
/>
|
||||
<Text fontSize="$sm" color={text}>
|
||||
onCheckedChange={(checked) => setAcceptedTerms(Boolean(checked))}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
<Check size={14} color={primary} />
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
<Text
|
||||
fontSize="$sm"
|
||||
color={text}
|
||||
onPress={() => setAcceptedTerms((prev) => !prev)}
|
||||
flex={1}
|
||||
>
|
||||
{copy?.checkboxTerms ?? t(
|
||||
'events.legalConsent.checkboxTerms',
|
||||
'I have read and accept the Terms & Conditions, Privacy Policy, and Right of Withdrawal.',
|
||||
)}
|
||||
</Text>
|
||||
</label>
|
||||
</XStack>
|
||||
) : null}
|
||||
{requireWaiver ? (
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
<XStack space="$3" alignItems="flex-start">
|
||||
<Checkbox
|
||||
id="legal-waiver"
|
||||
size="$4"
|
||||
checked={acceptedWaiver}
|
||||
onChange={(event) => setAcceptedWaiver(event.target.checked)}
|
||||
style={checkboxStyle}
|
||||
/>
|
||||
<Text fontSize="$sm" color={text}>
|
||||
onCheckedChange={(checked) => setAcceptedWaiver(Boolean(checked))}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
<Check size={14} color={primary} />
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
<Text
|
||||
fontSize="$sm"
|
||||
color={text}
|
||||
onPress={() => setAcceptedWaiver((prev) => !prev)}
|
||||
flex={1}
|
||||
>
|
||||
{copy?.checkboxWaiver ?? t(
|
||||
'events.legalConsent.checkboxWaiver',
|
||||
'I expressly request immediate provision of the digital service and understand my right of withdrawal expires once fulfilled.',
|
||||
)}
|
||||
</Text>
|
||||
</label>
|
||||
</XStack>
|
||||
) : null}
|
||||
</YStack>
|
||||
</MobileSheet>
|
||||
|
||||
@@ -23,6 +23,23 @@ vi.mock('@tamagui/react-native-web-lite', () => ({
|
||||
Pressable: ({ children }: { children: React.ReactNode }) => <button type="button">{children}</button>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/checkbox', () => ({
|
||||
Checkbox: Object.assign(
|
||||
({ children, checked, onCheckedChange, id }: any) => (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={id}
|
||||
checked={checked}
|
||||
onChange={(event) => onCheckedChange?.(event.target.checked)}
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
),
|
||||
{ Indicator: ({ children }: { children: React.ReactNode }) => <span>{children}</span> },
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/sheet', () => {
|
||||
const Sheet = ({ children }: { children: React.ReactNode }) => <div>{children}</div>;
|
||||
Sheet.Frame = ({ children }: { children: React.ReactNode }) => <div>{children}</div>;
|
||||
|
||||
Reference in New Issue
Block a user