From a1d7db5e7eb48a5a0db989fc65bd49260eb3416b Mon Sep 17 00:00:00 2001 From: payacom <59262811+payacom@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:58:15 +0330 Subject: [PATCH] lang --- ecosystem.config.cjs | 5 + next.config.ts | 43 +- package.json | 3 +- public/favicon-16.png | Bin 0 -> 616 bytes public/favicon.png | Bin 0 -> 1287 bytes public/images/icons/192x192.png | Bin 11045 -> 9008 bytes public/images/icons/256x256.png | Bin 11045 -> 12685 bytes public/images/icons/512x512.png | Bin 20728 -> 26267 bytes public/images/icons/apple-touch-icon.png | Bin 11045 -> 8405 bytes public/images/icons/carousel-next.png | Bin 0 -> 744 bytes public/images/icons/carousel-prev.png | Bin 0 -> 743 bytes public/images/icons/google.png | Bin 0 -> 640 bytes public/images/icons/logo-mark.svg | 18 + public/images/icons/maskable-192.png | Bin 0 -> 7094 bytes public/images/icons/maskable-512.png | Bin 0 -> 26123 bytes public/images/icons/medal-star.png | Bin 0 -> 5927 bytes public/images/icons/reels-follow-add.svg | 4 + public/images/icons/reels-more.png | Bin 0 -> 281 bytes public/images/icons/reels-share-story.png | Bin 0 -> 904 bytes public/images/icons/reels-share-story.svg | 5 + public/images/icons/shield-tick.svg | 3 + public/images/icons/star1.png | Bin 0 -> 3483 bytes public/images/icons/story.png | Bin 0 -> 1039 bytes public/manifest.json | 16 +- public/service-worker.js | 51 +- public/sitemap-0.xml | 14 +- scripts/generate-pwa-icons.mjs | 94 + src/api/fetchPosts.ts | 4 +- src/api/fetchStories.ts | 20 + .../(auth)/(login)/change-passowrd/page.tsx | 228 +- .../(login)/forget-password-otp/page.tsx | 149 +- .../(auth)/(login)/forget-password/page.tsx | 142 +- src/app/(auth)/(login)/login-2fa/page.tsx | 85 + .../(login)/login-with-username/page.tsx | 197 +- src/app/(auth)/(login)/login/page.tsx | 117 +- src/app/(auth)/(login)/verify-otp/page.tsx | 171 +- .../(auth)/(register)/register-otp/page.tsx | 139 +- .../(register)/register/complete/page.tsx | 74 +- .../(register)/register/fullname/page.tsx | 211 +- .../(auth)/(register)/register/loading.tsx | 15 + src/app/(auth)/(register)/register/page.tsx | 111 +- .../(register)/register/password/page.tsx | 153 +- .../(register)/register/username/page.tsx | 172 +- src/app/(auth)/AuthProviders.tsx | 64 +- src/app/(auth)/layout.tsx | 20 +- src/app/(auth)/verify/auth/page.tsx | 253 +-- src/app/(auth)/verify/avatar/page.tsx | 145 +- src/app/(auth)/verify/colors/page.tsx | 203 +- src/app/(auth)/verify/confirm/page.tsx | 216 +- .../(auth)/verify/cooperation-type/page.tsx | 132 +- src/app/(auth)/verify/expertise/page.tsx | 147 +- src/app/(auth)/verify/gender/page.tsx | 146 +- src/app/(auth)/verify/location/page.tsx | 281 +-- src/app/(auth)/verify/national-cart/page.tsx | 182 +- .../(auth)/verify/public-relations/page.tsx | 117 +- src/app/(auth)/verify/services/page.tsx | 100 +- src/app/(auth)/verify/sizes/page.tsx | 252 ++- .../(projects)/academy/[[...slug]]/page.tsx | 25 +- .../academy/[id]/[title]/CourseClient.tsx | 161 +- .../(projects)/academy/[id]/[title]/page.tsx | 13 +- src/app/(projects)/academy/layout.tsx | 3 +- src/app/(projects)/academy/loading.tsx | 15 + .../academy/payment/failed/page.tsx | 47 +- .../academy/payment/success/page.tsx | 34 +- .../[academyId]/AcademyProfileClient.tsx | 129 +- .../academy/profile/[academyId]/page.tsx | 14 +- src/app/(projects)/new-project/layout.tsx | 39 +- .../[id]/[title]/ProjectDetailClient.tsx | 44 +- .../(projects)/projects/[id]/[title]/page.tsx | 23 +- src/app/(projects)/projects/layout.tsx | 3 +- src/app/(projects)/projects/page.tsx | 25 +- src/app/[[...slug]]/page.tsx | 181 +- src/app/about-us/layout.tsx | 12 +- src/app/about-us/page.tsx | 41 +- src/app/api/auth/config/route.ts | 10 + src/app/billboards/[[...slug]]/page.tsx | 155 +- src/app/billboards/[id]/[title]/page.tsx | 143 +- src/app/billboards/loading.tsx | 5 + src/app/billboards/new/[id]/page.tsx | 74 +- src/app/billboards/new/page.tsx | 16 +- src/app/billboards/payment/failed/page.tsx | 114 +- src/app/billboards/payment/success/page.tsx | 77 +- src/app/billboards/profile/[...id]/page.tsx | 79 +- .../billboards/profile/[id]/[title]/page.tsx | 71 +- src/app/explore/[id]/page.tsx | 14 +- src/app/explore/layout.tsx | 12 +- src/app/explore/page.tsx | 55 +- src/app/global-error.tsx | 39 + src/app/globals.css | 23 + src/app/layout.tsx | 128 +- src/app/loading.tsx | 8 +- src/app/manifest.webmanifest/route.ts | 42 + src/app/new-post/layout.tsx | 9 +- src/app/new-post/page.tsx | 264 ++- src/app/offer/[id]/page.tsx | 40 +- src/app/offer/payment/failed/page.tsx | 10 +- src/app/offer/payment/success/page.tsx | 14 +- src/app/posts/[id]/[[...slug]]/page.tsx | 25 +- src/app/robots.ts | 23 +- src/app/search/page.tsx | 49 +- src/app/settings/academy/my-courses/page.tsx | 578 ++--- .../settings/academy/storeProfile/page.tsx | 221 +- src/app/settings/academy/wallet/page.tsx | 68 +- .../settings/chats/[username]/[id]/page.tsx | 161 +- src/app/settings/chats/layout.tsx | 13 +- src/app/settings/chats/page.tsx | 258 ++- .../settings/chats/rooms/[roomId]/page.tsx | 714 ++++++ src/app/settings/chats/rooms/page.tsx | 162 ++ src/app/settings/edit/Authentication/page.tsx | 184 +- src/app/settings/edit/License/page.tsx | 34 +- src/app/settings/edit/avatar/page.tsx | 141 +- src/app/settings/edit/bio/page.tsx | 95 +- src/app/settings/edit/colors/page.tsx | 31 +- .../settings/edit/cooperation-type/page.tsx | 111 +- src/app/settings/edit/expertise/page.tsx | 124 +- src/app/settings/edit/google-account/page.tsx | 70 + src/app/settings/edit/layout.tsx | 10 +- src/app/settings/edit/location/page.tsx | 40 +- src/app/settings/edit/page.tsx | 81 +- src/app/settings/edit/password/page.tsx | 162 +- .../settings/edit/public-relations/page.tsx | 134 +- src/app/settings/edit/services/page.tsx | 122 +- src/app/settings/edit/shaba/page.tsx | 117 +- src/app/settings/edit/sizes/page.tsx | 239 +- src/app/settings/edit/two-factor/page.tsx | 212 ++ src/app/settings/edit/username/page.tsx | 13 +- .../favorites/FavoritesPageClient.tsx | 22 + src/app/settings/favorites/page.tsx | 23 +- src/app/settings/financial/layout.tsx | 10 +- src/app/settings/financial/page.tsx | 89 +- src/app/settings/layout.tsx | 11 +- src/app/settings/loading.tsx | 18 + .../my-billboards/[id]/[title]/page.tsx | 22 +- src/app/settings/my-billboards/layout.tsx | 10 +- src/app/settings/my-billboards/page.tsx | 158 +- .../my-billboards/profile/[id]/page.tsx | 85 +- src/app/settings/notifications/layout.tsx | 10 +- src/app/settings/notifications/page.tsx | 221 +- src/app/settings/offers/layout.tsx | 10 +- src/app/settings/offers/page.tsx | 128 +- src/app/settings/page.tsx | 68 +- src/app/settings/profile/page.tsx | 17 +- .../settings/profile/user-settings/layout.tsx | 10 +- .../settings/profile/user-settings/page.tsx | 164 +- src/app/settings/profile/visitors/layout.tsx | 10 +- src/app/settings/profile/visitors/page.tsx | 117 +- src/app/settings/tickets/layout.tsx | 10 +- src/app/settings/tickets/new/page.tsx | 78 +- src/app/settings/tickets/page.tsx | 163 +- src/app/settings/workroom/[id]/page.tsx | 28 +- src/app/settings/workroom/layout.tsx | 10 +- src/app/settings/workroom/page.tsx | 113 +- src/app/settings/workroom/user/[id]/page.tsx | 28 +- src/app/sitemap.ts | 45 +- src/app/users/[username]/page.tsx | 153 +- src/components/BackgroundPrefetch.tsx | 6 +- src/components/ClientErrorBoundary.tsx | 48 + src/components/Layout.tsx | 12 +- src/components/NewBillboard/Step1.tsx | 65 +- src/components/NewBillboard/Step2.tsx | 46 +- src/components/NewBillboard/Step3.tsx | 32 +- src/components/NewBillboard/Step4.tsx | 72 +- src/components/NewBillboard/Step5.tsx | 72 +- .../NewBillboard/step1/ImageUploader.tsx | 8 +- .../NewBillboard/step1/LocationSelector.tsx | 15 +- src/components/PwaHead.tsx | 50 + src/components/RegisterSW.tsx | 51 +- src/components/ScrollRestorationInit.tsx | 12 + src/components/TabNavigation.tsx | 25 +- src/components/TitleGuardian.tsx | 15 +- src/components/academy/AcademyFilter.tsx | 79 +- src/components/academy/AcademyFilterModal.tsx | 28 +- src/components/academy/AcademyPackageItem.tsx | 5 +- src/components/academy/CommentsModal.tsx | 497 +---- .../academy/CourseCommentsContent.tsx | 367 +++ src/components/academy/InfinitePakage.tsx | 29 +- src/components/academy/MainModelCard.tsx | 100 +- .../academy/MainModelCardActions.tsx | 102 +- src/components/academy/ModelHead.tsx | 10 +- src/components/app-sidebar.tsx | 61 +- src/components/auth/AuthButton.tsx | 10 +- src/components/auth/AuthNextButton.tsx | 10 +- src/components/auth/AuthOtpInput.tsx | 8 +- src/components/auth/AuthOtpStatus.tsx | 26 +- src/components/auth/AuthPageLayout.tsx | 7 +- src/components/auth/AuthPasswordInput.tsx | 6 +- src/components/auth/AuthRules.tsx | 10 +- src/components/auth/ExpertisePicker.tsx | 81 +- src/components/auth/GoogleSignInButton.tsx | 128 +- src/components/auth/RegisterRouteGuard.tsx | 4 +- .../billboards/AdvertisingProfileContent.tsx | 34 + .../billboards/BillboardAdNotFound.tsx | 17 + .../billboards/BillboardDetailContent.tsx | 132 ++ .../billboards/BillboardNotFound.tsx | 17 + .../BillboardContactInfoModal.tsx | 12 +- .../BillboardDescriptionModal.tsx | 9 +- .../BillboardPage/BillboardDetails.tsx | 15 +- .../BillboardPage/BillboardFeaturesModal.tsx | 10 +- .../BillboardPage/BillboardServicesModal.tsx | 9 +- .../BillboardPage/BillboardsFilter.tsx | 82 +- .../billboards/BillboardPage/FilterModal.tsx | 26 +- .../billboards/BillboardsListSection.tsx | 28 + .../billboards/BillboardsLoadingContent.tsx | 18 + .../billboards/InfiniteBillboard.tsx | 30 +- .../BillboardCommentsModal.tsx | 12 +- .../BillboardLocationModal.tsx | 29 +- .../MainBillboardCard/MainBillboardCard.tsx | 67 +- .../MainBillboardCardActions.tsx | 123 +- .../billboards/NewBillboard/Step1.tsx | 65 +- .../billboards/NewBillboard/Step2.tsx | 46 +- .../billboards/NewBillboard/Step3.tsx | 32 +- .../billboards/NewBillboard/Step4.tsx | 72 +- .../billboards/NewBillboard/Step5.tsx | 70 +- .../NewBillboard/step1/ImageUploader.tsx | 8 +- .../NewBillboard/step1/LocationSelector.tsx | 15 +- .../billboards/Profile/AdProfileContent.tsx | 17 +- .../billboards/Profile/AdProfileHead.tsx | 167 +- .../Profile/AdProfileHeadRowTwo.tsx | 4 +- src/components/chart-area-interactive.tsx | 36 +- .../chat/AddChatRoomMembersModal.tsx | 105 + src/components/chat/AttachmentMenu.tsx | 20 +- src/components/chat/ChatActionBar.tsx | 92 +- src/components/chat/ChatDeleteBar.tsx | 9 +- src/components/chat/ChatGuideModal.tsx | 45 +- src/components/chat/ChatHeader.tsx | 19 +- src/components/chat/ChatHeaderMenu.tsx | 14 +- src/components/chat/ChatMessageCard.tsx | 209 +- src/components/chat/ChatMessageList.tsx | 270 ++- src/components/chat/ChatRoomMemberPicker.tsx | 168 ++ src/components/chat/CreateChatRoomModal.tsx | 313 +++ src/components/chat/FileMessageBubble.tsx | 18 +- src/components/chat/ForwardMessageModal.tsx | 19 +- src/components/chat/ImageModal.tsx | 9 +- src/components/chat/LocationMessageBubble.tsx | 8 +- src/components/chat/MediaPreviewModal.tsx | 19 +- src/components/chat/MessageInput.tsx | 81 +- src/components/chat/MessageList.tsx | 6 +- src/components/chat/MultiImageModal.tsx | 22 +- src/components/chat/SharedPostBubble.tsx | 4 +- src/components/chat/SharedStoryBubble.tsx | 6 +- src/components/chat/TimedMessagePicker.tsx | 13 +- src/components/chat/TypingIndicator.tsx | 8 +- src/components/chat/VideoMessageBubble.tsx | 77 +- src/components/chat/ViewOnceMediaBubble.tsx | 6 +- src/components/chat/ViewOnceToggle.tsx | 9 +- src/components/chat/VoiceMessagePlayer.tsx | 8 +- src/components/course/CourseForm.tsx | 233 +- src/components/course/CourseList.tsx | 39 +- src/components/course/CourseManager.tsx | 112 +- src/components/course/CustomDialog.tsx | 3 +- src/components/course/SubscriptionCard.tsx | 36 +- src/components/elements/Container.tsx | 2 +- src/components/elements/ThemeSwitcher.tsx | 52 +- src/components/explore/ExploreAcademyCard.tsx | 33 +- src/components/explore/ExploreFilterBar.tsx | 39 +- src/components/explore/ExploreGrid.tsx | 148 +- src/components/explore/ExploreReelsView.tsx | 27 +- src/components/explore/ExploreVideoThumb.tsx | 51 +- src/components/explore/ReelsAcademyCard.tsx | 15 +- src/components/i18n/LocalePageShell.tsx | 37 + src/components/main/BaseUrl.ts | 2 +- src/components/main/DirectionMapModal.tsx | 28 +- src/components/main/Header.tsx | 30 +- src/components/main/PrivacyPolicyModal.tsx | 17 +- src/components/main/ReportModal.tsx | 20 +- .../main/Services/AddServiceModal.tsx | 109 +- src/components/main/Services/ServiceItem.tsx | 15 +- src/components/main/SharePostModal.tsx | 22 +- src/components/main/UserInfo.tsx | 11 +- src/components/main/UserInfoClient.tsx | 17 + src/components/main/VerificationBadge.tsx | 23 +- .../models/AppearanceColorSelect.tsx | 4 +- src/components/models/FilterModal.tsx | 78 +- src/components/models/InfinitePosts.tsx | 66 +- .../models/MainModelCard/MainModelCard.tsx | 153 +- .../MainModelCard/MainModelCardActions.tsx | 24 +- .../models/MainModelCard/SwipeImageSlider.tsx | 78 +- src/components/models/ModelDetailModal.tsx | 41 +- .../models/ModelPage/LocationModal.tsx | 20 +- .../models/ModelPage/MainModelCardPost.tsx | 18 +- .../models/ModelPage/ModelContent.tsx | 67 +- .../models/ModelPage/ModelContentPosts.tsx | 75 +- .../models/ModelPage/ModelContentProjects.tsx | 6 +- src/components/models/ModelPage/ModelHead.tsx | 12 +- .../models/ModelPage/ModelHeadRowThree.tsx | 35 +- .../models/ModelPage/ModelHeadRowTwo.tsx | 8 +- .../models/ModelPage/ProfileUserListModal.tsx | 8 +- src/components/models/ModelServiceModal.tsx | 15 +- src/components/models/ModelsFilter.tsx | 15 +- src/components/posts/CaptionWithMentions.tsx | 193 +- src/components/posts/ExpandableCaption.tsx | 154 ++ .../posts/FollowingSuggestionsCarousel.tsx | 40 +- src/components/posts/PostCommentsContent.tsx | 34 +- .../posts/PostExpertiseRequiredModal.tsx | 48 + src/components/posts/PostFeedView.tsx | 149 +- src/components/posts/PostLinkPicker.tsx | 347 +++ .../posts/PostLocationNoticeModal.tsx | 54 + src/components/posts/ReelsImageCarousel.tsx | 84 +- src/components/posts/ReelsPostActions.tsx | 279 ++- .../posts/ReelsPostAnalyticsModal.tsx | 238 ++ src/components/posts/ReelsPostCard.tsx | 261 ++- src/components/posts/ReelsPostOptionsMenu.tsx | 219 ++ src/components/posts/ReelsTabBar.tsx | 33 +- src/components/posts/SendPostModal.tsx | 18 +- src/components/posts/TagUsersPicker.tsx | 14 +- src/components/posts/VideoCoverPicker.tsx | 148 ++ src/components/profile/ExpandableBio.tsx | 48 + src/components/projects/FilterModal.tsx | 40 +- src/components/projects/InfiniteProjects.tsx | 42 +- src/components/projects/MainProjectCard.tsx | 44 +- src/components/projects/NewProject/Step1.tsx | 44 +- src/components/projects/NewProject/Step2.tsx | 58 +- src/components/projects/NewProject/Step3.tsx | 48 +- .../NewProject/step1/AgeSelection.tsx | 12 +- .../NewProject/step1/ExpertiseSelector.tsx | 71 +- .../NewProject/step1/GenderSelection.tsx | 9 +- .../NewProject/step1/LocationSelector.tsx | 10 +- .../NewProject/step1/PublicTypeSelection.tsx | 9 +- .../projects/ProjectPage/ProjectCreator.tsx | 10 +- .../ProjectPage/ProjectRequestForm.tsx | 32 +- .../projects/ProjectPage/ProjectRequests.tsx | 6 +- src/components/projects/ProjectsFilter.tsx | 21 +- .../Workroom/ProjectRequestsAction.tsx | 27 +- .../Workroom/ProjectWorkroomActions.tsx | 10 +- .../projects/Workroom/ProjectWorkroomRate.tsx | 18 +- .../Workroom/ProjectWorkroomSelectedUser.tsx | 11 +- src/components/pwa/PwaInstallPrompt.tsx | 51 +- src/components/search/InfiniteSearch.tsx | 47 +- src/components/search/SearchCard.tsx | 10 +- src/components/search/SearchFilter.tsx | 12 +- src/components/section-cards.tsx | 110 +- src/components/settings/FavoritesGrid.tsx | 9 +- .../settings/LanguageSettingRow.tsx | 68 + src/components/settings/ListDateSeparator.tsx | 17 + src/components/settings/NotificationRow.tsx | 101 + src/components/settings/SettingsClient.tsx | 74 + src/components/settings/SettingsShell.tsx | 4 +- src/components/settings/UserDetails.tsx | 4 +- src/components/settings/VisitorRow.tsx | 68 + .../my-billboards/BillboardLocationLabels.tsx | 42 + .../settings/my-billboards/FilterModal.tsx | 35 +- .../MyBillboardStatusActions.tsx | 30 +- .../my-billboards/Profile/AddressFields.tsx | 14 +- .../my-billboards/Profile/AvatarUploader.tsx | 10 +- .../Profile/ContactInfoFields.tsx | 16 +- .../Profile/ProfileBasicFields.tsx | 12 +- .../settings/offers/CommentModal.tsx | 25 +- .../settings/offers/ConfirmModal.tsx | 6 +- src/components/settings/offers/OffersItem.tsx | 19 +- .../settings/profile/AccountActionModals.tsx | 41 +- .../settings/profile/ProfileContent.tsx | 23 +- .../settings/settings/CompleteRegister.tsx | 6 +- src/components/settings/settings/Logout.tsx | 27 +- src/components/settings/settings/Roules.tsx | 4 +- .../settings/settings/Suggestions.tsx | 152 ++ src/components/site-header.tsx | 27 +- src/components/stories/StoriesBar.tsx | 124 +- src/components/stories/StoryAvatarOpen.tsx | 75 + src/components/stories/StoryEditor.tsx | 20 +- src/components/stories/StoryRing.tsx | 105 + src/components/stories/StoryViewer.tsx | 197 +- src/components/ui/BackToTopButton.tsx | 5 +- src/components/ui/BoldIcon.tsx | 2 +- src/components/ui/Category.tsx | 11 +- src/components/ui/LanguageSwitcher.tsx | 55 +- src/components/ui/SellersCart.tsx | 35 +- src/components/ui/ThemeToggleButton.tsx | 7 +- src/config/googleAuth.ts | 3 + src/config/pageSeo.ts | 139 +- src/config/pageSeo.types.ts | 6 + src/config/seoConfig.ts | 10 +- src/config/site.ts | 1 + src/constants/exploreFilters.ts | 21 +- src/constants/index.ts | 83 +- src/constants/notificationFilters.ts | 177 ++ src/constants/reelsFeed.ts | 3 + src/contexts/ExploreReelsUiContext.tsx | 2 +- src/contexts/LanguageProvider.tsx | 88 + src/contexts/ThemeContext.tsx | 7 +- src/hooks/useListScrollRestoration.ts | 36 + src/hooks/useLongPress.ts | 34 +- src/hooks/usePageTitle.ts | 33 +- src/hooks/usePostSocialActions.tsx | 81 +- src/hooks/useStoryMetaForUser.ts | 38 + src/lib/api/upstreamProxy.ts | 1 + src/lib/auth/googleClientId.ts | 10 + src/lib/auth/postLogin.ts | 136 +- src/lib/auth/session.ts | 40 +- src/lib/billboards/displayTypeLabel.ts | 36 + src/lib/buildAcademyListSeo.ts | 140 +- src/lib/buildBillboardsListSeo.ts | 150 ++ src/lib/buildCourseDetailSeo.ts | 39 +- src/lib/buildHomeListSeo.ts | 170 ++ src/lib/buildProfileSeo.ts | 135 ++ src/lib/buildProjectDetailSeo.ts | 57 +- src/lib/buildProjectsListSeo.ts | 91 +- src/lib/captionLength.ts | 41 + src/lib/chat/dedupeMessages.ts | 33 +- src/lib/chat/formatMessageTime.ts | 168 +- src/lib/chat/getCopyableMessageText.ts | 111 + src/lib/chat/groupMessagesByDate.ts | 8 +- src/lib/chat/messageGrouping.ts | 5 + src/lib/chat/onlineStatus.ts | 20 + src/lib/chat/reactions.ts | 15 + src/lib/chat/socketClient.ts | 69 +- src/lib/chat/threadCache.ts | 22 + src/lib/chat/timedMessages.ts | 41 +- src/lib/chat/viewOnce.ts | 15 +- src/lib/formatCoursePriceLabel.ts | 20 +- src/lib/formatViewCount.ts | 15 + src/lib/groupItemsByDate.ts | 47 + src/lib/i18n.ts | 43 +- src/lib/i18n/clientLanguage.ts | 56 + src/lib/i18n/constants.ts | 16 + src/lib/i18n/index.ts | 76 + src/lib/i18n/pageDirection.ts | 34 + src/lib/i18n/pwaLang.ts | 16 + src/lib/i18n/registry.ts | 100 + src/lib/i18n/resolveLanguage.ts | 61 + src/lib/i18n/resources.ts | 58 + src/lib/i18n/seo.ts | 164 ++ src/lib/i18n/server.ts | 57 + src/lib/i18n/syncDocumentLanguage.ts | 9 + src/lib/i18n/translate.ts | 27 + src/lib/iranSheba.ts | 82 + src/lib/listScrollRestoration.ts | 47 + src/lib/postExpertise.ts | 17 + src/lib/postLinkCaption.ts | 228 ++ src/lib/postLocationNotice.ts | 52 + src/lib/postPackageCaption.ts | 1 + src/lib/postSlug.ts | 47 +- src/lib/pwa/install.ts | 11 +- src/lib/pwaConfig.ts | 111 + src/lib/reelsSeedPost.ts | 16 + src/lib/safeStorage.ts | 28 + src/lib/seo/formatSeoTitle.ts | 21 + src/lib/subExpertiseDisplay.ts | 37 + src/lib/validation/username.ts | 10 +- src/locales/en/common.json | 1965 +++++++++++++++++ src/locales/en/seo.json | 208 ++ src/locales/fa/common.json | 1965 +++++++++++++++++ src/locales/fa/seo.json | 208 ++ src/providers/ChatSocketProvider.tsx | 35 + src/types/types.ts | 23 +- src/utils/generatePageMetadata.ts | 56 +- 445 files changed, 23456 insertions(+), 8865 deletions(-) create mode 100644 public/favicon-16.png create mode 100644 public/favicon.png create mode 100644 public/images/icons/carousel-next.png create mode 100644 public/images/icons/carousel-prev.png create mode 100644 public/images/icons/google.png create mode 100644 public/images/icons/logo-mark.svg create mode 100644 public/images/icons/maskable-192.png create mode 100644 public/images/icons/maskable-512.png create mode 100644 public/images/icons/medal-star.png create mode 100644 public/images/icons/reels-follow-add.svg create mode 100644 public/images/icons/reels-more.png create mode 100644 public/images/icons/reels-share-story.png create mode 100644 public/images/icons/reels-share-story.svg create mode 100644 public/images/icons/shield-tick.svg create mode 100644 public/images/icons/star1.png create mode 100644 public/images/icons/story.png create mode 100644 scripts/generate-pwa-icons.mjs create mode 100644 src/app/(auth)/(login)/login-2fa/page.tsx create mode 100644 src/app/(auth)/(register)/register/loading.tsx create mode 100644 src/app/(projects)/academy/loading.tsx create mode 100644 src/app/api/auth/config/route.ts create mode 100644 src/app/billboards/loading.tsx create mode 100644 src/app/global-error.tsx create mode 100644 src/app/manifest.webmanifest/route.ts create mode 100644 src/app/settings/chats/rooms/[roomId]/page.tsx create mode 100644 src/app/settings/chats/rooms/page.tsx create mode 100644 src/app/settings/edit/google-account/page.tsx create mode 100644 src/app/settings/edit/two-factor/page.tsx create mode 100644 src/app/settings/favorites/FavoritesPageClient.tsx create mode 100644 src/app/settings/loading.tsx create mode 100644 src/components/ClientErrorBoundary.tsx create mode 100644 src/components/PwaHead.tsx create mode 100644 src/components/ScrollRestorationInit.tsx create mode 100644 src/components/academy/CourseCommentsContent.tsx create mode 100644 src/components/billboards/AdvertisingProfileContent.tsx create mode 100644 src/components/billboards/BillboardAdNotFound.tsx create mode 100644 src/components/billboards/BillboardDetailContent.tsx create mode 100644 src/components/billboards/BillboardNotFound.tsx create mode 100644 src/components/billboards/BillboardsListSection.tsx create mode 100644 src/components/billboards/BillboardsLoadingContent.tsx create mode 100644 src/components/chat/AddChatRoomMembersModal.tsx create mode 100644 src/components/chat/ChatRoomMemberPicker.tsx create mode 100644 src/components/chat/CreateChatRoomModal.tsx create mode 100644 src/components/i18n/LocalePageShell.tsx create mode 100644 src/components/main/UserInfoClient.tsx create mode 100644 src/components/posts/ExpandableCaption.tsx create mode 100644 src/components/posts/PostExpertiseRequiredModal.tsx create mode 100644 src/components/posts/PostLinkPicker.tsx create mode 100644 src/components/posts/PostLocationNoticeModal.tsx create mode 100644 src/components/posts/ReelsPostAnalyticsModal.tsx create mode 100644 src/components/posts/ReelsPostOptionsMenu.tsx create mode 100644 src/components/posts/VideoCoverPicker.tsx create mode 100644 src/components/profile/ExpandableBio.tsx create mode 100644 src/components/settings/LanguageSettingRow.tsx create mode 100644 src/components/settings/ListDateSeparator.tsx create mode 100644 src/components/settings/NotificationRow.tsx create mode 100644 src/components/settings/SettingsClient.tsx create mode 100644 src/components/settings/VisitorRow.tsx create mode 100644 src/components/settings/my-billboards/BillboardLocationLabels.tsx create mode 100644 src/components/settings/settings/Suggestions.tsx create mode 100644 src/components/stories/StoryAvatarOpen.tsx create mode 100644 src/components/stories/StoryRing.tsx create mode 100644 src/config/googleAuth.ts create mode 100644 src/config/pageSeo.types.ts create mode 100644 src/config/site.ts create mode 100644 src/constants/notificationFilters.ts create mode 100644 src/contexts/LanguageProvider.tsx create mode 100644 src/hooks/useListScrollRestoration.ts create mode 100644 src/hooks/useStoryMetaForUser.ts create mode 100644 src/lib/auth/googleClientId.ts create mode 100644 src/lib/billboards/displayTypeLabel.ts create mode 100644 src/lib/buildBillboardsListSeo.ts create mode 100644 src/lib/buildHomeListSeo.ts create mode 100644 src/lib/buildProfileSeo.ts create mode 100644 src/lib/captionLength.ts create mode 100644 src/lib/chat/getCopyableMessageText.ts create mode 100644 src/lib/chat/onlineStatus.ts create mode 100644 src/lib/chat/reactions.ts create mode 100644 src/lib/formatViewCount.ts create mode 100644 src/lib/groupItemsByDate.ts create mode 100644 src/lib/i18n/clientLanguage.ts create mode 100644 src/lib/i18n/constants.ts create mode 100644 src/lib/i18n/index.ts create mode 100644 src/lib/i18n/pageDirection.ts create mode 100644 src/lib/i18n/pwaLang.ts create mode 100644 src/lib/i18n/registry.ts create mode 100644 src/lib/i18n/resolveLanguage.ts create mode 100644 src/lib/i18n/resources.ts create mode 100644 src/lib/i18n/seo.ts create mode 100644 src/lib/i18n/server.ts create mode 100644 src/lib/i18n/syncDocumentLanguage.ts create mode 100644 src/lib/i18n/translate.ts create mode 100644 src/lib/iranSheba.ts create mode 100644 src/lib/listScrollRestoration.ts create mode 100644 src/lib/postExpertise.ts create mode 100644 src/lib/postLinkCaption.ts create mode 100644 src/lib/postLocationNotice.ts create mode 100644 src/lib/postPackageCaption.ts create mode 100644 src/lib/pwaConfig.ts create mode 100644 src/lib/safeStorage.ts create mode 100644 src/lib/seo/formatSeoTitle.ts create mode 100644 src/lib/subExpertiseDisplay.ts create mode 100644 src/locales/en/common.json create mode 100644 src/locales/en/seo.json create mode 100644 src/locales/fa/common.json create mode 100644 src/locales/fa/seo.json create mode 100644 src/providers/ChatSocketProvider.tsx diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 264d4cc..c4920f5 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -18,6 +18,11 @@ module.exports = { NEXT_PUBLIC_SOCKET_URL: "https://api.modstagram.ir", NEXT_PUBLIC_IMAGE_BASE_URL: "https://api.modstagram.ir/storage", NEXT_PUBLIC_BASE_URL: "/api/v1", + NEXT_PUBLIC_SITE_URL: "https://modstagram.com", + GOOGLE_CLIENT_ID: + "91028483292-vba2ktgbvus1p5ce18ir04h1b5c50gnn.apps.googleusercontent.com", + NEXT_PUBLIC_GOOGLE_CLIENT_ID: + "91028483292-vba2ktgbvus1p5ce18ir04h1b5c50gnn.apps.googleusercontent.com", }, }, ], diff --git a/next.config.ts b/next.config.ts index b5f4120..90f7a8a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -65,7 +65,46 @@ const nextConfig = { async headers() { return [ { - source: '/(.*)', + source: "/service-worker.js", + headers: [ + { + key: "Content-Type", + value: "application/javascript; charset=utf-8", + }, + { + key: "Cache-Control", + value: "public, max-age=0, must-revalidate", + }, + { + key: "Service-Worker-Allowed", + value: "/", + }, + ], + }, + { + source: "/manifest.webmanifest", + headers: [ + { + key: "Content-Type", + value: "application/manifest+json; charset=utf-8", + }, + { + key: "Cache-Control", + value: "no-cache, no-store, must-revalidate", + }, + ], + }, + { + source: "/manifest.json", + headers: [ + { + key: "Content-Type", + value: "application/manifest+json; charset=utf-8", + }, + ], + }, + { + source: "/(.*)", headers: [ { key: 'X-DNS-Prefetch-Control', value: 'on' }, { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' }, @@ -73,6 +112,8 @@ const nextConfig = { { key: 'X-Frame-Options', value: 'SAMEORIGIN' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'origin-when-cross-origin' }, + { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(self), payment=()' }, + { key: 'X-Permitted-Cross-Domain-Policies', value: 'none' }, { key: 'Cache-Control', value: 'public, s-maxage=1, stale-while-revalidate=59' }, ], }, diff --git a/package.json b/package.json index 9ee987f..a2eed67 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "start:local": "next start --port 3004 --hostname 0.0.0.0", "pm2:prod": "pm2 start ecosystem.config.cjs --update-env", "lint": "next lint", - "postbuild": "next-sitemap" + "postbuild": "next-sitemap", + "generate:pwa-icons": "node scripts/generate-pwa-icons.mjs" }, "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/public/favicon-16.png b/public/favicon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..da45d182dea6b3d0384ea36e3384a0f38cda4cd1 GIT binary patch literal 616 zcmV-u0+;=XP)0{+W5Z(F*C`Hhn zTXCUDoJl5`OsGW(B0e_VsNhB^if_dV;zk+@rqqB@7gj1(p=d#4+WE=3mxLnLI&ipm z?sv|2&OP@#N-3q-6dc0#>cp}Tbwi>qD_E#~>|9-9Bvz614-+qe<%L8Xh`6hm6Kdah zfF{8%)`;W;Y_1;MCTSAMgY8#T7|ShcR#QGiv*`Uw!GejcbHFY%6cIPnKt~EJ7kW;? z+LR${le!SkLa$(H<0EH%xlm~)`Pp^e1Z*zB&a!dtOhI?X1gtp$$)dpq0+Z18P}*tu zDf%0)!}KS!-#-ewFF<7ms(BEj^2QuO=v8_@!N3ln(>`mfPI&tCxdC?*3+=Q09%G( zgAaox5eamhgl%VGd)YKiupMQCb+(!qbwgc+5XmZ6lCH}u$ep{=#$+#NU4J}dC_)kv z(SnGmB_>&y3un2I1eb&fzoAGJYgi&>k>~$P+5Ls3r97cX7LE{;VakVCj;Z8dCO+aT z131{4$c@1ewl_t;x7416znNgRw?u!o%0MtA!M_2R2(T{icpj(#00002u5BN6j5vo@`@E(X(_K3y50LT zbLVb%wBHPlD<9{Jix4{(uOQcjn zg4@JV6u{vM#!PuXxq>7X1xRoQEnU+Tgu(3EiDqh98wqYqhpR94ilfPN6!(cP(#rm3 zUhPDKNsIkB%LQTJMk|l-M!?dU?XNVLP|-w!M<&dh2p~wJ7t+i38>IRIF%7uuA!jj+ zbwNg1k8yJ?k03ERxx_RqG(!a-#lv6#ik~smE6!9G?Z*&2SD$w;K^VI<7)c`n)A-+hW6CSUH1s_9prGgP4 zkF0`cHo#OrjLZuQN^rrjMXE%U3COWU3PkwnOX0p{Fmp9L_@0i%+eY*QNOG&7WDk6O znEuw<1rM))mAk@Y=X&7DI<-a=$(q~-*wO@Bo1yeu$S75i(w{yL%XY${)4G21Z!n=0 zZYxwTqzC{JB32ZBqd)Zp!wN56f!;p-4w~8@>#!D*=7n9y!*ikepIRZa3=)f>xm^ML z@>f{qwq`ZB-vi^^Fe)Ej*#sL8K;1srbP($I!@5SOYlKgK4$pnGPhD8ELj`tpL*psf zdkWew=-7q>V7vp9s$j-aIMJ!&S8QXR-Ka*Vd2ImV(ziClo|CX<4;XL4f_k;^vx6`q z4`!`VUclw})lz9YkEyHAcEfiqI(FugDi^up>eh4709Ne=<88RZXKz7VF05_T$)jg9 zYGpq|`!4Q&<16L6df=fI`k%CrQfWK>fXe~6bOmO-tH6^Y0Q6o9KDeiA-hvnZ#2jTW38h{dr zDWOv!3ca?)+mEVyUj1Bo5h4j_{(9w4cEZGR7@h}t4Z0zA5jLhEoIb^}(Gk1|lK*90oc(VoRfkfs zkObohNyRd5&kq9NurHl!@z$j>@NRcs{Q~>Xmyu(ru4BhgKS)xC@<~?SGlY-Q68lkl zcTLTZJj)&BFuA(Q;L(*?X3EVhOk7rBVy2a~@kCvgRUMf*)i0AGHK99rqe?TYA`;6* zM{)&BTm0gH;?)1VndomRE~Y(UDWVFuFJmftdOJ1hUF%NcSBg x-Pda+mj7Fti;RrYfF(1#s+u_;WJZ?f{{m}yzD(Jqd945d002ovPDHLkV1l4DV1xhw literal 0 HcmV?d00001 diff --git a/public/images/icons/192x192.png b/public/images/icons/192x192.png index 92ed3bd55500ca91021c2b36614fdb1b1acd07e8..7ff105356f7ad7d45134cb3b4810106a05c6381a 100644 GIT binary patch literal 9008 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 *8UiiPxRfkZ=`(`vg|D9 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 P0T2A9#tr0kv(Wzm?!N-f literal 20728 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 aR09Ce99l7yfISBQ0000 + + + + diff --git a/public/images/icons/maskable-192.png b/public/images/icons/maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..ac01b2d0c8727a8ece866b15b6b433de6b378be0 GIT binary patch literal 7094 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 Ls&eIzQE&baf28L) literal 0 HcmV?d00001 diff --git a/public/images/icons/maskable-512.png b/public/images/icons/maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..e91e943fac9ae3eb3a537037f2d6f42cd889f522 GIT binary patch literal 26123 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 literal 0 HcmV?d00001 diff --git a/public/images/icons/reels-follow-add.svg b/public/images/icons/reels-follow-add.svg new file mode 100644 index 0000000..02fd41e --- /dev/null +++ b/public/images/icons/reels-follow-add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/icons/reels-more.png b/public/images/icons/reels-more.png new file mode 100644 index 0000000000000000000000000000000000000000..dc3044747d010b10ddf4b08d4b10bfc14dab15d1 GIT binary patch literal 281 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj=R92;Ln;{GOmgHpWFX+WocZRC zZvsjk3X8tKJf*zzRjr7iiva(_TfPdW{F7F!j@VFgQN-@^bKlwiZL7ZgnbPp4>2%kz zZHs;u?VYI`R^9S{kG-wT!}_^P_jmM1J?ES1(fh%GZ_%a0f^ySl8~QVz+j)yQTRpAo zL!;-4x#|CSJQ+=DHCR_&yIwpjXjcRl4~%CN{I_{p!yB{HU2X9fxfvM#|BtK7tpxgv4dgRs ZhQ-{A3^mx^m;yx@JYD@<);T3K0RUu+ahw1E literal 0 HcmV?d00001 diff --git a/public/images/icons/reels-share-story.png b/public/images/icons/reels-share-story.png new file mode 100644 index 0000000000000000000000000000000000000000..7abcd5c520757877ce2df34820c73d2fac90985e GIT binary patch literal 904 zcmV;319$w1P)L=lmyn~Dpuu0#;68!fJ^3$ePeE~MaJ zL9}jK)Sb-OcG5}xpisYH#V@tSX0GQ2W@wX1QxGxSGw;50@4S=y-kqn>KmPq+fk#)zg{s>12JvGB2i!rplrG@ ztwZcWZ(6!T6^WgS^nWF!-U-D2Ks@W5% zh#$|*86<#x-AW_}bCZTU7vV$UXkyL4d{3afd|9b5)zjbpSQ@Z|x31@mDEqz}fhj;C zdxwAt6tiieiufis$rBKfRW6x3ve3236Ut_!EHmLayn=+D=jIHYYr<>YxhDW(KHzH( zQ*&!;YrhIU?KE20=~FFxQqBD!LJM8q6F?V?cFEikN!i6+-nQ*Ds!`2w-sJ|3Oq1O? z-gR9Hc-}A_G{x6qsRISx6iRl8N(ROvkw`EU)sNzA_JenX=pN3co|%pb@m2U8;IpP> zpLhsSCx$|%yMa)2-ZIfjlUS@4@3awXbGs~abt-Ar*d1nVmt|~$zu3+91VG#Xd=2MQ zjj!}kIHnuSP75*3M&f-f)3+#7A|JxBcq3vx3Za;OYgRly5%H{f0;7U_8O%q(&v4za z>I|=vfKZJDgEInYv4~h*cIpfR_%rKE`q`+gtQ<@Dc>;!E^rGa$;EEaxH7Ap1iu<(_ zNX~woIPfZFQ9__-dS6DXQ;sK~CfLh!5V@rMp&-m{DlSYbF@9!pHkNA6!=NS)ln?F; z6RpNbg9H=-kt%dSkvj=Xt7H;1Sk}pZ2#r)^o&ZP(T~H%Jo8f-~@(f!PR-_Q$#W+q# zyP0-)NjgJIl@D$#vE0F!Ux6YOx{~H~&h(X|V-5%rwoO`5_DjTUTpfau94({1=J(1M z;fq+jQTf<`9?ob-lp2D{6NLf#o+m<;qWtnp@-=GqnwYR_J3@stz zCL;O?{06QBD4^T6(G9 + + + + diff --git a/public/images/icons/shield-tick.svg b/public/images/icons/shield-tick.svg new file mode 100644 index 0000000..b048de6 --- /dev/null +++ b/public/images/icons/shield-tick.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/icons/star1.png b/public/images/icons/star1.png new file mode 100644 index 0000000000000000000000000000000000000000..f4fe3895b2ae714bf29625d974fc6d9358603b8d GIT binary patch literal 3483 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 { - console.log("Service Worker installing..."); - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - return cache.addAll([ - "/", - "/manifest.json", - "/images/icons/256x256.png", - "/images/icons/512x512.png", - ]); - }) + caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) ); self.skipWaiting(); }); -// فعال‌سازی Service Worker self.addEventListener("activate", (event) => { - console.log("Service Worker activated."); event.waitUntil( caches .keys() .then((keys) => Promise.all( - keys - .filter((key) => key !== CACHE_NAME) - .map((key) => caches.delete(key)) + keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)) ) ) .then(() => self.clients.claim()) ); }); -// هندل درخواست‌ها self.addEventListener("fetch", (event) => { const url = new URL(event.request.url); - // navigation: همیشه از شبکه — HTML کش نشود تا title/meta همیشه تازه باشد if (event.request.mode === "navigate") { event.respondWith(fetch(event.request)); return; } - // آیکون‌ها: network-first - if (url.pathname.startsWith("/images/icons/")) { + if ( + url.pathname.startsWith("/images/icons/") || + url.pathname.endsWith("manifest.webmanifest") || + url.pathname.endsWith("manifest.json") || + url.pathname.endsWith("favicon.png") + ) { event.respondWith( fetch(event.request) .then((res) => { if (res.ok) { const clone = res.clone(); - caches - .open(CACHE_NAME) - .then((cache) => cache.put(event.request, clone)); + caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone)); } return res; }) @@ -64,15 +63,13 @@ self.addEventListener("fetch", (event) => { return; } - // سایر فایل‌های استاتیک: stale-while-revalidate if ( url.pathname.startsWith("/images/") || url.pathname.endsWith(".png") || url.pathname.endsWith(".jpg") || url.pathname.endsWith(".jpeg") || url.pathname.endsWith(".gif") || - url.pathname.endsWith(".webp") || - url.pathname.endsWith("manifest.json") + url.pathname.endsWith(".webp") ) { event.respondWith( caches.match(event.request).then((cached) => { diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml index c4b95eb..0f23262 100644 --- a/public/sitemap-0.xml +++ b/public/sitemap-0.xml @@ -1,12 +1,12 @@ -https://modstagram.com/icon.png2026-07-17T12:03:53.291Zdaily0.7 -https://modstagram.com/robots.txt2026-07-17T12:03:53.292Zdaily0.7 -https://modstagram.com/sitemap.xml2026-07-17T12:03:53.292Zdaily0.7 -https://modstagram.com/academy/payment/failed2026-07-17T12:03:53.292Zdaily0.7 -https://modstagram.com/academy/payment/success2026-07-17T12:03:53.292Zdaily0.7 -https://modstagram.com/about-us2026-07-17T12:03:53.292Zmonthly0.8 -https://modstagram.com/explore2026-07-17T12:03:53.292Zdaily0.7 +https://modstagram.com/icon.png2026-07-21T17:52:19.573Zdaily0.7 +https://modstagram.com/robots.txt2026-07-21T17:52:19.576Zdaily0.7 +https://modstagram.com/sitemap.xml2026-07-21T17:52:19.576Zdaily0.7 +https://modstagram.com/academy/payment/success2026-07-21T17:52:19.576Zdaily0.7 +https://modstagram.com/academy/payment/failed2026-07-21T17:52:19.576Zdaily0.7 +https://modstagram.com/about-us2026-07-21T17:52:19.576Zmonthly0.8 +https://modstagram.com/explore2026-07-21T17:52:19.576Zdaily0.7 https://modstagram.comdaily1 https://modstagram.com/projectsdaily0.9 https://modstagram.com/billboardsdaily0.9 diff --git a/scripts/generate-pwa-icons.mjs b/scripts/generate-pwa-icons.mjs new file mode 100644 index 0000000..2f4fdc4 --- /dev/null +++ b/scripts/generate-pwa-icons.mjs @@ -0,0 +1,94 @@ +import sharp from "sharp"; +import path from "path"; +import { fileURLToPath } from "url"; +import fs from "fs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const iconsDir = path.join(__dirname, "../public/images/icons"); +const publicDir = path.join(__dirname, "../public"); +const source = path.join(iconsDir, "logo-mark.svg"); + +const BRAND = { r: 255, g: 16, b: 125, alpha: 1 }; +const WHITE = { r: 255, g: 255, b: 255, alpha: 1 }; + +async function renderSquareIcon(size, paddingRatio = 0.18) { + const inner = Math.round(size * (1 - paddingRatio * 2)); + const padding = Math.round((size - inner) / 2); + + const mark = await sharp(source, { density: 512 }) + .resize(inner, inner, { + fit: "contain", + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .png() + .toBuffer(); + + return sharp({ + create: { + width: size, + height: size, + channels: 4, + background: WHITE, + }, + }) + .composite([{ input: mark, left: padding, top: padding }]) + .png() + .toFile(path.join(iconsDir, `${size}x${size}.png`)); +} + +async function renderMaskableIcon(size) { + const inner = Math.round(size * 0.52); + const padding = Math.round((size - inner) / 2); + + const mark = await sharp(source, { density: 512 }) + .resize(inner, inner, { + fit: "contain", + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .png() + .toBuffer(); + + await sharp({ + create: { + width: size, + height: size, + channels: 4, + background: BRAND, + }, + }) + .composite([{ input: mark, left: padding, top: padding }]) + .png() + .toFile(path.join(iconsDir, `maskable-${size}.png`)); + + console.log(`Created maskable-${size}.png`); +} + +if (!fs.existsSync(source)) { + console.error("Missing logo-mark.svg"); + process.exit(1); +} + +for (const size of [192, 256, 512]) { + await renderSquareIcon(size); + console.log(`Created ${size}x${size}.png`); +} + +await sharp(path.join(iconsDir, "512x512.png")) + .resize(180, 180) + .png() + .toFile(path.join(iconsDir, "apple-touch-icon.png")); +console.log("Created apple-touch-icon.png"); + +await renderMaskableIcon(192); +await renderMaskableIcon(512); + +await sharp(path.join(iconsDir, "192x192.png")) + .resize(32, 32) + .png() + .toFile(path.join(publicDir, "favicon.png")); + +await sharp(path.join(publicDir, "favicon.png")) + .resize(16, 16) + .toFile(path.join(publicDir, "favicon-16.png")); + +console.log("Created favicon.png"); diff --git a/src/api/fetchPosts.ts b/src/api/fetchPosts.ts index 0e931d4..3cccc7e 100644 --- a/src/api/fetchPosts.ts +++ b/src/api/fetchPosts.ts @@ -19,7 +19,9 @@ export async function fetchPosts( feedMode?: "grid" | "reels"; seedPostId?: string; sort?: "latest"; - reelsTab?: "for_you" | "following" | "saved"; + reelsTab?: "for_you" | "following" | "saved" | "near_me"; + lat?: string; + lng?: string; heightMin?: string; heightMax?: string; weightMin?: string; diff --git a/src/api/fetchStories.ts b/src/api/fetchStories.ts index 24e3b6a..4b72eaa 100644 --- a/src/api/fetchStories.ts +++ b/src/api/fetchStories.ts @@ -15,6 +15,7 @@ export type StoryItem = { _id: string; media_path: string; media_type: "image" | "video"; + linked_post_id?: string | null; overlays?: StoryTextOverlay[]; createdAt?: string; expires_at?: string; @@ -33,6 +34,7 @@ export type StoryFeedUser = { }; stories: StoryItem[]; has_unviewed: boolean; + is_following?: boolean; }; export type StoriesFeedResponse = { @@ -166,3 +168,21 @@ export async function fetchStoryViewers( } return res.json(); } + +export async function sharePostToStory( + postId: string, + token: string +): Promise { + const res = await fetch(`${getApiBaseUrl()}/stories/share-post`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ postId }), + }); + if (!res.ok) { + const json = await res.json().catch(() => ({})); + throw new Error(json?.message || "خطا در انتشار استوری"); + } +} diff --git a/src/app/(auth)/(login)/change-passowrd/page.tsx b/src/app/(auth)/(login)/change-passowrd/page.tsx index 9a38a24..3659ddf 100644 --- a/src/app/(auth)/(login)/change-passowrd/page.tsx +++ b/src/app/(auth)/(login)/change-passowrd/page.tsx @@ -12,30 +12,11 @@ import { useFormik } from "formik"; import * as yup from "yup"; import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; - -const schema = yup.object().shape({ - otp: yup - .string() - .matches(/^\d{6}$/, "کد وارد شده صحیح نیست") - .required("کد تایید را وارد کنید"), - password: yup - .string() - .required("کلمه عبور نمی\u200Cتواند خالی باشد") - .min(8, "کلمه عبور نباید کوتاه تر از 8 کاراکتر باشد") - .matches( - /^(?=.*[a-zA-Zآ-ی])(?=.*\d).{8,}$/, - "کلمه عبور باید شامل حروف و اعداد باشد" - ), - confirmPassword: yup - .string() - .required("تکرار کلمه عبور نمی\u200Cتواند خالی باشد") - .oneOf( - [yup.ref("password")], - "تکرار کلمه عبور باید با کلمه عبور مطابقت داشته باشد" - ), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function ChangePassword() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const [otpError, setOtpError] = useState(false); @@ -56,6 +37,29 @@ function ChangePassword() { ); const isEmailReset = resetChannel === "email"; + const schema = useMemo( + () => + yup.object().shape({ + otp: yup + .string() + .matches(/^\d{6}$/, t("auth.otpInvalid")) + .required(t("auth.otpRequired")), + password: yup + .string() + .required(t("auth.passwordRequired")) + .min(8, t("auth.passwordMin")) + .matches( + /^(?=.*[a-zA-Zآ-ی])(?=.*\d).{8,}$/, + t("auth.passwordLettersNumbers") + ), + confirmPassword: yup + .string() + .required(t("auth.confirmPasswordRequired")) + .oneOf([yup.ref("password")], t("auth.confirmPasswordMismatch")), + }), + [t] + ); + const formik = useFormik({ initialValues: { otp: "", @@ -65,7 +69,7 @@ function ChangePassword() { validationSchema: schema, onSubmit: async (values) => { if (!resetIdentifier) { - toast.error("ابتدا از صفحه فراموشی رمز، کد دریافت کنید"); + toast.error(t("auth.getResetCodeFirst")); router.push("/forget-password"); return; } @@ -81,107 +85,109 @@ function ChangePassword() { }); localStorage.removeItem("reset_identifier"); localStorage.removeItem("reset_channel"); - toast.success("کلمه عبور با موفقیت تغییر کرد"); + toast.success(t("auth.passwordChangedSuccess")); router.push("/login-with-username"); } catch (err: unknown) { setOtpError(true); const message = (err as { response?: { data?: { message?: string } } })?.response?.data - ?.message || "تغییر کلمه عبور ناموفق بود"; + ?.message || t("auth.changePasswordFailed"); toast.error(message); } }, }); return ( - -
- -

- {isEmailReset - ? "کد ارسال‌شده به ایمیل و کلمه عبور جدید را وارد کنید." - : "کد ارسال‌شده به موبایل و کلمه عبور جدید را وارد کنید."} -

-
- { - formik.setFieldValue("otp", otp); - setOtpError(false); - }} - error={ - otpError || Boolean(formik.touched.otp && formik.errors.otp) - } - disabled={loading} - /> - {formik.touched.otp && formik.errors.otp && ( - {formik.errors.otp} - )} - - - {formik.touched.password && formik.errors.password && ( - - {formik.errors.password} - - )} - - - {formik.touched.confirmPassword && formik.errors.confirmPassword && ( - - {formik.errors.confirmPassword} - - )} - - + +
+ +

+ {isEmailReset + ? t("auth.changePasswordEmailHint") + : t("auth.changePasswordMobileHint")} +

+ - تغییر کلمه عبور - - + { + formik.setFieldValue("otp", otp); + setOtpError(false); + }} + error={ + otpError || Boolean(formik.touched.otp && formik.errors.otp) + } + disabled={loading} + /> + {formik.touched.otp && formik.errors.otp && ( + {formik.errors.otp} + )} - - - ارسال مجدد کد - - - - - بازگشت به ورود - - -
-
+ + {formik.touched.password && formik.errors.password && ( + + {formik.errors.password} + + )} + + + {formik.touched.confirmPassword && formik.errors.confirmPassword && ( + + {formik.errors.confirmPassword} + + )} + + + {t("auth.changePassword")} + + + + + + {t("auth.resendCodeLink")} + + + + + {t("auth.backToLogin")} + + +
+
+ ); } diff --git a/src/app/(auth)/(login)/forget-password-otp/page.tsx b/src/app/(auth)/(login)/forget-password-otp/page.tsx index 0ea0242..ab36360 100644 --- a/src/app/(auth)/(login)/forget-password-otp/page.tsx +++ b/src/app/(auth)/(login)/forget-password-otp/page.tsx @@ -6,7 +6,7 @@ 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, { useState } from "react"; +import React, { useMemo, useState } from "react"; import AuthOtpInput from "@/components/auth/AuthOtpInput"; import AuthOtpStatus from "@/components/auth/AuthOtpStatus"; import useAxios from "@/hooks/useAxios"; @@ -16,16 +16,11 @@ import { useRouter } from "next/navigation"; import { IVerifyOtp } from "@/types/types"; import { setAuthSession } from "@/lib/auth/session"; import { AUTH_SUCCESS_DELAY_MS, pause } from "@/lib/auth/feedback"; - -// Validation schema using Yup -const schema = yup.object({ - otp: yup - .string() - .matches(/^\d{6}$/, "کد وارد شده صحیح نیست") - .required("کد تایید را وارد کنید"), -}); +import { useTranslation } from "react-i18next"; +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); @@ -35,6 +30,18 @@ function ForgetPasswordOtp() { const { request, loading } = useAxios(); const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + + const schema = useMemo( + () => + yup.object({ + otp: yup + .string() + .matches(/^\d{6}$/, t("auth.otpInvalid")) + .required(t("auth.otpRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { otp: "", @@ -67,67 +74,75 @@ function ForgetPasswordOtp() { }); return ( - -
- - { - if (!mobile) return; - setResendLoading(true); - try { - await request("POST", "/login", { mobile }); - formik.setFieldValue("otp", ""); - setOtpError(false); - setIsSuccess(false); - } finally { - setResendLoading(false); - } - }} - /> -
- { - formik.setFieldValue("otp", otp); - setOtpError(false); - setIsSuccess(false); + + +
+ + { + if (!mobile) return; + setResendLoading(true); + try { + await request("POST", "/login", { mobile }); + formik.setFieldValue("otp", ""); + setOtpError(false); + setIsSuccess(false); + } finally { + setResendLoading(false); + } }} - onComplete={() => setTimeout(() => formik.submitForm(), 400)} - error={otpError || Boolean(formik.touched.otp && formik.errors.otp)} - success={isSuccess} - disabled={loading || otpExpired || isSuccess} /> - {formik.touched.otp && formik.errors.otp && ( - {formik.errors.otp} - )} - - تایید کد - - - - - با نام کاربری و کلمه عبور خود وارد شوید - - - - - اصلاح شماره موبایل - - - -
-
+ { + formik.setFieldValue("otp", otp); + setOtpError(false); + setIsSuccess(false); + }} + onComplete={() => setTimeout(() => formik.submitForm(), 400)} + error={otpError || Boolean(formik.touched.otp && formik.errors.otp)} + success={isSuccess} + disabled={loading || otpExpired || isSuccess} + /> + {formik.touched.otp && formik.errors.otp && ( + {formik.errors.otp} + )} + + {t("auth.verifyCode")} + + + + + {t("auth.loginWithUsername")} + + + + + {t("auth.editMobile")} + + + +
+
+ ); } diff --git a/src/app/(auth)/(login)/forget-password/page.tsx b/src/app/(auth)/(login)/forget-password/page.tsx index 4a1c14b..b8a778e 100644 --- a/src/app/(auth)/(login)/forget-password/page.tsx +++ b/src/app/(auth)/(login)/forget-password/page.tsx @@ -5,32 +5,39 @@ import AuthHead from "@/components/auth/AuthHead"; import AuthInput from "@/components/auth/AuthInput"; import Container from "@/components/elements/Container"; import Link from "next/link"; -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; - -const schema = yup.object({ - identifier: yup - .string() - .required("ایمیل یا شماره موبایل الزامی است") - .test("identifier", "ایمیل یا شماره موبایل معتبر نیست", (value) => { - if (!value) return false; - const trimmed = value.trim(); - return ( - /^(09\d{9})$/.test(trimmed) || - /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed) - ); - }), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function ForgetPasswordPage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const [destination, setDestination] = useState(""); + const schema = useMemo( + () => + yup.object({ + identifier: yup + .string() + .required(t("auth.identifierRequired")) + .test("identifier", t("auth.identifierInvalid"), (value) => { + if (!value) return false; + const trimmed = value.trim(); + return ( + /^(09\d{9})$/.test(trimmed) || + /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed) + ); + }), + }), + [t] + ); + const formik = useFormik({ initialValues: { identifier: "" }, validationSchema: schema, @@ -55,69 +62,70 @@ function ForgetPasswordPage() { setDestination(response.destination); } - toast.success(response.message || "کد بازیابی ارسال شد"); + toast.success(response.message || t("auth.resetCodeSentDefault")); router.push("/change-passowrd"); } catch (err: unknown) { const message = (err as { response?: { data?: { message?: string } } })?.response?.data - ?.message || "ارسال کد ناموفق بود"; + ?.message || t("auth.sendCodeFailed"); toast.error(message); } }, }); return ( - -
- -

- ایمیل (برای حساب گوگل) یا شماره موبایل خود را وارد کنید تا کد بازیابی - ارسال شود. -

-
- - {formik.touched.identifier && formik.errors.identifier && ( - - {formik.errors.identifier} - - )} - {destination && ( - - کد به {destination} ارسال شد - - )} - + +
+ +

+ {t("auth.forgetPasswordDesc")} +

+ - ارسال کد بازیابی - - - - - بازگشت به ورود - - -
-
+ + {formik.touched.identifier && formik.errors.identifier && ( + + {formik.errors.identifier} + + )} + {destination && ( + + {t("auth.codeSentTo", { destination })} + + )} + + {t("auth.sendResetCode")} + + + + + {t("auth.backToLogin")} + + +
+
+ ); } diff --git a/src/app/(auth)/(login)/login-2fa/page.tsx b/src/app/(auth)/(login)/login-2fa/page.tsx new file mode 100644 index 0000000..da138e5 --- /dev/null +++ b/src/app/(auth)/(login)/login-2fa/page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import AuthHead from "@/components/auth/AuthHead"; +import AuthOtpInput from "@/components/auth/AuthOtpInput"; +import AuthButton from "@/components/auth/AuthButton"; +import Container from "@/components/elements/Container"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; +import useAxios from "@/hooks/useAxios"; +import { completeLogin, getSafeRedirectPath } from "@/lib/auth/postLogin"; +import { IVerifyOtp } from "@/types/types"; +import { useRouter } from "next/navigation"; +import React, { useEffect, useState } from "react"; +import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; + +export default function LoginTwoFactorPage() { + const { t } = useTranslation("common"); + const router = useRouter(); + const { request, loading } = useAxios(); + const [code, setCode] = useState(""); + const [tempToken, setTempToken] = useState(""); + + useEffect(() => { + const token = + typeof window !== "undefined" + ? sessionStorage.getItem("login_2fa_temp") + : null; + if (!token) { + router.replace("/login"); + return; + } + setTempToken(token); + }, [router]); + + const handleVerify = async () => { + if (!tempToken || code.length !== 6) return; + + try { + const response = (await request("POST", "/auth/totp/verify-login", { + temp_token: tempToken, + code, + })) as IVerifyOtp; + + sessionStorage.removeItem("login_2fa_temp"); + + const loggedIn = await completeLogin(router, response, { + redirectTo: getSafeRedirectPath(), + }); + + if (!loggedIn) { + toast.error(t("auth.invalidServerResponse")); + } + } catch { + toast.error(t("settings.edit.twoFactor.invalidCode")); + } + }; + + return ( + + +
+ +

+ {t("auth.twoFactorHint")} +

+ setTimeout(handleVerify, 300)} + disabled={loading || !tempToken} + /> + + {t("auth.verifyCode")} + +
+
+
+ ); +} diff --git a/src/app/(auth)/(login)/login-with-username/page.tsx b/src/app/(auth)/(login)/login-with-username/page.tsx index cbe2722..203b787 100644 --- a/src/app/(auth)/(login)/login-with-username/page.tsx +++ b/src/app/(auth)/(login)/login-with-username/page.tsx @@ -8,7 +8,7 @@ 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, { useState } from "react"; +import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -21,16 +21,11 @@ import { } from "@/lib/auth/postLogin"; import toast from "react-hot-toast"; import { useAuthSessionRedirect } from "@/hooks/useAuthSessionRedirect"; - -const schema = yup.object().shape({ - username: yup.string().required("نام کاربری الزامی است"), - password: yup - .string() - .required("کلمه عبور نمی\u200Cتواند خالی باشد") - .min(8, "کلمه عبور نباید کوتاه تر از 8 کاراکتر باشد"), -}); +import { useTranslation } from "react-i18next"; +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); @@ -40,6 +35,18 @@ function LoginWithUsername() { const redirectPath = getSafeRedirectPath(); useAuthSessionRedirect(); + const schema = useMemo( + () => + yup.object().shape({ + username: yup.string().required(t("auth.usernameRequired")), + password: yup + .string() + .required(t("auth.passwordRequired")) + .min(8, t("auth.passwordMin")), + }), + [t] + ); + const formik = useFormik({ initialValues: { username: "", @@ -66,7 +73,7 @@ function LoginWithUsername() { }); if (!loggedIn) { - const message = "پاسخ سرور نامعتبر بود. دوباره تلاش کنید."; + const message = t("auth.invalidServerResponse"); setAuthError(true); setErrorMessage(message); toast.error(message); @@ -75,7 +82,10 @@ function LoginWithUsername() { setIsSuccess(true); } catch (err: any) { - const message = getAxiosErrorMessage(err); + const message = getAxiosErrorMessage( + err, + i18n.language === "en" ? "en" : "fa" + ); setAuthError(true); setIsSuccess(false); setErrorMessage(message); @@ -85,88 +95,91 @@ function LoginWithUsername() { }); return ( - -
- -
- { - setAuthError(false); - setErrorMessage(""); - setIsSuccess(false); - formik.handleChange(e); - }} - onBlur={formik.handleBlur} - disabled={loading || isSuccess} - /> - {formik.touched.username && formik.errors.username && !authError && ( - - {formik.errors.username} - - )} - { - setAuthError(false); - setErrorMessage(""); - setIsSuccess(false); - formik.handleChange(e); - }} - onBlur={formik.handleBlur} - disabled={loading || isSuccess} - /> - {formik.touched.password && formik.errors.password && !authError && ( - - {formik.errors.password} - - )} - {authError && errorMessage && ( - - {errorMessage} - - )} - + +
+ + - ورود - - - - - کلمه عبور خود را فراموش کرده اید؟ - - - - - حساب کاربری ندارید؟ ثبت نام - - - -
-
+ { + setAuthError(false); + setErrorMessage(""); + setIsSuccess(false); + formik.handleChange(e); + }} + onBlur={formik.handleBlur} + disabled={loading || isSuccess} + /> + {formik.touched.username && formik.errors.username && !authError && ( + + {formik.errors.username} + + )} + { + setAuthError(false); + setErrorMessage(""); + setIsSuccess(false); + formik.handleChange(e); + }} + onBlur={formik.handleBlur} + disabled={loading || isSuccess} + /> + {formik.touched.password && formik.errors.password && !authError && ( + + {formik.errors.password} + + )} + {authError && errorMessage && ( + + {errorMessage} + + )} + + {t("auth.login")} + + + + + {t("auth.forgotPassword")} + + + + + {t("auth.noAccount")}{" "} + {t("auth.signUp")} + + + +
+
+ ); } diff --git a/src/app/(auth)/(login)/login/page.tsx b/src/app/(auth)/(login)/login/page.tsx index 9fb93ba..4a493ef 100644 --- a/src/app/(auth)/(login)/login/page.tsx +++ b/src/app/(auth)/(login)/login/page.tsx @@ -6,27 +6,21 @@ import AuthInput from "@/components/auth/AuthInput"; import Container from "@/components/elements/Container"; import AuthRules from "@/components/auth/AuthRules"; import Link from "next/link"; -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; import { useRouter } from "next/navigation"; import { markOtpSent } from "@/lib/auth/otpTimer"; - -// Validation schema using Yup -const schema = yup.object({ - mobile: yup - .string() - .matches(/^(09\d{9})$/, "شماره موبایل معتبر نیست") - .required("شماره موبایل الزامی است"), -}); - import { getSafeRedirectPath } from "@/lib/auth/postLogin"; -import GoogleSignInButton from "@/components/auth/GoogleSignInButton"; +import { GoogleSignInButton } from "@/app/(auth)/AuthProviders"; import { MOBILE_NUMERIC_INPUT_PROPS } from "@/lib/auth/inputProps"; import { useAuthSessionRedirect } from "@/hooks/useAuthSessionRedirect"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function Login() { + const { t } = useTranslation("common"); const router = useRouter(); const [isModalOpen, setModalOpen] = useState(false); const { request, loading } = useAxios(); @@ -37,6 +31,17 @@ function Login() { ? `/login-with-username?redirect=${encodeURIComponent(redirectPath)}` : "/login-with-username"; + const schema = useMemo( + () => + yup.object({ + mobile: yup + .string() + .matches(/^(09\d{9})$/, t("auth.mobileInvalid")) + .required(t("auth.mobileRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { mobile: "", @@ -55,51 +60,53 @@ function Login() { }); return ( - -
- -
- - {formik.touched.mobile && formik.errors.mobile && ( - - {formik.errors.mobile} + + +
+ + + + {formik.touched.mobile && formik.errors.mobile && ( + + {formik.errors.mobile} + + )} + + {t("auth.sendCode")} + + + + + + {t("auth.loginWithUsername")} - )} - - ارسال کد - - - - - - با نام کاربری و کلمه عبور خود وارد شوید - - - - -
-
+ + + +
+
+ ); } diff --git a/src/app/(auth)/(login)/verify-otp/page.tsx b/src/app/(auth)/(login)/verify-otp/page.tsx index 306c3f4..c0503b1 100644 --- a/src/app/(auth)/(login)/verify-otp/page.tsx +++ b/src/app/(auth)/(login)/verify-otp/page.tsx @@ -6,7 +6,7 @@ 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, { useState } from "react"; +import React, { useMemo, useState } from "react"; import AuthOtpInput from "@/components/auth/AuthOtpInput"; import AuthOtpStatus from "@/components/auth/AuthOtpStatus"; import useAxios from "@/hooks/useAxios"; @@ -18,16 +18,11 @@ import { completeLogin, getSafeRedirectPath, } from "@/lib/auth/postLogin"; - -// Validation schema using Yup -const schema = yup.object({ - otp: yup - .string() - .matches(/^\d{6}$/, "کد وارد شده صحیح نیست") - .required("کد تایید را وارد کنید"), -}); +import { useTranslation } from "react-i18next"; +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); @@ -37,6 +32,18 @@ function VerifyOtp() { const { request, loading } = useAxios(); const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + + const schema = useMemo( + () => + yup.object({ + otp: yup + .string() + .matches(/^\d{6}$/, t("auth.otpInvalid")) + .required(t("auth.otpRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { otp: "", @@ -56,21 +63,13 @@ function VerifyOtp() { otp: values.otp.trim(), })) as IVerifyOtp; - switch (response?.page) { - case "home": - setIsSuccess(true); - await completeLogin(router, response, { - redirectTo: getSafeRedirectPath(), - }); - break; - case "auth-page": - setIsSuccess(true); - await completeLogin(router, response); - break; - default: - break; + const loggedIn = await completeLogin(router, response, { + redirectTo: getSafeRedirectPath(), + }); + + if (loggedIn) { + setIsSuccess(true); } - // router.push("/dashboard"); } catch (err: any) { setOtpError(true); setIsSuccess(false); @@ -80,68 +79,76 @@ function VerifyOtp() { }); return ( - -
- - { - if (!mobile) return; - setResendLoading(true); - try { - await request("POST", "/login", { mobile }); - formik.setFieldValue("otp", ""); - setOtpError(false); - setIsSuccess(false); - } finally { - setResendLoading(false); - } - }} - /> -
- { - formik.setFieldValue("otp", otp); - setOtpError(false); - setIsSuccess(false); + + +
+ + { + if (!mobile) return; + setResendLoading(true); + try { + await request("POST", "/login", { mobile }); + formik.setFieldValue("otp", ""); + setOtpError(false); + setIsSuccess(false); + } finally { + setResendLoading(false); + } }} - onComplete={() => setTimeout(() => formik.submitForm(), 400)} - error={otpError || Boolean(formik.touched.otp && formik.errors.otp)} - success={isSuccess} - disabled={loading || otpExpired || isSuccess} /> - - {formik.touched.otp && formik.errors.otp && ( - {formik.errors.otp} - )} - - تایید کد - - - - - با نام کاربری و کلمه عبور خود وارد شوید - - - - - اصلاح شماره موبایل - - - -
-
+ { + formik.setFieldValue("otp", otp); + setOtpError(false); + setIsSuccess(false); + }} + onComplete={() => setTimeout(() => formik.submitForm(), 400)} + error={otpError || Boolean(formik.touched.otp && formik.errors.otp)} + success={isSuccess} + disabled={loading || otpExpired || isSuccess} + /> + + {formik.touched.otp && formik.errors.otp && ( + {formik.errors.otp} + )} + + {t("auth.verifyCode")} + + + + + {t("auth.loginWithUsername")} + + + + + {t("auth.editMobile")} + + + +
+
+ ); } diff --git a/src/app/(auth)/(register)/register-otp/page.tsx b/src/app/(auth)/(register)/register-otp/page.tsx index cdc1546..c953ec2 100644 --- a/src/app/(auth)/(register)/register-otp/page.tsx +++ b/src/app/(auth)/(register)/register-otp/page.tsx @@ -7,7 +7,7 @@ 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, { useState } from "react"; +import React, { useMemo, useState } from "react"; import AuthOtpInput from "@/components/auth/AuthOtpInput"; import AuthOtpStatus from "@/components/auth/AuthOtpStatus"; import useAxios from "@/hooks/useAxios"; @@ -16,16 +16,11 @@ import * as yup from "yup"; import { IVerifyOtp } from "@/types/types"; import { useRouter } from "next/navigation"; import { completeLogin } from "@/lib/auth/postLogin"; - -// Validation schema using Yup -const schema = yup.object({ - otp: yup - .string() - .matches(/^\d{6}$/, "کد وارد شده صحیح نیست") - .required("کد تایید را وارد کنید"), -}); +import { useTranslation } from "react-i18next"; +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); @@ -35,6 +30,18 @@ function RegisterOtp() { const { request, loading } = useAxios(); const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + + const schema = useMemo( + () => + yup.object({ + otp: yup + .string() + .matches(/^\d{6}$/, t("auth.otpInvalid")) + .required(t("auth.otpRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { otp: "", @@ -65,62 +72,70 @@ function RegisterOtp() { }); return ( - -
- - { - if (!mobile) return; - setResendLoading(true); - try { - await request("POST", "/register", { mobile }); - formik.setFieldValue("otp", ""); - setOtpError(false); - setIsSuccess(false); - } finally { - setResendLoading(false); - } - }} - /> -
- { - formik.setFieldValue("otp", otp); - setOtpError(false); - setIsSuccess(false); + + +
+ + { + if (!mobile) return; + setResendLoading(true); + try { + await request("POST", "/register", { mobile }); + formik.setFieldValue("otp", ""); + setOtpError(false); + setIsSuccess(false); + } finally { + setResendLoading(false); + } }} - onComplete={() => setTimeout(() => formik.submitForm(), 400)} - error={otpError || Boolean(formik.touched.otp && formik.errors.otp)} - success={isSuccess} - disabled={loading || otpExpired || isSuccess} /> - {formik.touched.otp && formik.errors.otp && ( - {formik.errors.otp} - )} - - تایید کد - - - - - اصلاح شماره موبایل - - - -
-
+ { + formik.setFieldValue("otp", otp); + setOtpError(false); + setIsSuccess(false); + }} + onComplete={() => setTimeout(() => formik.submitForm(), 400)} + error={otpError || Boolean(formik.touched.otp && formik.errors.otp)} + success={isSuccess} + disabled={loading || otpExpired || isSuccess} + /> + {formik.touched.otp && formik.errors.otp && ( + {formik.errors.otp} + )} + + {t("auth.verifyCode")} + + + + + {t("auth.editMobile")} + + + +
+
+ ); } diff --git a/src/app/(auth)/(register)/register/complete/page.tsx b/src/app/(auth)/(register)/register/complete/page.tsx index f91fcf5..ffc3fa2 100644 --- a/src/app/(auth)/(register)/register/complete/page.tsx +++ b/src/app/(auth)/(register)/register/complete/page.tsx @@ -12,8 +12,11 @@ import { useRouter } from "next/navigation"; import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import { getSafeRedirectPath } from "@/lib/auth/postLogin"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function RegisterCompletePage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const [action, setAction] = useState<"app" | "continue" | null>(null); @@ -32,47 +35,50 @@ function RegisterCompletePage() { router.push(getSafeRedirectPath()); } } catch (err: any) { - toast.error(err?.response?.data?.message || "خطا در تکمیل ثبت‌نام"); + toast.error( + err?.response?.data?.message || t("auth.registrationFinishFailed") + ); } finally { setAction(null); } }; return ( - -
- - -

- می‌توانید وارد برنامه شوید یا ثبت‌نام کامل (تایید هویت و پروفایل) - را ادامه دهید. -

-
+ + +
+ + +

+ {t("auth.registerCompleteDesc")} +

+
- -
- finishRegistration("continue")} - className=" w-full sm:w-auto" - disabled={loading} - loading={loading && action === "continue"} - > - ادامه ثبت نام - - finishRegistration("app")} - className="!border-[#FF0000] !text-[#FF0000] w-full sm:w-auto" - disabled={loading} - loading={loading && action === "app"} - > - ورود به برنامه - -
-
-
-
+ +
+ finishRegistration("continue")} + className=" w-full sm:w-auto" + disabled={loading} + loading={loading && action === "continue"} + > + {t("auth.continueRegistration")} + + finishRegistration("app")} + className="!border-[#FF0000] !text-[#FF0000] w-full sm:w-auto" + disabled={loading} + loading={loading && action === "app"} + > + {t("auth.enterApp")} + +
+
+
+
+ ); } diff --git a/src/app/(auth)/(register)/register/fullname/page.tsx b/src/app/(auth)/(register)/register/fullname/page.tsx index 5788cab..af0e183 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, { useState } from "react"; +import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -15,13 +15,11 @@ import { useRouter } from "next/navigation"; import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import { getSafeRedirectPath } from "@/lib/auth/postLogin"; - -const schema = yup.object().shape({ - name: yup.string().trim().required("نام الزامی است").min(2, "نام کوتاه است"), - family: yup.string().trim().optional(), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function FullNamePage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const [nameSaved, setNameSaved] = useState(false); @@ -29,6 +27,19 @@ function FullNamePage() { null ); + const schema = useMemo( + () => + yup.object().shape({ + name: yup + .string() + .trim() + .required(t("auth.nameRequired")) + .min(2, t("auth.nameTooShort")), + family: yup.string().trim().optional(), + }), + [t] + ); + const formik = useFormik({ initialValues: { family: "", @@ -50,9 +61,9 @@ function FullNamePage() { } setNameSaved(true); - toast.success("نام با موفقیت ثبت شد"); + toast.success(t("auth.nameSavedSuccess")); } catch (err: any) { - toast.error(err?.response?.data?.message || "خطا در ذخیره نام"); + toast.error(err?.response?.data?.message || t("auth.saveNameFailed")); } finally { setSubmitting(false); } @@ -73,105 +84,109 @@ function FullNamePage() { router.push(getSafeRedirectPath()); } } catch (err: any) { - toast.error(err?.response?.data?.message || "خطا در تکمیل ثبت‌نام"); + toast.error( + err?.response?.data?.message || t("auth.registrationFinishFailed") + ); } finally { setChoiceLoading(null); } }; return ( - -
- - -
- - {formik.touched.name && formik.errors.name && ( - - {formik.errors.name} - - )} + + + + + +
+ + {formik.touched.name && formik.errors.name && ( + + {formik.errors.name} + + )} - - {formik.touched.family && formik.errors.family && ( - - {formik.errors.family} - - )} + + {formik.touched.family && formik.errors.family && ( + + {formik.errors.family} + + )} - {nameSaved && ( -

- نام شما ثبت شد. یکی از گزینه‌های زیر را انتخاب کنید. -

- )} -
-
- - - {!nameSaved ? ( - - ثبت و ادامه - - ) : ( -
- finishRegistration("continue")} - className=" w-full sm:w-auto" - disabled={Boolean(choiceLoading)} - loading={choiceLoading === "continue"} - > - ادامه ثبت نام - - finishRegistration("app")} - className="!border-[#FF0000] !text-[#FF0000] w-full sm:w-auto" - disabled={Boolean(choiceLoading)} - loading={choiceLoading === "app"} - > - ورود به برنامه - + {nameSaved && ( +

+ {t("auth.nameSavedChoose")} +

+ )}
- )} -
- -
+ + + + {!nameSaved ? ( + + {t("auth.saveAndContinue")} + + ) : ( +
+ finishRegistration("continue")} + className=" w-full sm:w-auto" + disabled={Boolean(choiceLoading)} + loading={choiceLoading === "continue"} + > + {t("auth.continueRegistration")} + + finishRegistration("app")} + className="!border-[#FF0000] !text-[#FF0000] w-full sm:w-auto" + disabled={Boolean(choiceLoading)} + loading={choiceLoading === "app"} + > + {t("auth.enterApp")} + +
+ )} +
+ + +
); } diff --git a/src/app/(auth)/(register)/register/loading.tsx b/src/app/(auth)/(register)/register/loading.tsx new file mode 100644 index 0000000..b2ac385 --- /dev/null +++ b/src/app/(auth)/(register)/register/loading.tsx @@ -0,0 +1,15 @@ +"use client"; + +import IOSSpinner from "@/components/ui/IOSSpinner"; +import { useTranslation } from "react-i18next"; + +export default function RegisterLoading() { + const { t } = useTranslation("common"); + + return ( +
+ + {t("auth.pageLoading")} +
+ ); +} diff --git a/src/app/(auth)/(register)/register/page.tsx b/src/app/(auth)/(register)/register/page.tsx index d0fed7f..25d3c11 100644 --- a/src/app/(auth)/(register)/register/page.tsx +++ b/src/app/(auth)/(register)/register/page.tsx @@ -5,28 +5,34 @@ 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, { useState } from "react"; +import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; import { useRouter } from "next/navigation"; import { markOtpSent } from "@/lib/auth/otpTimer"; -import GoogleSignInButton from "@/components/auth/GoogleSignInButton"; +import { GoogleSignInButton } from "@/app/(auth)/AuthProviders"; import { MOBILE_NUMERIC_INPUT_PROPS } from "@/lib/auth/inputProps"; - -// Validation schema using Yup -const schema = yup.object({ - mobile: yup - .string() - .matches(/^(09\d{9})$/, "شماره موبایل معتبر نیست") - .required("شماره موبایل الزامی است"), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function Register() { + const { t } = useTranslation("common"); const router = useRouter(); const [isModalOpen, setModalOpen] = useState(false); const { request, loading } = useAxios(); + const schema = useMemo( + () => + yup.object({ + mobile: yup + .string() + .matches(/^(09\d{9})$/, t("auth.mobileInvalid")) + .required(t("auth.mobileRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { mobile: "", @@ -45,46 +51,53 @@ function Register() { }); return ( - -
- -
- - {formik.touched.mobile && formik.errors.mobile && ( - - {formik.errors.mobile} + + +
+ + + + {formik.touched.mobile && formik.errors.mobile && ( + + {formik.errors.mobile} + + )} + + {t("auth.sendCode")} + + + + - -
-
+ + +
+
+ ); } diff --git a/src/app/(auth)/(register)/register/password/page.tsx b/src/app/(auth)/(register)/register/password/page.tsx index 5309c1e..f2be62a 100644 --- a/src/app/(auth)/(register)/register/password/page.tsx +++ b/src/app/(auth)/(register)/register/password/page.tsx @@ -7,7 +7,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React from "react"; +import React, { useMemo } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -15,29 +15,33 @@ import { useRouter } from "next/navigation"; import { IVerifyOtp } from "@/types/types"; import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; - -const schema = yup.object().shape({ - password: yup - .string() - .required("کلمه عبور نمی\u200Cتواند خالی باشد") - .min(8, "کلمه عبور نباید کوتاه تر از 8 کاراکتر باشد") - .matches( - /^(?=.*[a-zA-Zآ-ی])(?=.*\d).{8,}$/, - "کلمه عبور باید شامل حروف و اعداد باشد" - ), - confirmPassword: yup - .string() - .required("تکرار کلمه عبور نمی\u200Cتواند خالی باشد") - .oneOf( - [yup.ref("password")], - "تکرار کلمه عبور باید با کلمه عبور مطابقت داشته باشد" - ), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function PasswordPage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); + const schema = useMemo( + () => + yup.object().shape({ + password: yup + .string() + .required(t("auth.passwordRequired")) + .min(8, t("auth.passwordMin")) + .matches( + /^(?=.*[a-zA-Zآ-ی])(?=.*\d).{8,}$/, + t("auth.passwordLettersNumbers") + ), + confirmPassword: yup + .string() + .required(t("auth.confirmPasswordRequired")) + .oneOf([yup.ref("password")], t("auth.confirmPasswordMismatch")), + }), + [t] + ); + const formik = useFormik({ initialValues: { password: "", @@ -52,68 +56,71 @@ function PasswordPage() { router.push("/register/fullname"); } catch (err: any) { const message = - err?.response?.data?.message || "ذخیره کلمه عبور ناموفق بود"; + err?.response?.data?.message || t("auth.savePasswordFailed"); toast.error(message); } }, }); return ( - -
- - -
- - {formik.touched.password && formik.errors.password && ( - - {formik.errors.password} - - )} + + + + + +
+ + {formik.touched.password && formik.errors.password && ( + + {formik.errors.password} + + )} - - {formik.touched.confirmPassword && formik.errors.confirmPassword && ( - - {formik.errors.confirmPassword} - - )} -
-
+ + {formik.touched.confirmPassword && + formik.errors.confirmPassword && ( + + {formik.errors.confirmPassword} + + )} +
+
- - - ثبت و ادامه - - -
-
+ + + {t("auth.saveAndContinue")} + + + + + ); } diff --git a/src/app/(auth)/(register)/register/username/page.tsx b/src/app/(auth)/(register)/register/username/page.tsx index b78d2d3..a478ac5 100644 --- a/src/app/(auth)/(register)/register/username/page.tsx +++ b/src/app/(auth)/(register)/register/username/page.tsx @@ -7,21 +7,25 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; +import * as yup from "yup"; import { useRouter } from "next/navigation"; import { IVerifyOtp } from "@/types/types"; import AuthNextButton from "@/components/auth/AuthNextButton"; import UsernameSuggestions from "@/components/auth/UsernameSuggestions"; -import { usernameFormSchema } from "@/lib/validation/usernameSchema"; import { sanitizeUsernameInput, USERNAME_MIN_LENGTH, - USERNAME_VALIDATION_MESSAGE, + USERNAME_MAX_LENGTH, + USERNAME_REGEX, } from "@/lib/validation/username"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function UsernamePage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const [isDuplicate, setIsDuplicate] = useState(false); @@ -29,11 +33,31 @@ function UsernamePage() { const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + const schema = useMemo( + () => + yup.object({ + username: yup + .string() + .required(t("auth.usernameRequired")) + .min( + USERNAME_MIN_LENGTH, + t("auth.usernameMinLength", { min: USERNAME_MIN_LENGTH }) + ) + .max( + USERNAME_MAX_LENGTH, + t("auth.usernameMaxLength", { max: USERNAME_MAX_LENGTH }) + ) + .matches(/^[a-zA-Z0-9]/, t("auth.usernameStartRule")) + .matches(USERNAME_REGEX, t("auth.usernameCharsRule")), + }), + [t] + ); + const formik = useFormik({ initialValues: { username: "", }, - validationSchema: usernameFormSchema, + validationSchema: schema, onSubmit: async (values) => { setIsDuplicate(false); setSuggestions([]); @@ -45,7 +69,13 @@ function UsernamePage() { }) as IVerifyOtp; localStorage.setItem("username", values.username.trim().toLowerCase()); - router.push("/register/password"); + const authProvider = + typeof window !== "undefined" + ? localStorage.getItem("auth_provider") + : null; + router.push( + authProvider === "google" ? "/register/fullname" : "/register/password" + ); } catch (err: any) { const data = err?.response?.data; if (data?.message?.includes("تکراری")) { @@ -72,73 +102,81 @@ function UsernamePage() { }; return ( - -
- - -
-

- حداقل {USERNAME_MIN_LENGTH} کاراکتر؛ فقط حروف انگلیسی، اعداد و - . _ - -

- handleUsernameChange(e.target.value)} - onBlur={formik.handleBlur} - /> + + + + + +
+

+ {t("auth.usernameHint", { + max: USERNAME_MAX_LENGTH, + min: USERNAME_MIN_LENGTH, + })} +

+ handleUsernameChange(e.target.value)} + onBlur={formik.handleBlur} + /> - {isDuplicate && ( - - نام کاربری تکراری است - - )} + {isDuplicate && ( + + {t("auth.usernameDuplicate")} + + )} - {formik.touched.username && formik.errors.username && !isDuplicate && ( - - {formik.errors.username} - - )} + {formik.touched.username && + formik.errors.username && + !isDuplicate && ( + + {formik.errors.username} + + )} - {!formik.errors.username && !isDuplicate && ( - - {USERNAME_VALIDATION_MESSAGE} - - )} + {!formik.errors.username && !isDuplicate && ( + + {t("auth.usernameCharsRule")} + + )} - -
-
+ +
+
- - - ثبت و ادامه - - -
-
+ + + {t("auth.saveAndContinue")} + + + + + ); } diff --git a/src/app/(auth)/AuthProviders.tsx b/src/app/(auth)/AuthProviders.tsx index 7ff1ce2..7e82c7f 100644 --- a/src/app/(auth)/AuthProviders.tsx +++ b/src/app/(auth)/AuthProviders.tsx @@ -1,16 +1,72 @@ "use client"; -import { GoogleAuthRootProvider } from "@/components/auth/GoogleSignInButton"; +import GoogleSignInButtonBase, { + GoogleAuthRootProvider, +} from "@/components/auth/GoogleSignInButton"; import RegisterRouteGuard from "@/components/auth/RegisterRouteGuard"; +import { GOOGLE_OAUTH_CLIENT_ID } from "@/config/googleAuth"; +import { createContext, useContext, useEffect, useState } from "react"; + +const GoogleClientIdContext = createContext(""); + +export function useGoogleClientId(): string { + return useContext(GoogleClientIdContext); +} + +export function GoogleSignInButton( + props: Omit, "clientId"> +) { + const clientId = useGoogleClientId(); + return ( + + ); +} export default function AuthProviders({ children, + googleClientId = "", }: { children: React.ReactNode; + googleClientId?: string; }) { + const [resolvedClientId, setResolvedClientId] = useState( + googleClientId || GOOGLE_OAUTH_CLIENT_ID + ); + + useEffect(() => { + let cancelled = false; + + async function loadClientId() { + const fallback = googleClientId || GOOGLE_OAUTH_CLIENT_ID; + setResolvedClientId(fallback); + + try { + const res = await fetch("/api/auth/config", { cache: "no-store" }); + if (!res.ok) return; + const data = (await res.json()) as { googleClientId?: string }; + if (!cancelled) { + setResolvedClientId(data?.googleClientId || fallback); + } + } catch { + // keep fallback + } + } + + void loadClientId(); + + return () => { + cancelled = true; + }; + }, [googleClientId]); + return ( - - {children} - + + + {children} + + ); } diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index d9a1192..7594a1b 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,18 +1,22 @@ import type { Metadata } from "next"; import AuthProviders from "./AuthProviders"; +import { getGoogleClientId } from "@/lib/auth/googleClientId"; +import { generateSeoPageMetadata } from "@/lib/i18n/server"; -export const metadata: Metadata = { - title: { - default: "ورود و ثبت‌نام | مدستاگرام", - }, - description: "ورود یا ثبت‌نام در مدستاگرام", - robots: { index: false, follow: false }, -}; +export const dynamic = "force-dynamic"; + +export async function generateMetadata(): Promise { + return generateSeoPageMetadata("auth", { index: false }); +} export default function AuthLayout({ children, }: { children: React.ReactNode; }) { - return {children}; + const googleClientId = getGoogleClientId(); + + return ( + {children} + ); } diff --git a/src/app/(auth)/verify/auth/page.tsx b/src/app/(auth)/verify/auth/page.tsx index cfb56a1..dcbff1a 100644 --- a/src/app/(auth)/verify/auth/page.tsx +++ b/src/app/(auth)/verify/auth/page.tsx @@ -5,7 +5,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React from "react"; +import React, { useMemo } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -19,30 +19,38 @@ import persian from "react-date-object/calendars/persian"; import persian_fa from "react-date-object/locales/persian_fa"; import Image from "next/image"; import moment from "jalali-moment"; - -// Validation schema using Yup -const schema = yup.object().shape({ - birthDate: yup.string().required(" انتخاب تاریخ تولد الزامی است"), - shaba: yup - .string() - .matches(/^(?=.{24}$)[0-9]*$/, "شماره شبا باید ۲۴ رقم باشد") - .required("شماره شبا الزامی است"), - nationalCode: yup - .string() - .matches(/^\d{10}$/, "کد ملی باید ۱۰ رقم باشد") - .required("کد ملی الزامی است"), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function AuthPage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); - + const p2e = (es: string): string => - es.replace(/[۰-۹]/g, (d) => String.fromCharCode("۰۱۲۳۴۵۶۷۸۹".indexOf(d) + 48)); - + es.replace(/[۰-۹]/g, (d) => + String.fromCharCode("۰۱۲۳۴۵۶۷۸۹".indexOf(d) + 48) + ); + const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + const schema = useMemo( + () => + yup.object().shape({ + birthDate: yup.string().required(t("auth.birthDateRequired")), + shaba: yup + .string() + .matches(/^(?=.{24}$)[0-9]*$/, t("auth.shabaInvalid")) + .required(t("auth.shabaRequired")), + nationalCode: yup + .string() + .matches(/^\d{10}$/, t("auth.nationalCodeInvalid")) + .required(t("auth.nationalCodeRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { nationalCode: "", @@ -56,129 +64,128 @@ function AuthPage() { mobile: mobile, birthday: moment(p2e(values.birthDate), "jYYYY/jMM/jDD").format( "YYYY/MM/DD" - ), // تبدیل به میلادی + ), national_code: values.nationalCode, shaba: values.shaba, }); router.push("/verify/location"); } catch (err: any) { - toast.error(err?.message || "خطای نامشخصی رخ داد."); + toast.error(err?.response?.data?.message || t("auth.unknownError")); } finally { setSubmitting(false); } }, }); - console.log(formik.values); - return ( - -
- - احراز هویت -
- -
- -
- جهت احراز هویت -
-
- {/* کد ملی */} - + + + + + + {t("auth.identityVerification")} + +
+
- {/* تاریخ تولد */} -
- { - const formattedDate = value?.format("YYYY/MM/DD") || ""; // اگر مقدار `null` بود، رشته خالی - formik.setFieldValue("birthDate", formattedDate); // مقدار فرم را تنظیم کنید - }} - calendarPosition="bottom-center" - placeholder="تاریخ تولد" - inputClass={`w-full dir-ltr max-w-[290px] p-3 !pr-6 text-center rounded-2xl border border-border-primary-light dark:border-border-primary-dark bg-secondary-light dark:bg-secondary-dark font-medium border mt-2 !p-1 h-[36px] ${ - formik.touched.birthDate && formik.errors.birthDate - ? "border-red-500 dark:border-red-500" - : "border-gray-300" - }`} - /> - -
-
- {formik.touched.nationalCode && formik.errors.nationalCode && ( - - {formik.errors.nationalCode} - - )} - {formik.touched.birthDate && formik.errors.birthDate && ( - - {formik.errors.birthDate} - - )} - جهت واریز حق الزحمه - {/* شماره شبا */} -
- IR - - {formik.touched.shaba && formik.errors.shaba && ( - - {formik.errors.shaba} +
+ + {t("auth.forIdentityVerification")} - )} -
+
+
+ +
+
+ { + const formattedDate = value?.format("YYYY/MM/DD") || ""; + formik.setFieldValue("birthDate", formattedDate); + }} + calendarPosition="bottom-center" + placeholder={t("auth.birthDatePlaceholder")} + inputClass={`w-full dir-ltr max-w-[290px] p-3 !pr-6 text-center rounded-2xl border border-border-primary-light dark:border-border-primary-dark bg-secondary-light dark:bg-secondary-dark font-medium border mt-2 !p-1 h-[36px] ${ + formik.touched.birthDate && formik.errors.birthDate + ? "border-red-500 dark:border-red-500" + : "border-gray-300" + }`} + /> + +
+
- - شماره شبا باید به نام خود شخص باشد - -
- + {formik.touched.nationalCode && formik.errors.nationalCode && ( + + {formik.errors.nationalCode} + + )} + {formik.touched.birthDate && formik.errors.birthDate && ( + + {formik.errors.birthDate} + + )} + {t("auth.forPayment")} +
+ IR + + {formik.touched.shaba && formik.errors.shaba && ( + + {formik.errors.shaba} + + )} +
- router.push("/verify/location")}> - - ثبت و ادامه - - - - + {t("auth.shabaOwnerHint")} +
+ + + router.push("/verify/location")}> + + {t("auth.saveAndContinue")} + + + + + ); } diff --git a/src/app/(auth)/verify/avatar/page.tsx b/src/app/(auth)/verify/avatar/page.tsx index 040e37c..0d0f3e7 100644 --- a/src/app/(auth)/verify/avatar/page.tsx +++ b/src/app/(auth)/verify/avatar/page.tsx @@ -6,19 +6,20 @@ import AuthPageLayout, { AuthPageContent, } from "@/components/auth/AuthPageLayout"; import AuthNextButton from "@/components/auth/AuthNextButton"; -import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; +import { IMAGE_BASE_URL } from "@/components/main/BaseUrl"; import useAxios from "@/hooks/useAxios"; import { IProfileData } from "@/types/types"; import Image from "next/image"; import { useRouter } from "next/navigation"; import toast from "react-hot-toast"; - -// کتابخانه برای crop import Cropper from "react-easy-crop"; import getCroppedImg from "@/helpers/cropImage"; import { Area } from "react-easy-crop"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function AvatarPage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const [userDetail, setUserDetail] = useState(null); @@ -45,7 +46,6 @@ function AvatarPage() { fetchData(); }, []); - // انتخاب تصویر const selectImage = async (event: React.ChangeEvent) => { if (event.target.files && event.target.files[0]) { const file = event.target.files[0]; @@ -58,37 +58,28 @@ function AvatarPage() { } }; - // حذف تصویر - // const removeImage = () => { - // setAvatar(null); - // setOriginalImage(null); - // }; - - // دریافت مختصات crop شده const onCropComplete = useCallback( - (croppedArea: Area, croppedPixels: Area) => { + (_croppedArea: Area, croppedPixels: Area) => { setCroppedAreaPixels(croppedPixels); }, [] ); - // تبدیل crop شده به blob const createCroppedImage = async () => { if (!avatar || !croppedAreaPixels) return null; const croppedImage = await getCroppedImg(avatar, croppedAreaPixels); return croppedImage; }; - // آپلود تصویر const uploadImage = async () => { if (!avatar) { - toast.error("لطفا تصویر پروفایل را انتخاب کنید"); + toast.error(t("auth.selectProfileImage")); return; } setLoadingUpload(true); const croppedBlob = await createCroppedImage(); if (!croppedBlob) { - toast.error("خطا در برش تصویر"); + toast.error(t("auth.cropError")); setLoadingUpload(false); return; } @@ -101,16 +92,15 @@ function AvatarPage() { await request("POST", "/verify/profile_image", formData, { headers: { "Content-Type": "multipart/form-data" }, }); - toast.success("تصویر با موفقیت ثبت شد!"); + toast.success(t("auth.imageSavedSuccess")); navigateHandler(); } catch (err) { console.log("Upload error:", err); - toast.error("خطا در ارسال تصویر!"); + toast.error(t("auth.imageUploadError")); setLoadingUpload(false); } }; - // ناوبری بعد از آپلود const navigateHandler = () => { if (userDetail?.user_type === "user") { router.push("/verify/expertise"); @@ -120,68 +110,71 @@ function AvatarPage() { }; return ( - - - تصویر پروفایل + + + + + {t("auth.profileImageTitle")} + -
- {avatar ? ( - + {avatar ? ( + + ) : ( +
+ + )} +
- + - - - -
+ + + + - - - ثبت و ادامه - - -
+ + + {t("auth.saveAndContinue")} + + + + ); } -export default AvatarPage; \ No newline at end of file +export default AvatarPage; diff --git a/src/app/(auth)/verify/colors/page.tsx b/src/app/(auth)/verify/colors/page.tsx index f71d675..4c5888d 100644 --- a/src/app/(auth)/verify/colors/page.tsx +++ b/src/app/(auth)/verify/colors/page.tsx @@ -5,7 +5,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import * as yup from "yup"; import { useRouter } from "next/navigation"; @@ -13,15 +13,12 @@ import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; import Image from "next/image"; - -// Validation schema using Yup -const schema = yup.object().shape({ - selectedHairImage: yup.string().required("رنگ مو الزامی است"), - selectedEyeImage: yup.string().required(" رنگ چشم الزامی است"), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function Colors() { - const [selectedButton, setSelectedButton] = useState(1); // Default: 1 for "قد" + const { t } = useTranslation("common"); + const [selectedButton, setSelectedButton] = useState(1); const [selectedHairImage, setSelectedHairImage] = useState(""); const [selectedEyeImage, setSelectedEyeImage] = useState(""); const router = useRouter(); @@ -32,6 +29,15 @@ function Colors() { typeof window !== "undefined" ? localStorage.getItem("expertise") || "" : "" ); + const schema = useMemo( + () => + yup.object().shape({ + selectedHairImage: yup.string().required(t("auth.hairColorRequired")), + selectedEyeImage: yup.string().required(t("auth.eyeColorRequired")), + }), + [t] + ); + useEffect(() => { if (expertise && expertise !== "مدل") { router.replace("/verify/public-relations"); @@ -49,8 +55,6 @@ function Colors() { } }; - console.log(selectedHairImage, selectedEyeImage); - const sendSizesHandler = async () => { try { const data = { @@ -69,7 +73,6 @@ function Colors() { setSelectedButton(buttonIndex); }; - // تصاویر رنگ چشم و رنگ مو const eyeImages = Array.from( { length: 26 }, (_, i) => `/images/eyes/${String(i + 1).padStart(2, "0")}.png` @@ -79,9 +82,8 @@ function Colors() { (_, i) => `/images/hairs/${String(i + 1).padStart(2, "0")}.png` ); - // تابع برای ذخیره نام فایل انتخاب شده const handleImageSelect = (image: string, type: "eye" | "hair") => { - const fileName = image.split("/").pop(); // استخراج فقط نام فایل + const fileName = image.split("/").pop(); if (type === "eye") { setSelectedEyeImage(fileName || ""); } else if (type === "hair") { @@ -89,98 +91,97 @@ function Colors() { } }; - - - return ( - - - سایز -
- -
+ + + + {t("auth.sizesTitle")} +
+ +
-

مشخصات ظاهری

+

+ {t("auth.appearanceDetails")} +

- {/* Buttons for categories */} -
- + +
+ +
+ {selectedButton === 1 && ( +
+ {eyeImages.map((image) => ( + {image} handleImageSelect(image, "eye")} + /> + ))} +
+ )} + + {selectedButton === 2 && ( +
+ {hairImages.map((image) => ( + {image} handleImageSelect(image, "hair")} + /> + ))} +
+ )} +
+
+ + router.push("/verify/public-relations")}> + - رنگ چشم - - -
- - {/* Display images based on selected button */} -
- {selectedButton === 1 && ( -
- {eyeImages.map((image) => ( - {image} handleImageSelect(image, "eye")} - /> - ))} -
- )} - - {selectedButton === 2 && ( -
- {hairImages.map((image) => ( - {image} handleImageSelect(image, "hair")} - /> - ))} -
- )} -
-
- - router.push("/verify/public-relations")}> - - ثبت و ادامه - - -
+ {t("auth.saveAndContinue")} + + + + ); } diff --git a/src/app/(auth)/verify/confirm/page.tsx b/src/app/(auth)/verify/confirm/page.tsx index 49e12d5..1804a9b 100644 --- a/src/app/(auth)/verify/confirm/page.tsx +++ b/src/app/(auth)/verify/confirm/page.tsx @@ -12,14 +12,16 @@ import VerificationBadge from "@/components/main/VerificationBadge"; import { useUser } from "@/hooks/useUser"; import { formatFullName } from "@/lib/formatFullName"; import useAxios from "@/hooks/useAxios"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function Confirm() { + const { t } = useTranslation("common"); const user = useUser(); const { request } = useAxios(); const router = useRouter(); const [showModal, setShowModal] = useState(false); const [navigating, setNavigating] = useState(false); - const [verifiedStatus, setVerifiedStatus] = useState(null); useEffect(() => { @@ -43,114 +45,122 @@ function Confirm() { setShowModal(true); setNavigating(false); }; + return ( - -
- - ثبت نام شما تکمیل شد - - - ورود شما به خانواده مدستاگرام را تبریک می گوییم{" "} - -
- -
- {formatFullName(user?.first_name, user?.last_name)} -
- - {user?.user_name} - - + + +
+ + {t("auth.registrationComplete")} + + + {t("auth.welcomeMessage")} + +
+ +
+ {formatFullName(user?.first_name, user?.last_name)} +
+ + {user?.user_name} + + +
-
- - پس از تایید مدارک و احراز هویت، تیک آبی یا طلایی کنار نام کاربری شما - قرار می گیرد - + + {t("auth.verificationBadgeHint")} + -
-
- - - - -
-
- - - - +
+
+ + + + +
+
+ + + + +
+ + {t("auth.saveAndContinue")} + + + {t("auth.verificationTimeOffice")} + + + {t("auth.verificationTimeOffHours")} + + setShowModal(false)} + height="200px" + > +

+ {t("auth.shareWorkHint")} +

+
+ + {t("auth.createPost")} + + + {t("auth.later")} + +
+
- {/* */} - - ثبت و ادامه - - - زمان تایید مدارک 5 دقیقه تا 1 ساعت در ساعات اداری و - - - 3 تا 8 ساعت در ساعات غیر اداری - - setShowModal(false)} - height="200px" - > -

- .برای بهتر دیده شدن نمونه کارهای خود را به اشتراگ بگذراید -

-
- - ثبت پست - - - بعدا - -
-
-
-
+ +
); } diff --git a/src/app/(auth)/verify/cooperation-type/page.tsx b/src/app/(auth)/verify/cooperation-type/page.tsx index 99663dc..a772a24 100644 --- a/src/app/(auth)/verify/cooperation-type/page.tsx +++ b/src/app/(auth)/verify/cooperation-type/page.tsx @@ -5,7 +5,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React from "react"; +import React, { useMemo } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -13,18 +13,24 @@ import { useRouter } from "next/navigation"; import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; - -// Validation schema using Yup -const schema = yup.object().shape({ - selectedButton: yup.boolean().required("انتخاب نوع همکاری الزامی است"), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function CooperationType() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + const schema = useMemo( + () => + yup.object().shape({ + selectedButton: yup.boolean().required(t("auth.cooperationRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { selectedButton: null, @@ -39,7 +45,7 @@ function CooperationType() { }); router.push("/verify/public-relations"); } catch (err: any) { - toast.error(err?.message || "خطای نامشخصی رخ داد."); + toast.error(err?.response?.data?.message || t("auth.unknownError")); } finally { setSubmitting(false); } @@ -47,62 +53,66 @@ function CooperationType() { }); return ( - -
- - نوع همکاری -
- -
- -

