MAJOR UPPDATE: "First Ai"

feat: add AI categorization for products and enhance user management

- Integrated AI service for category suggestions in receipt import and product management.
- Added premium subscription feature for users with corresponding API endpoints.
- Implemented admin interface for managing pending product suggestions.
- Enhanced user management to include premium status and corresponding UI updates.
- Updated database schema to support new fields for premium status and product status.
This commit is contained in:
Nils-Johan Gynther
2026-04-19 10:34:21 +02:00
parent 0286ab0991
commit 054a19ed7c
30 changed files with 917 additions and 77 deletions
+37 -1
View File
@@ -7,6 +7,7 @@ interface User {
username: string;
email: string;
role: string;
isPremium: boolean;
firstName?: string;
lastName?: string;
createdAt: string;
@@ -109,6 +110,21 @@ export default function AnvandareClient({ users: initial, currentUserId }: Props
setCopiedPw(false);
}
async function handlePremiumChange(id: number, isPremium: boolean) {
setError('');
const res = await fetch(`/api/admin-users/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isPremium }),
});
if (res.ok) {
setUsers((prev) => prev.map((u) => (u.id === id ? { ...u, isPremium } : u)));
} else {
const data = await res.json().catch(() => ({}));
setError(data.message ?? 'Kunde inte ändra plan');
}
}
async function handleEmailSave(id: number) {
setError('');
const res = await fetch(`/api/admin-users/${id}`, {
@@ -241,7 +257,7 @@ export default function AnvandareClient({ users: initial, currentUserId }: Props
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
<thead>
<tr style={{ background: '#f1f5f9', textAlign: 'left' }}>
{['Användare', 'E-post', 'Roll', 'Åtgärder'].map((h) => (
{['Användare', 'E-post', 'Roll', 'Plan', 'Åtgärder'].map((h) => (
<th
key={h}
style={{ padding: '0.6rem 0.8rem', borderBottom: '2px solid #e2e8f0' }}
@@ -367,6 +383,26 @@ export default function AnvandareClient({ users: initial, currentUserId }: Props
)}
</td>
{/* Plan */}
<td style={{ padding: '0.6rem 0.8rem' }}>
<select
value={user.isPremium ? 'paid' : 'free'}
onChange={(e) => handlePremiumChange(user.id, e.target.value === 'paid')}
style={{
padding: '0.25rem 0.4rem',
border: '1px solid #cbd5e1',
borderRadius: 4,
fontSize: 13,
background: user.isPremium ? '#fef9c3' : '#f8fafc',
color: user.isPremium ? '#854d0e' : '#334155',
fontWeight: user.isPremium ? 600 : 400,
}}
>
<option value="free">Free</option>
<option value="paid">Paid </option>
</select>
</td>
{/* Åtgärder */}
<td style={{ padding: '0.6rem 0.8rem' }}>
{isSelf ? (