'use client';
import { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import Image from 'next/image';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import zhifubaoImg from "../assets/zhifubao.jpg";
import weixinImg from "../assets/weixin.jpg";
import { CalendarIcon, MinusIcon, PlusIcon } from './Icons';
dayjs.extend(utc);
dayjs.extend(timezone);
const DEFAULT_TZ = 'Asia/Shanghai';
const getBrowserTimeZone = () => {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
return tz || DEFAULT_TZ;
}
return DEFAULT_TZ;
};
const TZ = getBrowserTimeZone();
dayjs.tz.setDefault(TZ);
const nowInTz = () => dayjs().tz(TZ);
const toTz = (input) => (input ? dayjs.tz(input, TZ) : nowInTz());
const formatDate = (input) => toTz(input).format('YYYY-MM-DD');
export function DatePicker({ value, onChange }) {
const [isOpen, setIsOpen] = useState(false);
const [currentMonth, setCurrentMonth] = useState(() => value ? toTz(value) : nowInTz());
useEffect(() => {
const close = () => setIsOpen(false);
if (isOpen) window.addEventListener('click', close);
return () => window.removeEventListener('click', close);
}, [isOpen]);
const year = currentMonth.year();
const month = currentMonth.month();
const handlePrevMonth = (e) => {
e.stopPropagation();
setCurrentMonth(currentMonth.subtract(1, 'month').startOf('month'));
};
const handleNextMonth = (e) => {
e.stopPropagation();
setCurrentMonth(currentMonth.add(1, 'month').startOf('month'));
};
const handleSelect = (e, day) => {
e.stopPropagation();
const dateStr = formatDate(`${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`);
const today = nowInTz().startOf('day');
const selectedDate = toTz(dateStr).startOf('day');
if (selectedDate.isAfter(today)) return;
onChange(dateStr);
setIsOpen(false);
};
const daysInMonth = currentMonth.daysInMonth();
const firstDayOfWeek = currentMonth.startOf('month').day();
const days = [];
for (let i = 0; i < firstDayOfWeek; i++) days.push(null);
for (let i = 1; i <= daysInMonth; i++) days.push(i);
return (
e.stopPropagation()}>
setIsOpen(!isOpen)}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 12px',
height: '40px',
background: 'rgba(0,0,0,0.2)',
borderRadius: '8px',
cursor: 'pointer',
border: '1px solid transparent',
transition: 'all 0.2s'
}}
>
{value || '选择日期'}
{isOpen && (
{year}年 {month + 1}月
{['日', '一', '二', '三', '四', '五', '六'].map(d => (
{d}
))}
{days.map((d, i) => {
if (!d) return
;
const dateStr = formatDate(`${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`);
const isSelected = value === dateStr;
const today = nowInTz().startOf('day');
const current = toTz(dateStr).startOf('day');
const isToday = current.isSame(today);
const isFuture = current.isAfter(today);
return (
!isFuture && handleSelect(e, d)}
style={{
height: 28,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '13px',
borderRadius: '6px',
cursor: isFuture ? 'not-allowed' : 'pointer',
background: isSelected ? 'var(--primary)' : isToday ? 'rgba(255,255,255,0.1)' : 'transparent',
color: isFuture ? 'var(--muted)' : isSelected ? '#000' : 'var(--text)',
fontWeight: isSelected || isToday ? 600 : 400,
opacity: isFuture ? 0.3 : 1
}}
onMouseEnter={(e) => {
if (!isSelected && !isFuture) e.currentTarget.style.background = 'rgba(255,255,255,0.1)';
}}
onMouseLeave={(e) => {
if (!isSelected && !isFuture) e.currentTarget.style.background = isToday ? 'rgba(255,255,255,0.1)' : 'transparent';
}}
>
{d}
);
})}
)}
);
}
export function DonateTabs() {
const [method, setMethod] = useState('wechat');
return (
{method === 'alipay' ? (
) : (
)}
);
}
export function NumericInput({ value, onChange, step = 1, min = 0, placeholder }) {
const decimals = String(step).includes('.') ? String(step).split('.')[1].length : 0;
const fmt = (n) => Number(n).toFixed(decimals);
const inc = () => {
const v = parseFloat(value);
const base = isNaN(v) ? 0 : v;
const next = base + step;
onChange(fmt(next));
};
const dec = () => {
const v = parseFloat(value);
const base = isNaN(v) ? 0 : v;
const next = Math.max(min, base - step);
onChange(fmt(next));
};
return (
);
}
export function Stat({ label, value, delta }) {
const dir = delta > 0 ? 'up' : delta < 0 ? 'down' : '';
return (
{label}
{value}
);
}