- آیا مایل به همکاری خارج از محل سکونت خود هستید؟ -

- -
-
- formik.setFieldValue("selectedButton", true)} - className={`mt-5 text-xs w-full max-w-[250px] flex items-center justify-center flex-row-reverse gap-2 px-1 ${ - formik.values.selectedButton === true - ? "btn-modern--selected text-white" - : "" - }`} - > - بله خوشحال هم می شم - - formik.setFieldValue("selectedButton", false)} - className={`mt-5 text-xs w-full max-w-[250px] flex items-center justify-center flex-row-reverse gap-2 px-1 ${ - formik.values.selectedButton === false - ? "btn-modern--selected text-white" - : "" - }`} - > - نه شهر خودم رو ترجیح میدم - -
- - {formik.errors.selectedButton && formik.touched.selectedButton && ( -
- {formik.errors.selectedButton} + + + + + + {t("auth.cooperationTypeTitle")} + +
+
- )} -
- - router.push("/verify/public-relations")}> - - ثبت و ادامه - - - - +

+ {t("auth.cooperationQuestion")} +

+ +
+
+ formik.setFieldValue("selectedButton", true)} + className={`mt-5 text-xs w-full max-w-[250px] flex items-center justify-center flex-row-reverse gap-2 px-1 ${ + formik.values.selectedButton === true + ? "btn-modern--selected text-white" + : "" + }`} + > + {t("auth.cooperationYes")} + + formik.setFieldValue("selectedButton", false)} + className={`mt-5 text-xs w-full max-w-[250px] flex items-center justify-center flex-row-reverse gap-2 px-1 ${ + formik.values.selectedButton === false + ? "btn-modern--selected text-white" + : "" + }`} + > + {t("auth.cooperationNo")} + +
+ + {formik.errors.selectedButton && formik.touched.selectedButton && ( +
+ {formik.errors.selectedButton} +
+ )} +
+ + + router.push("/verify/public-relations")}> + + {t("auth.saveAndContinue")} + + + + + ); } diff --git a/src/app/(auth)/verify/expertise/page.tsx b/src/app/(auth)/verify/expertise/page.tsx index b17a8bb..4842147 100644 --- a/src/app/(auth)/verify/expertise/page.tsx +++ b/src/app/(auth)/verify/expertise/page.tsx @@ -5,7 +5,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import * as yup from "yup"; import { useRouter } from "next/navigation"; @@ -14,24 +14,42 @@ import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; import ExpertisePicker from "@/components/auth/ExpertisePicker"; import { IExpertise } from "@/types/types"; - -const schema = yup.object().shape({ - subExpertise: yup - .array() - .of(yup.string().required("حداقل یک زیرمهارت را انتخاب کنید")) - .min(1, "حداقل یک زیرمهارت را انتخاب کنید"), - expertise: yup.string().required("انتخاب نوع تخصص الزامی است"), -}); +import { + resolveDisplaySubExpertise, + subExpertiseListIncludes, +} from "@/lib/subExpertiseDisplay"; +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function Expertise() { + const { t } = useTranslation("common"); const [expertise, setExpertise] = useState(""); const [expertiseList, setExpertiseList] = useState(null); const [subExpertise, setSubExpertise] = useState([]); + const [displaySubExpertise, setDisplaySubExpertise] = useState( + null + ); const router = useRouter(); const { request, loading } = useAxios(); const mobile = typeof window !== "undefined" ? localStorage.getItem("mobile") : null; + const schema = useMemo( + () => + yup.object().shape({ + subExpertise: yup + .array() + .of(yup.string().required(t("auth.subExpertiseMin"))) + .min(1, t("auth.subExpertiseMin")), + displaySubExpertise: yup + .string() + .nullable() + .required(t("auth.displaySubExpertiseRequired")), + expertise: yup.string().required(t("auth.expertiseRequired")), + }), + [t] + ); + const fetchData = async () => { try { const response = await request<{ expertises: IExpertise[] }>( @@ -48,10 +66,44 @@ function Expertise() { fetchData(); }, []); + const handleSubExpertiseToggle = (value: string) => { + setSubExpertise((prev) => { + const next = prev.includes(value) + ? prev.filter((item) => item !== value) + : [...prev, value]; + + setDisplaySubExpertise((current) => { + if (current === value && !next.includes(value)) { + return next[0] ?? null; + } + if (current && !next.includes(current)) { + return next[0] ?? null; + } + if (!current && next.length === 1) { + return next[0]; + } + return current; + }); + + return next; + }); + }; + const handleCheck = async () => { + const resolvedDisplay = subExpertiseListIncludes( + subExpertise, + displaySubExpertise + ) + ? displaySubExpertise + : resolveDisplaySubExpertise(subExpertise, displaySubExpertise); + try { - await schema.validate({ expertise, subExpertise }); - await sendExpertiseTypeHandler(); + await schema.validate({ + expertise, + subExpertise, + displaySubExpertise: resolvedDisplay, + }); + await sendExpertiseTypeHandler(resolvedDisplay); } catch (validationError: any) { toast.error(validationError.message, { duration: 4000, @@ -59,59 +111,62 @@ function Expertise() { } }; - const sendExpertiseTypeHandler = async () => { + const sendExpertiseTypeHandler = async (resolvedDisplay: string | null) => { try { const data = { mobile, expertise, sub_expertise: subExpertise, + display_sub_expertise: resolvedDisplay, }; await request("POST", "/verify/expertise", data); localStorage.setItem("expertise", expertise); + if (resolvedDisplay) { + localStorage.setItem("display_sub_expertise", resolvedDisplay); + } router.push("/verify/services"); } catch (error: any) { - toast.error(error?.message || "خطای نامشخصی رخ داد."); + toast.error(error?.response?.data?.message || t("auth.unknownError")); } }; return ( - - - تخصص + + + + {t("auth.expertiseTitle")} - + -

در چه زمینه ای تخصص دارید؟

+

{t("auth.expertiseQuestion")}

- { - setExpertise(value); - setSubExpertise([]); - }} - onSubExpertiseToggle={(value) => { - setSubExpertise((prev) => - prev.includes(value) - ? prev.filter((item) => item !== value) - : [...prev, value] - ); - }} - /> -
+ { + setExpertise(value); + setSubExpertise([]); + setDisplaySubExpertise(null); + }} + onSubExpertiseToggle={handleSubExpertiseToggle} + onDisplaySubExpertiseChange={setDisplaySubExpertise} + /> +
- router.push("/verify/services")}> - - ثبت و ادامه - - -
+ router.push("/verify/services")}> + + {t("auth.saveAndContinue")} + + + + ); } diff --git a/src/app/(auth)/verify/gender/page.tsx b/src/app/(auth)/verify/gender/page.tsx index 4c67b61..b699bd0 100644 --- a/src/app/(auth)/verify/gender/page.tsx +++ b/src/app/(auth)/verify/gender/page.tsx @@ -5,7 +5,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React from "react"; +import React, { useMemo } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -14,13 +14,11 @@ import AuthNextButton from "@/components/auth/AuthNextButton"; import toast from "react-hot-toast"; import AuthUserDetails from "@/components/auth/AuthUserDetails"; import Image from "next/image"; - -// Validation schema using Yup -const schema = yup.object().shape({ - selectedButton: yup.string().required("جنسیت الزامی است"), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function Gender() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); const mobile = @@ -28,6 +26,14 @@ function Gender() { const expertise = typeof window !== "undefined" ? localStorage.getItem("expertise") : null; + const schema = useMemo( + () => + yup.object().shape({ + selectedButton: yup.string().required(t("auth.genderRequired")), + }), + [t] + ); + const formik = useFormik({ initialValues: { selectedButton: "", @@ -45,7 +51,7 @@ function Gender() { router.push("/verify/services"); } } catch (err: any) { - toast.error(err?.message || "خطای نامشخصی رخ داد."); + toast.error(err?.response?.data?.message || t("auth.unknownError")); } finally { setSubmitting(false); } @@ -53,70 +59,72 @@ function Gender() { }); return ( - -
- - جنسیت -
- -
- -
-
- formik.setFieldValue("selectedButton", "female")} - className={`mt-5 w-full max-w-[250px] !border-[#FC8EAC] flex items-center justify-center flex-row-reverse gap-2 ${ - formik.values.selectedButton === "female" - ? "btn-modern--selected text-white" - : "" - }`} - > - gender icon - خانم - - formik.setFieldValue("selectedButton", "male")} - className={`mt-5 w-full max-w-[250px] !border-[#3E8BFF] flex items-center justify-center flex-row-reverse gap-2 ${ - formik.values.selectedButton === "male" - ? "!bg-[#3E8BFF] !text-white" - : "" - }`} - > - gender icon - آقا - -
- - {formik.errors.selectedButton && formik.touched.selectedButton && ( -
- {formik.errors.selectedButton} + + + + + {t("auth.genderTitle")} +
+
- )} -
- - router.push("/verify/sizes")}> - - ثبت و ادامه - - - - +
+
+ formik.setFieldValue("selectedButton", "female")} + className={`mt-5 w-full max-w-[250px] !border-[#FC8EAC] flex items-center justify-center flex-row-reverse gap-2 ${ + formik.values.selectedButton === "female" + ? "btn-modern--selected text-white" + : "" + }`} + > + gender icon + {t("auth.female")} + + formik.setFieldValue("selectedButton", "male")} + className={`mt-5 w-full max-w-[250px] !border-[#3E8BFF] flex items-center justify-center flex-row-reverse gap-2 ${ + formik.values.selectedButton === "male" + ? "!bg-[#3E8BFF] !text-white" + : "" + }`} + > + gender icon + {t("auth.male")} + +
+ + {formik.errors.selectedButton && formik.touched.selectedButton && ( +
+ {formik.errors.selectedButton} +
+ )} +
+ + + router.push("/verify/sizes")}> + + {t("auth.saveAndContinue")} + + + + + ); } diff --git a/src/app/(auth)/verify/location/page.tsx b/src/app/(auth)/verify/location/page.tsx index c5bfbdd..0c2f79a 100644 --- a/src/app/(auth)/verify/location/page.tsx +++ b/src/app/(auth)/verify/location/page.tsx @@ -5,7 +5,7 @@ import AuthPageLayout, { AuthFormFooter, AuthPageContent, } from "@/components/auth/AuthPageLayout"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import useAxios from "@/hooks/useAxios"; import { useFormik } from "formik"; import * as yup from "yup"; @@ -16,19 +16,13 @@ import AuthUserDetails from "@/components/auth/AuthUserDetails"; import SelectBox from "@/components/elements/SelectBox"; import { ICity, IProvince } from "@/types/types"; import Map, { GeolocateControl, Marker } from "react-map-gl"; - import "mapbox-gl/dist/mapbox-gl.css"; import Image from "next/image"; - -// Validation schema using Yup -const schema = yup.object().shape({ - markerCoordinate: yup.mixed().required(" انتخاب لوکیشن الزامی است"), - address: yup.string().required(" آدرس الزامی است"), - cityId: yup.string().required(" انتخاب شهر الزامی است"), - stateId: yup.string().required(" انتخاب استان الزامی است"), -}); +import { useTranslation } from "react-i18next"; +import LocalePageShell from "@/components/i18n/LocalePageShell"; function LocationPage() { + const { t } = useTranslation("common"); const router = useRouter(); const { request, loading } = useAxios(); @@ -40,6 +34,17 @@ function LocationPage() { lng: number; } | null>(null); + const schema = useMemo( + () => + yup.object().shape({ + markerCoordinate: yup.mixed().required(t("auth.locationRequired")), + address: yup.string().required(t("auth.addressRequired")), + cityId: yup.string().required(t("auth.cityRequired")), + stateId: yup.string().required(t("auth.provinceRequired")), + }), + [t] + ); + const fetchStates = async () => { try { const response = await request<{ provinces: IProvince[] }>( @@ -52,7 +57,6 @@ function LocationPage() { } }; - // Fetch cities when a province is selected const fetchCities = async (provinceId: string) => { try { const response = await request<{ cities: ICity[] }>( @@ -97,18 +101,17 @@ function LocationPage() { }); router.push("/verify/national-cart"); } catch (err: any) { - toast.error(err?.message || "خطای نامشخصی رخ داد."); + toast.error(err?.response?.data?.message || t("auth.unknownError")); } finally { setSubmitting(false); } }, }); - // Handle province change const handleProvinceChange = (e: React.ChangeEvent) => { const selectedProvinceId = e.target.value; formik.setFieldValue("stateId", selectedProvinceId); - fetchCities(selectedProvinceId); // Fetch cities for the selected province + fetchCities(selectedProvinceId); }; const handleMapClick = (event: any) => { @@ -121,135 +124,137 @@ function LocationPage() { }; return ( - -
- - لوکیشن -
- -
+ + + + + {t("auth.locationTitle")} +
+ +
- - مشخص کنید در کدام شهر قادر به انجام فعالیت هستید - - -
- - - {allStates?.map((item: IProvince) => ( - - ))} - - {formik.touched.stateId && formik.errors.stateId && ( - - {formik.errors.stateId} + + {t("auth.locationActivityHint")} - )} - formik.setFieldValue("cityId", e.target.value)} - > - - {cities?.map((city: ICity) => ( - - ))} - - {formik.touched.cityId && formik.errors.cityId && ( - - {formik.errors.cityId} - - )} - - {formik.touched.address && formik.errors.address && ( diff --git a/src/components/PwaHead.tsx b/src/components/PwaHead.tsx new file mode 100644 index 0000000..36fc35f --- /dev/null +++ b/src/components/PwaHead.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useEffect } from "react"; +import { useAppLanguage } from "@/contexts/LanguageProvider"; +import { + getPwaLabels, + PWA_VERSION, + toPwaLang, +} from "@/lib/pwaConfig"; + +function upsertLink(rel: string, href: string) { + let link = document.querySelector(`link[rel="${rel}"]`); + if (!link) { + link = document.createElement("link"); + link.rel = rel; + document.head.appendChild(link); + } + link.href = href; +} + +function upsertMeta(name: string, content: string) { + let meta = document.querySelector(`meta[name="${name}"]`); + if (!meta) { + meta = document.createElement("meta"); + meta.name = name; + document.head.appendChild(meta); + } + meta.content = content; +} + +export default function PwaHead() { + const { language, ready } = useAppLanguage(); + + useEffect(() => { + if (!ready) return; + + const pwaLang = toPwaLang(language); + const labels = getPwaLabels(pwaLang); + + upsertLink( + "manifest", + `/manifest.webmanifest?lang=${pwaLang}&v=${PWA_VERSION}` + ); + upsertLink("apple-touch-icon", "/images/icons/apple-touch-icon.png"); + upsertMeta("apple-mobile-web-app-title", labels.short_name); + upsertMeta("application-name", labels.short_name); + }, [language, ready]); + + return null; +} diff --git a/src/components/RegisterSW.tsx b/src/components/RegisterSW.tsx index 9ae627a..e20513b 100644 --- a/src/components/RegisterSW.tsx +++ b/src/components/RegisterSW.tsx @@ -1,17 +1,52 @@ "use client"; + import { useEffect } from "react"; -import { ICON_VERSION } from "@/components/main/BaseUrl"; +import { PWA_VERSION } from "@/lib/pwaConfig"; + +function isIosSafari(): boolean { + if (typeof navigator === "undefined") return false; + const ua = navigator.userAgent; + return /iphone|ipad|ipod/i.test(ua) && !/crios|fxios|edgios/i.test(ua); +} + +async function cleanupStaleServiceWorkers() { + if (!("serviceWorker" in navigator)) return; + + try { + const registrations = await navigator.serviceWorker.getRegistrations(); + await Promise.all( + registrations.map((registration) => registration.unregister()) + ); + } catch { + // ignore + } +} export default function RegisterSW() { useEffect(() => { - if ("serviceWorker" in navigator) { - navigator.serviceWorker - .register(`/service-worker.js?v=${ICON_VERSION}`) - .then(() => { - console.log("Service Worker Registered!"); - }); + if (!("serviceWorker" in navigator)) return; + + const register = async () => { + try { + const registration = await navigator.serviceWorker.register( + `/service-worker.js?v=${PWA_VERSION}`, + { scope: "/" } + ); + await registration.update(); + } catch (error) { + console.error("Service Worker registration failed:", error); + if (isIosSafari()) { + await cleanupStaleServiceWorkers(); + } + } + }; + + if (document.readyState === "complete") { + void register(); + } else { + window.addEventListener("load", () => void register(), { once: true }); } }, []); - return null; // این کامپوننت هیچ UI نداره، فقط Service Worker رو ثبت می‌کنه. + return null; } diff --git a/src/components/ScrollRestorationInit.tsx b/src/components/ScrollRestorationInit.tsx new file mode 100644 index 0000000..74c4c96 --- /dev/null +++ b/src/components/ScrollRestorationInit.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { useEffect } from "react"; +import { initManualScrollRestoration } from "@/lib/listScrollRestoration"; + +export default function ScrollRestorationInit() { + useEffect(() => { + initManualScrollRestoration(); + }, []); + + return null; +} diff --git a/src/components/TabNavigation.tsx b/src/components/TabNavigation.tsx index c63fd50..1e408e1 100644 --- a/src/components/TabNavigation.tsx +++ b/src/components/TabNavigation.tsx @@ -5,13 +5,15 @@ import { useEffect, useState } from "react"; import BoldIcon from "@/components/ui/BoldIcon"; import { cn } from "@/lib/utils"; import { PAGE_SHELL_CLASS } from "@/constants/pageLayout"; +import { readLocalStorage } from "@/lib/safeStorage"; +import { useTranslation } from "react-i18next"; -const tabs = [ - { href: "/", icon: "home-2", label: "خانه" }, - { href: "/academy", icon: "teacher", label: "آموزشگاه" }, - { href: "/projects", icon: "briefcase", label: "پروژه‌ها" }, - { href: "/billboards", icon: "flash-circle", label: "بیلبورد" }, - { href: "/settings", icon: "setting", label: "تنظیمات" }, +const TAB_KEYS = [ + { href: "/", icon: "home-2", labelKey: "tabs.home" }, + { href: "/academy", icon: "teacher", labelKey: "tabs.academy" }, + { href: "/projects", icon: "briefcase", labelKey: "tabs.projects" }, + { href: "/billboards", icon: "flash-circle", labelKey: "tabs.billboards" }, + { href: "/settings", icon: "setting", labelKey: "tabs.settings" }, ] as const; type TabNavigationProps = { @@ -19,6 +21,7 @@ type TabNavigationProps = { }; export default function TabNavigation({ currentPage }: TabNavigationProps) { + const { t } = useTranslation("common"); const [mounted, setMounted] = useState(false); const [isDark, setIsDark] = useState(false); @@ -29,7 +32,7 @@ export default function TabNavigation({ currentPage }: TabNavigationProps) { useEffect(() => { if (typeof window === "undefined") return; const checkTheme = () => { - const theme = localStorage.getItem("theme"); + const theme = readLocalStorage("theme"); const isDarkMode = theme === "dark" || (theme === "system" && @@ -51,7 +54,7 @@ export default function TabNavigation({ currentPage }: TabNavigationProps) { )} >
- {tabs.map((tab) => { + {TAB_KEYS.map((tab) => { const isActive = currentPage === tab.href || (tab.href === "/settings" && currentPage.startsWith("/settings")) || @@ -74,17 +77,17 @@ export default function TabNavigation({ currentPage }: TabNavigationProps) { isActive ? "text-[#ff107d]" : isDark - ? "text-zinc-400" + ? "text-white" : "text-zinc-600" )} /> - {tab.label} + {t(tab.labelKey)} ); diff --git a/src/components/TitleGuardian.tsx b/src/components/TitleGuardian.tsx index e144c2a..c894265 100644 --- a/src/components/TitleGuardian.tsx +++ b/src/components/TitleGuardian.tsx @@ -2,16 +2,17 @@ import { useEffect, useRef } from "react"; import { usePathname } from "next/navigation"; -import { defaultSEOConfig } from "@/config/seoConfig"; - -const DEFAULT_TITLE = - typeof defaultSEOConfig.title === "string" - ? defaultSEOConfig.title - : "مدستاگرام"; +import { useTranslation } from "react-i18next"; export default function TitleGuardian() { + const { t } = useTranslation("common"); const pathname = usePathname(); - const lastValidTitle = useRef(DEFAULT_TITLE); + const defaultTitle = t("header.logoAlt"); + const lastValidTitle = useRef(defaultTitle); + + useEffect(() => { + lastValidTitle.current = defaultTitle; + }, [defaultTitle]); useEffect(() => { if (document.title.trim()) { diff --git a/src/components/academy/AcademyFilter.tsx b/src/components/academy/AcademyFilter.tsx index 9c67498..9aec0ac 100644 --- a/src/components/academy/AcademyFilter.tsx +++ b/src/components/academy/AcademyFilter.tsx @@ -1,11 +1,12 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import toast from "react-hot-toast"; import BoldIcon from "@/components/ui/BoldIcon"; import useAxios from "@/hooks/useAxios"; import { getCreateAcademyPath } from "@/lib/getCreateContentPath"; +import { useTranslation } from "react-i18next"; import AcademyFilterModal, { AcademyFilterValues, } from "./AcademyFilterModal"; @@ -40,20 +41,23 @@ const TOOLBAR_ICON_SIZE = 25; const toolbarIconButtonClass = "inline-flex h-[25px] w-[25px] items-center justify-center shrink-0 p-0 leading-none"; -const sortOptions = [ - { value: "createdAt_desc", label: "جدیدترین" }, - { value: "createdAt_asc", label: "قدیمی‌ترین" }, - { value: "likes_desc", label: "محبوب‌ترین" }, - { value: "price_asc", label: "ارزان‌ترین" }, - { value: "price_desc", label: "گران‌ترین" }, -]; +const SORT_KEYS = [ + "createdAt_desc", + "createdAt_asc", + "likes_desc", + "price_asc", + "price_desc", +] as const; -const courseTypes = [ - { value: "", label: "همه دوره‌ها" }, - { value: "normal", label: "معمولی" }, - { value: "pro", label: "پرو" }, - { value: "free", label: "رایگان" }, -]; +const SORT_LABEL_KEYS: Record<(typeof SORT_KEYS)[number], string> = { + createdAt_desc: "newest", + createdAt_asc: "oldest", + likes_desc: "popular", + price_asc: "cheapest", + price_desc: "priciest", +}; + +const TYPE_KEYS = ["", "normal", "pro", "free"] as const; const emptyFilters: AcademyFilterValues = { search: "", @@ -96,18 +100,45 @@ function buildFinalFilters(filters: AcademyFilterValues) { } export default function AcademyFilter({ onFilterChange }: AcademyFilterProps) { + const { t } = useTranslation("common"); const router = useRouter(); const searchParams = useSearchParams(); + const sortOptions = useMemo( + () => + SORT_KEYS.map((value) => ({ + value, + label: t(`academy.filter.sort.${SORT_LABEL_KEYS[value]}`), + })), + [t] + ); + + const courseTypes = useMemo( + () => + TYPE_KEYS.map((value) => ({ + value, + label: t( + value === "" + ? "academy.filter.types.all" + : `academy.filter.types.${value}` + ), + })), + [t] + ); + const [showFilterModal, setShowFilterModal] = useState(false); const [filters, setFilters] = useState(emptyFilters); - const [categories, setCategories] = useState([ - { value: "", label: "همه دسته‌ها" }, - ]); + const [categories, setCategories] = useState([]); const [categoriesLoading, setCategoriesLoading] = useState(false); const [userType, setUserType] = useState(null); const { request } = useAxios(); + const allCategoriesLabel = t("academy.filter.allCategories"); + + useEffect(() => { + setCategories([{ value: "", label: allCategoriesLabel }]); + }, [allCategoriesLabel]); + useEffect(() => { if (typeof window !== "undefined") { setUserType(localStorage.getItem("usertype")); @@ -134,7 +165,7 @@ export default function AcademyFilter({ onFilterChange }: AcademyFilterProps) { } const formattedCategories: CategoryOption[] = [ - { value: "", label: "همه دسته‌ها" }, + { value: "", label: allCategoriesLabel }, ...categoriesList.map((cat) => ({ value: cat.title, label: cat.title, @@ -145,14 +176,14 @@ export default function AcademyFilter({ onFilterChange }: AcademyFilterProps) { setCategories(formattedCategories); } catch (err) { - console.log("خطا در دریافت دسته‌بندی آکادمی:", err); + console.log("Academy categories fetch error:", err); } finally { setCategoriesLoading(false); } }; fetchAcademyCategories(); - }, []); + }, [allCategoriesLabel, request]); useEffect(() => { setFilters({ @@ -199,7 +230,7 @@ export default function AcademyFilter({ onFilterChange }: AcademyFilterProps) { const handleCreateAcademy = () => { if (!userType) { - toast.error("لطفا ابتدا در سایت وارد شوید یا ثبت نام کنید."); + toast.error(t("academy.loginRequired")); return; } router.push(getCreateAcademyPath()); @@ -215,7 +246,7 @@ export default function AcademyFilter({ onFilterChange }: AcademyFilterProps) { - - )} -
+ {about_us.length <= maxLength ? ( + about_us + ) : ( + <> + {expanded ? about_us : about_us.slice(0, maxLength) + "..."} + + + )} +
); } diff --git a/src/components/billboards/Profile/AdProfileHeadRowTwo.tsx b/src/components/billboards/Profile/AdProfileHeadRowTwo.tsx index ec898ef..ba36894 100644 --- a/src/components/billboards/Profile/AdProfileHeadRowTwo.tsx +++ b/src/components/billboards/Profile/AdProfileHeadRowTwo.tsx @@ -25,7 +25,7 @@ function AdProfileHeadRowTwo({ width={22} height={22} alt={"verify icon"} - src={`/images/icons/star1.svg`} + src={`/images/icons/star1.png`} />
@@ -36,7 +36,7 @@ function AdProfileHeadRowTwo({ width={22} height={22} alt={"verify icon"} - src={`/images/icons/medal-star.svg`} + src={`/images/icons/medal-star.png`} />
diff --git a/src/components/chart-area-interactive.tsx b/src/components/chart-area-interactive.tsx index d55e2fa..0850e69 100644 --- a/src/components/chart-area-interactive.tsx +++ b/src/components/chart-area-interactive.tsx @@ -29,6 +29,7 @@ import { import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import useAxios from "@/hooks/useAxios"; import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; interface Payment { _id: string; @@ -58,11 +59,11 @@ interface ChartDataItem { const chartConfig = { amount: { - label: "مبلغ فروش", + label: "amount", color: "#ff107d", }, count: { - label: "تعداد فروش", + label: "count", color: "#10b981", }, } satisfies ChartConfig; @@ -98,6 +99,7 @@ const generateDateRange = (startDate: Date, endDate: Date): string[] => { }; export function ChartAreaInteractive() { + const { t } = useTranslation("common"); const isMobile = useIsMobile(); const [timeRange, setTimeRange] = useState("90d"); const [isLoading, setIsLoading] = useState(false); @@ -252,7 +254,7 @@ export function ChartAreaInteractive() { } } catch (err) { console.log("get error:", err); - toast.error("خطا در دریافت اطلاعات"); + toast.error(t("settings.academy.chart.fetchError")); setChartData([]); } finally { setIsLoading(false); @@ -273,8 +275,8 @@ export function ChartAreaInteractive() { return ( - مقدار فروش - در حال بارگذاری داده‌ها... + {t("settings.academy.chart.salesAmount")} + {t("settings.academy.chart.loadingData")}
@@ -295,7 +297,7 @@ export function ChartAreaInteractive() {
- آمار فروش آکادمی + {t("settings.academy.chart.salesStats")}
@@ -306,9 +308,9 @@ export function ChartAreaInteractive() { variant="outline" className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex" > - ۹۰ روز - ۳۰ روز - ۷ روز + {t("settings.academy.chart.days90")} + {t("settings.academy.chart.days30")} + {t("settings.academy.chart.days7")} @@ -350,8 +352,8 @@ export function ChartAreaInteractive() { d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> -

هیچ داده‌ای برای نمایش وجود ندارد

-

هنوز فروشی ثبت نشده است

+

{t("settings.academy.chart.noData")}

+

{t("settings.academy.chart.noSalesYet")}

) : ( @@ -410,11 +412,11 @@ export function ChartAreaInteractive() { if (name === "amount") { return [ `${Number(value).toLocaleString("fa-IR")}`, - "مبلغ فروش", + t("settings.academy.chart.salesAmountLabel"), ]; } if (name === "count") { - return [`${value}`, "تعداد فروش"]; + return [`${value}`, t("settings.academy.chart.salesCountLabel")]; } return [value, name]; }} diff --git a/src/components/chat/AddChatRoomMembersModal.tsx b/src/components/chat/AddChatRoomMembersModal.tsx new file mode 100644 index 0000000..5b566b4 --- /dev/null +++ b/src/components/chat/AddChatRoomMembersModal.tsx @@ -0,0 +1,105 @@ +"use client"; + +import React, { useState } from "react"; +import Modal from "@/components/elements/Modal"; +import RoundedButton from "@/components/elements/RoundedButton"; +import useAxios from "@/hooks/useAxios"; +import ChatRoomMemberPicker, { + ChatRoomMember, +} from "@/components/chat/ChatRoomMemberPicker"; +import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; + +type Props = { + open: boolean; + onClose: () => void; + onAdded: () => void; + roomId: string; + existingMemberIds?: string[]; +}; + +export default function AddChatRoomMembersModal({ + open, + onClose, + onAdded, + roomId, + existingMemberIds = [], +}: Props) { + const { t } = useTranslation("common"); + const { request } = useAxios(); + const [selected, setSelected] = useState([]); + const [submitting, setSubmitting] = useState(false); + + const handleClose = () => { + setSelected([]); + onClose(); + }; + + const submit = async () => { + const memberIds = selected + .map((member) => member._id) + .filter((id) => !existingMemberIds.includes(id)); + + if (memberIds.length === 0) { + toast.error(t("chats.toast.selectNewMember")); + return; + } + + setSubmitting(true); + try { + await request("POST", `/chat-rooms/${roomId}/members`, { + member_ids: memberIds, + }); + toast.success(t("chats.toast.membersAdded")); + setSelected([]); + onAdded(); + onClose(); + } catch (err: unknown) { + const message = + (err as { response?: { data?: { error?: string; message?: string } } }) + ?.response?.data?.error || + (err as { response?: { data?: { message?: string } } })?.response?.data + ?.message; + toast.error(message || t("chats.toast.addMembersFailed")); + } finally { + setSubmitting(false); + } + }; + + return ( + +
+
+

+ {t("chats.modal.addMembersTitle")} +

+ +
+ void submit()} + className="h-10 w-full max-w-[200px] text-sm" + > + {submitting ? t("chats.actions.addingMembers") : t("chats.actions.add")} + + + {t("chats.actions.cancel")} + +
+
+
+
+ ); +} diff --git a/src/components/chat/AttachmentMenu.tsx b/src/components/chat/AttachmentMenu.tsx index 1888e1a..3081c4b 100644 --- a/src/components/chat/AttachmentMenu.tsx +++ b/src/components/chat/AttachmentMenu.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import ChatBoldIcon, { type ChatBoldIconName } from "./ChatBoldIcon"; +import { useTranslation } from "react-i18next"; export type AttachmentType = "image" | "video" | "file" | "location" | "camera"; @@ -13,17 +14,17 @@ interface AttachmentMenuProps { anchorRef?: React.RefObject; } -const items: { +const attachmentConfig: { type: AttachmentType; - label: string; + labelKey: string; icon: ChatBoldIconName; color: string; }[] = [ - { type: "camera", label: "دوربین", icon: "camera", color: "bg-red-500" }, - { type: "image", label: "گالری", icon: "gallery", color: "bg-purple-500" }, - { type: "video", label: "ویدیو", icon: "video", color: "bg-blue-500" }, - { type: "file", label: "فایل", icon: "file", color: "bg-orange-500" }, - { type: "location", label: "موقعیت", icon: "location", color: "bg-green-500" }, + { type: "camera", labelKey: "chats.attachment.camera", icon: "camera", color: "bg-red-500" }, + { type: "image", labelKey: "chats.attachment.gallery", icon: "gallery", color: "bg-purple-500" }, + { type: "video", labelKey: "chats.attachment.video", icon: "video", color: "bg-blue-500" }, + { type: "file", labelKey: "chats.attachment.file", icon: "file", color: "bg-orange-500" }, + { type: "location", labelKey: "chats.attachment.location", icon: "location", color: "bg-green-500" }, ]; export default function AttachmentMenu({ @@ -32,6 +33,7 @@ export default function AttachmentMenu({ onSelect, anchorRef, }: AttachmentMenuProps) { + const { t } = useTranslation("common"); const menuRef = useRef(null); useEffect(() => { @@ -61,7 +63,7 @@ export default function AttachmentMenu({ transition={{ duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] }} className="glass-panel absolute bottom-full left-0 z-[60] mb-3 min-w-[200px] rounded-2xl p-2 shadow-xl" > - {items.map((item, i) => ( + {attachmentConfig.map((item, i) => ( - {item.label} + {t(item.labelKey)} ))} diff --git a/src/components/chat/ChatActionBar.tsx b/src/components/chat/ChatActionBar.tsx index 3da259a..5d742a6 100644 --- a/src/components/chat/ChatActionBar.tsx +++ b/src/components/chat/ChatActionBar.tsx @@ -3,18 +3,30 @@ import { motion } from "framer-motion"; import BoldIcon from "@/components/ui/BoldIcon"; import ChatBoldIcon from "./ChatBoldIcon"; +import { QUICK_REACTIONS } from "@/lib/chat/reactions"; +import { useTranslation } from "react-i18next"; interface ChatActionBarProps { onReply: () => void; - onForward: () => void; + onForward?: () => void; + onCopy?: () => void; onCancel: () => void; + onReact?: (emoji: string) => void; + hideForward?: boolean; + disableCopy?: boolean; } export default function ChatActionBar({ onReply, onForward, + onCopy, onCancel, + onReact, + hideForward = false, + disableCopy = false, }: ChatActionBarProps) { + const { t } = useTranslation("common"); + return ( -
- - - +
+ {onReact ? ( +
+ {QUICK_REACTIONS.map((emoji) => ( + + ))} +
+ ) : null} +
+ + + {onCopy ? ( + + ) : null} + {!hideForward && onForward ? ( + + ) : null} +
); diff --git a/src/components/chat/ChatDeleteBar.tsx b/src/components/chat/ChatDeleteBar.tsx index 90d7ed3..feac5ce 100644 --- a/src/components/chat/ChatDeleteBar.tsx +++ b/src/components/chat/ChatDeleteBar.tsx @@ -2,6 +2,7 @@ import { motion } from "framer-motion"; import BoldIcon from "@/components/ui/BoldIcon"; +import { useTranslation } from "react-i18next"; interface ChatDeleteBarProps { selectedCount: number; @@ -16,6 +17,8 @@ export default function ChatDeleteBar({ onCancel, loading = false, }: ChatDeleteBarProps) { + const { t } = useTranslation("common"); + return ( - انصراف + {t("chats.actions.cancel")}
diff --git a/src/components/chat/ChatGuideModal.tsx b/src/components/chat/ChatGuideModal.tsx index 696a2ad..1d92cd2 100644 --- a/src/components/chat/ChatGuideModal.tsx +++ b/src/components/chat/ChatGuideModal.tsx @@ -2,31 +2,34 @@ import Modal from "../elements/Modal"; import ChatBoldIcon from "./ChatBoldIcon"; +import { useTranslation } from "react-i18next"; interface ChatGuideModalProps { open: boolean; onClose: () => void; } -const ITEMS = [ - { - title: "حذف", - body: "امکان حذف پیام‌ها حتی از روی سرور فقط تا ۲۴ ساعت گذشته", - icon: "delete" as const, - }, - { - title: "پیام یک‌بارمصرف", - body: "حذف پیام بعد از نمایش برای دریافت‌کننده", - icon: "eye" as const, - }, - { - title: "پیام زماندار", - body: "حذف پیام در زمان مشخص بعد از نمایش برای دریافت‌کننده", - icon: "timer" as const, - }, -]; - export default function ChatGuideModal({ open, onClose }: ChatGuideModalProps) { + const { t } = useTranslation("common"); + + const items = [ + { + title: t("chats.guide.deleteTitle"), + body: t("chats.guide.deleteBody"), + icon: "delete" as const, + }, + { + title: t("chats.guide.viewOnceTitle"), + body: t("chats.guide.viewOnceBody"), + icon: "eye" as const, + }, + { + title: t("chats.guide.timedTitle"), + body: t("chats.guide.timedBody"), + icon: "timer" as const, + }, + ]; + if (!open) return null; return ( @@ -34,20 +37,20 @@ export default function ChatGuideModal({ open, onClose }: ChatGuideModalProps) {

- راهنمای چت + {t("chats.guide.title")}

    - {ITEMS.map((item) => ( + {items.map((item) => (
  • void; deleteMode?: boolean; }) { + const { t } = useTranslation("common"); const router = useRouter(); const [searchOpen, setSearchOpen] = useState(false); - const isOnline = user?.last_online === "آنلاین"; + const onlineLabel = t("chats.online"); + const offlineLabel = t("chats.offline"); + const isOnline = isUserOnline(user?.last_online, onlineLabel); const displayName = [user?.first_name, user?.last_name].filter(Boolean).join(" ").trim() || user?.user_name || @@ -55,7 +60,7 @@ function ChatHeader({ type="button" onClick={() => router.back()} className={chatHeaderCircleBtn} - aria-label="بازگشت" + aria-label={t("chats.actions.back")} > @@ -75,7 +80,7 @@ function ChatHeader({ ? "chat-header-status-dot--online" : "chat-header-status-dot--offline" )} - title={isOnline ? "آنلاین" : "آفلاین"} + title={isOnline ? onlineLabel : offlineLabel} aria-hidden />
    @@ -84,9 +89,9 @@ function ChatHeader({ {isOnline ? ( - آنلاین + {onlineLabel} ) : ( - user?.last_online || "آفلاین" + resolveOnlineLabel(user?.last_online, onlineLabel, offlineLabel) )}
    @@ -115,7 +120,7 @@ function ChatHeader({ {deleteMode && (

    - فقط پیام‌های خودتان در ۲۴ ساعت گذشته قابل حذف هستند + {t("chats.deleteHint")}

    )} @@ -131,7 +136,7 @@ function ChatHeader({ type="search" value={searchQuery} onChange={(e) => onSearchChange?.(e.target.value)} - placeholder="جستجو در پیام‌ها…" + placeholder={t("chats.searchInMessages")} className="glass-chat-input mt-2 w-full rounded-full px-4 py-2 text-sm outline-none" /> diff --git a/src/components/chat/ChatHeaderMenu.tsx b/src/components/chat/ChatHeaderMenu.tsx index e26aa41..99b84ab 100644 --- a/src/components/chat/ChatHeaderMenu.tsx +++ b/src/components/chat/ChatHeaderMenu.tsx @@ -8,6 +8,7 @@ import { motion, AnimatePresence } from "framer-motion"; import { User } from "@/types/types"; import useAxios from "@/hooks/useAxios"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; interface ChatHeaderMenuProps { user: User | undefined; @@ -26,6 +27,7 @@ export default function ChatHeaderMenu({ buttonClassName, onStartDeleteMode, }: ChatHeaderMenuProps) { + const { t } = useTranslation("common"); const [open, setOpen] = useState(false); const [guideOpen, setGuideOpen] = useState(false); const [isBlocked, setIsBlocked] = useState(Boolean(user?.is_blocked)); @@ -55,7 +57,7 @@ export default function ChatHeaderMenu({ type="button" onClick={() => setOpen((o) => !o)} className={cn(buttonClassName, "relative")} - aria-label="منو" + aria-label={t("chats.aria.menu")} > @@ -82,7 +84,7 @@ export default function ChatHeaderMenu({ }} > - جستجو در چت + {t("chats.header.searchInChat")} {isBlocked ? ( ) : ( )} diff --git a/src/components/chat/ChatMessageCard.tsx b/src/components/chat/ChatMessageCard.tsx index 074fe06..e68e779 100644 --- a/src/components/chat/ChatMessageCard.tsx +++ b/src/components/chat/ChatMessageCard.tsx @@ -20,6 +20,7 @@ import { useSwipeToReply } from "@/hooks/useSwipeToReply"; import BoldIcon from "@/components/ui/BoldIcon"; import ChatBoldIcon from "./ChatBoldIcon"; import { cn } from "@/lib/utils"; +import { groupReactions } from "@/lib/chat/reactions"; import { formatCountdown, getRemainingSeconds, @@ -29,9 +30,11 @@ import { useViewOnceMedia } from "@/hooks/useViewOnceMedia"; import ViewOnceMediaBubble from "./ViewOnceMediaBubble"; import { isViewOnceMediaType } from "@/lib/chat/viewOnce"; import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; import { type BubbleGroupPosition, getBubbleRadius, + getMediaBubbleRadius, getMessageRowSpacing, } from "@/lib/chat/messageGrouping"; import SharedPostBubble, { parseSharedPost } from "./SharedPostBubble"; @@ -41,14 +44,16 @@ import SharedStoryBubble, { } from "./SharedStoryBubble"; function ViewOnceBadge() { + const { t } = useTranslation("common"); return ( - ۱ + {t("chats.viewOnceBadge")} ); } function TimedBadge({ expiresAt }: { expiresAt: string }) { + const { t } = useTranslation("common"); const [remaining, setRemaining] = useState(() => getRemainingSeconds(expiresAt) ); @@ -65,7 +70,7 @@ function TimedBadge({ expiresAt }: { expiresAt: string }) { return ( {formatCountdown(remaining)} @@ -99,6 +104,7 @@ export interface ChatMessage { userName: string; displayName: string; }; + reactions?: Array<{ userId: string; emoji: string }>; } interface ChatMessageCardProps { @@ -122,6 +128,76 @@ interface ChatMessageCardProps { const LONG_PRESS_MS = 2000; +function PreviewOverlay({ + preview, + fileType, + onClose, +}: { + preview: string; + fileType?: string; + onClose: () => void; +}) { + const { t } = useTranslation("common"); + useEffect(() => { + window.history.pushState({ chatMediaPreview: true }, ""); + const onPopState = () => onClose(); + window.addEventListener("popstate", onPopState); + return () => window.removeEventListener("popstate", onPopState); + }, [onClose]); + + const requestClose = () => { + if (window.history.state?.chatMediaPreview) { + window.history.back(); + return; + } + onClose(); + }; + + return ( + + + e.stopPropagation()} + > + {fileType === "video" ? ( + + + ); +} + function parseForwarded(content: string): ChatMessage["forwardedFrom"] | null { try { const line = content.split("\n")[0]; @@ -156,6 +232,7 @@ const ChatMessageCard = ({ highlighted = false, registerRef, }: ChatMessageCardProps) => { + const { t } = useTranslation("common"); const currentUserId = String(userDetail?._id ?? ""); const isSender = !!currentUserId && String(message.senderId) === currentUserId; @@ -181,13 +258,13 @@ const ChatMessageCard = ({ onSwipeReply?.(message); }, [message, onSwipeReply]); - const { pressing, shouldBlockClick, cancelPress, handlers: longPressHandlers } = + const { pressing, shouldBlockClick, handlers: longPressHandlers } = useLongPress(handleOpenActions, { delay: LONG_PRESS_MS }); const { dragProps, replyOpacity, replyScale } = useSwipeToReply( handleSwipeReply, swipeEnabled, - isSender ? "left" : "right" + "left" ); const swipeDragStart = @@ -217,7 +294,6 @@ const ChatMessageCard = ({ : longPressHandlers; const handleDragStart = () => { - cancelPress(); swipeDragStart?.(); }; @@ -230,6 +306,7 @@ const ChatMessageCard = ({ : ""; const locationData = parseLocationContent(message.content); const hasLocation = message.fileType === "location" || !!locationData; + const reactionGroups = groupReactions(message.reactions); if ( !message.content && @@ -262,6 +339,7 @@ const ChatMessageCard = ({ : message.content?.replace(/^\{.*\}\n?/, "").trim() || ""; const showTextBubble = + !sharedStory && textContent && fileType !== "file" && fileType !== "location" && @@ -269,6 +347,7 @@ const ChatMessageCard = ({ const emojiOnly = showTextBubble && !message.file && isEmojiOnly(textContent); const borderRadius = getBubbleRadius(isSender, groupPosition); + const mediaBorderRadius = getMediaBubbleRadius(); const rowSpacing = getMessageRowSpacing(groupPosition, isGroupedWithPrev); const timeLabel = formatBubbleTime(message.createdAt); const isPending = message.status === "pending"; @@ -288,7 +367,7 @@ const ChatMessageCard = ({ try { await completeViewOnce(message._id); } catch { - toast.error("حذف پیام یک‌بارمصرف ناموفق بود"); + toast.error(t("chats.toast.viewOnceDeleteFailed")); } }, [completeViewOnce, message._id]); @@ -304,7 +383,7 @@ const ChatMessageCard = ({ setViewOncePreview({ url, type: "image" }); } } catch { - toast.error("باز کردن پیام یک‌بارمصرف ممکن نیست"); + toast.error(t("chats.toast.viewOnceOpenFailed")); } }, [message._id, openViewOnce]); @@ -314,7 +393,7 @@ const ChatMessageCard = ({ {message.expiresAt ? ( ) : message.selfDestructSeconds ? ( - + ) : null} @@ -337,7 +416,7 @@ const ChatMessageCard = ({ {message.expiresAt ? ( ) : message.selfDestructSeconds ? ( - + ) : null} @@ -377,7 +456,7 @@ const ChatMessageCard = ({ }} className={cn( "relative flex w-full gap-2", - isSender ? "justify-end pl-10" : "justify-start pr-10", + isSender ? "justify-end" : "justify-start", rowSpacing, deleteMode && !isSender && "opacity-60", deleteMode && isSender && !canSelectForDelete && "opacity-40", @@ -396,7 +475,11 @@ const ChatMessageCard = ({ ? "border-[#0095f6] bg-[#0095f6] text-white" : "border-neutral-400 bg-transparent" )} - aria-label={selectedForDelete ? "لغو انتخاب" : "انتخاب پیام"} + aria-label={ + selectedForDelete + ? t("chats.aria.deselectMessage") + : t("chats.aria.selectMessage") + } > {selectedForDelete && } @@ -483,8 +566,14 @@ const ChatMessageCard = ({ ) : fileUrl ? (
    { + if (shouldBlockClick()) { + e.preventDefault(); + e.stopPropagation(); + return; + } e.stopPropagation(); openPreview(fileUrl); }} @@ -497,6 +586,7 @@ const ChatMessageCard = ({ alt="" className="object-cover" sizes="208px" + style={{ borderRadius: mediaBorderRadius }} />
    @@ -519,14 +609,24 @@ const ChatMessageCard = ({ ) : fileUrl ? (
    { + if (shouldBlockClick()) { + e.preventDefault(); + e.stopPropagation(); + return; + } e.stopPropagation(); openPreview(fileUrl); }} > {isViewOnce && isSender && } - +
    ) : null)} @@ -573,10 +673,14 @@ const ChatMessageCard = ({ ) : null)} {fileType === "file" && fileUrl && ( -
    +
    @@ -584,7 +688,11 @@ const ChatMessageCard = ({ )} {fileType === "location" && locationData && ( -
    +
    @@ -614,7 +722,7 @@ const ChatMessageCard = ({ )} {showTextBubble && !message.file && emojiOnly && ( -

    {textContent}

    +

    {textContent}

    )} {showTextBubble && !message.file && !emojiOnly && ( @@ -625,10 +733,28 @@ const ChatMessageCard = ({ )} style={bubbleStyle} > -

    {textContent}

    +

    {textContent}

    )} + {reactionGroups.length > 0 ? ( +
    + {reactionGroups.map(({ emoji, count }) => ( + + {emoji} + {count > 1 ? ` ${count}` : ""} + + ))} +
    + ) : null}
    @@ -687,46 +813,11 @@ const ChatMessageCard = ({ )} {preview && ( - setPreview(null)} - > - - e.stopPropagation()} - > - {fileType === "video" ? ( - - + setPreview(null)} + /> )} diff --git a/src/components/chat/ChatMessageList.tsx b/src/components/chat/ChatMessageList.tsx index 5168a00..1b2d752 100644 --- a/src/components/chat/ChatMessageList.tsx +++ b/src/components/chat/ChatMessageList.tsx @@ -7,19 +7,21 @@ import { joinChatRoom, joinUserRoom, releaseChatSocket, + subscribeChatSocketConnection, } from "@/lib/chat/socketClient"; import { apiClient } from "@/hooks/useAxios"; import { chatThreadQueryKey } from "@/lib/chat/queryKeys"; import { - appendMessageToThreadCache, + mergeFetchedThreadPage, normalizeThreadId, upsertMessageInThreadCache, + type ChatThreadData, } from "@/lib/chat/threadCache"; import ChatMessageCard, { ChatMessage } from "./ChatMessageCard"; import ChatDateSeparator from "./ChatDateSeparator"; import { groupMessagesByDate } from "@/lib/chat/groupMessagesByDate"; -import { dedupeChatMessages, filterResolvedPendingMessages, findPendingMatchForServer } from "@/lib/chat/dedupeMessages"; -import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; +import { dedupeChatMessages, filterResolvedPendingMessages } from "@/lib/chat/dedupeMessages"; +import { useInfiniteQuery, useQueryClient, keepPreviousData } from "@tanstack/react-query"; import { User } from "@/types/types"; import { ChatMessagesSkeleton } from "@/components/ui/ChatSkeletons"; import TypingIndicator from "./TypingIndicator"; @@ -33,6 +35,7 @@ import { } from "@/lib/chat/messageGrouping"; import { cn } from "@/lib/utils"; import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; function belongsToThread( msg: ChatMessage, @@ -65,7 +68,7 @@ interface ChatMessageListProps { getStableKey?: (id: string) => string; onExpirePending?: (ids: string[]) => void; onIncomingMessage?: (message: ChatMessage) => void; - onPruneResolvedPending?: (ids: string[]) => void; + onScrollToMessageReady?: (scrollToMessage: (targetId: string) => void) => void; /** فضای اضافه وقتی بنر پاسخ/زماندار بالای input است */ extraBottomRem?: number; } @@ -89,9 +92,10 @@ const ChatMessageList = ({ getStableKey = (id) => id, onExpirePending, onIncomingMessage, - onPruneResolvedPending, + onScrollToMessageReady, extraBottomRem = 0, }: ChatMessageListProps) => { + const { t, i18n } = useTranslation("common"); const scrollRef = useRef(null); const prevScrollHeight = useRef(0); const prevPendingCountRef = useRef(0); @@ -101,16 +105,16 @@ const ChatMessageList = ({ const seenMessageKeysRef = useRef(new Set()); const prevMessageCountRef = useRef(0); const messageRefs = useRef(new Map()); + const seenForDestructRef = useRef(new Set()); const highlightTimerRef = useRef | null>(null); const [highlightedMessageId, setHighlightedMessageId] = useState( null ); + const [socketConnected, setSocketConnected] = useState(true); const onIncomingMessageRef = useRef(onIncomingMessage); const onTypingChangeRef = useRef(onTypingChange); - const onPruneResolvedPendingRef = useRef(onPruneResolvedPending); onIncomingMessageRef.current = onIncomingMessage; onTypingChangeRef.current = onTypingChange; - onPruneResolvedPendingRef.current = onPruneResolvedPending; const senderId = normalizeThreadId(userDetail?._id ?? getStoredUserId()); const receiverId = normalizeThreadId( @@ -119,6 +123,10 @@ const ChatMessageList = ({ const threadReady = Boolean(senderId && receiverId); const queryKey = chatThreadQueryKey(senderId, receiverId); + useEffect(() => { + return subscribeChatSocketConnection(setSocketConnected); + }, []); + const { data, fetchNextPage, @@ -140,7 +148,14 @@ const ChatMessageList = ({ `/chat?senderId=${sid}&receiverId=${rid}&page=${pageParam}&limit=50`, { baseURL: getApiBaseUrl() } ); - const msgs = (response.data.messages || []) as ChatMessage[]; + const fetched = (response.data.messages || []) as ChatMessage[]; + const cachedData = queryClient.getQueryData( + chatThreadQueryKey(sid, rid) + ); + const msgs = + pageParam === 1 + ? mergeFetchedThreadPage(fetched, cachedData?.pages?.[0]?.messages) + : fetched; return { messages: msgs, nextPage: @@ -158,6 +173,10 @@ const ChatMessageList = ({ gcTime: 10 * 60 * 1000, refetchOnMount: false, refetchOnWindowFocus: false, + placeholderData: keepPreviousData, + refetchInterval: + socketConnected || pendingMessages.length > 0 ? false : 4000, + refetchIntervalInBackground: false, }); const hasCachedMessages = Boolean( @@ -241,26 +260,57 @@ const ChatMessageList = ({ socket.on("newMessage", onNewMessage); - socket.on( - "messageStatusUpdate", - ({ messageId, status }: { messageId: string; status: ChatMessage["status"] }) => { - queryClient.setQueryData( - chatThreadQueryKey(senderId, receiverId), - (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { - if (!oldData) return oldData; - return { - ...oldData, - pages: oldData.pages.map((page) => ({ - ...page, - messages: page.messages.map((m) => - m._id === messageId ? { ...m, status } : m - ), - })), - }; - } - ); - } - ); + const onMessageStatusUpdate = ({ + messageId, + status, + }: { + messageId: string; + status: ChatMessage["status"]; + }) => { + queryClient.setQueryData( + chatThreadQueryKey(senderId, receiverId), + (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { + if (!oldData) return oldData; + return { + ...oldData, + pages: oldData.pages.map((page) => ({ + ...page, + messages: page.messages.map((m) => + m._id === messageId ? { ...m, status } : m + ), + })), + }; + } + ); + }; + + socket.on("messageStatusUpdate", onMessageStatusUpdate); + + const onMessageReaction = ({ + messageId, + reactions, + }: { + messageId: string; + reactions: ChatMessage["reactions"]; + }) => { + queryClient.setQueryData( + chatThreadQueryKey(senderId, receiverId), + (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { + if (!oldData) return oldData; + return { + ...oldData, + pages: oldData.pages.map((page) => ({ + ...page, + messages: page.messages.map((m) => + m._id === messageId ? { ...m, reactions } : m + ), + })), + }; + } + ); + }; + + socket.on("messageReaction", onMessageReaction); let typingHideTimer: ReturnType | null = null; socket.on("userTyping", ({ userId }: { userId: string }) => { @@ -278,62 +328,61 @@ const ChatMessageList = ({ } }); - socket.on( - "messagesDeleted", - ({ messageIds }: { messageIds: string[] }) => { - if (!messageIds?.length) return; - const idSet = new Set(messageIds); - queryClient.setQueryData( - chatThreadQueryKey(senderId, receiverId), - (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { - if (!oldData) return oldData; - return { - ...oldData, - pages: oldData.pages.map((page) => ({ - ...page, - messages: page.messages.filter((m) => !idSet.has(m._id)), - })), - }; - } - ); - } - ); + const onMessagesDeleted = ({ messageIds }: { messageIds: string[] }) => { + if (!messageIds?.length) return; + const idSet = new Set(messageIds); + queryClient.setQueryData( + chatThreadQueryKey(senderId, receiverId), + (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { + if (!oldData) return oldData; + return { + ...oldData, + pages: oldData.pages.map((page) => ({ + ...page, + messages: page.messages.filter((m) => !idSet.has(m._id)), + })), + }; + } + ); + }; - socket.on( - "messageSelfDestructStarted", - ({ - messageId, - expiresAt, - }: { - messageId: string; - expiresAt: string; - }) => { - if (!messageId || !expiresAt) return; - queryClient.setQueryData( - chatThreadQueryKey(senderId, receiverId), - (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { - if (!oldData) return oldData; - return { - ...oldData, - pages: oldData.pages.map((page) => ({ - ...page, - messages: page.messages.map((m) => - m._id === messageId ? { ...m, expiresAt } : m - ), - })), - }; - } - ); - } - ); + socket.on("messagesDeleted", onMessagesDeleted); + + const onSelfDestructStarted = ({ + messageId, + expiresAt, + }: { + messageId: string; + expiresAt: string; + }) => { + if (!messageId || !expiresAt) return; + queryClient.setQueryData( + chatThreadQueryKey(senderId, receiverId), + (oldData: { pages: { messages: ChatMessage[] }[] } | undefined) => { + if (!oldData) return oldData; + return { + ...oldData, + pages: oldData.pages.map((page) => ({ + ...page, + messages: page.messages.map((m) => + m._id === messageId ? { ...m, expiresAt } : m + ), + })), + }; + } + ); + }; + + socket.on("messageSelfDestructStarted", onSelfDestructStarted); return () => { if (typingHideTimer) clearTimeout(typingHideTimer); socket.off("connect", onConnect); socket.off("newMessage", onNewMessage); - socket.off("messageStatusUpdate"); - socket.off("messagesDeleted"); - socket.off("messageSelfDestructStarted"); + socket.off("messageStatusUpdate", onMessageStatusUpdate); + socket.off("messageReaction", onMessageReaction); + socket.off("messagesDeleted", onMessagesDeleted); + socket.off("messageSelfDestructStarted", onSelfDestructStarted); socket.off("userTyping"); socket.off("userStoppedTyping"); releaseChatSocket(); @@ -355,22 +404,10 @@ const ChatMessageList = ({ ); }, [data, pendingMessages, searchQuery]); - useEffect(() => { - if (!pendingMessages.length || !data?.pages?.length) return; - const server = data.pages.flatMap((page) => page.messages); - const resolvedIds = pendingMessages - .filter((p) => - server.some((s) => s._id === p._id || findPendingMatchForServer(s, [p])) - ) - .map((p) => p._id); - if (resolvedIds.length) { - onPruneResolvedPendingRef.current?.(resolvedIds); - } - }, [data, pendingMessages]); - + const chatLang = i18n.language === "en" ? "en" : "fa"; const grouped = useMemo( - () => groupMessagesByDate(allMessages), - [allMessages] + () => groupMessagesByDate(allMessages, chatLang), + [allMessages, chatLang] ); useEffect(() => { @@ -493,11 +530,46 @@ const ChatMessageList = ({ return; } - toast.error("پیام اصلی یافت نشد"); + toast.error(t("chats.toast.originalMessageNotFound")); }, - [allMessages, fetchNextPage, hasNextPage] + [allMessages, fetchNextPage, hasNextPage, t] ); + useEffect(() => { + onScrollToMessageReady?.(scrollToMessage); + }, [onScrollToMessageReady, scrollToMessage]); + + useEffect(() => { + const container = scrollRef.current; + if (!container || !threadReady) return; + + const socket = acquireChatSocket(); + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) return; + const id = entry.target.getAttribute("data-message-id"); + if (!id || seenForDestructRef.current.has(id)) return; + + const msg = allMessages.find((m) => m._id === id); + if (!msg || msg._id.startsWith("temp")) return; + if (String(msg.senderId) === String(senderId)) return; + + seenForDestructRef.current.add(id); + socket.emit("messageSeen", { + messageId: msg._id, + senderId: msg.senderId, + receiverId: senderId, + }); + }); + }, + { root: container, threshold: 0.55 } + ); + + messageRefs.current.forEach((el) => observer.observe(el)); + return () => observer.disconnect(); + }, [allMessages, senderId, threadReady]); + useEffect(() => { return () => { if (highlightTimerRef.current) clearTimeout(highlightTimerRef.current); @@ -508,7 +580,7 @@ const ChatMessageList = ({
    ) : isError ? (
    -

    بارگذاری پیام‌ها ناموفق بود.

    +

    {t("chats.loadMessagesFailed")}

    ) : grouped.length === 0 ? (

    - هنوز پیامی رد و بدل نشده است. + {t("chats.messagesEmpty")}

    ) : (
    diff --git a/src/components/chat/ChatRoomMemberPicker.tsx b/src/components/chat/ChatRoomMemberPicker.tsx new file mode 100644 index 0000000..bb2faf8 --- /dev/null +++ b/src/components/chat/ChatRoomMemberPicker.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Image from "next/image"; +import useAxios from "@/hooks/useAxios"; +import { buildStorageUrl } from "@/components/main/BaseUrl"; +import BoldIcon from "@/components/ui/BoldIcon"; +import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; + +export type ChatRoomMember = { + _id: string; + user_name: string; + first_name?: string; + last_name?: string; + profile_image?: string; +}; + +type Props = { + selected: ChatRoomMember[]; + onChange: (users: ChatRoomMember[]) => void; + max?: number; +}; + +function avatarSrc(profileImage?: string) { + if (!profileImage) return null; + return profileImage.startsWith("http") + ? profileImage + : buildStorageUrl(profileImage); +} + +function displayName(user: ChatRoomMember) { + return ( + [user.first_name, user.last_name].filter(Boolean).join(" ") || user.user_name + ); +} + +export default function ChatRoomMemberPicker({ + selected, + onChange, + max = 20, +}: Props) { + const { t } = useTranslation("common"); + const { request } = useAxios(); + const [query, setQuery] = useState(""); + const [results, setResults] = useState([]); + const [searching, setSearching] = useState(false); + + useEffect(() => { + if (query.trim().length < 2) { + setResults([]); + return; + } + + const timer = window.setTimeout(async () => { + setSearching(true); + try { + const res = await request<{ result?: ChatRoomMember[] }>( + "GET", + `/search-web?search=${encodeURIComponent(query.trim())}&type=user&limit=20`, + null, + { noToast: true } + ); + const list = res?.result || []; + setResults(list.filter((u) => !selected.some((s) => s._id === u._id))); + } catch { + setResults([]); + } finally { + setSearching(false); + } + }, 300); + + return () => window.clearTimeout(timer); + }, [query, request, selected]); + + const addUser = (user: ChatRoomMember) => { + if (selected.some((item) => item._id === user._id)) return; + if (selected.length >= max) { + toast.error(t("chats.toast.maxUsers", { max })); + return; + } + onChange([...selected, user]); + setQuery(""); + setResults([]); + }; + + const removeUser = (userId: string) => { + onChange(selected.filter((item) => item._id !== userId)); + }; + + return ( +
    +
    + + setQuery(e.target.value)} + placeholder={t("chats.placeholder.searchUsername")} + className="w-full rounded-full border border-neutral-200 bg-white py-2.5 text-sm ltr:pl-10 ltr:pr-4 rtl:pl-4 rtl:pr-10 dark:border-neutral-700 dark:bg-neutral-900" + /> +
    + + {searching ? ( +

    {t("chats.searching")}

    + ) : null} + + {results.length > 0 ? ( +
      + {results.map((user) => ( +
    • + +
    • + ))} +
    + ) : null} + + {selected.length > 0 ? ( +
    + {selected.map((user) => ( + + @{user.user_name} + + + ))} +
    + ) : null} +
    + ); +} diff --git a/src/components/chat/CreateChatRoomModal.tsx b/src/components/chat/CreateChatRoomModal.tsx new file mode 100644 index 0000000..9b1d2a5 --- /dev/null +++ b/src/components/chat/CreateChatRoomModal.tsx @@ -0,0 +1,313 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import Image from "next/image"; +import Modal from "@/components/elements/Modal"; +import RoundedButton from "@/components/elements/RoundedButton"; +import RoundedInput from "@/components/elements/RoundedInput"; +import SelectBox from "@/components/elements/SelectBox"; +import useAxios from "@/hooks/useAxios"; +import { ICity, IExpertise, IProvince } from "@/types/types"; +import toast from "react-hot-toast"; +import BoldIcon from "@/components/ui/BoldIcon"; +import ChatRoomMemberPicker, { + ChatRoomMember, +} from "@/components/chat/ChatRoomMemberPicker"; +import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; + +type Props = { + open: boolean; + onClose: () => void; + onCreated: () => void; +}; + +type RoomVisibility = "public" | "private"; + +export default function CreateChatRoomModal({ open, onClose, onCreated }: Props) { + const { t } = useTranslation("common"); + const { request } = useAxios(); + const fileRef = useRef(null); + const [title, setTitle] = useState(""); + const [visibility, setVisibility] = useState("public"); + const [provinceId, setProvinceId] = useState(""); + const [cityId, setCityId] = useState(""); + const [expertise, setExpertise] = useState(""); + const [invitedMembers, setInvitedMembers] = useState([]); + const [imageFile, setImageFile] = useState(null); + const [imagePreview, setImagePreview] = useState(null); + const [provinces, setProvinces] = useState([]); + const [cities, setCities] = useState([]); + const [expertiseList, setExpertiseList] = useState([]); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (!open) return; + setTitle(""); + setVisibility("public"); + setProvinceId(""); + setCityId(""); + setExpertise(""); + setInvitedMembers([]); + setImageFile(null); + setImagePreview(null); + + void request<{ provinces: IProvince[] }>("GET", "/provinces", null, { + noToast: true, + }).then((res) => setProvinces(res?.provinces ?? [])); + + void request<{ expertises: IExpertise[] }>("GET", "/expertise", null, { + noToast: true, + }).then((res) => setExpertiseList(res?.expertises ?? [])); + }, [open, request]); + + useEffect(() => { + if (!provinceId) { + setCities([]); + setCityId(""); + return; + } + void request<{ cities: ICity[] }>( + "GET", + `/cities/${provinceId}`, + null, + { noToast: true } + ).then((res) => setCities(res?.cities ?? [])); + }, [provinceId, request]); + + const onPickImage = (file: File | null) => { + setImageFile(file); + if (!file) { + setImagePreview(null); + return; + } + const reader = new FileReader(); + reader.onload = () => setImagePreview(String(reader.result)); + reader.readAsDataURL(file); + }; + + const submit = async () => { + if (!title.trim()) { + toast.error(t("chats.toast.roomNameRequired")); + return; + } + if (visibility === "private" && invitedMembers.length === 0) { + toast.error(t("chats.toast.privateRoomMembersRequired")); + return; + } + + setSubmitting(true); + try { + const form = new FormData(); + form.append("title", title.trim()); + form.append("visibility", visibility); + if (visibility === "public") { + if (provinceId) form.append("province_id", provinceId); + if (cityId) form.append("city_id", cityId); + if (expertise) form.append("expertise", expertise); + } else { + form.append( + "member_ids", + JSON.stringify(invitedMembers.map((member) => member._id)) + ); + } + if (imageFile) form.append("room_image", imageFile); + + await request("POST", "/chat-rooms", form); + toast.success(t("chats.toast.roomCreated")); + onCreated(); + onClose(); + } catch (err: unknown) { + const message = + (err as { response?: { data?: { error?: string; message?: string } } }) + ?.response?.data?.error || + (err as { response?: { data?: { message?: string } } })?.response?.data + ?.message; + toast.error(message || t("chats.toast.createRoomFailed")); + } finally { + setSubmitting(false); + } + }; + + return ( + +
    +
    + + {t("chats.modal.createRoomTitle")} + + + + onPickImage(e.target.files?.[0] ?? null)} + /> + +
    +

    + {t("chats.modal.roomName")} +

    + setTitle(e.target.value)} + placeholder={t("chats.placeholder.roomName")} + className="text-center text-sm" + /> +
    + +
    +

    + {t("chats.modal.roomType")} +

    +
    + + +
    +
    + + {visibility === "private" ? ( +
    +

    + {t("chats.modal.inviteUsers")} +

    + +

    + {t("chats.modal.privateRoomHint")} +

    +
    + ) : ( + <> +
    +

    + {t("chats.modal.provinceOptional")} +

    + { + setProvinceId(e.target.value); + setCityId(""); + }} + className="text-sm" + > + + {provinces.map((item) => ( + + ))} + +
    + +
    +

    + {t("chats.modal.cityOptional")} +

    + setCityId(e.target.value)} + disabled={!provinceId} + className="text-sm" + > + + {cities.map((item) => ( + + ))} + +
    + +
    +

    + {t("chats.modal.expertiseOptional")} +

    + setExpertise(e.target.value)} + className="text-sm" + > + + {expertiseList.map((item) => ( + + ))} + +
    + +

    + {t("chats.modal.publicRoomHint")} +

    + + )} + +
    + void submit()} + className="h-10 w-full max-w-[200px] text-sm" + > + {submitting ? t("chats.actions.creatingRoom") : t("chats.actions.createRoom")} + + + {t("chats.actions.cancel")} + +
    +
    +
    +
    + ); +} diff --git a/src/components/chat/FileMessageBubble.tsx b/src/components/chat/FileMessageBubble.tsx index 6056c3a..9a55bdc 100644 --- a/src/components/chat/FileMessageBubble.tsx +++ b/src/components/chat/FileMessageBubble.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import BoldIcon from "@/components/ui/BoldIcon"; import IOSSpinner from "@/components/ui/IOSSpinner"; import { cn } from "@/lib/utils"; +import { useTranslation } from "react-i18next"; interface FileMessageBubbleProps { url: string; @@ -11,12 +12,13 @@ interface FileMessageBubbleProps { isOutgoing?: boolean; } -/** Telegram-style file message with upload/download progress */ export default function FileMessageBubble({ url, - fileName = "فایل پیوست", + fileName, isOutgoing = false, }: FileMessageBubbleProps) { + const { t } = useTranslation("common"); + const resolvedFileName = fileName ?? t("chats.fileAttachment"); const [progress, setProgress] = useState(0); const [downloading, setDownloading] = useState(false); const isPending = url.startsWith("blob:"); @@ -38,7 +40,7 @@ export default function FileMessageBubble({ if (xhr.status === 200) { const a = document.createElement("a"); a.href = URL.createObjectURL(xhr.response); - a.download = fileName; + a.download = resolvedFileName; a.click(); setProgress(100); } @@ -77,7 +79,7 @@ export default function FileMessageBubble({ )}
    -

    {fileName}

    +

    {resolvedFileName}

    {(isPending || downloading) && (
    )} - {isPending ? "در حال آپلود…" : downloading ? `${progress}%` : "فایل"} + {isPending + ? t("chats.fileStatus.uploading") + : downloading + ? `${progress}%` + : t("chats.file")}
    {!isPending && ( @@ -95,7 +101,7 @@ export default function FileMessageBubble({ type="button" onClick={handleDownload} className="gentle-transition rounded-full p-2 text-neutral-500 hover:bg-neutral-200/80 active:scale-90 dark:hover:bg-neutral-700" - aria-label="دانلود" + aria-label={t("chats.aria.download")} > diff --git a/src/components/chat/ForwardMessageModal.tsx b/src/components/chat/ForwardMessageModal.tsx index e0f22a0..d746f9d 100644 --- a/src/components/chat/ForwardMessageModal.tsx +++ b/src/components/chat/ForwardMessageModal.tsx @@ -7,6 +7,7 @@ import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl"; import useAxios from "@/hooks/useAxios"; import toast from "react-hot-toast"; import IOSSpinner from "@/components/ui/IOSSpinner"; +import { useTranslation } from "react-i18next"; export interface ChatUserItem { _id: string; @@ -30,7 +31,6 @@ interface ForwardMessageModalProps { }; }; currentUserId: string; - /** جلوگیری از دوباره‌نمایش در چت فعلی هنگام فوروارد به گیرندهٔ همین مکالمه */ currentReceiverId?: string; } @@ -39,8 +39,8 @@ export default function ForwardMessageModal({ onClose, message, currentUserId, - currentReceiverId, }: ForwardMessageModalProps) { + const { t } = useTranslation("common"); const { request } = useAxios(); const [users, setUsers] = useState([]); const [selected, setSelected] = useState>(new Set()); @@ -74,7 +74,7 @@ export default function ForwardMessageModal({ const next = new Set(prev); if (next.has(id)) next.delete(id); else if (next.size < 50) next.add(id); - else toast.error("حداکثر ۵۰ کاربر"); + else toast.error(t("chats.toast.maxForwardUsers")); return next; }); }; @@ -86,7 +86,8 @@ export default function ForwardMessageModal({ ? JSON.stringify({ forwardedFrom: message.forwardedFrom }) : ""; const content = - (fwdMeta ? fwdMeta + "\n" : "") + (message.content || "پیام فوروارد"); + (fwdMeta ? fwdMeta + "\n" : "") + + (message.content || t("chats.forwardedMessage")); try { const targets = Array.from(selected); @@ -100,11 +101,11 @@ export default function ForwardMessageModal({ }) ) ); - toast.success(`پیام به ${selected.size} نفر ارسال شد`); + toast.success(t("chats.toast.forwardSent", { count: selected.size })); onClose(); setSelected(new Set()); } catch { - toast.error("خطا در فوروارد"); + toast.error(t("chats.toast.forwardFailed")); } finally { setSending(false); } @@ -128,7 +129,7 @@ export default function ForwardMessageModal({ className="glass-modal-panel max-h-[70vh] w-full max-w-md overflow-hidden rounded-t-3xl sm:rounded-3xl" >
    - فوروارد به ({selected.size}/50) + {t("chats.modal.forwardTo", { selected: selected.size })}
    {loading ? ( @@ -169,7 +170,7 @@ export default function ForwardMessageModal({ onClick={onClose} className="flex-1 rounded-full py-2.5 text-sm text-neutral-500" > - انصراف + {t("chats.actions.cancel")}
    diff --git a/src/components/chat/ImageModal.tsx b/src/components/chat/ImageModal.tsx index 3dcc75c..7667be2 100644 --- a/src/components/chat/ImageModal.tsx +++ b/src/components/chat/ImageModal.tsx @@ -1,6 +1,9 @@ +"use client"; + import Image from "next/image"; import RoundedButton from "@/components/elements/RoundedButton"; import Modal from "../elements/Modal"; +import { useTranslation } from "react-i18next"; interface ImageModalProps { isOpen: boolean; @@ -15,6 +18,8 @@ const ImageModal = ({ cancelImage, confirmImage, }: ImageModalProps) => { + const { t } = useTranslation("common"); + if (!isOpen || !selectedImage) return null; return ( @@ -32,13 +37,13 @@ const ImageModal = ({ onClick={confirmImage} className="text-green-500 border-green-500 p-2 rounded-2xl w-20" > - ارسال + {t("chats.actions.send")} - لغو + {t("chats.actions.cancel")}
    diff --git a/src/components/chat/LocationMessageBubble.tsx b/src/components/chat/LocationMessageBubble.tsx index 247d04c..9c2aa6c 100644 --- a/src/components/chat/LocationMessageBubble.tsx +++ b/src/components/chat/LocationMessageBubble.tsx @@ -1,6 +1,7 @@ "use client"; import BoldIcon from "@/components/ui/BoldIcon"; +import { useTranslation } from "react-i18next"; export interface LocationData { lat: number; @@ -29,6 +30,7 @@ export default function LocationMessageBubble({ location, isOutgoing = false, }: LocationMessageBubbleProps) { + const { t } = useTranslation("common"); const mapsUrl = `https://www.google.com/maps?q=${location.lat},${location.lng}`; const embedUrl = `https://maps.google.com/maps?q=${location.lat},${location.lng}&z=15&output=embed`; @@ -40,7 +42,7 @@ export default function LocationMessageBubble({ >