From ef2e86861a8d3c77bd148191baefaa17657c3249 Mon Sep 17 00:00:00 2001 From: payacom <59262811+payacom@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:09:25 +0330 Subject: [PATCH] instagram --- .../(login)/forget-password-otp/page.tsx | 3 - .../(login)/login-with-username/page.tsx | 3 - src/app/(auth)/(login)/login/page.tsx | 13 +- src/app/(auth)/(login)/verify-otp/page.tsx | 3 - .../(auth)/(register)/register-otp/page.tsx | 3 - .../(register)/register/fullname/page.tsx | 125 +++++++++---- src/app/(auth)/(register)/register/page.tsx | 13 +- src/app/(auth)/verify/national-cart/page.tsx | 12 +- src/app/about-us/page.tsx | 2 +- src/app/auth/instagram/callback/page.tsx | 4 + src/app/instagram-data-deletion/page.tsx | 40 +++++ src/app/offer/[id]/page.tsx | 25 +-- src/app/privacy/page.tsx | 45 +++++ .../settings/edit/instagram-import/page.tsx | 100 +++++++++++ .../edit/instagram-import/select/page.tsx | 168 ++++++++++++++++++ src/components/academy/MainModelCard.tsx | 20 +-- src/components/auth/AuthLanguageToggle.tsx | 37 ++++ src/components/auth/GoogleSignInButton.tsx | 11 -- src/components/auth/InstagramSignInButton.tsx | 14 -- src/components/main/ScoreRateStats.tsx | 29 +++ .../models/MainModelCard/MainModelCard.tsx | 20 +-- .../models/ModelPage/MainModelCardPost.tsx | 20 +-- .../models/ModelPage/ModelContent.tsx | 1 + .../models/ModelPage/ModelContentPosts.tsx | 27 +-- .../models/ModelPage/ModelHeadRowTwo.tsx | 27 +-- .../Workroom/ProjectRequestsAction.tsx | 51 ++---- .../Workroom/ProjectWorkroomSelectedUser.tsx | 33 +--- src/components/search/SearchCard.tsx | 15 +- src/config/instagramAuth.ts | 9 +- src/locales/en/common.json | 30 +++- src/locales/fa/common.json | 30 +++- 31 files changed, 636 insertions(+), 297 deletions(-) create mode 100644 src/app/instagram-data-deletion/page.tsx create mode 100644 src/app/privacy/page.tsx create mode 100644 src/app/settings/edit/instagram-import/select/page.tsx create mode 100644 src/components/auth/AuthLanguageToggle.tsx create mode 100644 src/components/main/ScoreRateStats.tsx diff --git a/src/app/(auth)/(login)/forget-password-otp/page.tsx b/src/app/(auth)/(login)/forget-password-otp/page.tsx index ab36360..1d03e76 100644 --- a/src/app/(auth)/(login)/forget-password-otp/page.tsx +++ b/src/app/(auth)/(login)/forget-password-otp/page.tsx @@ -4,7 +4,6 @@ import AuthButton from "@/components/auth/AuthButton"; import AuthHead from "@/components/auth/AuthHead"; import Container from "@/components/elements/Container"; -import AuthRules from "@/components/auth/AuthRules"; import Link from "next/link"; import React, { useMemo, useState } from "react"; import AuthOtpInput from "@/components/auth/AuthOtpInput"; @@ -22,7 +21,6 @@ import LocalePageShell from "@/components/i18n/LocalePageShell"; function ForgetPasswordOtp() { const { t } = useTranslation("common"); const router = useRouter(); - const [isModalOpen, setModalOpen] = useState(false); const [otpExpired, setOtpExpired] = useState(false); const [resendLoading, setResendLoading] = useState(false); const [isSuccess, setIsSuccess] = useState(false); @@ -139,7 +137,6 @@ function ForgetPasswordOtp() { {t("auth.editMobile")} - diff --git a/src/app/(auth)/(login)/login-with-username/page.tsx b/src/app/(auth)/(login)/login-with-username/page.tsx index 203b787..ef0a26b 100644 --- a/src/app/(auth)/(login)/login-with-username/page.tsx +++ b/src/app/(auth)/(login)/login-with-username/page.tsx @@ -6,7 +6,6 @@ import AuthHead from "@/components/auth/AuthHead"; import AuthInput from "@/components/auth/AuthInput"; import AuthPasswordInput from "@/components/auth/AuthPasswordInput"; import Container from "@/components/elements/Container"; -import AuthRules from "@/components/auth/AuthRules"; import Link from "next/link"; import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; @@ -27,7 +26,6 @@ import LocalePageShell from "@/components/i18n/LocalePageShell"; function LoginWithUsername() { const { t, i18n } = useTranslation("common"); const router = useRouter(); - const [isModalOpen, setModalOpen] = useState(false); const [isSuccess, setIsSuccess] = useState(false); const [authError, setAuthError] = useState(false); const [errorMessage, setErrorMessage] = useState(""); @@ -176,7 +174,6 @@ function LoginWithUsername() { {t("auth.signUp")} - diff --git a/src/app/(auth)/(login)/login/page.tsx b/src/app/(auth)/(login)/login/page.tsx index e4d885f..43d75a2 100644 --- a/src/app/(auth)/(login)/login/page.tsx +++ b/src/app/(auth)/(login)/login/page.tsx @@ -4,9 +4,9 @@ import AuthButton from "@/components/auth/AuthButton"; import AuthHead from "@/components/auth/AuthHead"; import AuthInput from "@/components/auth/AuthInput"; import Container from "@/components/elements/Container"; -import AuthRules from "@/components/auth/AuthRules"; +import AuthLanguageToggle from "@/components/auth/AuthLanguageToggle"; import Link from "next/link"; -import React, { useMemo, useState } from "react"; +import React, { useMemo } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -22,7 +22,6 @@ import LocalePageShell from "@/components/i18n/LocalePageShell"; function Login() { const { t } = useTranslation("common"); const router = useRouter(); - const [isModalOpen, setModalOpen] = useState(false); const { request, loading } = useAxios(); useAuthSessionRedirect(); const redirectPath = getSafeRedirectPath(); @@ -62,7 +61,8 @@ function Login() { return ( -
+
+
- - +
diff --git a/src/app/(auth)/(login)/verify-otp/page.tsx b/src/app/(auth)/(login)/verify-otp/page.tsx index c0503b1..2cb8bc8 100644 --- a/src/app/(auth)/(login)/verify-otp/page.tsx +++ b/src/app/(auth)/(login)/verify-otp/page.tsx @@ -4,7 +4,6 @@ import AuthButton from "@/components/auth/AuthButton"; import AuthHead from "@/components/auth/AuthHead"; import Container from "@/components/elements/Container"; -import AuthRules from "@/components/auth/AuthRules"; import Link from "next/link"; import React, { useMemo, useState } from "react"; import AuthOtpInput from "@/components/auth/AuthOtpInput"; @@ -24,7 +23,6 @@ import LocalePageShell from "@/components/i18n/LocalePageShell"; function VerifyOtp() { const { t } = useTranslation("common"); const router = useRouter(); - const [isModalOpen, setModalOpen] = useState(false); const [otpExpired, setOtpExpired] = useState(false); const [resendLoading, setResendLoading] = useState(false); const [isSuccess, setIsSuccess] = useState(false); @@ -145,7 +143,6 @@ function VerifyOtp() { {t("auth.editMobile")} -
diff --git a/src/app/(auth)/(register)/register-otp/page.tsx b/src/app/(auth)/(register)/register-otp/page.tsx index c953ec2..72497d0 100644 --- a/src/app/(auth)/(register)/register-otp/page.tsx +++ b/src/app/(auth)/(register)/register-otp/page.tsx @@ -5,7 +5,6 @@ import AuthButton from "@/components/auth/AuthButton"; import AuthHead from "@/components/auth/AuthHead"; import Container from "@/components/elements/Container"; -import AuthRules from "@/components/auth/AuthRules"; import Link from "next/link"; import React, { useMemo, useState } from "react"; import AuthOtpInput from "@/components/auth/AuthOtpInput"; @@ -22,7 +21,6 @@ import LocalePageShell from "@/components/i18n/LocalePageShell"; function RegisterOtp() { const { t } = useTranslation("common"); const router = useRouter(); - const [isModalOpen, setModalOpen] = useState(false); const [otpExpired, setOtpExpired] = useState(false); const [resendLoading, setResendLoading] = useState(false); const [isSuccess, setIsSuccess] = useState(false); @@ -132,7 +130,6 @@ function RegisterOtp() { {t("auth.editMobile")} - diff --git a/src/app/(auth)/(register)/register/fullname/page.tsx b/src/app/(auth)/(register)/register/fullname/page.tsx index af0e183..f1673db 100644 --- a/src/app/(auth)/(register)/register/fullname/page.tsx +++ b/src/app/(auth)/(register)/register/fullname/page.tsx @@ -7,7 +7,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React, { useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -17,6 +17,15 @@ import toast from "react-hot-toast"; import { getSafeRedirectPath } from "@/lib/auth/postLogin"; import { useTranslation } from "react-i18next"; import LocalePageShell from "@/components/i18n/LocalePageShell"; +import Image from "next/image"; +import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; + +function splitInstagramFullName(fullName: string): { name: string; family: string } { + const trimmed = fullName.trim(); + if (!trimmed) return { name: "", family: "" }; + const [first, ...rest] = trimmed.split(/\s+/); + return { name: first, family: rest.join(" ") }; +} function FullNamePage() { const { t } = useTranslation("common"); @@ -26,6 +35,25 @@ function FullNamePage() { const [choiceLoading, setChoiceLoading] = useState<"app" | "continue" | null>( null ); + const [avatar, setAvatar] = useState(null); + + const isInstagramProvider = + typeof window !== "undefined" && + localStorage.getItem("auth_provider") === "instagram"; + const instagramFullName = + isInstagramProvider ? localStorage.getItem("instagram_full_name") || "" : ""; + const instagramSuggested = useMemo( + () => splitInstagramFullName(instagramFullName), + [instagramFullName] + ); + + useEffect(() => { + if (!isInstagramProvider) return; + request<{ user: { profile_image?: string | null } }>("GET", "/profile") + .then((res) => setAvatar(res?.user?.profile_image || null)) + .catch(() => {}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const schema = useMemo( () => @@ -40,36 +68,6 @@ function FullNamePage() { [t] ); - const formik = useFormik({ - initialValues: { - family: "", - name: "", - }, - validationSchema: schema, - onSubmit: async (values, { setSubmitting }) => { - try { - await request("POST", "/register/fullname", { - first_name: values.name.trim(), - last_name: values.family.trim() || undefined, - }); - - localStorage.setItem("first_name", values.name.trim()); - if (values.family.trim()) { - localStorage.setItem("last_name", values.family.trim()); - } else { - localStorage.removeItem("last_name"); - } - - setNameSaved(true); - toast.success(t("auth.nameSavedSuccess")); - } catch (err: any) { - toast.error(err?.response?.data?.message || t("auth.saveNameFailed")); - } finally { - setSubmitting(false); - } - }, - }); - const finishRegistration = async (action: "app" | "continue") => { setChoiceLoading(action); try { @@ -92,6 +90,43 @@ function FullNamePage() { } }; + const formik = useFormik({ + initialValues: { + family: instagramSuggested.family, + name: instagramSuggested.name, + }, + validationSchema: schema, + onSubmit: async (values, { setSubmitting }) => { + try { + await request("POST", "/register/fullname", { + first_name: values.name.trim(), + last_name: values.family.trim() || undefined, + }); + + localStorage.setItem("first_name", values.name.trim()); + if (values.family.trim()) { + localStorage.setItem("last_name", values.family.trim()); + } else { + localStorage.removeItem("last_name"); + } + + setNameSaved(true); + toast.success(t("auth.nameSavedSuccess")); + + // Instagram signups flow straight into the expertise/onboarding wizard — + // no manual "enter app or continue" choice, matching the phone/Google flow's + // choice screen being skipped only for this provider. + if (isInstagramProvider) { + await finishRegistration("continue"); + } + } catch (err: any) { + toast.error(err?.response?.data?.message || t("auth.saveNameFailed")); + } finally { + setSubmitting(false); + } + }, + }); + return ( @@ -102,6 +137,16 @@ function FullNamePage() {
+ {isInstagramProvider && avatar && ( + + )} - {formik.touched.name && formik.errors.name && ( + {formik.touched.name && formik.errors.name ? ( {formik.errors.name} + ) : ( + isInstagramProvider && + instagramSuggested.name && + formik.values.name === instagramSuggested.name && ( + + {t("auth.nameSuggestedFromInstagram")} + + ) )} - {t("auth.nameSavedChoose")} + {isInstagramProvider + ? t("auth.instagramContinuingToProfile") + : t("auth.nameSavedChoose")}

)}
@@ -161,7 +216,7 @@ function FullNamePage() { > {t("auth.saveAndContinue")} - ) : ( + ) : !isInstagramProvider ? (
- )} + ) : null}
diff --git a/src/app/(auth)/(register)/register/page.tsx b/src/app/(auth)/(register)/register/page.tsx index 0ac2301..b0069a5 100644 --- a/src/app/(auth)/(register)/register/page.tsx +++ b/src/app/(auth)/(register)/register/page.tsx @@ -4,8 +4,8 @@ import AuthButton from "@/components/auth/AuthButton"; import AuthHead from "@/components/auth/AuthHead"; import AuthInput from "@/components/auth/AuthInput"; import Container from "@/components/elements/Container"; -import AuthRules from "@/components/auth/AuthRules"; -import React, { useMemo, useState } from "react"; +import AuthLanguageToggle from "@/components/auth/AuthLanguageToggle"; +import React, { useMemo } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -26,7 +26,6 @@ function normalizeMobileInput(value: string): string { function Register() { const { t, i18n } = useTranslation("common"); const router = useRouter(); - const [isModalOpen, setModalOpen] = useState(false); const { request, loading } = useAxios(); const schema = useMemo( @@ -86,7 +85,8 @@ function Register() { return ( -
+
+
{t("auth.login")} - - +
diff --git a/src/app/(auth)/verify/national-cart/page.tsx b/src/app/(auth)/verify/national-cart/page.tsx index d7a620b..aeef786 100644 --- a/src/app/(auth)/verify/national-cart/page.tsx +++ b/src/app/(auth)/verify/national-cart/page.tsx @@ -13,7 +13,7 @@ import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; import Image from "next/image"; import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; -import AuthRules from "@/components/auth/AuthRules"; +import Link from "next/link"; import { useTranslation } from "react-i18next"; import LocalePageShell from "@/components/i18n/LocalePageShell"; @@ -24,7 +24,6 @@ function NationalCart() { const [nationalCartImg, setNationalCartImg] = useState(null); const [isCheckedOne, setIsCheckedOne] = useState(false); - const [isModalOpen, setModalOpen] = useState(false); const toggleCheckBox = () => setIsCheckedOne(!isCheckedOne); @@ -144,15 +143,14 @@ function NationalCart() { type="checkbox" onChange={toggleCheckBox} /> - setModalOpen(true)} + {t("auth.acceptRulesLabel")} - +
- - router.push("/verify/confirm")}> diff --git a/src/app/about-us/page.tsx b/src/app/about-us/page.tsx index 6a19d6a..9cddb14 100644 --- a/src/app/about-us/page.tsx +++ b/src/app/about-us/page.tsx @@ -19,7 +19,7 @@ function AboutUs() {
{t("aboutPage.phoneLabel")} - 09128893712 + 02167586
diff --git a/src/app/auth/instagram/callback/page.tsx b/src/app/auth/instagram/callback/page.tsx index dbcecd4..4454eae 100644 --- a/src/app/auth/instagram/callback/page.tsx +++ b/src/app/auth/instagram/callback/page.tsx @@ -16,6 +16,7 @@ import { useTranslation } from "react-i18next"; type InstagramCallbackResponse = IVerifyOtp & { instagram_username?: string | null; + instagram_full_name?: string | null; instagram_bio?: string | null; instagram_profile_picture_url?: string | null; }; @@ -92,6 +93,9 @@ export default function InstagramCallbackPage() { if (raw?.instagram_username) { localStorage.setItem("instagram_username", raw.instagram_username); } + if (raw?.instagram_full_name) { + localStorage.setItem("instagram_full_name", raw.instagram_full_name); + } if (raw?.instagram_bio) { localStorage.setItem("instagram_bio", raw.instagram_bio); } diff --git a/src/app/instagram-data-deletion/page.tsx b/src/app/instagram-data-deletion/page.tsx new file mode 100644 index 0000000..f891be9 --- /dev/null +++ b/src/app/instagram-data-deletion/page.tsx @@ -0,0 +1,40 @@ +"use client"; + +import Container from "@/components/elements/Container"; +import AuthLanguageToggle from "@/components/auth/AuthLanguageToggle"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; +import { useTranslation } from "react-i18next"; +import { useSearchParams } from "next/navigation"; + +export default function InstagramDataDeletionStatusPage() { + const { t } = useTranslation("common"); + const searchParams = useSearchParams(); + const code = searchParams.get("code"); + + return ( + + +
+
+

+ {t("settings.edit.instagramImport.dataDeletionStatusTitle")} +

+ +
+ +

+ {code + ? t("settings.edit.instagramImport.dataDeletionStatusDone") + : t("settings.edit.instagramImport.dataDeletionInstructions")} +

+ + {code && ( +

+ {t("settings.edit.instagramImport.dataDeletionStatusCode")}: {code} +

+ )} +
+
+
+ ); +} diff --git a/src/app/offer/[id]/page.tsx b/src/app/offer/[id]/page.tsx index 84d6055..5fc1f2d 100644 --- a/src/app/offer/[id]/page.tsx +++ b/src/app/offer/[id]/page.tsx @@ -7,12 +7,12 @@ import RoundedDiv from "@/components/elements/RoundedDiv"; import { selectionCardClass } from "@/lib/ui/buttonStyles"; import { BASE_URL } from "@/components/main/BaseUrl"; import UserInfo from "@/components/main/UserInfo"; +import ScoreRateStats from "@/components/main/ScoreRateStats"; import PageTitle from "@/components/settings/PageTitle"; import useAxios from "@/hooks/useAxios"; import { IOfferType, User } from "@/types/types"; import axios from "axios"; import { useFormik } from "formik"; -import Image from "next/image"; import { useRouter } from "next/navigation"; import React, { useState, useEffect, useMemo } from "react"; import toast from "react-hot-toast"; @@ -209,27 +209,10 @@ function TicketChat({ params }: ITicketChatProps) { last_name={userDetail?.last_name} user_name={userDetail?.user_name} noLink + trailingContent={ + + } /> -
- {userDetail?.user_score || "0"} - -
-
- {userDetail?.rate || "0"} - -
) : null; diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx new file mode 100644 index 0000000..6ad01a4 --- /dev/null +++ b/src/app/privacy/page.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Container from "@/components/elements/Container"; +import AuthLanguageToggle from "@/components/auth/AuthLanguageToggle"; +import useAxios from "@/hooks/useAxios"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; + +export default function PrivacyPage() { + const { t } = useTranslation("common"); + const [rules, setRules] = useState(""); + const { request, loading, error } = useAxios(); + + useEffect(() => { + request<{ value: string }>("GET", "/settings/terms-and-conditions") + .then((data) => setRules(data?.value || "")) + .catch(() => {}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + +
+
+

{t("auth.rules")}

+ +
+ + {loading ? ( +

{t("auth.rulesLoading")}

+ ) : error ? ( +

{t("auth.rulesError")}

+ ) : ( +
+ )} +
+ + + ); +} diff --git a/src/app/settings/edit/instagram-import/page.tsx b/src/app/settings/edit/instagram-import/page.tsx index 0a6b7f0..657e741 100644 --- a/src/app/settings/edit/instagram-import/page.tsx +++ b/src/app/settings/edit/instagram-import/page.tsx @@ -5,6 +5,7 @@ import PageTitle from "@/components/settings/PageTitle"; import LocalePageShell from "@/components/i18n/LocalePageShell"; import InstagramSignInButton from "@/components/auth/InstagramSignInButton"; import useAxios from "@/hooks/useAxios"; +import Link from "next/link"; import React, { useCallback, useEffect, useState } from "react"; import toast from "react-hot-toast"; import { useTranslation } from "react-i18next"; @@ -15,6 +16,7 @@ type InstagramStatus = { instagram_username: string | null; instagram_account_type: string | null; connected_at: string | null; + instagram_sync_prefs?: { posts: boolean; auto_update: boolean }; }; export default function InstagramImportSettingsPage() { @@ -56,6 +58,10 @@ export default function InstagramImportSettingsPage() { }; }, []); + const [showModeChoice, setShowModeChoice] = useState(false); + const [importingAuto, setImportingAuto] = useState(false); + const [togglingAutoOff, setTogglingAutoOff] = useState(false); + const handleDisconnect = async () => { setDisconnecting(true); try { @@ -69,6 +75,35 @@ export default function InstagramImportSettingsPage() { } }; + const handleAutomaticImport = async () => { + setImportingAuto(true); + try { + await request("POST", "/auth/instagram/import", { mode: "automatic" }); + toast.success(t("settings.edit.instagramImport.automaticStartSuccess")); + setShowModeChoice(false); + await loadStatus(); + } catch { + toast.error(t("settings.edit.instagramImport.importError")); + } finally { + setImportingAuto(false); + } + }; + + const handleTurnOffAutoUpdate = async () => { + setTogglingAutoOff(true); + try { + await request("POST", "/auth/instagram/import/auto-update", {}); + toast.success(t("settings.edit.instagramImport.autoUpdateOffSuccess")); + await loadStatus(); + } catch { + toast.error(t("settings.edit.instagramImport.importError")); + } finally { + setTogglingAutoOff(false); + } + }; + + const autoUpdateOn = Boolean(status?.instagram_sync_prefs?.auto_update); + return ( @@ -86,6 +121,71 @@ export default function InstagramImportSettingsPage() { @{status.instagram_username}

) : null} + + {autoUpdateOn && ( +

+ {t("settings.edit.instagramImport.autoUpdateOnStatus")} +

+ )} + + {!showModeChoice ? ( + + ) : ( +
+

+ {t("settings.edit.instagramImport.chooseModeTitle")} +

+ + + {t("settings.edit.instagramImport.manualMode")} + + + {t("settings.edit.instagramImport.manualModeHint")} + + + + +
+ )} + + {autoUpdateOn && ( + + )} + + )} + +
+ {media.map((item) => { + const disabled = !item.supported || item.already_imported; + const selected = selectedIds.has(item.id); + return ( + + ); + })} +
+ + )} + + {eligibleIds.length > 0 && ( +
+ +
+ )} +
+
+ ); +} diff --git a/src/components/academy/MainModelCard.tsx b/src/components/academy/MainModelCard.tsx index ce2454f..9ea68a0 100644 --- a/src/components/academy/MainModelCard.tsx +++ b/src/components/academy/MainModelCard.tsx @@ -19,6 +19,7 @@ import { } from "@/lib/postLinkCaption"; import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import VerificationBadge from "@/components/main/VerificationBadge"; +import ScoreRateStats from "@/components/main/ScoreRateStats"; import { useUserById } from "@/hooks/getUserById"; import "slick-carousel/slick/slick.css"; import "slick-carousel/slick/slick-theme.css"; @@ -438,24 +439,7 @@ function MainModelCard({ {cuorse_name} -
- {newScore || 0} - {t("searchPage.starIconAlt")} -
-
- {rating || 0} - {t("searchPage.rateIconAlt")} -
+
diff --git a/src/components/auth/AuthLanguageToggle.tsx b/src/components/auth/AuthLanguageToggle.tsx new file mode 100644 index 0000000..16b390e --- /dev/null +++ b/src/components/auth/AuthLanguageToggle.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useAppLanguage } from "@/contexts/LanguageProvider"; +import { ENABLED_LANGUAGES, LANGUAGE_LABELS, type AppLanguage } from "@/lib/i18n/registry"; +import { cn } from "@/lib/utils"; + +export default function AuthLanguageToggle({ className }: { className?: string }) { + const { language, setLanguage } = useAppLanguage(); + + const handleChange = (next: AppLanguage) => { + if (next === language) return; + void setLanguage(next); + }; + + return ( +
+ {ENABLED_LANGUAGES.map((code) => { + const active = language === code; + return ( + + ); + })} +
+ ); +} diff --git a/src/components/auth/GoogleSignInButton.tsx b/src/components/auth/GoogleSignInButton.tsx index 34b0cba..e6fc987 100644 --- a/src/components/auth/GoogleSignInButton.tsx +++ b/src/components/auth/GoogleSignInButton.tsx @@ -143,17 +143,6 @@ function GoogleSignInInner({ return (
-
- - - {mode === "register" - ? t("auth.orRegisterWith") - : mode === "link" - ? t("settings.edit.googleAccount.orLinkWith") - : t("auth.orLoginWith")} - -
-
{ const nonce = generateNonce(); sessionStorage.setItem("ig_oauth_mode", mode); @@ -71,13 +64,6 @@ export default function InstagramSignInButton({ return (
-
- - - {dividerLabel} - -
-
diff --git a/src/components/models/ModelPage/MainModelCardPost.tsx b/src/components/models/ModelPage/MainModelCardPost.tsx index 923e5da..78b3c97 100644 --- a/src/components/models/ModelPage/MainModelCardPost.tsx +++ b/src/components/models/ModelPage/MainModelCardPost.tsx @@ -8,6 +8,7 @@ import Link from "next/link"; import CaptionWithMentions from "@/components/posts/CaptionWithMentions"; import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import VerificationBadge from "@/components/main/VerificationBadge"; +import ScoreRateStats from "@/components/main/ScoreRateStats"; import { useUserById } from "@/hooks/getUserById"; import Slider from "react-slick"; import "slick-carousel/slick/slick.css"; @@ -194,24 +195,7 @@ function MainModelCard({ postData }: { postData: Post }) { {user_name} -
- {user_score || 0} - star icon -
-
- {rate || 0} - star icon -
+
diff --git a/src/components/models/ModelPage/ModelContent.tsx b/src/components/models/ModelPage/ModelContent.tsx index b0d914f..526cfea 100644 --- a/src/components/models/ModelPage/ModelContent.tsx +++ b/src/components/models/ModelPage/ModelContent.tsx @@ -476,6 +476,7 @@ function ModelContent({ user, searchParams }: ModelContentProps) { _id={user?._id} token={token} privateLocked={Boolean(user?.private_content_locked)} + profileUser={user} /> ))}
diff --git a/src/components/models/ModelPage/ModelContentPosts.tsx b/src/components/models/ModelPage/ModelContentPosts.tsx index c931e31..81d416b 100644 --- a/src/components/models/ModelPage/ModelContentPosts.tsx +++ b/src/components/models/ModelPage/ModelContentPosts.tsx @@ -1,14 +1,13 @@ "use client"; -import React, { useEffect, useState, useMemo, useCallback } from "react"; +import React, { useMemo, useCallback } from "react"; import { navigateWithSavedScroll } from "@/lib/listScrollRestoration"; import { useListScrollRestoration } from "@/hooks/useListScrollRestoration"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useRouter } from "next/navigation"; import { fetchPosts } from "@/api/fetchPosts"; import { Post, User } from "@/types/types"; -import axios from "axios"; -import { BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; +import { buildStorageUrl } from "@/components/main/BaseUrl"; import Image from "next/image"; import BoldIcon from "@/components/ui/BoldIcon"; import { ChatListSkeleton } from "@/components/ui/ChatSkeletons"; @@ -22,6 +21,7 @@ interface InfinitePostsProps { token: string; _id: string; privateLocked?: boolean; + profileUser?: User; filters: { expertise?: string; province?: string; @@ -36,27 +36,10 @@ function ModelContentPosts({ token, _id, privateLocked = false, + profileUser, }: InfinitePostsProps) { const { t } = useTranslation("common"); const router = useRouter(); - const [allUsers, setAllUsers] = useState([]); - - const fetchUsers = async () => { - try { - const response = await axios.get(`${BASE_URL}/users?limit=1000`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (response?.data?.users) { - setAllUsers(response.data.users); - } - } catch (err) { - console.error("خطا در دریافت کاربران:", err); - } - }; - - useEffect(() => { - fetchUsers(); - }, []); const filterKey = useMemo(() => JSON.stringify({ ...filters, _id }), [filters, _id]); @@ -111,7 +94,7 @@ function ModelContentPosts({ const posts: Post[] = data?.pages?.flatMap((page) => page.posts) ?? []; const filteredPosts = posts.filter((post) => { - const user = allUsers.find((u) => u._id === post.userId); + const user = profileUser?._id === post.userId ? profileUser : undefined; if (!user) return true; let include = true; diff --git a/src/components/models/ModelPage/ModelHeadRowTwo.tsx b/src/components/models/ModelPage/ModelHeadRowTwo.tsx index a977d39..6d0b208 100644 --- a/src/components/models/ModelPage/ModelHeadRowTwo.tsx +++ b/src/components/models/ModelPage/ModelHeadRowTwo.tsx @@ -1,6 +1,6 @@ import { useUserById } from "@/hooks/getUserById"; import VerificationBadge from "@/components/main/VerificationBadge"; -import Image from "next/image"; +import ScoreRateStats from "@/components/main/ScoreRateStats"; import React from "react"; import { cn } from "@/lib/utils"; @@ -32,29 +32,8 @@ function ModelHeadRowTwo({ const verify_badge = verifyBadgeProp ?? user?.verify_badge; const is_Register = isRegisterProp ?? user?.is_Register; return ( -
-
- <> - {user_score ? user_score : "0"} - {"star - -
-
- <> - {rate ? rate : "0"} - {"rate - -
+
+
- +
+ + } + /> +
-
+
{t("projects.workroom.offerPrice", { amount: Number(item?.price).toLocaleString(), @@ -103,30 +108,6 @@ function ProjectRequestsAction({ })} ) : null} -
-
- - {item?.user?.user_score ? item?.user?.user_score : "0"} - - -
-
- {item?.user?.rate ? item?.user?.rate : "0"} - -
-
); diff --git a/src/components/projects/Workroom/ProjectWorkroomSelectedUser.tsx b/src/components/projects/Workroom/ProjectWorkroomSelectedUser.tsx index 32c03d9..43aafe0 100644 --- a/src/components/projects/Workroom/ProjectWorkroomSelectedUser.tsx +++ b/src/components/projects/Workroom/ProjectWorkroomSelectedUser.tsx @@ -1,8 +1,8 @@ "use client"; import UserInfo from "@/components/main/UserInfo"; +import ScoreRateStats from "@/components/main/ScoreRateStats"; import { SelectedUser } from "@/types/types"; -import Image from "next/image"; import React from "react"; import { useTranslation } from "react-i18next"; @@ -27,35 +27,10 @@ function ProjectWorkroomSelectedUser({ user_level={selectedUser?.user_level} last_name={selectedUser?.last_name} user_name={selectedUser?.user_name} + trailingContent={ + + } /> -
-
- <> - - {selectedUser?.user_score ? selectedUser?.user_score : "0"} - - {"verify - -
-
- <> - {selectedUser?.rate ? selectedUser?.rate : "0"} - {"verify - -
-
); diff --git a/src/components/search/SearchCard.tsx b/src/components/search/SearchCard.tsx index 3343a3f..f6c51af 100644 --- a/src/components/search/SearchCard.tsx +++ b/src/components/search/SearchCard.tsx @@ -1,7 +1,7 @@ import { User } from "@/types/types"; import React from "react"; import UserInfo from "../main/UserInfo"; -import Image from "next/image"; +import ScoreRateStats from "../main/ScoreRateStats"; import Link from "next/link"; function SearchCard({ user }: { user: User }) { @@ -19,18 +19,7 @@ function SearchCard({ user }: { user: User }) { last_name={user?.last_name} user_name={user?.user_name} noLink - trailingContent={ - - - {user?.user_score ? user?.user_score : "0"} - star icon - - - {user?.rate ? user?.rate : "0"} - rate icon - - - } + trailingContent={} /> ); diff --git a/src/config/instagramAuth.ts b/src/config/instagramAuth.ts index 2680570..955571c 100644 --- a/src/config/instagramAuth.ts +++ b/src/config/instagramAuth.ts @@ -5,10 +5,13 @@ */ export const INSTAGRAM_OAUTH_CLIENT_ID = ""; -/** Must exactly match INSTAGRAM_REDIRECT_URI configured on the backend and in the Meta App. */ +/** + * Fixed (not derived from window.location) so it exactly matches + * INSTAGRAM_REDIRECT_URI on the backend and the URI registered in the Meta App, + * regardless of whether the user is on modstagram.com or www.modstagram.com. + */ export function getInstagramRedirectUri(): string { - if (typeof window === "undefined") return ""; - return `${window.location.origin}/auth/instagram/callback`; + return "https://modstagram.com/auth/instagram/callback"; } export type InstagramAuthMode = "login" | "register" | "link"; diff --git a/src/locales/en/common.json b/src/locales/en/common.json index ca4412b..8e5511e 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -397,7 +397,33 @@ "unlinkedHint": "Only Instagram Business or Creator accounts can connect. Once connected, you can import your bio, posts, reels, and profile picture from Instagram.", "disconnectButton": "Disconnect Instagram", "disconnectSuccess": "Instagram account disconnected", - "disconnectError": "Could not disconnect Instagram account" + "disconnectError": "Could not disconnect Instagram account", + "updateButton": "Update Instagram posts", + "chooseModeTitle": "Choose how to import your posts", + "manualMode": "Manual", + "manualModeHint": "Pick which of your Instagram posts to bring over to Modstagram", + "automaticMode": "Automatic", + "automaticModeHint": "Your new Instagram posts will be added to Modstagram automatically", + "autoUpdateOnStatus": "Automatic sync: on", + "turnOffAutoUpdate": "Turn off automatic sync", + "autoUpdateOffSuccess": "Automatic sync turned off", + "importSuccess": "{{count}} posts imported", + "importError": "Error importing posts", + "automaticStartSuccess": "Automatic sync turned on", + "selectPageTitle": "Select posts", + "selectAll": "Select all", + "deselectAll": "Deselect all", + "importSelectedButton": "Import selected posts", + "noMediaFound": "No posts found", + "alreadyImportedBadge": "Already imported", + "unsupportedTypeBadge": "Not supported", + "selectAtLeastOne": "Select at least one post", + "mediaLoadError": "Error loading Instagram posts", + "cancelChoice": "Cancel", + "dataDeletionStatusTitle": "Instagram data deletion status", + "dataDeletionStatusDone": "Your request to delete data received from Instagram (connection token, imported posts, and Instagram profile info) has been completed.", + "dataDeletionStatusCode": "Confirmation code", + "dataDeletionInstructions": "To delete the data Modstagram received from your Instagram account (connection token, imported posts, and profile info), go to Settings > Import from Instagram > Disconnect inside Modstagram, or remove Modstagram's access from Instagram's Settings > Apps and websites. Either way, this data is deleted automatically." }, "twoFactor": { "loadError": "Could not load two-factor status", @@ -619,6 +645,8 @@ "instagramLoginFailed": "Instagram sign-in failed", "instagramAccountTypeError": "Only Instagram Business or Creator accounts can connect.", "usernameSuggestedFromInstagram": "This username was suggested from Instagram; you can change it", + "nameSuggestedFromInstagram": "This name was suggested from Instagram; you can change it", + "instagramContinuingToProfile": "Your name was saved. Continuing to the next step...", "orLoginWith": "Or continue with", "orRegisterWith": "Or sign up with", "twoFactorTitle": "Two-factor authentication", diff --git a/src/locales/fa/common.json b/src/locales/fa/common.json index 36f514d..f0550ae 100644 --- a/src/locales/fa/common.json +++ b/src/locales/fa/common.json @@ -397,7 +397,33 @@ "unlinkedHint": "فقط حساب‌های Business یا Creator اینستاگرام قابل اتصال هستند. پس از اتصال می‌توانید بیو، پست‌ها، ریلزها و عکس پروفایل خود را از اینستاگرام وارد کنید.", "disconnectButton": "قطع اتصال اینستاگرام", "disconnectSuccess": "اتصال اینستاگرام قطع شد", - "disconnectError": "خطا در قطع اتصال اینستاگرام" + "disconnectError": "خطا در قطع اتصال اینستاگرام", + "updateButton": "بروزرسانی پست‌های اینستاگرام", + "chooseModeTitle": "روش انتقال پست‌ها را انتخاب کنید", + "manualMode": "دستی", + "manualModeHint": "از بین پست‌های اینستاگرامتان انتخاب کنید کدام‌ها به مدستاگرام منتقل شوند", + "automaticMode": "خودکار", + "automaticModeHint": "پست‌های جدید اینستاگرام شما به‌طور خودکار به مدستاگرام اضافه می‌شوند", + "autoUpdateOnStatus": "همگام‌سازی خودکار: فعال", + "turnOffAutoUpdate": "غیرفعال کردن همگام‌سازی خودکار", + "autoUpdateOffSuccess": "همگام‌سازی خودکار غیرفعال شد", + "importSuccess": "{{count}} پست منتقل شد", + "importError": "خطا در انتقال پست‌ها", + "automaticStartSuccess": "همگام‌سازی خودکار فعال شد", + "selectPageTitle": "انتخاب پست‌ها", + "selectAll": "انتخاب همه", + "deselectAll": "لغو انتخاب همه", + "importSelectedButton": "انتقال پست‌های انتخاب‌شده", + "noMediaFound": "پستی برای نمایش یافت نشد", + "alreadyImportedBadge": "قبلاً منتقل شده", + "unsupportedTypeBadge": "پشتیبانی نمی‌شود", + "selectAtLeastOne": "حداقل یک پست انتخاب کنید", + "mediaLoadError": "خطا در دریافت پست‌های اینستاگرام", + "cancelChoice": "انصراف", + "dataDeletionStatusTitle": "وضعیت حذف داده‌های اینستاگرام", + "dataDeletionStatusDone": "درخواست حذف داده‌های دریافت‌شده از اینستاگرام (توکن اتصال، پست‌های وارد‌شده و اطلاعات پروفایل اینستاگرام) با موفقیت انجام شد.", + "dataDeletionStatusCode": "کد پیگیری", + "dataDeletionInstructions": "برای حذف داده‌هایی که مدستاگرام از حساب اینستاگرام شما دریافت کرده (توکن اتصال، پست‌های وارد‌شده و اطلاعات پروفایل)، می‌توانید از داخل مدستاگرام در مسیر «تنظیمات > وارد کردن از اینستاگرام > قطع اتصال» اقدام کنید، یا از داخل اینستاگرام در مسیر «تنظیمات > برنامه‌ها و وب‌سایت‌ها» دسترسی مدستاگرام را حذف کنید؛ در هر دو حالت این داده‌ها به‌صورت خودکار حذف می‌شوند." }, "twoFactor": { "loadError": "خطا در دریافت وضعیت ورود دو مرحله‌ای", @@ -621,6 +647,8 @@ "instagramLoginFailed": "ورود با اینستاگرام ناموفق بود", "instagramAccountTypeError": "فقط حساب‌های Business یا Creator اینستاگرام قابل اتصال هستند.", "usernameSuggestedFromInstagram": "این نام کاربری از اینستاگرام پیشنهاد شده؛ می‌توانید تغییرش دهید", + "nameSuggestedFromInstagram": "این نام از اینستاگرام پیشنهاد شده؛ می‌توانید تغییرش دهید", + "instagramContinuingToProfile": "نام شما ثبت شد. در حال انتقال به مرحله‌ی بعد...", "twoFactorTitle": "ورود دو مرحله‌ای", "twoFactorHint": "کد ۶ رقمی Google Authenticator را وارد کنید", "mobileInvalid": "شماره موبایل معتبر نیست",