Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
thecybernanny-webapp
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
asranov0003
thecybernanny-webapp
Commits
85392706
Commit
85392706
authored
Jul 31, 2025
by
asranov0003
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: fetch tariffs and payment methods
parent
dddd8417
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
244 additions
and
39 deletions
+244
-39
Subscription.css
src/pages/Subscription/Subscription.css
+67
-0
Subscription.tsx
src/pages/Subscription/Subscription.tsx
+68
-39
billingSlice.ts
src/stores/slices/billingSlice.ts
+95
-0
store.ts
src/stores/store.ts
+2
-0
billing.types.ts
src/types/billing.types.ts
+12
-0
No files found.
src/pages/Subscription/Subscription.css
View file @
85392706
.subscription
{
padding-top
:
60px
;
}
.subscription
.cbtn
{
margin
:
1rem
0
;
}
.subscription__tariffs__list
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
gap
:
0.5rem
;
margin
:
1rem
0
;
}
.subscription__tariff
{
min-width
:
100px
;
padding
:
1rem
;
border-radius
:
10px
;
background
:
var
(
--content-bg-color
);
text-align
:
center
;
cursor
:
pointer
;
}
.subscription__tariff-selected
{
background
:
var
(
--primary-color
);
color
:
var
(
--on-text-color
);
}
.subscription__payments__list
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
flex-wrap
:
wrap
;
gap
:
0.5rem
;
margin
:
1rem
0
;
}
.subscription__payment
{
width
:
100px
;
height
:
100px
;
padding
:
1rem
;
border-radius
:
10px
;
background
:
var
(
--content-bg-color
);
text-align
:
center
;
cursor
:
pointer
;
}
.subscription__payment-selected
{
background
:
var
(
--primary-color
);
color
:
var
(
--on-text-color
);
}
.subscription__payment__icon
{
width
:
30px
;
height
:
30px
;
object-fit
:
contain
;
}
.subscription__payment__name
{
font-size
:
0.65rem
;
display
:
-webkit-box
;
-webkit-line-clamp
:
2
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
\ No newline at end of file
src/pages/Subscription/Subscription.tsx
View file @
85392706
import
React
from
"react"
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
"./Subscription.css"
;
import
CButton
from
"../../components/CButton"
;
import
{
useTranslation
}
from
"react-i18next"
;
const
tariffs
=
[
{
id
:
1
,
title
:
"Yearly"
,
amount
:
25
,
},
{
id
:
2
,
title
:
"3-Months"
,
amount
:
15
,
},
{
id
:
3
,
title
:
"Monthly"
,
amount
:
10
,
},
];
const
paymentMethods
=
[
{
id
:
1
,
paymethod_name
:
"Карта РФ: Мир, Visa, Mastercard"
,
},
{
id
:
2
,
paymethod_name
:
"СБП (Все банки РФ)"
,
},
{
id
:
3
,
paymethod_name
:
"МИР"
,
},
];
import
{
useAppDispatch
,
useAppSelector
,
type
RootState
,
}
from
"../../stores/store"
;
import
{
fetchPaymentMethods
,
fetchTariffs
,
}
from
"../../stores/slices/billingSlice"
;
import
type
{
IPaymentMethod
,
ITariff
}
from
"../../types/billing.types"
;
import
CLoading
from
"../../components/CLoading"
;
const
Subscription
:
React
.
FC
=
()
=>
{
const
[
selectedTariff
,
setSelectedTariff
]
=
useState
<
ITariff
|
null
>
(
null
);
const
[
selectedPaymentMethod
,
setSelectedPaymentMethod
]
=
useState
<
IPaymentMethod
|
null
>
(
null
);
const
{
tariffs
,
paymentMethods
,
isTariffsLoading
,
isPaymentMethodsLoading
}
=
useAppSelector
((
state
:
RootState
)
=>
state
.
billing
);
const
dispatch
=
useAppDispatch
();
const
{
t
}
=
useTranslation
();
useEffect
(()
=>
{
dispatch
(
fetchTariffs
());
dispatch
(
fetchPaymentMethods
());
},
[
dispatch
]);
useEffect
(()
=>
{
if
(
tariffs
.
length
>
0
)
{
setSelectedTariff
(
tariffs
[
0
]);
}
},
[
tariffs
]);
useEffect
(()
=>
{
if
(
paymentMethods
.
length
>
0
)
{
setSelectedPaymentMethod
(
paymentMethods
[
0
]);
}
},
[
paymentMethods
]);
return
(
<
div
className=
"subscription wrapper"
>
{
isTariffsLoading
&&
isPaymentMethodsLoading
&&
tariffs
.
length
===
0
&&
paymentMethods
.
length
===
0
&&
(
<
div
className=
"cloading__center"
>
<
CLoading
/>
</
div
>
)
}
<
div
className=
"subscription__tariffs"
>
<
h3
className=
"subscription__tariffs__title"
>
{
t
(
"subscription.selectTariff"
)
}
...
...
@@ -48,7 +58,15 @@ const Subscription: React.FC = () => {
<
div
className=
"subscription__tariffs__list"
>
{
tariffs
.
map
((
tariff
)
=>
(
<
div
key=
{
tariff
.
id
}
className=
"subscription__tariff"
>
<
div
key=
{
tariff
.
id
}
className=
{
`subscription__tariff ${
selectedTariff?.id === tariff?.id
? "subscription__tariff-selected"
: ""
}`
}
onClick=
{
()
=>
setSelectedTariff
(
tariff
)
}
>
<
h4
className=
"subscription__tariff__title"
>
{
tariff
.
title
}
</
h4
>
<
p
className=
"subscription__tariff__amount"
>
$
{
tariff
.
amount
}
</
p
>
</
div
>
...
...
@@ -63,10 +81,21 @@ const Subscription: React.FC = () => {
<
div
className=
"subscription__payments__list"
>
{
paymentMethods
.
map
((
method
)
=>
(
<
div
key=
{
method
.
id
}
className=
"subscription__payment"
>
<
p
className=
"subscription__payment__name"
>
{
method
.
paymethod_name
}
</
p
>
<
div
key=
{
method
.
id
}
className=
{
`subscription__payment ${
selectedPaymentMethod?.id === method?.id
? "subscription__payment-selected"
: ""
}`
}
onClick=
{
()
=>
setSelectedPaymentMethod
(
method
)
}
>
<
img
src=
{
`https://cabinet.thecybernanny.com/new/payicons/${method.icon}`
}
alt=
{
method
.
icon
}
className=
"subscription__payment__icon"
/>
<
p
className=
"subscription__payment__name"
>
{
method
.
name
}
</
p
>
</
div
>
))
}
</
div
>
...
...
src/stores/slices/billingSlice.ts
0 → 100644
View file @
85392706
import
{
createAsyncThunk
,
createSlice
}
from
"@reduxjs/toolkit"
;
import
{
sendRpcRequest
}
from
"../../services/apiClient"
;
import
type
{
IPaymentMethod
,
ITariff
}
from
"../../types/billing.types"
;
interface
IBillingState
{
tariffs
:
ITariff
[];
isTariffsLoading
:
boolean
;
tariffsError
:
string
|
null
;
paymentMethods
:
IPaymentMethod
[];
isPaymentMethodsLoading
:
boolean
;
paymentMethodsError
:
string
|
null
;
}
const
initialState
:
IBillingState
=
{
tariffs
:
[],
isTariffsLoading
:
false
,
tariffsError
:
null
,
paymentMethods
:
[],
isPaymentMethodsLoading
:
false
,
paymentMethodsError
:
null
,
};
export
const
fetchTariffs
=
createAsyncThunk
(
"billing/fetchTariffs"
,
async
(
_
,
{
rejectWithValue
})
=>
{
try
{
const
response
=
await
sendRpcRequest
<
{
list
:
ITariff
[]
}
>
(
"billing.tariffs"
);
return
response
.
list
;
}
catch
(
error
:
unknown
)
{
if
(
typeof
error
===
"object"
&&
error
!==
null
&&
"message"
in
error
)
{
return
rejectWithValue
(
error
.
message
);
}
return
rejectWithValue
(
"Unknown error occurred"
);
}
}
);
export
const
fetchPaymentMethods
=
createAsyncThunk
(
"billing/fetchPaymentMethods"
,
async
(
_
,
{
rejectWithValue
})
=>
{
try
{
const
response
=
await
sendRpcRequest
<
{
list
:
IPaymentMethod
[]
}
>
(
"billing.paymentMethods"
);
return
response
.
list
;
}
catch
(
error
:
unknown
)
{
if
(
typeof
error
===
"object"
&&
error
!==
null
&&
"message"
in
error
)
{
return
rejectWithValue
(
error
.
message
);
}
return
rejectWithValue
(
"Unknown error occurred"
);
}
}
);
const
billingSlice
=
createSlice
({
name
:
"billing"
,
initialState
,
reducers
:
{},
extraReducers
:
(
builder
)
=>
{
builder
.
addCase
(
fetchTariffs
.
pending
,
(
state
)
=>
{
state
.
isTariffsLoading
=
true
;
state
.
tariffsError
=
null
;
})
.
addCase
(
fetchTariffs
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
isTariffsLoading
=
false
;
state
.
tariffs
=
action
.
payload
;
})
.
addCase
(
fetchTariffs
.
rejected
,
(
state
,
action
)
=>
{
state
.
isTariffsLoading
=
false
;
state
.
tariffsError
=
action
.
payload
as
string
;
})
.
addCase
(
fetchPaymentMethods
.
pending
,
(
state
)
=>
{
state
.
isPaymentMethodsLoading
=
true
;
state
.
paymentMethodsError
=
null
;
})
.
addCase
(
fetchPaymentMethods
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
isPaymentMethodsLoading
=
false
;
state
.
paymentMethods
=
action
.
payload
;
})
.
addCase
(
fetchPaymentMethods
.
rejected
,
(
state
,
action
)
=>
{
state
.
isPaymentMethodsLoading
=
false
;
state
.
paymentMethodsError
=
action
.
payload
as
string
;
});
},
});
export
default
billingSlice
.
reducer
;
src/stores/store.ts
View file @
85392706
...
...
@@ -10,6 +10,7 @@ import callHistorySlice from "./slices/callHistorySlice";
import
messengerHistorySlice
from
"./slices/messengerHistorySlice"
;
import
usageLimitSlice
from
"./slices/usageLimitSlice"
;
import
dataSlice
from
"./slices/dataSlice"
;
import
billingSlice
from
"./slices/billingSlice"
;
import
{
useDispatch
,
useSelector
,
...
...
@@ -29,6 +30,7 @@ export const store = configureStore({
messengerHistory
:
messengerHistorySlice
,
usageLimit
:
usageLimitSlice
,
data
:
dataSlice
,
billing
:
billingSlice
,
},
});
...
...
src/types/billing.types.ts
0 → 100644
View file @
85392706
export
interface
ITariff
{
id
:
string
;
title
:
string
;
amount
:
string
;
}
export
interface
IPaymentMethod
{
id
:
string
;
name
:
string
;
icon
:
string
;
enabled
:
boolean
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment