Skip to content

Commit bf525ff

Browse files
committed
feat: implement edit subscription and date formatting
1 parent 3a5699f commit bf525ff

File tree

4 files changed

+93
-35
lines changed

4 files changed

+93
-35
lines changed

src/App.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,34 @@ function App() {
1212
// Initialize state lazily from storage to avoid useEffect sync issues
1313
const [subscriptions, setSubscriptions] = useState<Subscription[]>(() => storage.loadSubscriptions());
1414
const [isModalOpen, setIsModalOpen] = useState(false);
15+
const [editingSubscription, setEditingSubscription] = useState<Subscription | null>(null);
1516

1617
// Save to storage whenever subscriptions change
1718
useEffect(() => {
1819
storage.saveSubscriptions(subscriptions);
1920
}, [subscriptions]);
2021

2122
const handleAddSubscription = (newSub: Omit<Subscription, 'id'>) => {
22-
const subWithId: Subscription = {
23-
...newSub,
24-
id: crypto.randomUUID()
25-
};
26-
setSubscriptions(prev => [...prev, subWithId]);
23+
if (editingSubscription) {
24+
setSubscriptions(prev => prev.map(sub =>
25+
sub.id === editingSubscription.id
26+
? { ...newSub, id: editingSubscription.id }
27+
: sub
28+
));
29+
setEditingSubscription(null);
30+
} else {
31+
const subWithId: Subscription = {
32+
...newSub,
33+
id: crypto.randomUUID()
34+
};
35+
setSubscriptions(prev => [...prev, subWithId]);
36+
}
37+
setIsModalOpen(false);
38+
};
39+
40+
const handleEditSubscription = (sub: Subscription) => {
41+
setEditingSubscription(sub);
42+
setIsModalOpen(true);
2743
};
2844

2945
const handleDeleteSubscription = (id: string) => {
@@ -89,6 +105,7 @@ function App() {
89105
category={sub.category}
90106
expirationDate={sub.expirationDate}
91107
onDelete={handleDeleteSubscription}
108+
onEdit={handleEditSubscription}
92109
/>
93110
))
94111
)}
@@ -105,7 +122,11 @@ function App() {
105122
{isModalOpen && (
106123
<AddSubscriptionModal
107124
onAdd={handleAddSubscription}
108-
onClose={() => setIsModalOpen(false)}
125+
onClose={() => {
126+
setIsModalOpen(false);
127+
setEditingSubscription(null);
128+
}}
129+
initialData={editingSubscription}
109130
/>
110131
)}
111132
</Layout>

src/components/ui/AddSubscriptionModal.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,17 @@ import type { Subscription, BillingFrequency } from '../../types/subscription';
66
interface AddSubscriptionModalProps {
77
onAdd: (sub: Omit<Subscription, 'id'>) => void;
88
onClose: () => void;
9+
initialData?: Subscription | null;
910
}
1011

11-
export const AddSubscriptionModal: React.FC<AddSubscriptionModalProps> = ({ onAdd, onClose }) => {
12+
export const AddSubscriptionModal: React.FC<AddSubscriptionModalProps> = ({ onAdd, onClose, initialData }) => {
1213
const [formData, setFormData] = useState({
13-
name: '',
14-
price: '',
15-
frequency: 'monthly' as BillingFrequency,
16-
category: 'Entertainment',
17-
nextRenewal: new Date().toISOString().split('T')[0],
18-
expirationDate: ''
14+
name: initialData?.name || '',
15+
price: initialData?.price.toString() || '',
16+
frequency: (initialData?.frequency || 'monthly') as BillingFrequency,
17+
category: initialData?.category || 'Entertainment',
18+
nextRenewal: initialData?.nextRenewal || new Date().toISOString().split('T')[0],
19+
expirationDate: initialData?.expirationDate || ''
1920
});
2021

2122
const handleSubmit = (e: React.FormEvent) => {
@@ -44,9 +45,12 @@ export const AddSubscriptionModal: React.FC<AddSubscriptionModalProps> = ({ onAd
4445
</button>
4546
</div>
4647

48+
4749
{/* Form Content */}
4850
<div className="p-8">
49-
<Display as="h2" variant="medium" className="mb-6">New Subscription Entry</Display>
51+
<Display as="h2" variant="medium" className="mb-6">
52+
{initialData ? 'Edit Subscription' : 'New Subscription Entry'}
53+
</Display>
5054

5155
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
5256
<div className="space-y-2">
@@ -140,7 +144,7 @@ export const AddSubscriptionModal: React.FC<AddSubscriptionModalProps> = ({ onAd
140144
className="px-6 py-3 bg-ink text-paper font-mono text-sm hover:bg-signal transition-colors flex items-center gap-2"
141145
>
142146
<Check size={16} />
143-
CONFIRM ENTRY
147+
{initialData ? 'UPDATE ENTRY' : 'CONFIRM ENTRY'}
144148
</button>
145149
</div>
146150
</form>

src/components/ui/SubscriptionCard.tsx

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react';
22
import { Display, Body, Mono } from './Typography';
3+
import { formatDate } from '../../utils/date';
4+
5+
import type { Subscription } from '../../types/subscription';
36

47
interface SubscriptionProps {
58
id: string; // Added ID for deletion
@@ -11,6 +14,7 @@ interface SubscriptionProps {
1114
category: string;
1215
expirationDate?: string;
1316
onDelete?: (id: string) => void;
17+
onEdit?: (sub: Subscription) => void;
1418
}
1519

1620
export const SubscriptionCard: React.FC<SubscriptionProps> = ({
@@ -22,7 +26,8 @@ export const SubscriptionCard: React.FC<SubscriptionProps> = ({
2226
nextPayment,
2327
category,
2428
expirationDate,
25-
onDelete
29+
onDelete,
30+
onEdit
2631
}) => {
2732
return (
2833
<div className="group relative border-b border-structural p-4 md:px-6 hover:bg-concrete/50 transition-colors duration-200">
@@ -36,14 +41,25 @@ export const SubscriptionCard: React.FC<SubscriptionProps> = ({
3641
<Display as="h3" variant="medium" className="text-xl md:text-2xl">{name}</Display>
3742
<div className="flex items-center gap-2 mt-1">
3843
<Mono variant="label" className="inline-block">{category}</Mono>
39-
{onDelete && (
40-
<button
41-
onClick={() => onDelete(id)}
42-
className="md:hidden text-xs font-mono text-red-500 hover:underline uppercase"
43-
>
44-
[DELETE]
45-
</button>
46-
)}
44+
<Mono variant="label" className="inline-block">{category}</Mono>
45+
<div className="flex gap-2">
46+
{onEdit && (
47+
<button
48+
onClick={() => onEdit({ id, name, price, frequency: cycle, nextRenewal: nextPayment, category, expirationDate })}
49+
className="md:hidden text-xs font-mono text-ink/50 hover:underline uppercase"
50+
>
51+
[EDIT]
52+
</button>
53+
)}
54+
{onDelete && (
55+
<button
56+
onClick={() => onDelete(id)}
57+
className="md:hidden text-xs font-mono text-red-500 hover:underline uppercase"
58+
>
59+
[DELETE]
60+
</button>
61+
)}
62+
</div>
4763
</div>
4864
</div>
4965
</div>
@@ -52,11 +68,11 @@ export const SubscriptionCard: React.FC<SubscriptionProps> = ({
5268
<div className="flex items-end md:items-center justify-between md:justify-end gap-2 md:gap-12 min-w-[300px]">
5369
<div className="text-right">
5470
<Body variant="caption" className="block mb-1">Next Billing</Body>
55-
<Mono variant="code" className="bg-transparent text-ink/70">{nextPayment}</Mono>
71+
<Mono variant="code" className="bg-transparent text-ink/70">{formatDate(nextPayment)}</Mono>
5672
{expirationDate && (
5773
<div className="mt-2">
5874
<Body variant="caption" className="block mb-1 text-ink/50">Expires</Body>
59-
<Mono variant="code" className="bg-transparent text-ink/70 text-xs">{expirationDate}</Mono>
75+
<Mono variant="code" className="bg-transparent text-ink/70 text-xs">{formatDate(expirationDate)}</Mono>
6076
</div>
6177
)}
6278
</div>
@@ -70,15 +86,26 @@ export const SubscriptionCard: React.FC<SubscriptionProps> = ({
7086
</div>
7187
<div className="flex items-center gap-3">
7288
<Body variant="caption" className="text-right text-signal">/{cycle}</Body>
73-
{onDelete && (
74-
<button
75-
onClick={() => onDelete(id)}
76-
className="hidden md:block opacity-0 group-hover:opacity-100 transition-opacity text-xs font-mono text-red-500 hover:text-red-600 uppercase"
77-
title="Remove Entry"
78-
>
79-
[DELETE]
80-
</button>
81-
)}
89+
<div className="flex gap-2">
90+
{onEdit && (
91+
<button
92+
onClick={() => onEdit({ id, name, price, frequency: cycle, nextRenewal: nextPayment, category, expirationDate })}
93+
className="hidden md:block opacity-0 group-hover:opacity-100 transition-opacity text-xs font-mono text-ink/50 hover:text-ink uppercase"
94+
title="Edit Entry"
95+
>
96+
[EDIT]
97+
</button>
98+
)}
99+
{onDelete && (
100+
<button
101+
onClick={() => onDelete(id)}
102+
className="hidden md:block opacity-0 group-hover:opacity-100 transition-opacity text-xs font-mono text-red-500 hover:text-red-600 uppercase"
103+
title="Remove Entry"
104+
>
105+
[DELETE]
106+
</button>
107+
)}
108+
</div>
82109
</div>
83110
</div>
84111
</div>

src/utils/date.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
export const formatDate = (dateString: string): string => {
3+
if (!dateString) return '';
4+
const [year, month, day] = dateString.split('-');
5+
return `${day}/${month}/${year}`;
6+
};

0 commit comments

Comments
 (0)