instagram

This commit is contained in:
payacom
2026-08-13 13:09:25 +03:30
parent 54623fb720
commit ef2e86861a
31 changed files with 636 additions and 297 deletions

View File

@@ -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")}
</small>
</Link>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</div>
</Container>
</LocalePageShell>

View File

@@ -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() {
<span className="text-[#0033EA]">{t("auth.signUp")}</span>
</small>
</Link>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</div>
</Container>
</LocalePageShell>

View File

@@ -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 (
<LocalePageShell>
<Container>
<div className="p-4 flex flex-col items-center h-screen justify-center">
<div className="relative p-4 flex flex-col items-center h-screen justify-center">
<AuthLanguageToggle className="absolute top-6 left-1/2 -translate-x-1/2" />
<AuthHead title={t("auth.login")} />
<form
onSubmit={formik.handleSubmit}
@@ -99,12 +99,11 @@ function Login() {
{t("auth.loginWithUsername")}
</small>
</Link>
<button onClick={() => setModalOpen(true)} className="mb-2">
<Link href="/privacy" className="mb-2">
<small className="text-[#FF5C00] font-bold text-xs mt-8 block">
{t("auth.rules")}
</small>
</button>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</Link>
</div>
</Container>
</LocalePageShell>

View File

@@ -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")}
</small>
</Link>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</div>
</Container>
</LocalePageShell>

View File

@@ -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")}
</small>
</Link>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</div>
</Container>
</LocalePageShell>

View File

@@ -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<string | null>(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 (
<LocalePageShell>
<AuthPageLayout>
@@ -102,6 +137,16 @@ function FullNamePage() {
<AuthPageContent>
<AuthHead title={t("auth.fullNameTitle")} />
<div className="w-full max-w-sm flex flex-col items-center">
{isInstagramProvider && avatar && (
<Image
src={avatar.startsWith("data:") ? avatar : IMAGE_BASE_URL + avatar}
alt=""
width={72}
height={72}
unoptimized
className="mb-2 h-[72px] w-[72px] rounded-full object-cover"
/>
)}
<AuthInput
name="name"
type="text"
@@ -117,10 +162,18 @@ function FullNamePage() {
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.name && formik.errors.name && (
{formik.touched.name && formik.errors.name ? (
<small className="text-red-500 mt-2 block text-center">
{formik.errors.name}
</small>
) : (
isInstagramProvider &&
instagramSuggested.name &&
formik.values.name === instagramSuggested.name && (
<small className="text-gray-400 mt-2 block text-center text-xs">
{t("auth.nameSuggestedFromInstagram")}
</small>
)
)}
<AuthInput
@@ -146,7 +199,9 @@ function FullNamePage() {
{nameSaved && (
<p className="text-sm text-green-600 text-center mt-4">
{t("auth.nameSavedChoose")}
{isInstagramProvider
? t("auth.instagramContinuingToProfile")
: t("auth.nameSavedChoose")}
</p>
)}
</div>
@@ -161,7 +216,7 @@ function FullNamePage() {
>
{t("auth.saveAndContinue")}
</AuthNextButton>
) : (
) : !isInstagramProvider ? (
<div className="flex flex-col sm:flex-row gap-3 w-full max-w-sm justify-center items-center">
<AuthNextButton
type="button"
@@ -182,7 +237,7 @@ function FullNamePage() {
{t("auth.enterApp")}
</AuthNextButton>
</div>
)}
) : null}
</AuthFormFooter>
</form>
</AuthPageLayout>

View File

@@ -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 (
<LocalePageShell>
<Container>
<div className="p-4 flex flex-col items-center h-screen justify-center">
<div className="relative p-4 flex flex-col items-center h-screen justify-center">
<AuthLanguageToggle className="absolute top-6 left-1/2 -translate-x-1/2" />
<AuthHead title={t("auth.register")} />
<form
onSubmit={formik.handleSubmit}
@@ -134,12 +134,11 @@ function Register() {
<span className="text-[#0033EA]">{t("auth.login")}</span>
</small>
</Link>
<button type="button" onClick={() => setModalOpen(true)} className="mb-2">
<Link href="/privacy" className="mb-2">
<small className="text-[#FF5C00] font-bold text-xs mt-8 block">
{t("auth.rules")}
</small>
</button>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</Link>
</div>
</Container>
</LocalePageShell>

View File

@@ -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<string | null>(null);
const [isCheckedOne, setIsCheckedOne] = useState(false);
const [isModalOpen, setModalOpen] = useState<boolean>(false);
const toggleCheckBox = () => setIsCheckedOne(!isCheckedOne);
@@ -144,15 +143,14 @@ function NationalCart() {
type="checkbox"
onChange={toggleCheckBox}
/>
<span
onClick={() => setModalOpen(true)}
<Link
href="/privacy"
target="_blank"
className="text-xs font-bold cursor-pointer"
>
{t("auth.acceptRulesLabel")}
</span>
</Link>
</div>
<AuthRules isModalOpen={isModalOpen} setModalOpen={setModalOpen} />
</AuthPageContent>
<AuthFormFooter onSkip={() => router.push("/verify/confirm")}>

View File

@@ -19,7 +19,7 @@ function AboutUs() {
</div>
<div className="flex flex-col w-full mt-6 gap-1">
<span>{t("aboutPage.phoneLabel")}</span>
<span>09128893712</span>
<span>02167586</span>
</div>
</div>
<div className="w-full flex justify-center items-center">

View File

@@ -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);
}

View File

@@ -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 (
<LocalePageShell>
<Container>
<div className="mx-auto flex max-w-2xl flex-col px-4 py-6">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-lg font-bold">
{t("settings.edit.instagramImport.dataDeletionStatusTitle")}
</h1>
<AuthLanguageToggle />
</div>
<p className="text-sm leading-7 text-neutral-600 dark:text-neutral-300">
{code
? t("settings.edit.instagramImport.dataDeletionStatusDone")
: t("settings.edit.instagramImport.dataDeletionInstructions")}
</p>
{code && (
<p className="mt-4 text-xs text-neutral-400" dir="ltr">
{t("settings.edit.instagramImport.dataDeletionStatusCode")}: {code}
</p>
)}
</div>
</Container>
</LocalePageShell>
);
}

View File

@@ -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={
<ScoreRateStats score={userDetail?.user_score} rate={userDetail?.rate} />
}
/>
<div className="flex items-center justify-center gap-0.5">
<span>{userDetail?.user_score || "0"}</span>
<Image
width={21}
height={21}
alt=""
src={`/images/icons/medal-star.png`}
className="pb-1"
/>
</div>
<div className="flex items-center gap-0.5">
<span>{userDetail?.rate || "0"}</span>
<Image
width={21}
height={21}
alt=""
src={`/images/icons/star1.png`}
className="pb-1"
/>
</div>
</div>
) : null;

45
src/app/privacy/page.tsx Normal file
View File

@@ -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<string>("");
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 (
<LocalePageShell>
<Container>
<div className="mx-auto flex max-w-2xl flex-col px-4 py-6">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-lg font-bold">{t("auth.rules")}</h1>
<AuthLanguageToggle />
</div>
{loading ? (
<p>{t("auth.rulesLoading")}</p>
) : error ? (
<p>{t("auth.rulesError")}</p>
) : (
<div
className="text-sm leading-7"
dangerouslySetInnerHTML={{ __html: rules }}
/>
)}
</div>
</Container>
</LocalePageShell>
);
}

View File

@@ -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 (
<LocalePageShell>
<Container>
@@ -86,6 +121,71 @@ export default function InstagramImportSettingsPage() {
@{status.instagram_username}
</p>
) : null}
{autoUpdateOn && (
<p className="mt-4 text-sm font-semibold text-[#0C8002]">
{t("settings.edit.instagramImport.autoUpdateOnStatus")}
</p>
)}
{!showModeChoice ? (
<button
type="button"
onClick={() => setShowModeChoice(true)}
className="mt-6 rounded-2xl border-2 border-[#0095f6] px-4 py-2 text-sm font-semibold text-[#0095f6]"
>
{t("settings.edit.instagramImport.updateButton")}
</button>
) : (
<div className="mt-6 flex w-full flex-col gap-3">
<p className="text-sm font-semibold">
{t("settings.edit.instagramImport.chooseModeTitle")}
</p>
<Link
href="/settings/edit/instagram-import/select"
className="rounded-2xl border-2 border-gray-200 px-4 py-3 text-sm dark:border-gray-700"
>
<span className="block font-semibold">
{t("settings.edit.instagramImport.manualMode")}
</span>
<span className="mt-1 block text-xs text-neutral-500">
{t("settings.edit.instagramImport.manualModeHint")}
</span>
</Link>
<button
type="button"
onClick={handleAutomaticImport}
disabled={importingAuto}
className="rounded-2xl border-2 border-gray-200 px-4 py-3 text-right text-sm disabled:opacity-60 dark:border-gray-700"
>
<span className="block font-semibold">
{t("settings.edit.instagramImport.automaticMode")}
</span>
<span className="mt-1 block text-xs text-neutral-500">
{t("settings.edit.instagramImport.automaticModeHint")}
</span>
</button>
<button
type="button"
onClick={() => setShowModeChoice(false)}
className="text-xs text-neutral-400"
>
{t("settings.edit.instagramImport.cancelChoice")}
</button>
</div>
)}
{autoUpdateOn && (
<button
type="button"
onClick={handleTurnOffAutoUpdate}
disabled={togglingAutoOff}
className="mt-4 text-xs text-neutral-400 disabled:opacity-60"
>
{t("settings.edit.instagramImport.turnOffAutoUpdate")}
</button>
)}
<button
type="button"
onClick={handleDisconnect}

View File

@@ -0,0 +1,168 @@
"use client";
import Container from "@/components/elements/Container";
import PageTitle from "@/components/settings/PageTitle";
import LocalePageShell from "@/components/i18n/LocalePageShell";
import useAxios from "@/hooks/useAxios";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
type InstagramMediaItem = {
id: string;
caption: string | null;
media_type: "IMAGE" | "VIDEO" | "CAROUSEL_ALBUM";
media_url: string;
permalink: string;
timestamp: string;
supported: boolean;
already_imported: boolean;
};
export default function InstagramImportSelectPage() {
const { t } = useTranslation("common");
const router = useRouter();
const { request } = useAxios();
const [media, setMedia] = useState<InstagramMediaItem[]>([]);
const [loading, setLoading] = useState(true);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const [importing, setImporting] = useState(false);
useEffect(() => {
request<{ media: InstagramMediaItem[] }>("GET", "/auth/instagram/media")
.then((res) => setMedia(res?.media || []))
.catch(() => toast.error(t("settings.edit.instagramImport.mediaLoadError")))
.finally(() => setLoading(false));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const eligibleIds = media
.filter((item) => item.supported && !item.already_imported)
.map((item) => item.id);
const toggleItem = (id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
};
const toggleSelectAll = () => {
setSelectedIds((prev) =>
prev.size === eligibleIds.length ? new Set() : new Set(eligibleIds)
);
};
const handleImport = async () => {
if (selectedIds.size === 0) {
toast.error(t("settings.edit.instagramImport.selectAtLeastOne"));
return;
}
setImporting(true);
try {
const res = await request<{ imported_count: number }>(
"POST",
"/auth/instagram/import",
{ mode: "manual", mediaIds: Array.from(selectedIds) }
);
toast.success(
t("settings.edit.instagramImport.importSuccess", {
count: res?.imported_count ?? selectedIds.size,
})
);
router.push("/settings/edit/instagram-import");
} catch {
toast.error(t("settings.edit.instagramImport.importError"));
} finally {
setImporting(false);
}
};
return (
<LocalePageShell>
<Container className="pb-28">
<PageTitle>{t("settings.edit.instagramImport.selectPageTitle")}</PageTitle>
{loading ? (
<p className="py-8 text-center text-sm text-neutral-500">
{t("common.loading")}
</p>
) : media.length === 0 ? (
<p className="py-8 text-center text-sm text-neutral-500">
{t("settings.edit.instagramImport.noMediaFound")}
</p>
) : (
<>
{eligibleIds.length > 0 && (
<button
type="button"
onClick={toggleSelectAll}
className="my-3 text-xs font-semibold text-[#0095f6]"
>
{selectedIds.size === eligibleIds.length
? t("settings.edit.instagramImport.deselectAll")
: t("settings.edit.instagramImport.selectAll")}
</button>
)}
<div className="grid grid-cols-3 gap-1">
{media.map((item) => {
const disabled = !item.supported || item.already_imported;
const selected = selectedIds.has(item.id);
return (
<button
key={item.id}
type="button"
disabled={disabled}
onClick={() => toggleItem(item.id)}
className={cn(
"relative aspect-square overflow-hidden rounded-md",
disabled && "opacity-40"
)}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={item.media_url}
alt=""
className="h-full w-full object-cover"
/>
{selected && (
<div className="absolute inset-0 border-2 border-[#0095f6] bg-[#0095f6]/20" />
)}
{item.already_imported && (
<span className="absolute bottom-1 left-1 right-1 rounded bg-black/60 px-1 py-0.5 text-[10px] text-white">
{t("settings.edit.instagramImport.alreadyImportedBadge")}
</span>
)}
{!item.supported && !item.already_imported && (
<span className="absolute bottom-1 left-1 right-1 rounded bg-black/60 px-1 py-0.5 text-[10px] text-white">
{t("settings.edit.instagramImport.unsupportedTypeBadge")}
</span>
)}
</button>
);
})}
</div>
</>
)}
{eligibleIds.length > 0 && (
<div className="pointer-events-none fixed bottom-0 left-0 right-0 z-50 flex justify-center px-4 pb-6">
<button
type="button"
onClick={handleImport}
disabled={importing || selectedIds.size === 0}
className="pointer-events-auto w-full max-w-sm rounded-2xl bg-[#0095f6] px-4 py-3 text-sm font-semibold text-white disabled:opacity-50"
>
{t("settings.edit.instagramImport.importSelectedButton")} ({selectedIds.size})
</button>
</div>
)}
</Container>
</LocalePageShell>
);
}

View File

@@ -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}
<VerificationBadge verifyBadge={verify_badge} />
</h2>
<div className="flex items-center gap-0.5 justify-center">
<span className="mr-2">{newScore || 0}</span>
<Image
width={22}
height={22}
alt={t("searchPage.starIconAlt")}
src="/images/icons/medal-star.png"
/>
</div>
<div className="flex items-center gap-0.5 justify-center">
<span className="mr-2">{rating || 0}</span>
<Image
width={22}
height={22}
alt={t("searchPage.rateIconAlt")}
src="/images/icons/star1.png"
/>
</div>
<ScoreRateStats score={newScore} rate={rating} />
</div>
</div>
</div>

View File

@@ -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 (
<div className={cn("flex items-center gap-1.5", className)}>
{ENABLED_LANGUAGES.map((code) => {
const active = language === code;
return (
<button
key={code}
type="button"
onClick={() => handleChange(code)}
className={cn(
"rounded-full border px-3 py-1 text-xs font-semibold transition-colors",
active
? "border-[#0095f6] bg-[#0095f6]/10 text-[#0095f6]"
: "border-neutral-200 text-neutral-500 dark:border-neutral-700 dark:text-neutral-400"
)}
>
{LANGUAGE_LABELS[code].native}
</button>
);
})}
</div>
);
}

View File

@@ -143,17 +143,6 @@ function GoogleSignInInner({
return (
<div className="w-full max-w-[290px] mt-5 flex flex-col items-center">
<div className="relative flex items-center justify-center mb-4 w-full">
<span className="absolute inset-x-0 h-px bg-gray-200 dark:bg-gray-700" />
<span className="relative bg-white dark:bg-[#1a1a1a] px-3 text-xs text-gray-500">
{mode === "register"
? t("auth.orRegisterWith")
: mode === "link"
? t("settings.edit.googleAccount.orLinkWith")
: t("auth.orLoginWith")}
</span>
</div>
<div
className={cn(
"relative w-full max-w-[290px]",

View File

@@ -51,13 +51,6 @@ export default function InstagramSignInButton({
? t("settings.edit.instagramImport.linkButton")
: t("auth.instagramLogin");
const dividerLabel =
mode === "register"
? t("auth.orRegisterWithInstagram")
: mode === "link"
? t("settings.edit.instagramImport.orLinkWith")
: t("auth.orLoginWithInstagram");
const handleClick = () => {
const nonce = generateNonce();
sessionStorage.setItem("ig_oauth_mode", mode);
@@ -71,13 +64,6 @@ export default function InstagramSignInButton({
return (
<div className="w-full max-w-[290px] mt-5 flex flex-col items-center">
<div className="relative flex items-center justify-center mb-4 w-full">
<span className="absolute inset-x-0 h-px bg-gray-200 dark:bg-gray-700" />
<span className="relative bg-white dark:bg-[#1a1a1a] px-3 text-xs text-gray-500">
{dividerLabel}
</span>
</div>
<button
type="button"
onClick={handleClick}

View File

@@ -0,0 +1,29 @@
import Image from "next/image";
import { cn } from "@/lib/utils";
interface ScoreRateStatsProps {
score?: string | number | null;
rate?: string | number | null;
className?: string;
}
export default function ScoreRateStats({ score, rate, className }: ScoreRateStatsProps) {
return (
<span
dir="rtl"
className={cn(
"flex shrink-0 items-center gap-2 text-[11px] font-semibold text-current",
className
)}
>
<span className="flex items-center gap-0.5">
<span className="pt-[6px] text-sm leading-none tabular-nums">{score || "0"}</span>
<Image width={16} height={16} alt="star icon" src="/images/icons/star1.png" />
</span>
<span className="flex items-center gap-0.5">
<span className="pt-[6px] text-sm leading-none tabular-nums">{rate || "0"}</span>
<Image width={16} height={16} alt="rate icon" src="/images/icons/medal-star.png" />
</span>
</span>
);
}

View File

@@ -13,6 +13,7 @@ import MainModelCardActions from "./MainModelCardActions";
import Link from "next/link";
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";
@@ -238,24 +239,7 @@ function MainModelCard({
{user_name}
<VerificationBadge verifyBadge={verify_badge} />
</h2>
<div className="flex items-center gap-0.5 justify-center">
<span className="mr-2">{user_score || 0}</span>
<Image
width={22}
height={22}
alt="star icon"
src="/images/icons/star1.png"
/>
</div>
<div className="flex items-center gap-0.5 justify-center">
<span className="mr-2">{rate || 0}</span>
<Image
width={22}
height={22}
alt="rate icon"
src="/images/icons/medal-star.png"
/>
</div>
<ScoreRateStats score={user_score} rate={rate} />
</div>
<div className="flex justify-between items-center">
<div className="max-sm:text-xs">

View File

@@ -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}
<VerificationBadge verifyBadge={verify_badge} />
</span>
<div className="flex items-center gap-0.5 justify-center">
<span className="mr-2">{user_score || 0}</span>
<Image
width={22}
height={22}
alt="star icon"
src="/images/icons/medal-star.png"
/>
</div>
<div className="flex items-center gap-0.5 justify-center">
<span className="mr-2">{rate || 0}</span>
<Image
width={22}
height={22}
alt="star icon"
src="/images/icons/star1.png"
/>
</div>
<ScoreRateStats score={user_score} rate={rate} />
</div>
<div className="flex justify-between items-center">
<div className="max-sm:text-xs">

View File

@@ -476,6 +476,7 @@ function ModelContent({ user, searchParams }: ModelContentProps) {
_id={user?._id}
token={token}
privateLocked={Boolean(user?.private_content_locked)}
profileUser={user}
/>
))}
</div>

View File

@@ -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<User[]>([]);
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;

View File

@@ -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 (
<div className="py-2 grid w-full grid-cols-3 items-end text-xs md:text-sm font-semibold">
<div className="flex items-center">
<>
<span>{user_score ? user_score : "0"}</span>
<Image
width={22}
height={22}
alt={"star icon"}
src={`/images/icons/star1.png`}
/>
</>
</div>
<div className="flex items-center justify-center">
<>
<span>{rate ? rate : "0"}</span>
<Image
width={22}
height={22}
alt={"rate icon"}
src={`/images/icons/medal-star.png`}
/>
</>
</div>
<div className="py-2 flex w-full items-end justify-between text-xs md:text-sm font-semibold">
<ScoreRateStats score={user_score} rate={rate} />
<div
className={cn(
"flex min-w-0 max-w-full flex-col",

View File

@@ -4,10 +4,10 @@ import Modal from "@/components/elements/Modal";
import RoundedButton from "@/components/elements/RoundedButton";
import RoundedDiv from "@/components/elements/RoundedDiv";
import UserInfo from "@/components/main/UserInfo";
import ScoreRateStats from "@/components/main/ScoreRateStats";
import { buildStorageUrl } from "@/components/main/BaseUrl";
import useAxios from "@/hooks/useAxios";
import { IProjectRequest } from "@/types/types";
import Image from "next/image";
import { useRouter } from "next/navigation";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -77,17 +77,22 @@ function ProjectRequestsAction({
setShowConfirmModal(true);
}}
>
<UserInfo
first_name={item?.user?.first_name}
is_verified={item?.user?.verify_badge}
userId={item?.user?._id}
profile_image={item?.user?.profile_image}
user_level={item?.user?.user_level}
last_name={item?.user?.last_name}
user_name={item?.user?.user_name}
/>
<div className="min-w-0 flex-1">
<UserInfo
first_name={item?.user?.first_name}
is_verified={item?.user?.verify_badge}
userId={item?.user?._id}
profile_image={item?.user?.profile_image}
user_level={item?.user?.user_level}
last_name={item?.user?.last_name}
user_name={item?.user?.user_name}
trailingContent={
<ScoreRateStats score={item?.user?.user_score} rate={item?.user?.rate} />
}
/>
</div>
<div className="flex flex-col items-start gap-1.5 text-[10px] sm:text-xs">
<div className="flex shrink-0 flex-col items-start gap-1.5 text-[10px] sm:text-xs">
<span>
{t("projects.workroom.offerPrice", {
amount: Number(item?.price).toLocaleString(),
@@ -103,30 +108,6 @@ function ProjectRequestsAction({
})}
</span>
) : null}
<div className="flex gap-3">
<div className="flex items-center justify-center gap-0.5">
<span>
{item?.user?.user_score ? item?.user?.user_score : "0"}
</span>
<Image
width={21}
height={21}
alt=""
src={`/images/icons/medal-star.png`}
className="pb-1"
/>
</div>
<div className="flex items-center gap-0.5">
<span>{item?.user?.rate ? item?.user?.rate : "0"}</span>
<Image
width={21}
height={21}
alt=""
src={`/images/icons/star1.png`}
className="pb-1"
/>
</div>
</div>
</div>
</div>
);

View File

@@ -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={
<ScoreRateStats score={selectedUser?.user_score} rate={selectedUser?.rate} />
}
/>
<div className="flex gap-3">
<div className="flex items-center justify-center gap-0.5">
<>
<span>
{selectedUser?.user_score ? selectedUser?.user_score : "0"}
</span>
<Image
width={21}
height={21}
alt={"verify icon"}
src={`/images/icons/medal-star.png`}
className="pb-1"
/>
</>
</div>
<div className="flex items-center gap-0.5">
<>
<span>{selectedUser?.rate ? selectedUser?.rate : "0"}</span>
<Image
width={21}
height={21}
alt={"verify icon"}
src={`/images/icons/star1.png`}
className="pb-1"
/>
</>
</div>
</div>
</div>
</div>
);

View File

@@ -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={
<span className="flex shrink-0 items-center gap-2 text-[11px] font-normal text-neutral-500 dark:text-neutral-400">
<span className="flex items-center gap-0.5">
<span className="pt-[6px] text-sm leading-none">{user?.user_score ? user?.user_score : "0"}</span>
<Image width={16} height={16} alt="star icon" src="/images/icons/star1.png" />
</span>
<span className="flex items-center gap-0.5">
<span className="pt-[6px] text-sm leading-none">{user?.rate ? user?.rate : "0"}</span>
<Image width={16} height={16} alt="rate icon" src="/images/icons/medal-star.png" />
</span>
</span>
}
trailingContent={<ScoreRateStats score={user?.user_score} rate={user?.rate} />}
/>
</Link>
);

View File

@@ -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";

View File

@@ -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",

View File

@@ -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": "شماره موبایل معتبر نیست",