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
9ff5f1a8
Commit
9ff5f1a8
authored
Sep 29, 2025
by
asranov0003
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add unsubscribe btn
parent
b4eebb44
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
129 additions
and
5 deletions
+129
-5
en.json
src/locales/en/en.json
+3
-1
ru.json
src/locales/ru/ru.json
+3
-1
uz.json
src/locales/uz/uz.json
+3
-1
Settings.tsx
src/pages/Settings/Settings.tsx
+59
-1
billingSlice.ts
src/stores/slices/billingSlice.ts
+56
-1
billing.types.ts
src/types/billing.types.ts
+5
-0
No files found.
src/locales/en/en.json
View file @
9ff5f1a8
...
...
@@ -126,7 +126,9 @@
"deleteImages"
:
"Delete images"
,
"deleteImagesDesc"
:
"Are you sure you want to delete all images?"
,
"deleteMedia"
:
"Delete media"
,
"deleteMediaDesc"
:
"Are you sure you want to delete all media files?"
"deleteMediaDesc"
:
"Are you sure you want to delete all media files?"
,
"unsubscribe"
:
"Unsubscribe"
,
"unsubscribeDesc"
:
"Are you sure you want to unsubscribe?"
},
"permissions"
:
{
"title"
:
"Permissions"
,
...
...
src/locales/ru/ru.json
View file @
9ff5f1a8
...
...
@@ -126,7 +126,9 @@
"deleteImages"
:
"Удалить изображения"
,
"deleteImagesDesc"
:
"Вы действительно хотите удалить все изображения?"
,
"deleteMedia"
:
"Удалить медиа"
,
"deleteMediaDesc"
:
"Вы действительно хотите удалить все медиа-файлы?"
"deleteMediaDesc"
:
"Вы действительно хотите удалить все медиа-файлы?"
,
"unsubscribe"
:
"Отписаться"
,
"unsubscribeDesc"
:
"Вы уверены, что хотите отписаться?"
},
"permissions"
:
{
"title"
:
"Разрешения"
,
...
...
src/locales/uz/uz.json
View file @
9ff5f1a8
...
...
@@ -126,7 +126,9 @@
"deleteImages"
:
"Rasm fayllarni o'chirish"
,
"deleteImagesDesc"
:
"Siz haqiqatan ham barcha rasm fayllarni o'chirmoqchimisiz?"
,
"deleteMedia"
:
"Media fayllarni o'chirish"
,
"deleteMediaDesc"
:
"Siz haqiqatan ham barcha media fayllarni o'chirmoqchimisiz?"
"deleteMediaDesc"
:
"Siz haqiqatan ham barcha media fayllarni o'chirmoqchimisiz?"
,
"unsubscribe"
:
"Obunani bekor qilish"
,
"unsubscribeDesc"
:
"Obunani bekor qilishni xohlaysizmi?"
},
"permissions"
:
{
"title"
:
"Ruxsatlar"
,
...
...
src/pages/Settings/Settings.tsx
View file @
9ff5f1a8
...
...
@@ -7,7 +7,11 @@ import ru from "../../assets/images/flag-ru.png";
import
uz
from
"../../assets/images/flag-uz.png"
;
import
{
FiUserMinus
}
from
"react-icons/fi"
;
import
{
IoKeyOutline
,
IoPhonePortraitOutline
}
from
"react-icons/io5"
;
import
{
MdOutlineEdit
,
MdOutlineLogout
}
from
"react-icons/md"
;
import
{
MdOutlineCreditCardOff
,
MdOutlineEdit
,
MdOutlineLogout
,
}
from
"react-icons/md"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useAppDispatch
,
...
...
@@ -36,6 +40,10 @@ import {
editDevice
,
refreshDevices
,
}
from
"../../stores/slices/deviceSlice"
;
import
{
checkBalance
,
toggleSubscriptionAuto
,
}
from
"../../stores/slices/billingSlice"
;
const
deleteDatas
=
[
{
...
...
@@ -76,6 +84,7 @@ const Settings: React.FC = () => {
const
[
isOpenEditDevice
,
setIsOpenEditDevice
]
=
useState
(
false
);
const
[
isOpenDeleteDevice
,
setIsOpenDeleteDevice
]
=
useState
(
false
);
const
[
isOpenDeleteDataModal
,
setIsOpenDeleteDataModal
]
=
useState
(
false
);
const
[
isOpenUnsubscribeModal
,
setIsOpenUnsubscribeModal
]
=
useState
(
false
);
const
{
language
,
changeLanguage
}
=
useLanguage
();
const
{
t
}
=
useTranslation
();
const
{
isCheckPasswordLoading
,
errorCheckPassword
}
=
useAppSelector
(
...
...
@@ -87,6 +96,9 @@ const Settings: React.FC = () => {
const
{
devices
,
isDeletingDevice
,
isEditingDevice
}
=
useAppSelector
(
(
state
:
RootState
)
=>
state
.
device
);
const
{
balance
,
isToggleSubAutoLoading
}
=
useAppSelector
(
(
state
:
RootState
)
=>
state
.
billing
);
const
dispatch
=
useAppDispatch
();
const
toggleLogoutModal
=
()
=>
{
...
...
@@ -117,6 +129,10 @@ const Settings: React.FC = () => {
setIsOpenDeleteDevice
((
prev
)
=>
!
prev
);
};
const
toggleIsOpenUnsubscribeModal
=
()
=>
{
setIsOpenUnsubscribeModal
((
prev
)
=>
!
prev
);
};
const
handleEditDevice
=
async
()
=>
{
try
{
if
(
selectedDevice
)
{
...
...
@@ -175,6 +191,10 @@ const Settings: React.FC = () => {
}
},
[
session
]);
useEffect
(()
=>
{
dispatch
(
checkBalance
());
},
[
dispatch
]);
return
(
<
div
className=
"settings"
>
<
h3
className=
"settings__title"
>
{
t
(
"settings.title"
)
}
</
h3
>
...
...
@@ -327,6 +347,16 @@ const Settings: React.FC = () => {
{
t
(
"settings.deleteAccount"
)
}
</
div
>
{
balance
.
subAuto
&&
(
<
div
className=
"settings__content__action"
onClick=
{
toggleIsOpenUnsubscribeModal
}
>
<
MdOutlineCreditCardOff
className=
"settings__content__action__icon"
/>
{
t
(
"settings.unsubscribe"
)
}
</
div
>
)
}
<
Link
to=
{
"/settings/recover-password"
}
className=
"settings__content__action"
...
...
@@ -482,6 +512,34 @@ const Settings: React.FC = () => {
</
div
>
}
/>
<
CModal
isOpen=
{
isOpenUnsubscribeModal
}
onToggle=
{
toggleIsOpenUnsubscribeModal
}
content=
{
<
div
className=
"modal__box"
>
<
h3
className=
"modal__box__title"
>
{
t
(
"settings.unsubscribe"
)
}
</
h3
>
<
p
style=
{
{
textAlign
:
"center"
}
}
>
{
t
(
"settings.unsubscribeDesc"
)
}
</
p
>
<
div
className=
"modal__box__actions"
>
<
CButton
title=
{
t
(
"button.cancel"
)
}
onClick=
{
toggleIsOpenUnsubscribeModal
}
/>
<
CButton
title=
{
t
(
"button.ok"
)
}
variant=
"danger"
isLoading=
{
isToggleSubAutoLoading
}
onClick=
{
async
()
=>
{
await
dispatch
(
toggleSubscriptionAuto
(
false
));
toggleIsOpenUnsubscribeModal
();
}
}
/>
</
div
>
</
div
>
}
/>
</
div
>
);
};
...
...
src/stores/slices/billingSlice.ts
View file @
9ff5f1a8
import
{
createAsyncThunk
,
createSlice
}
from
"@reduxjs/toolkit"
;
import
{
sendRpcRequest
}
from
"../../services/apiClient"
;
import
type
{
IPaymentMethod
,
ITariff
}
from
"../../types/billing.types"
;
import
type
{
IBalance
,
IPaymentMethod
,
ITariff
,
}
from
"../../types/billing.types"
;
interface
IBillingState
{
tariffs
:
ITariff
[];
...
...
@@ -11,6 +15,8 @@ interface IBillingState {
paymentMethodsError
:
string
|
null
;
payUrl
:
string
;
isPayLoading
:
boolean
;
balance
:
IBalance
;
isToggleSubAutoLoading
:
boolean
;
}
const
initialState
:
IBillingState
=
{
...
...
@@ -22,6 +28,8 @@ const initialState: IBillingState = {
paymentMethodsError
:
null
,
payUrl
:
""
,
isPayLoading
:
false
,
balance
:
{
balance
:
0
,
subAuto
:
false
},
isToggleSubAutoLoading
:
false
,
};
export
const
fetchTariffs
=
createAsyncThunk
(
...
...
@@ -87,6 +95,38 @@ export const pay = createAsyncThunk(
}
);
export
const
checkBalance
=
createAsyncThunk
(
"billing/checkBalance"
,
async
(
_
,
{
rejectWithValue
})
=>
{
try
{
const
response
=
await
sendRpcRequest
<
IBalance
>
(
"billing.balance"
);
return
response
;
}
catch
(
error
:
unknown
)
{
if
(
typeof
error
===
"object"
&&
error
!==
null
&&
"message"
in
error
)
{
return
rejectWithValue
(
error
.
message
);
}
return
rejectWithValue
(
"Unknown error occurred"
);
}
}
);
export
const
toggleSubscriptionAuto
=
createAsyncThunk
(
"billing/toggleSubscriptionAuto"
,
async
(
value
:
boolean
,
{
rejectWithValue
})
=>
{
try
{
await
sendRpcRequest
(
"billing.setsubsauto"
,
{
value
});
return
value
;
}
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
,
...
...
@@ -130,6 +170,21 @@ const billingSlice = createSlice({
.
addCase
(
pay
.
rejected
,
(
state
,
action
)
=>
{
state
.
isPayLoading
=
false
;
state
.
payUrl
=
action
.
payload
as
string
;
})
.
addCase
(
checkBalance
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
balance
=
action
.
payload
;
})
.
addCase
(
toggleSubscriptionAuto
.
pending
,
(
state
)
=>
{
state
.
isToggleSubAutoLoading
=
true
;
})
.
addCase
(
toggleSubscriptionAuto
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
isToggleSubAutoLoading
=
false
;
state
.
balance
.
subAuto
=
action
.
payload
;
})
.
addCase
(
toggleSubscriptionAuto
.
rejected
,
(
state
)
=>
{
state
.
isToggleSubAutoLoading
=
false
;
});
},
});
...
...
src/types/billing.types.ts
View file @
9ff5f1a8
...
...
@@ -12,3 +12,8 @@ export interface IPaymentMethod {
icon
:
string
;
enabled
:
boolean
;
}
export
interface
IBalance
{
balance
:
number
;
subAuto
:
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