[eric] overlays: extract PlanPickerModal from MessageBubble for reuse

This commit is contained in:
ciregenz
2026-06-04 04:02:05 -07:00
parent 4688ca41dd
commit 609c4f617f
2 changed files with 89 additions and 36 deletions
@@ -0,0 +1,81 @@
import React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Modal from '@mui/material/Modal';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import PlanPicker from './PlanPicker';
import type {
OpenSwarmPlan,
CheckoutSource,
} from '@/shared/subscription/checkout';
interface Props {
open: boolean;
onClose: () => void;
title: string;
subtitle?: string;
source: CheckoutSource;
defaultPlan?: OpenSwarmPlan;
currentPlan?: OpenSwarmPlan;
onSubscribed?: (plan: OpenSwarmPlan) => void;
}
/** Centered modal around the compact PlanPicker; keeps pricing out of the page flow until asked for. */
const PlanPickerModal: React.FC<Props> = ({
open,
onClose,
title,
subtitle,
source,
defaultPlan,
currentPlan,
onSubscribed,
}) => {
const c = useClaudeTokens();
return (
<Modal
open={open}
onClose={onClose}
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', p: 2 }}
>
<Box sx={{
width: 460, maxWidth: '100%',
maxHeight: '90vh', overflowY: 'auto',
bgcolor: c.bg.surface, borderRadius: `${c.radius.xl}px`,
border: `1px solid ${c.border.subtle}`,
p: 3, outline: 'none',
boxShadow: '0 20px 60px rgba(0,0,0,0.4)',
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 1.5 }}>
<Typography sx={{ fontSize: '1.05rem', fontWeight: 700, color: c.text.primary }}>
{title}
</Typography>
<IconButton
size="small"
onClick={onClose}
sx={{ color: c.text.tertiary }}
aria-label="Close"
>
<CloseIcon sx={{ fontSize: 18 }} />
</IconButton>
</Box>
{subtitle && (
<Typography sx={{ fontSize: '0.75rem', color: c.text.muted, mb: 2 }}>
{subtitle}
</Typography>
)}
<PlanPicker
source={source}
defaultPlan={defaultPlan}
currentPlan={currentPlan}
compact
onSubscribed={onSubscribed}
/>
</Box>
</Modal>
);
};
export default PlanPickerModal;
@@ -24,7 +24,7 @@ import { shallowEqual } from 'react-redux';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { SKILL_COLOR } from '@/app/components/editor/richEditorUtils';
import PlanPicker from '@/app/components/overlays/PlanPicker';
import PlanPickerModal from '@/app/components/overlays/PlanPickerModal';
import { ErrorSlime } from '@/app/components/feedback/ErrorSlime';
const streamingCursorKeyframes = `
@@ -1189,43 +1189,15 @@ const MessageBubble: React.FC<Props> = React.memo(({ message, editing = false, o
)}
</Box>
<Modal
<PlanPickerModal
open={pickerOpen}
onClose={() => setPickerOpen(false)}
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', p: 2 }}
>
<Box sx={{
width: 460, maxWidth: '100%',
maxHeight: '90vh', overflowY: 'auto',
bgcolor: c.bg.surface, borderRadius: `${c.radius.xl}px`,
border: `1px solid ${c.border.subtle}`,
p: 3, outline: 'none',
boxShadow: '0 20px 60px rgba(0,0,0,0.4)',
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 1.5 }}>
<Typography sx={{ fontSize: '1.05rem', fontWeight: 700, color: c.text.primary }}>
Upgrade your plan
</Typography>
<IconButton
size="small"
onClick={() => setPickerOpen(false)}
sx={{ color: c.text.tertiary }}
aria-label="Close"
>
<CloseIcon sx={{ fontSize: 18 }} />
</IconButton>
</Box>
<Typography sx={{ fontSize: '0.75rem', color: c.text.muted, mb: 2 }}>
Pick a plan to keep going. Cancel anytime from Stripe.
</Typography>
<PlanPicker
source="upgrade_cta"
defaultPlan="pro_plus"
compact
onSubscribed={() => setPickerOpen(false)}
/>
</Box>
</Modal>
title="Upgrade your plan"
subtitle="Pick a plan to keep going. Cancel anytime from Stripe."
source="upgrade_cta"
defaultPlan="pro_plus"
onSubscribed={() => setPickerOpen(false)}
/>
</Box>
);
});