Commit d33f7701 by asranov0003

feat: post time limits

parent b3440087
......@@ -16,7 +16,11 @@
"selectDevice": "Select Device",
"noData": "No data found",
"downloadForAndroid": "Download for Android",
"downloadForIos": "Download for iOS"
"downloadForIos": "Download for iOS",
"pageInDevelopment": "Page in Development",
"pageInDevelopmentDesc": "This page is currently under development. Use the application to view recordings.",
"hours": "Hours",
"minutes": "Minutes"
},
"auth": {
"entrance": "Sign In",
......@@ -29,6 +33,7 @@
"forgetPassword": "Forgot password?",
"recover": "Recover",
"passwordRecovery": "Password recovery",
"passwordChange": "Password change",
"newPassword": "New password",
"newPasswordPlaceholder": "Enter new password",
"repeatPassword": "Repeat password",
......@@ -107,6 +112,8 @@
"account": "Account",
"deleteAccount": "Delete account",
"recoverPassword": "Recover password",
"changePassword": "Change password",
"subscribeNow": "Subscribe now",
"logout": "Logout"
},
"permissions": {
......@@ -144,12 +151,23 @@
},
"messengers": {
"title": "Chats",
"empty": "No chats found."
"messages": "Messages",
"allChats": "All chats",
"empty": "No chats found.",
"from": "From",
"to": "To"
},
"usageLimits": {
"title": "Phone usage time limits",
"desc": "Set a daily phone usage limit.",
"allowedApps": "Allowed apps will be still accessible."
"allowedApps": "Allowed apps will be still accessible.",
"setLimit": "Set a limit",
"hoursRequired": "Hours are required",
"minHour": "Minimum hours is 0",
"maxHour": "Maximum hours is 23",
"minutesRequired": "Minutes are required",
"minMinutes": "Minimum minutes is 0",
"maxMinutes": "Maximum minutes is 59"
},
"pincode": {
"enterTitle": "Enter your PIN Code",
......@@ -180,7 +198,16 @@
"selectTariff": "Select a tariff",
"selectPaymentMethod": "Select a payment method",
"cancellPaymentTitle": "Payment can be cancelled within 24 hours",
"pay": "Pay for a subscription"
"pay": "Pay for a subscription",
"notActive": "Subscription is not active",
"notActiveDesc": "The functionality of the cabinet has been limited. To restore functionality, you need to subscribe.",
"toTariffs": "To tariffs"
},
"download": {
"android": "Download for Android",
"ios": "Download for iOS",
"rustore": "Download from RuStore",
"webApp": "TgApp - for parents' convenience"
},
"button": {
"login": "Login",
......
......@@ -16,7 +16,11 @@
"selectDevice": "Выбрать устройство",
"noData": "Данные не найдены",
"downloadForAndroid": "Скачать для Android",
"downloadForIos": "Скачать для iOS"
"downloadForIos": "Скачать для iOS",
"pageInDevelopment": "Страница в разработке",
"pageInDevelopmentDesc": "Эта страница в настоящее время разрабатывается. Используйте приложение для просмотра записей.",
"hours": "Часы",
"minutes": "Минуты"
},
"auth": {
"entrance": "Вход",
......@@ -29,6 +33,7 @@
"forgetPassword": "Забыли пароль?",
"recover": "Восстановить",
"passwordRecovery": "Восстановление пароля",
"passwordChange": "Смена пароля",
"newPassword": "Новый пароль",
"newPasswordPlaceholder": "Введите новый пароль",
"repeatPassword": "Повторите пароль",
......@@ -107,6 +112,8 @@
"account": "Аккаунт",
"deleteAccount": "Удалить аккаунт",
"recoverPassword": "Восстановить пароль",
"changePassword": "Сменить пароль",
"subscribeNow": "Оформить подписку",
"logout": "Выйти"
},
"permissions": {
......@@ -144,12 +151,23 @@
},
"messengers": {
"title": "Чаты",
"empty": "Чаты не найдены."
"messages": "Сообщения",
"allChats": "Все чаты",
"empty": "Чаты не найдены.",
"from": "От",
"to": "Кому"
},
"usageLimits": {
"title": "Ограничения времени использования телефона",
"desc": "Установите дневное ограничение на использование телефона.",
"allowedApps": "Разрешенные приложения будут по-прежнему доступны."
"allowedApps": "Разрешенные приложения будут по-прежнему доступны.",
"setLimit": "Установить лимит",
"hoursRequired": "Часы обязательны",
"minHour": "Минимум часов - 0",
"maxHour": "Максимум часов - 23",
"minutesRequired": "Минуты обязательны",
"minMinutes": "Минимум минут - 0",
"maxMinutes": "Максимум минут - 59"
},
"pincode": {
"enterTitle": "Введите PIN-код",
......@@ -180,7 +198,16 @@
"selectTariff": "Выберите тариф",
"selectPaymentMethod": "Выберите способ оплаты",
"cancellPaymentTitle": "Платеж можно отменить в течении 24 часов",
"pay": "Оплатить подписку"
"pay": "Оплатить подписку",
"notActive": "Подписка не активна",
"notActiveDesc": "Функционал кабинета был ограничен. Для восстановления функционала необходимо оформить подписку.",
"toTariffs": "К тарифам"
},
"download": {
"android": "Скачать для Android",
"ios": "Скачать для iOS",
"rustore": "Скачать из RuStore",
"webApp": "TgApp - для удобства родителей"
},
"button": {
"login": "Войти",
......
......@@ -16,7 +16,11 @@
"selectDevice": "Qurilmani tanlang",
"noData": "Hech qanday ma'lumot topilmadi",
"downloadForAndroid": "Android uchun yuklab olish",
"downloadForIos": "iOS uchun yuklab olish"
"downloadForIos": "iOS uchun yuklab olish",
"pageInDevelopment": "Sahifa ishlab chiqilmoqda",
"pageInDevelopmentDesc": "Ushbu sahifa hozirda ishlab chiqilmoqda. Yozuvlarni ko'rish uchun ilovadan foydalaning.",
"hours": "Soatlar",
"minutes": "Daqiqalar"
},
"auth": {
"entrance": "Kirish",
......@@ -29,6 +33,7 @@
"forgetPassword": "Parolni unutdingizmi?",
"recover": "Tiklash",
"passwordRecovery": "Parolni tiklash",
"passwordChange": "Parolni o'zgartirish",
"newPassword": "Yangi parol",
"newPasswordPlaceholder": "Yangi parolni kiriting",
"repeatPassword": "Parolni takrorlang",
......@@ -107,6 +112,8 @@
"account": "Hisob",
"deleteAccount": "Hisobni o'chirish",
"recoverPassword": "Parolni tiklash",
"changePassword": "Parolni o'zgartirish",
"subscribeNow": "Obuna bo'lish",
"logout": "Chiqish"
},
"permissions": {
......@@ -144,12 +151,23 @@
},
"messengers": {
"title": "Chats",
"empty": "Xabarlar topilmadi."
"messages": "Xabarlar",
"allChats": "Barcha chatlar",
"empty": "Chatlar topilmadi.",
"from": "Kimdan",
"to": "Kimga"
},
"usageLimits": {
"title": "Telefon foydalanish vaqti chegaralari",
"desc": "Kunlik telefon foydalanish cheklovini o'rnating.",
"allowedApps": "Ruxsat berilgan ilovalar hali ham foydalanishga ochiq bo'ladi."
"allowedApps": "Ruxsat berilgan ilovalar hali ham foydalanishga ochiq bo'ladi.",
"setLimit": "Cheklov o'rnating",
"hoursRequired": "Soatlar talab qilinadi",
"minHour": "Minimal soatlar - 0",
"maxHour": "Maksimal soatlar - 23",
"minutesRequired": "Daqiqalar talab qilinadi",
"minMinutes": "Minimal daqiqalar - 0",
"maxMinutes": "Maksimal daqiqalar - 59"
},
"pincode": {
"enterTitle": "PIN kodni kiriting",
......@@ -180,7 +198,16 @@
"selectTariff": "Tarifni tanlang",
"selectPaymentMethod": "To'lov usulini tanlang",
"cancellPaymentTitle": "To'lovni 24 soat ichida bekor qilish mumkin",
"pay": "Obunani to'lash"
"pay": "Obunani to'lash",
"notActive": "Obuna faol emas",
"notActiveDesc": "Shaxsiy kabinet funksiyalari cheklangan. Funktsiyalarni tiklash uchun obuna bo'lishingiz kerak.",
"toTariffs": "Tariflarga"
},
"download": {
"android": "Android uchun yuklab olish",
"ios": "iOS uchun yuklab olish",
"rustore": "RuStore dan yuklab olish",
"webApp": "TgApp - ota-onalar uchun qulaylik"
},
"button": {
"login": "Kirish",
......
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import "./UsageLimits.css";
import SectionHeader from "../../layouts/SectionHeader";
import CButton from "../../components/CButton";
import { useTranslation } from "react-i18next";
import { useAppDispatch, type RootState } from "../../stores/store";
import { useSelector } from "react-redux";
import { fetchTimeLimits } from "../../stores/slices/usageLimitSlice";
import {
fetchTimeLimits,
postTimeLimits,
} from "../../stores/slices/usageLimitSlice";
import CLoading from "../../components/CLoading";
import { useForm } from "react-hook-form";
import CInput from "../../components/CInput";
import CModal from "../../components/CModal";
type FormValues = {
hours: string;
minutes: string;
};
const UsageLimits: React.FC = () => {
const [isOpenModal, setIsOpenModal] = useState(false);
const [selectedDayIndex, setSelectedDayIndex] = useState<number | null>(null);
const { selectedDevice } = useSelector((state: RootState) => state.device);
const { days, isLoadingDays } = useSelector(
const { days, isLoadingDays, loadingPostLimits } = useSelector(
(state: RootState) => state.usageLimit
);
const dispatch = useAppDispatch();
const { t } = useTranslation();
const {
register,
setValue,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
defaultValues: {
hours: "",
minutes: "",
},
});
const onToggleModal = () => {
setIsOpenModal((prev) => !prev);
};
const openDayModal = (day: number, index: number) => {
setSelectedDayIndex(index);
const hrs = Math.floor(day / 60);
const mins = day % 60;
setValue("hours", String(hrs));
setValue("minutes", String(mins));
setIsOpenModal(true);
};
const onSubmit = async (data: FormValues) => {
if (selectedDayIndex === null || !selectedDevice?.id) return;
const hrs = parseInt(data.hours) || 0;
const mins = parseInt(data.minutes) || 0;
const totalMinutes = hrs * 60 + mins;
const updatedDays = [...days];
updatedDays[selectedDayIndex] = totalMinutes;
try {
await dispatch(
postTimeLimits({
deviceId: selectedDevice.id,
days: updatedDays,
})
).unwrap();
setIsOpenModal(false);
} catch (error) {
console.error("Error updating time limits: ", error);
}
};
useEffect(() => {
if (!selectedDevice?.id) return;
dispatch(fetchTimeLimits(selectedDevice?.id));
}, [dispatch, selectedDevice]);
......@@ -58,7 +123,11 @@ const UsageLimits: React.FC = () => {
<div className="usagelimits__days">
{days.map((day, index) => {
return (
<div className="usagelimits__day" key={index}>
<div
className="usagelimits__day"
key={index}
onClick={() => openDayModal(day, index)}
>
<p>{weekDays[index]}</p>
<p>{formatTime(day)}</p>
</div>
......@@ -67,9 +136,52 @@ const UsageLimits: React.FC = () => {
</div>
)}
</div>
<CButton title={t("button.save")} />
</div>
<CModal
isOpen={isOpenModal}
onToggle={onToggleModal}
content={
<form className="modal__box" onSubmit={handleSubmit(onSubmit)}>
<h3 className="modal__box__title">Set a limit</h3>
<div className="modal__box__actions">
<CInput
label="Hours"
placeholder="0"
{...register("hours", {
required: "Hours are required",
min: { value: 0, message: "Minimum hours is 0" },
max: { value: 23, message: "Maximum hours is 23" },
})}
error={errors.hours?.message}
/>
<CInput
label="Minutes"
placeholder="0"
{...register("minutes", {
required: "Minutes are required",
min: { value: 0, message: "Minimum minutes is 0" },
max: { value: 59, message: "Maximum minutes is 59" },
})}
error={errors.minutes?.message}
/>
</div>
<div className="modal__box__actions">
<CButton title={t("button.cancel")} onClick={onToggleModal} />
<CButton
title={t("button.confirm")}
type="submit"
variant="primary"
isLoading={loadingPostLimits}
/>
</div>
</form>
}
/>
</div>
);
};
......
......@@ -4,12 +4,14 @@ import { sendRpcRequest } from "../../services/apiClient";
interface IUsageLimitState {
days: number[];
isLoadingDays: boolean;
loadingPostLimits: boolean;
errorDays: string | null;
}
const initialState: IUsageLimitState = {
days: [],
isLoadingDays: false,
loadingPostLimits: false,
errorDays: null,
};
......@@ -35,6 +37,31 @@ export const fetchTimeLimits = createAsyncThunk(
}
);
export const postTimeLimits = createAsyncThunk(
"timeLimits/postTimeLimits",
async (
{ deviceId, days }: { deviceId: string; days: number[] },
{ rejectWithValue }
) => {
try {
const response = await sendRpcRequest<{ days: number[] }>(
"apps.timelimitset",
{
deviceId,
days,
}
);
return response.days;
} catch (error: unknown) {
if (typeof error === "object" && error !== null && "message" in error) {
return rejectWithValue(error.message);
}
return rejectWithValue("An unknown error occurred");
}
}
);
const usageLimitSlice = createSlice({
name: "usageLimit",
initialState,
......@@ -52,6 +79,18 @@ const usageLimitSlice = createSlice({
.addCase(fetchTimeLimits.rejected, (state, action) => {
state.isLoadingDays = false;
state.errorDays = action.payload as string;
})
.addCase(postTimeLimits.pending, (state) => {
state.loadingPostLimits = true;
})
.addCase(postTimeLimits.fulfilled, (state, action) => {
state.loadingPostLimits = false;
state.days = action.payload;
})
.addCase(postTimeLimits.rejected, (state, action) => {
state.loadingPostLimits = false;
state.errorDays = action.payload as string;
});
},
});
......
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