Commit 9ff5f1a8 by asranov0003

feat: add unsubscribe btn

parent b4eebb44
...@@ -126,7 +126,9 @@ ...@@ -126,7 +126,9 @@
"deleteImages": "Delete images", "deleteImages": "Delete images",
"deleteImagesDesc": "Are you sure you want to delete all images?", "deleteImagesDesc": "Are you sure you want to delete all images?",
"deleteMedia": "Delete media", "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": { "permissions": {
"title": "Permissions", "title": "Permissions",
......
...@@ -126,7 +126,9 @@ ...@@ -126,7 +126,9 @@
"deleteImages": "Удалить изображения", "deleteImages": "Удалить изображения",
"deleteImagesDesc": "Вы действительно хотите удалить все изображения?", "deleteImagesDesc": "Вы действительно хотите удалить все изображения?",
"deleteMedia": "Удалить медиа", "deleteMedia": "Удалить медиа",
"deleteMediaDesc": "Вы действительно хотите удалить все медиа-файлы?" "deleteMediaDesc": "Вы действительно хотите удалить все медиа-файлы?",
"unsubscribe": "Отписаться",
"unsubscribeDesc": "Вы уверены, что хотите отписаться?"
}, },
"permissions": { "permissions": {
"title": "Разрешения", "title": "Разрешения",
......
...@@ -126,7 +126,9 @@ ...@@ -126,7 +126,9 @@
"deleteImages": "Rasm fayllarni o'chirish", "deleteImages": "Rasm fayllarni o'chirish",
"deleteImagesDesc": "Siz haqiqatan ham barcha rasm fayllarni o'chirmoqchimisiz?", "deleteImagesDesc": "Siz haqiqatan ham barcha rasm fayllarni o'chirmoqchimisiz?",
"deleteMedia": "Media fayllarni o'chirish", "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": { "permissions": {
"title": "Ruxsatlar", "title": "Ruxsatlar",
......
...@@ -7,7 +7,11 @@ import ru from "../../assets/images/flag-ru.png"; ...@@ -7,7 +7,11 @@ import ru from "../../assets/images/flag-ru.png";
import uz from "../../assets/images/flag-uz.png"; import uz from "../../assets/images/flag-uz.png";
import { FiUserMinus } from "react-icons/fi"; import { FiUserMinus } from "react-icons/fi";
import { IoKeyOutline, IoPhonePortraitOutline } from "react-icons/io5"; 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 { useTranslation } from "react-i18next";
import { import {
useAppDispatch, useAppDispatch,
...@@ -36,6 +40,10 @@ import { ...@@ -36,6 +40,10 @@ import {
editDevice, editDevice,
refreshDevices, refreshDevices,
} from "../../stores/slices/deviceSlice"; } from "../../stores/slices/deviceSlice";
import {
checkBalance,
toggleSubscriptionAuto,
} from "../../stores/slices/billingSlice";
const deleteDatas = [ const deleteDatas = [
{ {
...@@ -76,6 +84,7 @@ const Settings: React.FC = () => { ...@@ -76,6 +84,7 @@ const Settings: React.FC = () => {
const [isOpenEditDevice, setIsOpenEditDevice] = useState(false); const [isOpenEditDevice, setIsOpenEditDevice] = useState(false);
const [isOpenDeleteDevice, setIsOpenDeleteDevice] = useState(false); const [isOpenDeleteDevice, setIsOpenDeleteDevice] = useState(false);
const [isOpenDeleteDataModal, setIsOpenDeleteDataModal] = useState(false); const [isOpenDeleteDataModal, setIsOpenDeleteDataModal] = useState(false);
const [isOpenUnsubscribeModal, setIsOpenUnsubscribeModal] = useState(false);
const { language, changeLanguage } = useLanguage(); const { language, changeLanguage } = useLanguage();
const { t } = useTranslation(); const { t } = useTranslation();
const { isCheckPasswordLoading, errorCheckPassword } = useAppSelector( const { isCheckPasswordLoading, errorCheckPassword } = useAppSelector(
...@@ -87,6 +96,9 @@ const Settings: React.FC = () => { ...@@ -87,6 +96,9 @@ const Settings: React.FC = () => {
const { devices, isDeletingDevice, isEditingDevice } = useAppSelector( const { devices, isDeletingDevice, isEditingDevice } = useAppSelector(
(state: RootState) => state.device (state: RootState) => state.device
); );
const { balance, isToggleSubAutoLoading } = useAppSelector(
(state: RootState) => state.billing
);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const toggleLogoutModal = () => { const toggleLogoutModal = () => {
...@@ -117,6 +129,10 @@ const Settings: React.FC = () => { ...@@ -117,6 +129,10 @@ const Settings: React.FC = () => {
setIsOpenDeleteDevice((prev) => !prev); setIsOpenDeleteDevice((prev) => !prev);
}; };
const toggleIsOpenUnsubscribeModal = () => {
setIsOpenUnsubscribeModal((prev) => !prev);
};
const handleEditDevice = async () => { const handleEditDevice = async () => {
try { try {
if (selectedDevice) { if (selectedDevice) {
...@@ -175,6 +191,10 @@ const Settings: React.FC = () => { ...@@ -175,6 +191,10 @@ const Settings: React.FC = () => {
} }
}, [session]); }, [session]);
useEffect(() => {
dispatch(checkBalance());
}, [dispatch]);
return ( return (
<div className="settings"> <div className="settings">
<h3 className="settings__title">{t("settings.title")}</h3> <h3 className="settings__title">{t("settings.title")}</h3>
...@@ -327,6 +347,16 @@ const Settings: React.FC = () => { ...@@ -327,6 +347,16 @@ const Settings: React.FC = () => {
{t("settings.deleteAccount")} {t("settings.deleteAccount")}
</div> </div>
{balance.subAuto && (
<div
className="settings__content__action"
onClick={toggleIsOpenUnsubscribeModal}
>
<MdOutlineCreditCardOff className="settings__content__action__icon" />
{t("settings.unsubscribe")}
</div>
)}
<Link <Link
to={"/settings/recover-password"} to={"/settings/recover-password"}
className="settings__content__action" className="settings__content__action"
...@@ -482,6 +512,34 @@ const Settings: React.FC = () => { ...@@ -482,6 +512,34 @@ const Settings: React.FC = () => {
</div> </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> </div>
); );
}; };
......
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { sendRpcRequest } from "../../services/apiClient"; import { sendRpcRequest } from "../../services/apiClient";
import type { IPaymentMethod, ITariff } from "../../types/billing.types"; import type {
IBalance,
IPaymentMethod,
ITariff,
} from "../../types/billing.types";
interface IBillingState { interface IBillingState {
tariffs: ITariff[]; tariffs: ITariff[];
...@@ -11,6 +15,8 @@ interface IBillingState { ...@@ -11,6 +15,8 @@ interface IBillingState {
paymentMethodsError: string | null; paymentMethodsError: string | null;
payUrl: string; payUrl: string;
isPayLoading: boolean; isPayLoading: boolean;
balance: IBalance;
isToggleSubAutoLoading: boolean;
} }
const initialState: IBillingState = { const initialState: IBillingState = {
...@@ -22,6 +28,8 @@ const initialState: IBillingState = { ...@@ -22,6 +28,8 @@ const initialState: IBillingState = {
paymentMethodsError: null, paymentMethodsError: null,
payUrl: "", payUrl: "",
isPayLoading: false, isPayLoading: false,
balance: { balance: 0, subAuto: false },
isToggleSubAutoLoading: false,
}; };
export const fetchTariffs = createAsyncThunk( export const fetchTariffs = createAsyncThunk(
...@@ -87,6 +95,38 @@ export const pay = 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({ const billingSlice = createSlice({
name: "billing", name: "billing",
initialState, initialState,
...@@ -130,6 +170,21 @@ const billingSlice = createSlice({ ...@@ -130,6 +170,21 @@ const billingSlice = createSlice({
.addCase(pay.rejected, (state, action) => { .addCase(pay.rejected, (state, action) => {
state.isPayLoading = false; state.isPayLoading = false;
state.payUrl = action.payload as string; 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;
}); });
}, },
}); });
......
...@@ -12,3 +12,8 @@ export interface IPaymentMethod { ...@@ -12,3 +12,8 @@ export interface IPaymentMethod {
icon: string; icon: string;
enabled: boolean; enabled: boolean;
} }
export interface IBalance {
balance: number;
subAuto: boolean;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment