Skip to content

Commit bdc4c08

Browse files
Merge pull request #48 from HORNET-Storage/feature/api-migration-and-personal-mode
Feature/api migration and personal mode
2 parents f30f26d + 3eaf795 commit bdc4c08

36 files changed

+1846
-1367
lines changed

src/api/allowedUsers.api.ts

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import {
66
AllowedUsersNpubsResponse,
77
BulkImportRequest,
88
AllowedUsersNpub,
9+
AllowedUsersMode,
910
DEFAULT_TIERS
1011
} from '@app/types/allowedUsers.types';
1112

1213
// Settings Management
1314
export const getAllowedUsersSettings = async (): Promise<AllowedUsersSettings> => {
1415
const token = readToken();
15-
const response = await fetch(`${config.baseURL}/api/settings/allowed_users`, {
16+
const response = await fetch(`${config.baseURL}/api/settings`, {
1617
headers: {
1718
'Authorization': `Bearer ${token}`,
1819
},
@@ -22,25 +23,49 @@ export const getAllowedUsersSettings = async (): Promise<AllowedUsersSettings> =
2223

2324
const text = await response.text();
2425
try {
25-
const data: AllowedUsersApiResponse = JSON.parse(text);
26+
const data = JSON.parse(text);
27+
28+
// Extract allowed_users from the new nested structure
29+
const allowedUsersData = data.settings?.allowed_users;
30+
if (!allowedUsersData) {
31+
throw new Error('No allowed_users data found in response');
32+
}
2633

2734
// Transform tiers from backend format to frontend format
28-
let transformedTiers = data.allowed_users.tiers.map(tier => ({
29-
data_limit: (tier as any).datalimit || tier.data_limit || '',
30-
price: tier.price
31-
}));
35+
let transformedTiers = [];
36+
37+
// Check if tiers exist in response, otherwise use defaults
38+
if (allowedUsersData.tiers && Array.isArray(allowedUsersData.tiers)) {
39+
transformedTiers = allowedUsersData.tiers.map((tier: any) => ({
40+
name: tier.name || 'Unnamed Tier',
41+
price_sats: tier.price_sats || 0,
42+
monthly_limit_bytes: tier.monthly_limit_bytes || 0,
43+
unlimited: tier.unlimited || false
44+
}));
45+
} else {
46+
// Use default tiers for the mode if none provided
47+
const mode = allowedUsersData.mode as AllowedUsersMode;
48+
transformedTiers = DEFAULT_TIERS[mode] || DEFAULT_TIERS.free;
49+
}
3250

3351
// For free mode, reconstruct full UI options with active tier marked
34-
if (data.allowed_users.mode === 'free' && transformedTiers.length === 1) {
35-
const activeTierDataLimit = transformedTiers[0].data_limit;
52+
if (allowedUsersData.mode === 'free' && transformedTiers.length === 1) {
53+
const activeTierBytes = transformedTiers[0].monthly_limit_bytes;
3654
transformedTiers = DEFAULT_TIERS.free.map(defaultTier => ({
3755
...defaultTier,
38-
active: defaultTier.data_limit === activeTierDataLimit
56+
active: defaultTier.monthly_limit_bytes === activeTierBytes
3957
}));
4058
}
4159

60+
// For personal mode, reconstruct with single unlimited tier
61+
if (allowedUsersData.mode === 'personal' && transformedTiers.length === 1) {
62+
transformedTiers = DEFAULT_TIERS.personal;
63+
}
64+
4265
const transformedSettings = {
43-
...data.allowed_users,
66+
mode: allowedUsersData.mode || 'free',
67+
read_access: allowedUsersData.read_access || { enabled: true, scope: 'all_users' },
68+
write_access: allowedUsersData.write_access || { enabled: true, scope: 'all_users' },
4469
tiers: transformedTiers
4570
};
4671

@@ -53,33 +78,37 @@ export const getAllowedUsersSettings = async (): Promise<AllowedUsersSettings> =
5378
export const updateAllowedUsersSettings = async (settings: AllowedUsersSettings): Promise<{ success: boolean, message: string }> => {
5479
const token = readToken();
5580

56-
// Filter tiers based on mode - for free mode, only send active tier
57-
const tiersToSend = settings.mode === 'free'
81+
// Filter tiers based on mode - for free and personal modes, only send active tier
82+
const tiersToSend = (settings.mode === 'free' || settings.mode === 'personal')
5883
? settings.tiers.filter(tier => tier.active)
5984
: settings.tiers;
6085

61-
// Transform to nested format as expected by backend
86+
// Transform to nested format as expected by new unified backend API
6287
const nestedSettings = {
63-
"allowed_users": {
64-
"mode": settings.mode,
65-
"read_access": {
66-
"enabled": settings.read_access.enabled,
67-
"scope": settings.read_access.scope
68-
},
69-
"write_access": {
70-
"enabled": settings.write_access.enabled,
71-
"scope": settings.write_access.scope
72-
},
73-
"tiers": tiersToSend.map(tier => ({
74-
"datalimit": tier.data_limit || "1 GB per month", // Backend expects 'datalimit' not 'data_limit', fallback for empty values
75-
"price": tier.price || "0"
76-
}))
88+
"settings": {
89+
"allowed_users": {
90+
"mode": settings.mode,
91+
"read_access": {
92+
"enabled": settings.read_access.enabled,
93+
"scope": settings.read_access.scope
94+
},
95+
"write_access": {
96+
"enabled": settings.write_access.enabled,
97+
"scope": settings.write_access.scope
98+
},
99+
"tiers": tiersToSend.map(tier => ({
100+
"name": tier.name,
101+
"price_sats": tier.price_sats,
102+
"monthly_limit_bytes": tier.monthly_limit_bytes,
103+
"unlimited": tier.unlimited
104+
}))
105+
}
77106
}
78107
};
79108

80109
console.log('Sending to backend:', JSON.stringify(nestedSettings, null, 2));
81110

82-
const response = await fetch(`${config.baseURL}/api/settings/allowed_users`, {
111+
const response = await fetch(`${config.baseURL}/api/settings`, {
83112
method: 'POST',
84113
headers: {
85114
'Content-Type': 'application/json',
@@ -97,9 +126,10 @@ export const updateAllowedUsersSettings = async (settings: AllowedUsersSettings)
97126
}
98127

99128
try {
100-
return JSON.parse(text);
129+
return JSON.parse(text) || { success: true, message: 'Settings updated successfully' };
101130
} catch (jsonError) {
102-
throw new Error(`Invalid JSON response: ${text}`);
131+
// If response is not JSON, assume success if status was OK
132+
return { success: true, message: 'Settings updated successfully' };
103133
}
104134
};
105135

src/components/allowed-users/components/ModeSelector/ModeSelector.styles.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ export const Container = styled.div`
88

99
export const ModeGrid = styled.div`
1010
display: grid;
11-
grid-template-columns: repeat(3, 1fr);
11+
grid-template-columns: repeat(2, 1fr);
12+
grid-template-rows: repeat(2, 1fr);
1213
gap: 1rem;
1314
margin-bottom: 1.5rem;
1415
1516
${media.md} {
1617
grid-template-columns: 1fr;
18+
grid-template-rows: none;
1719
gap: 0.75rem;
1820
}
1921
`;
@@ -24,10 +26,14 @@ interface ModeButtonProps {
2426
}
2527

2628
export const ModeButton = styled(Button)<ModeButtonProps>`
27-
height: 60px;
29+
height: 80px;
2830
border-radius: 8px;
2931
font-weight: 600;
3032
transition: all 0.3s ease;
33+
display: flex;
34+
align-items: center;
35+
justify-content: center;
36+
padding: 8px 16px;
3137
3238
${({ $isActive, $color }) => $isActive && `
3339
background-color: ${$color} !important;
@@ -41,7 +47,7 @@ export const ModeButton = styled(Button)<ModeButtonProps>`
4147
}
4248
4349
${media.md} {
44-
height: 50px;
50+
height: 70px;
4551
}
4652
`;
4753

src/components/allowed-users/components/ModeSelector/ModeSelector.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,29 @@ interface ModeSelectorProps {
1010
}
1111

1212
const MODE_INFO = {
13+
personal: {
14+
label: 'Only Me',
15+
subtitle: '[Free]',
16+
description: 'Personal relay for single user with unlimited access',
17+
color: '#fa541c'
18+
},
19+
exclusive: {
20+
label: 'Invite Only',
21+
subtitle: '[Free]',
22+
description: 'Invite-only access with manual NPUB management',
23+
color: '#722ed1'
24+
},
1325
free: {
14-
label: 'Free Mode',
26+
label: 'Public Relay',
27+
subtitle: '[Free]',
1528
description: 'Open access with optional free tiers',
1629
color: '#1890ff'
1730
},
1831
paid: {
19-
label: 'Paid Mode',
32+
label: 'Subscription',
33+
subtitle: '[Paid]',
2034
description: 'Subscription-based access control',
2135
color: '#52c41a'
22-
},
23-
exclusive: {
24-
label: 'Exclusive Mode',
25-
description: 'Invite-only access with manual NPUB management',
26-
color: '#722ed1'
2736
}
2837
};
2938

@@ -35,7 +44,7 @@ export const ModeSelector: React.FC<ModeSelectorProps> = ({
3544
return (
3645
<S.Container>
3746
<S.ModeGrid>
38-
{(Object.keys(MODE_INFO) as AllowedUsersMode[]).map((mode) => {
47+
{(['personal', 'exclusive', 'free', 'paid'] as AllowedUsersMode[]).map((mode) => {
3948
const info = MODE_INFO[mode];
4049
const isActive = currentMode === mode;
4150

@@ -49,7 +58,16 @@ export const ModeSelector: React.FC<ModeSelectorProps> = ({
4958
$isActive={isActive}
5059
$color={info.color}
5160
>
52-
{info.label}
61+
<div style={{
62+
display: 'flex',
63+
flexDirection: 'column',
64+
alignItems: 'center',
65+
gap: '2px',
66+
lineHeight: '1.2'
67+
}}>
68+
<div style={{ fontSize: '16px', fontWeight: '600' }}>{info.label}</div>
69+
<div style={{ fontSize: '12px', opacity: 0.8, fontWeight: '400' }}>{info.subtitle}</div>
70+
</div>
5371
</S.ModeButton>
5472
</Tooltip>
5573
);

src/components/allowed-users/components/NPubManagement/NPubManagement.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,16 @@ export const NPubManagement: React.FC<NPubManagementProps> = ({
7272

7373
setUnifiedUsers(Array.from(allNpubs.values()));
7474
}, [readNpubs.npubs, writeNpubs.npubs]);
75-
const tierOptions = settings.tiers.map(tier => ({
76-
label: `${tier.data_limit} (${tier.price === '0' ? 'Free' : `${tier.price} sats`})`,
77-
value: tier.data_limit
78-
}));
75+
const tierOptions = settings.tiers.map(tier => {
76+
const displayFormat = tier.unlimited
77+
? 'unlimited'
78+
: `${(tier.monthly_limit_bytes / 1073741824).toFixed(tier.monthly_limit_bytes % 1073741824 === 0 ? 0 : 1)} GB per month`;
79+
80+
return {
81+
label: `${tier.name} - ${displayFormat} (${tier.price_sats === 0 ? 'Free' : `${tier.price_sats} sats`})`,
82+
value: tier.name
83+
};
84+
});
7985

8086
const handleAddNpub = async () => {
8187
try {
@@ -140,7 +146,7 @@ export const NPubManagement: React.FC<NPubManagementProps> = ({
140146
}
141147

142148
const lines = bulkText.split('\n').filter(line => line.trim());
143-
const defaultTier = settings.tiers[0]?.data_limit || 'basic';
149+
const defaultTier = settings.tiers[0]?.name || 'basic';
144150

145151
try {
146152
for (const line of lines) {

0 commit comments

Comments
 (0)