From 81bb8e9c14a8ad75e668429850800f8aa1b73c27 Mon Sep 17 00:00:00 2001 From: Mahyar Khanbolooki Date: Tue, 12 Aug 2025 03:09:22 +0330 Subject: [PATCH] add: settings page locales --- .../(Profile)/profile/settings/page.tsx | 283 ++++++++---------- src/app/layout.tsx | 2 +- src/components/utils/Accordion.tsx | 18 +- src/components/utils/SmartIcon.tsx | 49 +++ src/lib/helpers/smartIcon.tsx | 31 ++ src/lib/i18n.js | 4 + src/locales/fa/settings.json | 126 ++++++++ src/types/i18n/settings.ts | 69 +++++ src/types/iconType.ts | 12 + 9 files changed, 421 insertions(+), 173 deletions(-) create mode 100644 src/components/utils/SmartIcon.tsx create mode 100644 src/lib/helpers/smartIcon.tsx create mode 100644 src/locales/fa/settings.json create mode 100644 src/types/i18n/settings.ts create mode 100644 src/types/iconType.ts diff --git a/src/app/[name]/(Profile)/profile/settings/page.tsx b/src/app/[name]/(Profile)/profile/settings/page.tsx index c0ea532..c9fa157 100644 --- a/src/app/[name]/(Profile)/profile/settings/page.tsx +++ b/src/app/[name]/(Profile)/profile/settings/page.tsx @@ -7,12 +7,23 @@ import { TabHeader } from '@/components/tab/TabHeader' import { Switch } from '@/components/ui/switch'; import Accordion from '@/components/utils/Accordion'; import Seperator from '@/components/utils/Seperator'; -import { Key, KeySquare, Notification1, ArrowLeft, MedalStar, TickCircle } from 'iconsax-react' +import { SmartIcon } from '@/components/utils/SmartIcon'; +import { getI18nObject } from '@/lib/i18n'; +import { SettingsData } from '@/types/i18n/settings'; +import { Key, KeySquare, Notification1, ArrowLeft, TickCircle } from 'iconsax-react' import { useRouter } from 'next/navigation'; -import React from 'react' +import React, { Fragment } from 'react' +import { useTranslation } from 'react-i18next'; function UserSettingsIndex() { const router = useRouter(); + const { t } = useTranslation('settings'); + const data: SettingsData = getI18nObject(t, "data"); + + const onSwitched = (e: React.MouseEvent) => { + const target = e.target as HTMLElement; + console.log('Save changed setting?', target.id); + } const notifsTab = () => { @@ -22,106 +33,46 @@ function UserSettingsIndex() { className="bg-container rounded-container shadow-container p-4">
-

تنظیمات نوتیفیکیشن

+

{data.TabNotifications.Heading}

- -
- - - ورود به حساب - - -
+ {data.TabNotifications.Accordions.map((accordion, i) => { + return ( + + +
+ {accordion.Items.map((item, j) => { + return ( + + ) + })} +
+
+ +
+ ) + })}
@@ -138,45 +89,49 @@ function UserSettingsIndex() { className="bg-container rounded-container shadow-container p-4">
-

تغییر رمز عبور

+

{data.TabPassword.Heading}

e.preventDefault()} className='grid gap-6 p-2' > + {data.TabPassword.Inputs.map((input, i) => { + return ( + + ) + }) - + } - - - - -
-
رمز عبور میبایست:
-
  • حداقل 8 کاراکتر باشد
  • -
  • ترکیبی از حروف موچک و بزرگ باشد
  • -
  • شامل اعداد باشد
  • -
  • شامل کاراکتر های خاص (نماد ها) باشد
  • -
    + {data.TabPassword.Rules.map((rule, i) => { + return ( +
    +
    {rule.Heading}
    + {rule.Items.map((item, i) => { + return ( +
  • {item}
  • + ) + })} +
    + ) + })} @@ -194,8 +149,8 @@ function UserSettingsIndex() { className="bg-container rounded-container shadow-container p-4">
    -

    راه‌اندازی احراز هویت دو مرحله‌ای

    -

    احراز هویت دو مرحله‌ای امنیت اکانت شما را افزایش می‌دهد

    +

    {data.TabAuthenticator.Heading}

    +

    {data.TabAuthenticator.Description}

    @@ -205,7 +160,7 @@ function UserSettingsIndex() {

    - برای شروع فرآیند راه‌اندازی احراز هویت دو مرحله‌ای، روی دکمه زیر کلیک کنید + {data.TabAuthenticator.Tip}

    @@ -213,44 +168,46 @@ function UserSettingsIndex() {
    - ذخیره تغییرات + {data.TabAuthenticator.ButtonSubmit}
    -
    -
    راهنمای احراز هویت دو مرحله‌ای
    -

    این قابلیت امنیت حساب شما را به شدت افزایش می‌دهد

    -
    -
    - 1 -
    - - یک برنامه Authenticator مانند Google Authenticator نصب کنید - -
    -
    -
    - 2 -
    - - QR Code را اسکن کنید یا کد را به صورت دستی وارد کنید - -
    -
    -
    - 3 -
    - - کد 6 رقمی تولید شده توسط برنامه را وارد کنید - -
    + {data.TabAuthenticator.Guides.map((guide, i) => { + return ( +
    +
    {guide.Heading}
    +

    {guide.Description}

    + {guide.Items.map((item, i) => { + return ( +
    +
    + {item.Mark} +
    + + {item.Label} + +
    + ) + })} -
    - توجه: کد های پشتیبان خود را در مکانی امن نگهداری کنید -
    -
    + {guide.Notes.map((note, i) => { + return ( +
    + {note} +
    + ) + })} +
    + ) + })} @@ -262,7 +219,7 @@ function UserSettingsIndex() {
    -

    تنظیمات

    +

    {data.Heading}

    } /> } /> } /> diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 578a792..38b9140 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -16,7 +16,7 @@ export const viewport: Viewport = { themeColor: '#F4F5F9', } -const i18nNamespaces = ['common', 'menu', 'orders', 'auth', "rating", "chat", "notifications", 'parallels']; +const i18nNamespaces = ['settings', 'common', 'menu', 'orders', 'auth', "rating", "chat", "notifications", 'parallels']; export default async function RootLayout({ children, diff --git a/src/components/utils/Accordion.tsx b/src/components/utils/Accordion.tsx index d3d9a8c..9467af1 100644 --- a/src/components/utils/Accordion.tsx +++ b/src/components/utils/Accordion.tsx @@ -1,21 +1,22 @@ 'use client'; import useToggle from '@/hooks/helpers/useToggle'; +import { renderIcon } from '@/lib/helpers/smartIcon'; +import { IconType } from '@/types/iconType'; import clsx from 'clsx' import { motion } from 'framer-motion'; -import { Icon } from 'iconsax-react' import { ChevronDown } from 'lucide-react'; import React, { useEffect } from 'react' -interface DropdownProps extends React.HTMLAttributes { - initial?: boolean, +interface AccordionProps extends React.HTMLAttributes { + initial?: boolean children?: React.ReactNode title?: string - icon?: Icon + icon?: IconType; group?: string }; -function Accordion({ initial, group, title, icon: Icon, children, className, ...rest }: DropdownProps) { +function Accordion({ initial, group, title, icon, children, className, ...rest }: AccordionProps) { const { state, toggle, set } = useToggle(initial); useEffect(() => { @@ -36,6 +37,7 @@ function Accordion({ initial, group, title, icon: Icon, children, className, ... }; }, [group, set, state]); + return (
    - {Icon && - - } + {renderIcon(icon)} {title ?? ''}
    -
    +
    {children}
    diff --git a/src/components/utils/SmartIcon.tsx b/src/components/utils/SmartIcon.tsx new file mode 100644 index 0000000..1364c38 --- /dev/null +++ b/src/components/utils/SmartIcon.tsx @@ -0,0 +1,49 @@ +'use client'; + +import React from "react"; +import * as Iconsax from "iconsax-react"; +import { SmartIconType } from "@/types/iconType"; + +type IconsaxIconProps = { + size?: number; + color?: string; + className?: string; + variant?: string; +}; + +type IconComponentType = React.FC; + +export interface SmartIconProps { + icon?: SmartIconType; + name?: string; + size?: number; + color?: string; + className?: string; + variant?: string; +} + +export const SmartIcon: React.FC = ({ + icon, + name, + size, + color, + className, + variant, +}) => { + const iconName = (icon?.Name || name || "").charAt(0).toUpperCase() + (icon?.Name || name || "").slice(1); + + if (!iconName) return null; + + const IconComponent = Iconsax[iconName as keyof typeof Iconsax] as IconComponentType | undefined; + + if (!IconComponent) return null; + + return ( + + ); +}; diff --git a/src/lib/helpers/smartIcon.tsx b/src/lib/helpers/smartIcon.tsx new file mode 100644 index 0000000..934c33b --- /dev/null +++ b/src/lib/helpers/smartIcon.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { IconType, SmartIconType } from "@/types/iconType"; +import { SmartIcon } from '@/components/utils/SmartIcon'; + +export function isSmartIconType(icon: unknown): icon is SmartIconType { + return ( + typeof icon === 'object' && + icon !== null && + 'Name' in icon && + typeof (icon as SmartIconType).Name === 'string' + ); +} + +export function renderIcon(icon: IconType) { + if (React.isValidElement(icon)) { + return icon; // It's a React node + } else if (typeof icon === 'function') { + const IconComponent = icon; // It's a functional component + + // Create a ref to pass to the forwardRef component + const ref = React.createRef(); // Adjust the type based on your icon component + + return ; + } else if (isSmartIconType(icon)) { + // Handle SmartIconType + return ( + + ); + } + return null; // No valid icon provided +} \ No newline at end of file diff --git a/src/lib/i18n.js b/src/lib/i18n.js index 1f9856f..f6608f9 100644 --- a/src/lib/i18n.js +++ b/src/lib/i18n.js @@ -3,6 +3,10 @@ import { initReactI18next } from 'react-i18next/initReactI18next'; import resourcesToBackend from 'i18next-resources-to-backend'; import i18nConfig from '../../i18nConfig'; +export const getI18nObject = (t, x) => { + return t(x, { returnObjects: true }) +} + export default async function initTranslations( locale, namespaces, diff --git a/src/locales/fa/settings.json b/src/locales/fa/settings.json new file mode 100644 index 0000000..b251aa9 --- /dev/null +++ b/src/locales/fa/settings.json @@ -0,0 +1,126 @@ +{ + "data": { + "Heading": "تنظیمات", + "TabNotifications": { + "Title": "نوتیفیکیشن", + "Heading": "تنظیمات نوتیفیکیشن", + "Description": "", + "Accordions": [ + { + "Heading": "حساب", + "Items": [ + { + "Label": "ورود به حساب", + "HtmlName": "login-notif" + }, + { + "Label": "تغییر رمز عبور", + "HtmlName": "password-notif" + } + ] + }, + { + "Heading": "کیف پول", + "Items": [ + { + "Label": "افزایش شارژ", + "HtmlName": "wallet-recharge" + }, + { + "Label": "برداشت از کیف", + "HtmlName": "wallet-withdrawal" + }, + { + "Label": "استفاده از تخفیف", + "HtmlName": "redeem-discount" + } + ] + }, + { + "Heading": "سفارش", + "Items": [ + { + "Label": "وضعیت سفارش", + "HtmlName": "order-state" + }, + { + "Label": "تحویل", + "HtmlName": "order-deliver" + }, + { + "Label": "کنسلی", + "HtmlName": "order-canceled" + } + ] + } + ] + }, + "TabPassword": { + "Title": "رمز عبور", + "Heading": "تغییر رمز عبور", + "Description": "", + "Inputs": [ + { + "Label": "", + "Placeholder": "رمز عبور فعلی", + "HtmlName": "oldPassword", + "AutoComplete": "old-password" + }, + { + "Label": "", + "Placeholder": "رمز عبور جدید", + "HtmlName": "newPassword", + "AutoComplete": "off" + }, + { + "Label": "", + "Placeholder": "تکرار رمز عبور", + "HtmlName": "passwordRepeat", + "AutoComplete": "off" + } + ], + "Rules": [ + { + "Heading": "رمز عبور میبایست:", + "Items": [ + "حداقل 8 کاراکتر باشد", + "ترکیبی از حروف موچک و بزرگ باشد", + "شامل اعداد باشد", + "شامل کاراکتر های خاص (نماد ها) باشد" + ] + } + ], + "ButtonSubmit": "ذخیره تغییرات" + }, + "TabAuthenticator": { + "Title": "دو مرحله ای", + "Heading": "راه‌اندازی احراز هویت دو مرحله‌ای", + "Description": "احراز هویت دو مرحله‌ای امنیت اکانت شما را افزایش می‌دهد", + "Tip": "برای شروع فرآیند راه‌اندازی احراز هویت دو مرحله‌ای، روی دکمه زیر کلیک کنید", + "Guides": [ + { + "Heading": "راهنمای احراز هویت دو مرحله‌ای", + "Description": "این قابلیت امنیت حساب شما را به شدت افزایش می‌دهد", + "Items": [ + { + "Label": "یک برنامه Authenticator مانند Google Authenticator نصب کنید", + "Mark": "1" + }, + { + "Label": "QR Code را اسکن کنید یا کد را به صورت دستی وارد کنید", + "Mark": "2" + }, + { + "Label": "کد 6 رقمی تولید شده توسط برنامه را وارد کنید", + "Mark": "3" + } + ], + "Notes": [ + "توجه: کد های پشتیبان خود را در مکانی امن نگهداری کنید" + ] + } + ], + "ButtonSubmit": "راه‌اندازی کد دو مرحله‌ای" + } + } +} \ No newline at end of file diff --git a/src/types/i18n/settings.ts b/src/types/i18n/settings.ts new file mode 100644 index 0000000..4354eed --- /dev/null +++ b/src/types/i18n/settings.ts @@ -0,0 +1,69 @@ +import { SmartIconType } from "../iconType"; + +export interface Item { + Label: string; + HtmlName: string; + Icon?: SmartIconType; +} + +export interface Accordion { + Heading: string; + Icon?: SmartIconType; + Items: Item[]; +} + +export interface TabNotifications { + Title: string; + Heading: string; + Description?: string; + Accordions: Accordion[]; +} + +export interface InputField { + Label: string; + Placeholder?: string; + HtmlName?: string; + AutoComplete?: string; +} + +export interface RuleGroup { + Heading: string; + Items: string[]; +} + +export interface TabPassword { + Title: string; + Heading: string; + Description?: string; + Inputs: InputField[]; + Rules: RuleGroup[]; + ButtonSubmit: string; +} + +export interface GuideItem { + Label: string; + Mark: string; +} + +export interface Guide { + Heading: string; + Description: string; + Items: GuideItem[]; + Notes: string[]; +} + +export interface TabAuthenticator { + Title: string; + Heading: string; + Description?: string; + Tip: string; + Guides: Guide[]; + ButtonSubmit: string; +} + +export interface SettingsData { + Heading: string; + TabNotifications: TabNotifications; + TabPassword: TabPassword; + TabAuthenticator: TabAuthenticator; +} diff --git a/src/types/iconType.ts b/src/types/iconType.ts new file mode 100644 index 0000000..deee154 --- /dev/null +++ b/src/types/iconType.ts @@ -0,0 +1,12 @@ +export interface SmartIconType { + Name: string; + Size?: number; + Color?: string; + ClassName?: string; + Variant?: string; +} + +export type IconType = + React.ElementType | + React.ReactNode | + SmartIconType;