From 4f601aa1c43e5cf5d2adb18d5a3d6178923403f6 Mon Sep 17 00:00:00 2001
From: sina_sajjadi
Date: Wed, 1 Jan 2025 15:57:19 +0330
Subject: [PATCH] feat: implement RTL support, adjust layout for RTL languages,
and remove unused Last Reads page
---
src/components/modals/languages-modal.tsx | 111 ++++++++++++++++--
src/components/sidebar/list.tsx | 65 ++++++----
.../sticky-components/audio-controls.tsx | 11 +-
src/components/ui/range-input.tsx | 39 ++++--
src/pages/_app.tsx | 35 +++---
src/pages/duas/[slug].tsx | 2 +-
src/pages/last-reads.tsx | 69 -----------
7 files changed, 204 insertions(+), 128 deletions(-)
delete mode 100644 src/pages/last-reads.tsx
diff --git a/src/components/modals/languages-modal.tsx b/src/components/modals/languages-modal.tsx
index fa5f35f..6359b65 100644
--- a/src/components/modals/languages-modal.tsx
+++ b/src/components/modals/languages-modal.tsx
@@ -3,12 +3,98 @@ import { IoMdClose } from "react-icons/io";
import { useUI } from "../context/ui.context";
import { FaCheckCircle } from "react-icons/fa";
import { useTranslation } from "next-i18next"; // Import useTranslation
+import { useRouter } from "next/router";
interface Language {
code: string; // Assuming each language has a unique ID
name: string;
}
+
+const data = [
+ {
+ "name": "English",
+ "code": "en"
+ },
+ {
+ "name": "Spanish",
+ "code": "es"
+ },
+ {
+ "name": "German",
+ "code": "de"
+ },
+ {
+ "name": "Uzbek",
+ "code": "uz"
+ },
+ {
+ "name": "Portuguese",
+ "code": "pt"
+ },
+ {
+ "name": "Bengali",
+ "code": "bn"
+ },
+ {
+ "name": "Chinese",
+ "code": "zh"
+ },
+ {
+ "name": "Azerbaijani",
+ "code": "az"
+ },
+ {
+ "name": "Urdu",
+ "code": "ur"
+ },
+ {
+ "name": "French",
+ "code": "fr"
+ },
+ {
+ "name": "Turkish",
+ "code": "tr"
+ },
+ {
+ "name": "Indonesian",
+ "code": "id"
+ },
+ {
+ "name": "Swahili",
+ "code": "sw"
+ },
+ {
+ "name": "Russian",
+ "code": "ru"
+ },
+ {
+ "name": "Arabic",
+ "code": "ar"
+ },
+ {
+ "name": "Tajik",
+ "code": "tg"
+ },
+ {
+ "name": "Persian",
+ "code": "fa"
+ },
+ {
+ "name": "Gujarati",
+ "code": "gu"
+ },
+ {
+ "name": "Kashmiri",
+ "code": "ks"
+ },
+ {
+ "name": "Hausa",
+ "code": "ha"
+ }
+]
+
+
const LanguageModal = () => {
const [languages, setLanguages] = useState([]);
const [selectedLanguage, setSelectedLanguage] = useState("");
@@ -16,18 +102,19 @@ const LanguageModal = () => {
const [error, setError] = useState(null);
const { closeModal } = useUI();
const { t } = useTranslation("common"); // Initialize useTranslation hook for common.json
+ const router = useRouter();
useEffect(() => {
const fetchLanguages = async () => {
- try {
- setIsLoading(true);
- const response = await http.get("v1/languages/");
- setLanguages(response.data);
- } catch (err) {
- setError(t("failed_to_load_languages")); // Use translation for error message
- } finally {
+ // try {
+ // setIsLoading(true);
+ // const response = await http.get("v1/languages/");
+ setLanguages(data);
+ // } catch (err) {
+ // setError(t("failed_to_load_languages")); // Use translation for error message
+ // } finally {
setIsLoading(false);
- }
+ // }
};
fetchLanguages();
@@ -39,8 +126,12 @@ const LanguageModal = () => {
}, [t]); // Add t as dependency to ensure it updates with language change
const handleLanguageSelect = (code: string) => {
- setSelectedLanguage(code);
- localStorage.setItem("locale", code); // Persist selection in localStorage
+ router.push(router.asPath, undefined, { locale: code }).then(() => {
+ // Optionally you can trigger the language change effect after the page is pushed
+ setSelectedLanguage(code);
+ localStorage.setItem("locale", code); // Save the selected language in localStorage
+ window.location.reload();
+ });
};
if (isLoading) {
diff --git a/src/components/sidebar/list.tsx b/src/components/sidebar/list.tsx
index 2179e6a..e1a6c5b 100644
--- a/src/components/sidebar/list.tsx
+++ b/src/components/sidebar/list.tsx
@@ -13,13 +13,14 @@ import { useRouter } from "next/navigation";
import colorizeVowels from "../utils/colorize-vowels";
import { formatHijriDate } from "../utils/date-formaters";
import { Category, Dua, ListProps } from "../utils/types";
-import { useTranslation } from "next-i18next"; // Importing the translation hook
+import { useTranslation } from "next-i18next"; // Importing the translation hook
const List: React.FC = ({ tab, path, setPath, data, setData }) => {
const { t } = useTranslation("common"); // Initialize translation hook for "common" namespace
const [loading, setLoading] = useState(false);
const [currentDhikr, setCurrentDhikr] = useState("");
const [currentTranslation, setCurrentTranslation] = useState("");
+ const [error, setError] = useState(null); // State to hold error messages
const router = useRouter();
const today = new Date();
const dayOfWeek = new Intl.DateTimeFormat("en-US", {
@@ -32,6 +33,7 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => {
locale = localStorage.getItem("locale") || "en";
if (tab === "Today") {
setLoading(true);
+ setError(null); // Reset error state before starting the request
http
.get("web/mafatih-duas/dhikrs/?today=true")
.then((res) => {
@@ -47,11 +49,12 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => {
setLoading(false);
})
.catch((error) => {
+ setLoading(false); // Ensure loading state is false if request fails
+ setError(t("error.fetchDhikrData")); // Display a translated error message
console.error("Error fetching Dhikr data:", error);
- setLoading(false);
});
}
- }, [tab, dayOfWeek, locale]);
+ }, [tab, dayOfWeek, locale, t]);
const openCategory = (category: Category) => {
setData({ type: "children", data: category.children });
@@ -71,39 +74,49 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => {
useEffect(() => {
setData({ type: null, data: [] });
+ setError(null); // Reset error state before starting the request
if (tab === "Categories") {
setLoading(true);
- http.get("web/mafatih-categories/").then((res) => {
- setData({ type: "Categories", data: res.data });
- setPath([{ name: "", type: "Categories", data: res.data }]);
- setLoading(false);
- });
+ http
+ .get("web/mafatih-categories/")
+ .then((res) => {
+ setData({ type: "Categories", data: res.data });
+ setPath([{ name: "", type: "Categories", data: res.data }]);
+ setLoading(false);
+ })
+ .catch((error) => {
+ setLoading(false);
+ setError(t("error.fetchCategories")); // Display a translated error message
+ console.error("Error fetching categories:", error);
+ });
}
+
if (tab === "Today") {
- http.get("web/mafatih-duas/?today=true").then((res) => {
- setData({ type: null, data: res.data.results });
- });
+ http
+ .get("web/mafatih-duas/?today=true")
+ .then((res) => {
+ setData({ type: null, data: res.data.results });
+ })
+ .catch((error) => {
+ setError(t("error.fetchTodayDuas"));
+ console.error("Error fetching today's duas:", error);
+ });
}
- }, [setData, setPath, tab]);
+ }, [setData, setPath, tab, t]);
useEffect(() => {
if (data.data.length) {
const filteredData = data.data.filter((item: any) => !item.title); // Exclude objects with a "title"
filteredData.forEach((category: Category) => {
- // Filter out children that do not have a title
const hasDua = category.children.filter((item: any) => !item.title);
- // Make an HTTP GET request to fetch duas for the current category
http
.get(`web/mafatih-duas/?category=${category.id}`)
.then((res) => {
- console.log(res);
-
let duas = res.data.results;
if (duas.length) {
- // Deduplicate duas based on their 'id'
const uniqueDuasMap = new Map();
duas.forEach((dua: any) => {
if (!uniqueDuasMap.has(dua.id)) {
@@ -111,20 +124,17 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => {
}
});
- // Convert the Map back to an array of unique duas
duas = Array.from(uniqueDuasMap.values());
-
- // Assign the combined array to category.children
category.children = [...hasDua, ...duas];
- // Update the state with the modified filteredData
setData((prevState: Category) => ({
...prevState,
- data: filteredData, // Update state with the updated filtered data
+ data: filteredData,
}));
}
})
.catch((error) => {
+ setError(t("error.fetchDuasForCategory"));
console.error(
`Error fetching duas for category ${category.id}:`,
error
@@ -132,10 +142,14 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => {
});
});
}
- }, [path]);
+ }, [path, t]);
if (loading) {
- return {t("loading")}
; {/* Translating the loading message */}
+ return {t("loading")}
; // Translating the loading message
+ }
+
+ if (error) {
+ return {error}
; // Displaying the error message
}
return (
@@ -155,7 +169,8 @@ const List: React.FC = ({ tab, path, setPath, data, setData }) => {
/>
{t("todayDhikr")}
-
{/* Translating the title */}
+
{" "}
+ {/* Translating the title */}
diff --git a/src/components/sticky-components/audio-controls.tsx b/src/components/sticky-components/audio-controls.tsx
index b45d3c1..deb351f 100644
--- a/src/components/sticky-components/audio-controls.tsx
+++ b/src/components/sticky-components/audio-controls.tsx
@@ -16,12 +16,17 @@ const AudioControls = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(1); // Default duration to 1 to prevent division by zero
+ const [isRtl, setIsRtl] = useState(false);
const params = useParams();
const slug = params?.slug as string;
// Initialize the translation hook
const { t } = useTranslation("common");
-
+ useEffect(() => {
+ const storedLocale = localStorage.getItem('locale');
+ const rtlLanguages = ['ar', 'ur', 'fa', 'ks', 'tg', 'bn'];
+ setIsRtl(rtlLanguages.includes(storedLocale ?? ""));
+ }, []);
const play = () => {
if (audioRef.current && audio) {
if (lastPart && audio.audio_sync_data.length > 0) {
@@ -72,6 +77,10 @@ const AudioControls = () => {
function convertTo180(numerator, denominator) {
if (!numerator || !denominator) return -20; // Safeguard for invalid inputs
const newNumerator = (numerator * 200) / denominator;
+ if (isRtl) {
+
+ return -newNumerator; // Adjust to start from -20 degrees
+ }
return newNumerator; // Adjust to start from -20 degrees
}
function processSlug(slug: string): string {
diff --git a/src/components/ui/range-input.tsx b/src/components/ui/range-input.tsx
index d5dd447..56a334b 100644
--- a/src/components/ui/range-input.tsx
+++ b/src/components/ui/range-input.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from "react";
interface RangeInputProps {
value: number;
@@ -6,12 +6,33 @@ interface RangeInputProps {
disabled?: boolean;
}
-const RangeInput: React.FC = ({ value, onChange, disabled = false }) => {
+const RangeInput: React.FC = ({
+ value,
+ onChange,
+ disabled = false,
+}) => {
+ // State for detecting RTL
+ const [isRtl, setIsRtl] = useState(false);
+
+ useEffect(() => {
+ // Check language from localStorage
+ const storedLocale = localStorage.getItem("locale");
+ // Define RTL languages
+ const rtlLanguages = ["ar", "ur", "fa", "ks", "tg", "bn"];
+ // Set RTL state if the stored locale is in the RTL languages
+ if (storedLocale && rtlLanguages.includes(storedLocale)) {
+ setIsRtl(true);
+ } else {
+ setIsRtl(false);
+ }
+ }, []);
const handleChange = (event: React.ChangeEvent) => {
onChange(Number(event.target.value));
};
+ // Calculate the normalized value for RTL languages
+
return (
= ({ value, onChange, disabled = fal
disabled={disabled}
className={`w-28 h-[2px] outline-none transition-opacity duration-150 ease-in-out mr-3`}
style={{
- appearance: 'none',
+ appearance: "none",
opacity: disabled ? 0.5 : 1,
- background: `linear-gradient(to right, #F4846F ${(value - 80) / 1.2}%, #EBEBEB ${(value - 80) / 1.2}%)`,
+ background: isRtl
+ ? `linear-gradient(to left, #F4846F ${ (value - 80) / 1.2 }%, #EBEBEB ${(value - 80) / 1.2}%)`
+ : `linear-gradient(to right, #F4846F ${(value - 80) / 1.2}%, #EBEBEB ${(value - 80) / 1.2}%)`,
}}
/>
{value}%