Merge branch 'dev' into theme-provider

This commit is contained in:
2026-01-27 00:53:27 -06:00
3 changed files with 302 additions and 93 deletions

View File

@@ -12,6 +12,7 @@ import MemberFooter from '../components/MemberFooter';
import { User, Save, Lock, Heart, Users, Mail, BookUser, Camera, Upload, Trash2 } from 'lucide-react';
import { Avatar, AvatarImage, AvatarFallback } from '../components/ui/avatar';
import ChangePasswordDialog from '../components/ChangePasswordDialog';
import TransactionHistory from '../components/TransactionHistory';
const Profile = () => {
const { user } = useAuth();
@@ -24,6 +25,8 @@ const Profile = () => {
const fileInputRef = useRef(null);
const [maxFileSizeMB, setMaxFileSizeMB] = useState(50); // Default 50MB
const [maxFileSizeBytes, setMaxFileSizeBytes] = useState(52428800); // Default 50MB in bytes
const [transactions, setTransactions] = useState({ subscriptions: [], donations: [] });
const [transactionsLoading, setTransactionsLoading] = useState(true);
const [formData, setFormData] = useState({
// Personal Information
first_name: '',
@@ -58,6 +61,7 @@ const Profile = () => {
useEffect(() => {
fetchConfig();
fetchProfile();
fetchTransactions();
}, []);
const fetchConfig = async () => {
@@ -112,6 +116,19 @@ const Profile = () => {
}
};
const fetchTransactions = async () => {
try {
setTransactionsLoading(true);
const response = await api.get('/members/transactions');
setTransactions(response.data);
} catch (error) {
console.error('Failed to load transactions:', error);
// Don't show error toast - transactions are optional
} finally {
setTransactionsLoading(false);
}
};
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
@@ -703,6 +720,18 @@ const Profile = () => {
open={passwordDialogOpen}
onOpenChange={setPasswordDialogOpen}
/>
{/* Transaction History Section */}
<div className="mt-8">
<TransactionHistory
subscriptions={transactions.subscriptions}
donations={transactions.donations}
totalSubscriptionCents={transactions.total_subscription_amount_cents}
totalDonationCents={transactions.total_donation_amount_cents}
loading={transactionsLoading}
isAdmin={false}
/>
</div>
</div>
<MemberFooter />
</div>

View File

@@ -11,6 +11,7 @@ import { toast } from 'sonner';
import ConfirmationDialog from '../../components/ConfirmationDialog';
import ChangeRoleDialog from '../../components/ChangeRoleDialog';
import StatusBadge from '../../components/StatusBadge';
import TransactionHistory from '../../components/TransactionHistory';
const AdminUserView = () => {
const { userId } = useParams();
@@ -19,8 +20,8 @@ const AdminUserView = () => {
const [loading, setLoading] = useState(true);
const [resetPasswordLoading, setResetPasswordLoading] = useState(false);
const [resendVerificationLoading, setResendVerificationLoading] = useState(false);
const [subscriptions, setSubscriptions] = useState([]);
const [subscriptionsLoading, setSubscriptionsLoading] = useState(true);
const [transactions, setTransactions] = useState({ subscriptions: [], donations: [] });
const [transactionsLoading, setTransactionsLoading] = useState(true);
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
const [pendingAction, setPendingAction] = useState(null);
const [uploadingPhoto, setUploadingPhoto] = useState(false);
@@ -69,7 +70,7 @@ const AdminUserView = () => {
useEffect(() => {
fetchConfig();
fetchUserProfile();
fetchSubscriptions();
fetchTransactions();
}, [userId]);
useEffect(() => {
@@ -90,14 +91,15 @@ const AdminUserView = () => {
}
};
const fetchSubscriptions = async () => {
const fetchTransactions = async () => {
try {
const response = await api.get(`/admin/subscriptions?user_id=${userId}`);
setSubscriptions(response.data);
setTransactionsLoading(true);
const response = await api.get(`/admin/users/${userId}/transactions`);
setTransactions(response.data);
} catch (error) {
console.error('Failed to fetch subscriptions:', error);
console.error('Failed to fetch transactions:', error);
} finally {
setSubscriptionsLoading(false);
setTransactionsLoading(false);
}
};
@@ -484,91 +486,17 @@ const AdminUserView = () => {
</div>
</Card>
{/* Subscription Info (if applicable) */}
{user.role === 'member' && (
<Card className="p-8 bg-background rounded-2xl border border-[var(--neutral-800)] mt-8">
<h2 className="text-2xl font-semibold text-[var(--purple-ink)] mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
Subscription Information
</h2>
{subscriptionsLoading ? (
<p className="text-brand-purple " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading subscriptions...</p>
) : subscriptions.length === 0 ? (
<p className="text-brand-purple " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>No subscriptions found for this member.</p>
) : (
<div className="space-y-6">
{subscriptions.map((sub) => (
<div key={sub.id} className="p-6 bg-[var(--lavender-500)] rounded-xl border border-[var(--neutral-800)]">
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="text-lg font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
{sub.plan.name}
</h3>
<p className="text-sm text-brand-purple " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{sub.plan.billing_cycle}
</p>
</div>
<StatusBadge status={sub.status} />
</div>
<div className="grid md:grid-cols-2 gap-4 text-sm">
<div>
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Start Date</label>
<p className="text-[var(--purple-ink)]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{formatDateDisplayValue(sub.start_date)}
</p>
</div>
{sub.end_date && (
<div>
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>End Date</label>
<p className="text-[var(--purple-ink)]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{formatDateDisplayValue(sub.end_date)}
</p>
</div>
)}
<div>
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Base Amount</label>
<p className="text-[var(--purple-ink)]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
${(sub.base_subscription_cents / 100).toFixed(2)}
</p>
</div>
{sub.donation_cents > 0 && (
<div>
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Donation</label>
<p className="text-[var(--purple-ink)]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
${(sub.donation_cents / 100).toFixed(2)}
</p>
</div>
)}
<div>
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Paid</label>
<p className="text-[var(--purple-ink)] font-semibold" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
${(sub.amount_paid_cents / 100).toFixed(2)}
</p>
</div>
{sub.payment_method && (
<div>
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Payment Method</label>
<p className="text-[var(--purple-ink)]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{sub.payment_method}
</p>
</div>
)}
{sub.stripe_subscription_id && (
<div className="md:col-span-2">
<label className="text-brand-purple font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Stripe Subscription ID</label>
<p className="text-[var(--purple-ink)] text-xs font-mono" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{sub.stripe_subscription_id}
</p>
</div>
)}
</div>
</div>
))}
</div>
)}
</Card>
)}
{/* Transaction History */}
<div className="mt-8">
<TransactionHistory
subscriptions={transactions.subscriptions}
donations={transactions.donations}
totalSubscriptionCents={transactions.total_subscription_amount_cents}
totalDonationCents={transactions.total_donation_amount_cents}
loading={transactionsLoading}
isAdmin={true}
/>
</div>
{/* Admin Action Confirmation Dialog */}
<ConfirmationDialog