Remove redirect from subscription page
Redirects should only happen on the main beneficiary page (index.tsx). Other pages (subscription, equipment, share) just show their content without redirecting - user navigated there intentionally via menu. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2e72398818
commit
28323507f8
@ -17,7 +17,6 @@ import { usePaymentSheet } from '@stripe/stripe-react-native';
|
|||||||
import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows } from '@/constants/theme';
|
import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows } from '@/constants/theme';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { api } from '@/services/api';
|
import { api } from '@/services/api';
|
||||||
import { hasBeneficiaryDevices } from '@/services/BeneficiaryDetailController';
|
|
||||||
import { BeneficiaryMenu } from '@/components/ui/BeneficiaryMenu';
|
import { BeneficiaryMenu } from '@/components/ui/BeneficiaryMenu';
|
||||||
import type { Beneficiary } from '@/types';
|
import type { Beneficiary } from '@/types';
|
||||||
|
|
||||||
@ -102,20 +101,6 @@ export default function SubscriptionScreen() {
|
|||||||
|
|
||||||
const subscriptionState = getSubscriptionState();
|
const subscriptionState = getSubscriptionState();
|
||||||
|
|
||||||
// Self-guard redirect
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoading || !beneficiary || !id) return;
|
|
||||||
if (justSubscribed || showSuccessModal) return;
|
|
||||||
|
|
||||||
if (!hasBeneficiaryDevices(beneficiary)) {
|
|
||||||
const status = beneficiary.equipmentStatus;
|
|
||||||
if (status && ['ordered', 'shipped', 'delivered'].includes(status)) {
|
|
||||||
router.replace(`/(tabs)/beneficiaries/${id}/equipment-status`);
|
|
||||||
} else {
|
|
||||||
router.replace(`/(tabs)/beneficiaries/${id}/purchase`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [beneficiary, isLoading, id, justSubscribed, showSuccessModal]);
|
|
||||||
|
|
||||||
const handleSubscribe = async () => {
|
const handleSubscribe = async () => {
|
||||||
if (!beneficiary) {
|
if (!beneficiary) {
|
||||||
|
|||||||
@ -30,6 +30,7 @@ function normalizeStripeStatus(status) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper: Get subscription status from Stripe (source of truth)
|
* Helper: Get subscription status from Stripe (source of truth)
|
||||||
|
* Used for single beneficiary requests
|
||||||
*/
|
*/
|
||||||
async function getStripeSubscriptionStatus(stripeCustomerId) {
|
async function getStripeSubscriptionStatus(stripeCustomerId) {
|
||||||
if (!stripeCustomerId) {
|
if (!stripeCustomerId) {
|
||||||
@ -87,6 +88,71 @@ async function getStripeSubscriptionStatus(stripeCustomerId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: Batch fetch subscription statuses for multiple customers
|
||||||
|
* Makes 1 Stripe API call instead of N calls
|
||||||
|
*/
|
||||||
|
async function getBatchStripeSubscriptions(stripeCustomerIds) {
|
||||||
|
// Filter out null/undefined
|
||||||
|
const validIds = stripeCustomerIds.filter(Boolean);
|
||||||
|
|
||||||
|
if (validIds.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptionMap = {};
|
||||||
|
|
||||||
|
// Initialize all as free/none
|
||||||
|
for (const customerId of validIds) {
|
||||||
|
subscriptionMap[customerId] = { plan: 'free', status: 'none', hasSubscription: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// Fetch all subscriptions in one call (Stripe allows up to 100)
|
||||||
|
// We fetch all statuses and filter in code
|
||||||
|
const subscriptions = await stripe.subscriptions.list({
|
||||||
|
limit: 100,
|
||||||
|
expand: ['data.customer']
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[STRIPE BATCH] Fetched ${subscriptions.data.length} subscriptions in ${Date.now() - startTime}ms`);
|
||||||
|
|
||||||
|
// Build map of customer_id -> subscription
|
||||||
|
for (const sub of subscriptions.data) {
|
||||||
|
const customerId = typeof sub.customer === 'string' ? sub.customer : sub.customer?.id;
|
||||||
|
|
||||||
|
if (!customerId || !validIds.includes(customerId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const periodEndTimestamp = sub.cancel_at || sub.current_period_end;
|
||||||
|
const endDate = periodEndTimestamp ? new Date(periodEndTimestamp * 1000).toISOString() : null;
|
||||||
|
const normalizedStatus = normalizeStripeStatus(sub.status);
|
||||||
|
|
||||||
|
// Only update if this is a better status (active > trialing > past_due > etc)
|
||||||
|
const currentStatus = subscriptionMap[customerId]?.status || 'none';
|
||||||
|
const statusPriority = { active: 4, trialing: 3, past_due: 2, canceled: 1, expired: 0, none: 0 };
|
||||||
|
|
||||||
|
if (statusPriority[normalizedStatus] > statusPriority[currentStatus]) {
|
||||||
|
subscriptionMap[customerId] = {
|
||||||
|
plan: normalizedStatus === 'active' || normalizedStatus === 'trialing' ? 'premium' : 'free',
|
||||||
|
status: normalizedStatus,
|
||||||
|
hasSubscription: normalizedStatus === 'active' || normalizedStatus === 'trialing',
|
||||||
|
endDate: endDate,
|
||||||
|
cancelAtPeriodEnd: sub.cancel_at_period_end || false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return subscriptionMap;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[STRIPE BATCH] Error fetching subscriptions:', error.message);
|
||||||
|
return subscriptionMap; // Return map with defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware to verify JWT token
|
* Middleware to verify JWT token
|
||||||
*/
|
*/
|
||||||
@ -112,15 +178,17 @@ router.use(authMiddleware);
|
|||||||
* GET /api/me/beneficiaries
|
* GET /api/me/beneficiaries
|
||||||
* Returns list of beneficiaries the user has access to
|
* Returns list of beneficiaries the user has access to
|
||||||
* Now uses the proper beneficiaries table (not users)
|
* Now uses the proper beneficiaries table (not users)
|
||||||
|
* OPTIMIZED: Uses batch Stripe API call instead of N individual calls
|
||||||
*/
|
*/
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const startTime = Date.now();
|
||||||
const userId = req.user.userId;
|
const userId = req.user.userId;
|
||||||
|
|
||||||
// Get access records with beneficiary_id (points to beneficiaries table)
|
// Get access records with beneficiary_id (points to beneficiaries table)
|
||||||
const { data: accessRecords, error: accessError } = await supabase
|
const { data: accessRecords, error: accessError } = await supabase
|
||||||
.from('user_access')
|
.from('user_access')
|
||||||
.select('id, beneficiary_id, beneficiary_id, role, granted_at')
|
.select('id, beneficiary_id, role, granted_at')
|
||||||
.eq('accessor_id', userId);
|
.eq('accessor_id', userId);
|
||||||
|
|
||||||
if (accessError) {
|
if (accessError) {
|
||||||
@ -132,29 +200,48 @@ router.get('/', async (req, res) => {
|
|||||||
return res.json({ beneficiaries: [] });
|
return res.json({ beneficiaries: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get beneficiary details for each access record
|
// Get all beneficiary IDs
|
||||||
const beneficiaries = [];
|
const beneficiaryIds = accessRecords
|
||||||
for (const record of accessRecords) {
|
.map(r => r.beneficiary_id)
|
||||||
// Use beneficiary_id if available (new architecture), otherwise skip
|
.filter(Boolean);
|
||||||
const beneficiaryTableId = record.beneficiary_id;
|
|
||||||
if (!beneficiaryTableId) {
|
if (beneficiaryIds.length === 0) {
|
||||||
// Skip old records that don't have beneficiary_id
|
return res.json({ beneficiaries: [] });
|
||||||
console.log('[BENEFICIARY] Skipping legacy record without beneficiary_id:', record.id);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query from beneficiaries table (new architecture)
|
// Batch query: Get all beneficiaries in one DB call
|
||||||
console.log('[GET BENEFICIARIES] querying beneficiaries table for id:', beneficiaryTableId);
|
const { data: beneficiariesData, error: beneficiariesError } = await supabase
|
||||||
const { data: beneficiary, error: beneficiaryError } = await supabase
|
|
||||||
.from('beneficiaries')
|
.from('beneficiaries')
|
||||||
.select('id, name, phone, address, avatar_url, created_at, equipment_status, stripe_customer_id')
|
.select('id, name, phone, address, avatar_url, created_at, equipment_status, stripe_customer_id')
|
||||||
.eq('id', beneficiaryTableId)
|
.in('id', beneficiaryIds);
|
||||||
.single();
|
|
||||||
|
|
||||||
console.log('[GET BENEFICIARIES] got beneficiary:', beneficiary ? beneficiary.name : null, 'error:', beneficiaryError);
|
if (beneficiariesError) {
|
||||||
|
console.error('Get beneficiaries error:', beneficiariesError);
|
||||||
|
return res.status(500).json({ error: 'Failed to get beneficiaries' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
// Collect all Stripe customer IDs for batch request
|
||||||
|
const stripeCustomerIds = beneficiariesData
|
||||||
|
.map(b => b.stripe_customer_id)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Batch Stripe API call: 1 call instead of N
|
||||||
|
const stripeStartTime = Date.now();
|
||||||
|
const subscriptionMap = await getBatchStripeSubscriptions(stripeCustomerIds);
|
||||||
|
const stripeTime = Date.now() - stripeStartTime;
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
const beneficiaries = [];
|
||||||
|
for (const record of accessRecords) {
|
||||||
|
const beneficiary = beneficiariesData.find(b => b.id === record.beneficiary_id);
|
||||||
|
if (!beneficiary) continue;
|
||||||
|
|
||||||
|
const subscription = beneficiary.stripe_customer_id
|
||||||
|
? (subscriptionMap[beneficiary.stripe_customer_id] || { plan: 'free', status: 'none', hasSubscription: false })
|
||||||
|
: { plan: 'free', status: 'none', hasSubscription: false };
|
||||||
|
|
||||||
if (beneficiary) {
|
|
||||||
const subscription = await getStripeSubscriptionStatus(beneficiary.stripe_customer_id);
|
|
||||||
beneficiaries.push({
|
beneficiaries.push({
|
||||||
accessId: record.id,
|
accessId: record.id,
|
||||||
id: beneficiary.id,
|
id: beneficiary.id,
|
||||||
@ -171,7 +258,9 @@ router.get('/', async (req, res) => {
|
|||||||
equipmentStatus: beneficiary.equipment_status || 'none'
|
equipmentStatus: beneficiary.equipment_status || 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const totalTime = Date.now() - startTime;
|
||||||
|
console.log(`[GET BENEFICIARIES] ${beneficiaries.length} items in ${totalTime}ms (DB: ${dbTime}ms, Stripe: ${stripeTime}ms)`);
|
||||||
|
|
||||||
res.json({ beneficiaries });
|
res.json({ beneficiaries });
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ApiError, ApiResponse, AuthResponse, Beneficiary, BeneficiaryDashboardData, ChatResponse, DashboardSingleResponse, NotificationSettings } from '@/types';
|
import type { ApiError, ApiResponse, AuthResponse, Beneficiary, BeneficiaryDashboardData, ChatResponse, DashboardSingleResponse, NotificationSettings } from '@/types';
|
||||||
import * as Crypto from 'expo-crypto';
|
import * as Crypto from 'expo-crypto';
|
||||||
import * as FileSystem from 'expo-file-system';
|
import * as FileSystem from 'expo-file-system/legacy';
|
||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
|
|
||||||
// Callback for handling unauthorized responses (401)
|
// Callback for handling unauthorized responses (401)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user