feat(challenges): 添加自定义挑战类型并优化字段验证
- 新增 CUSTOM 挑战类型支持 - 移除 requirementLabel 必填验证,改为可选字段 - 添加挑战类型选择器的编辑模式禁用状态 - 优化日期选择器的多语言支持 - 完善中英文国际化文案 - 修复空 requirementLabel 导致的渲染问题
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import i18n from '@/i18n';
|
||||
import dayjs from 'dayjs';
|
||||
import { BlurView } from 'expo-blur';
|
||||
import * as Clipboard from 'expo-clipboard';
|
||||
@@ -53,6 +54,7 @@ const getTypeOptions = (t: (key: string) => string): { value: ChallengeType; lab
|
||||
{ value: ChallengeType.SLEEP, label: t('challenges.createCustom.typeLabels.sleep'), accent: '#7C3AED' },
|
||||
{ value: ChallengeType.MOOD, label: t('challenges.createCustom.typeLabels.mood'), accent: '#F97316' },
|
||||
{ value: ChallengeType.WEIGHT, label: t('challenges.createCustom.typeLabels.weight'), accent: '#22C55E' },
|
||||
{ value: ChallengeType.CUSTOM, label: t('challenges.createCustom.typeLabels.custom'), accent: '#8B5CF6' },
|
||||
];
|
||||
|
||||
const FALLBACK_IMAGE =
|
||||
@@ -97,7 +99,7 @@ export default function CreateCustomChallengeScreen() {
|
||||
const [progressUnit, setProgressUnit] = useState('');
|
||||
const [periodLabel, setPeriodLabel] = useState('');
|
||||
const [periodEdited, setPeriodEdited] = useState(false);
|
||||
const [rankingDescription] = useState('连续打卡榜');
|
||||
const [rankingDescription] = useState(t('challenges.createCustom.rankingDescription'));
|
||||
const [isPublic, setIsPublic] = useState(true);
|
||||
const [maxParticipants, setMaxParticipants] = useState('100');
|
||||
const [minimumEdited, setMinimumEdited] = useState(false);
|
||||
@@ -121,7 +123,6 @@ export default function CreateCustomChallengeScreen() {
|
||||
setEndDate(new Date(challenge.endAt || Date.now()));
|
||||
setTargetValue(String(challenge.progress?.target || ''));
|
||||
setMinimumCheckInDays(String(challenge.minimumCheckInDays || ''));
|
||||
setRequirementLabel(challenge.requirementLabel || '');
|
||||
setSummary(challenge.summary || '');
|
||||
setProgressUnit(challenge.unit || '');
|
||||
setPeriodLabel(challenge.periodLabel || '');
|
||||
@@ -177,10 +178,6 @@ export default function CreateCustomChallengeScreen() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requirementLabel.trim()) {
|
||||
Toast.warning(t('challenges.createCustom.alerts.requirementRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
const startTimestamp = dayjs(startDate).valueOf();
|
||||
const endTimestamp = dayjs(endDate).valueOf();
|
||||
@@ -221,7 +218,7 @@ export default function CreateCustomChallengeScreen() {
|
||||
targetValue: target,
|
||||
minimumCheckInDays: minDays,
|
||||
durationLabel,
|
||||
requirementLabel: requirementLabel.trim(),
|
||||
requirementLabel: '',
|
||||
summary: summary.trim() || undefined,
|
||||
progressUnit: progressUnit.trim(),
|
||||
periodLabel: periodLabel.trim() || undefined,
|
||||
@@ -513,16 +510,19 @@ export default function CreateCustomChallengeScreen() {
|
||||
<TouchableOpacity
|
||||
key={option.value}
|
||||
activeOpacity={0.9}
|
||||
onPress={() => setType(option.value)}
|
||||
onPress={() => !isEditMode && setType(option.value)}
|
||||
disabled={isEditMode}
|
||||
style={[
|
||||
styles.chip,
|
||||
active && { backgroundColor: `${option.accent}1A`, borderColor: option.accent },
|
||||
isEditMode && styles.chipDisabled,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.chipLabel,
|
||||
active && { color: option.accent, fontWeight: '700' },
|
||||
isEditMode && styles.chipLabelDisabled,
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
@@ -531,6 +531,7 @@ export default function CreateCustomChallengeScreen() {
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<Text style={styles.helperText}>{t('challenges.createCustom.fields.challengeTypeHelper')}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.fieldBlock}>
|
||||
@@ -592,7 +593,6 @@ export default function CreateCustomChallengeScreen() {
|
||||
|
||||
{renderField(t('challenges.createCustom.fields.minimumCheckInDays'), minimumCheckInDays, handleMinimumDaysChange, t('challenges.createCustom.fields.minimumCheckInDaysPlaceholder'), 'numeric')}
|
||||
|
||||
{renderField(t('challenges.createCustom.fields.challengeRequirement'), requirementLabel, setRequirementLabel, t('challenges.createCustom.fields.requirementPlaceholder'))}
|
||||
</View>
|
||||
|
||||
<View style={styles.formCard}>
|
||||
@@ -675,6 +675,9 @@ export default function CreateCustomChallengeScreen() {
|
||||
minimumDate={pickerType === 'end' ? dayjs(startDate).add(1, 'day').toDate() : dayjs().add(1, 'day').toDate()}
|
||||
onConfirm={handleConfirmDate}
|
||||
onCancel={() => setPickerType(null)}
|
||||
locale={i18n.language}
|
||||
confirmTextIOS={t('challenges.createCustom.datePicker.confirm')}
|
||||
cancelTextIOS={t('challenges.createCustom.datePicker.cancel')}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
@@ -833,10 +836,17 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
},
|
||||
chipDisabled: {
|
||||
opacity: 0.5,
|
||||
backgroundColor: '#f1f5f9',
|
||||
},
|
||||
chipLabel: {
|
||||
fontSize: 13,
|
||||
color: '#334155',
|
||||
},
|
||||
chipLabelDisabled: {
|
||||
color: '#94a3b8',
|
||||
},
|
||||
uploadRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
Reference in New Issue
Block a user