From 602358416075026b75be66ccb45d8f3afaa87d22 Mon Sep 17 00:00:00 2001 From: Siddhesh Bhupendra Kuakde Date: Thu, 23 Nov 2023 09:02:04 +0530 Subject: [PATCH] Feature request: Adding advertisement screen (#994) * Add/ test for OrgPost.tsx * fix: org post back to default * Added Dialog 2 * Updated Dialog UI * Removed Extra code * Updated Plugin store * fix: warnings and solves #951 & #948 * fix: warnings and solves #951 & #948 * fix: warnings and solves #951 & #948 * Fix: UI Redesign * fix: merge * fix * Update AddOnStore.tsx * Fixed Merge Errors * Add test: for OrgEntry * Test 3 * fix test 4 * chores: version changes * Add: Initial Websocket setup on talawa mobile web * Add: plugin logic * Add: plugin logic * removed extra * removed extra * Added: Tests * fix * Added WEBSOCKET_URL in .env.example * Advertisement Management Screen * Feature: Create and Delete advertisement * Only current OrgIDs are visible to admin * Showing advertisements in the User end app * Message: fix * formatting * update test * Fix # 1071 * Added test for entry file * Added test for entry file2 * Added test for entry file2 --- .env.example | 5 +- public/locales/en.json | 20 + public/locales/fr.json | 8 + public/locales/hi.json | 8 + public/locales/sp.json | 8 + public/locales/zh.json | 8 + schema.graphql | 1152 +++++++++++++++++ src/App.tsx | 2 + src/GraphQl/Mutations/mutations.ts | 29 +- src/GraphQl/Queries/Queries.ts | 14 +- .../AddOn/core/AddOnStore/AddOnStore.tsx | 2 +- .../Advertisements/Advertisement.module.css | 31 + .../Advertisements/Advertisement.test.tsx | 95 ++ .../Advertisements/Advertisements.tsx | 197 +++ .../AdvertisementEntry.module.css | 20 + .../AdvertisementEntry.test.tsx | 60 + .../AdvertisementEntry/AdvertisementEntry.tsx | 103 ++ .../AdvertisementRegister.module.css | 9 + .../AdvertisementRegister.tsx | 207 +++ .../IconComponent/IconComponent.tsx | 2 + .../OrganizationScreen/OrganizationScreen.tsx | 1 - .../PromotedPost/PromotedPost.module.css | 56 + .../UserPortal/PromotedPost/PromotedPost.tsx | 32 + src/screens/UserPortal/Home/Home.tsx | 37 +- src/state/reducers/routesReducer.test.ts | 19 + src/state/reducers/routesReducer.ts | 2 + 26 files changed, 2120 insertions(+), 7 deletions(-) create mode 100644 schema.graphql create mode 100644 src/components/Advertisements/Advertisement.module.css create mode 100644 src/components/Advertisements/Advertisement.test.tsx create mode 100644 src/components/Advertisements/Advertisements.tsx create mode 100644 src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.module.css create mode 100644 src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx create mode 100644 src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx create mode 100644 src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.module.css create mode 100644 src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx create mode 100644 src/components/UserPortal/PromotedPost/PromotedPost.module.css create mode 100644 src/components/UserPortal/PromotedPost/PromotedPost.tsx diff --git a/.env.example b/.env.example index d7e32ef01d..bd9529ea79 100644 --- a/.env.example +++ b/.env.example @@ -17,4 +17,7 @@ REACT_APP_USE_RECAPTCHA= # from here for reCAPTCHA v2 and "I'm not a robot" Checkbox, and paste the key here. # Note: In domains, fill localhost -REACT_APP_RECAPTCHA_SITE_KEY= \ No newline at end of file +REACT_APP_RECAPTCHA_SITE_KEY= + +# has to be inserted in the env file to use plugins and other websocket based features. +REACT_APP_BACKEND_WEBSOCKET_URL=ws://localhost:4000/graphql \ No newline at end of file diff --git a/public/locales/en.json b/public/locales/en.json index fb5d78bc2f..50945e8dde 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -65,6 +65,7 @@ "Block/Unblock": "Block/Unblock", "Plugins": "Plugins", "Plugin Store": "Plugin Store", + "Advertisement": "Advertisements", "allOrganizations": "All Organizations", "yourOrganization": "Your Organization", "notification": "Notification", @@ -656,6 +657,25 @@ "event": "Event", "organization": "Organization" }, + "advertisement": { + "title": "Advertisements", + "pHeading": "Manage Ads", + "activeAds": "Active Campaigns", + "archievedAds": "Completed Campaigns", + "pMessage": "Ads not present for this campaign.", + "delete": "Delete", + "Rname": "Enter name of Advertisement", + "Rtype": "Select type of Advertisement", + "Rlink": "Provide a link for content to be displayed", + "RstartDate": "Select Start Date", + "RendDate": "Select End Date", + "RClose": "Close the window", + "addNew": "Create new advertisement", + "EXname": "Ex. Cookie Shop", + "EXlink": "Ex. http://yourwebsite.com/photo", + "register": "Create Advertisement", + "close": "Close " + }, "userChat": { "chat": "Chat", "search": "Search", diff --git a/public/locales/fr.json b/public/locales/fr.json index 97116881e9..75522a0fed 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -646,6 +646,14 @@ "event": "Événement", "organization": "Organisation" }, + "advertisement": { + "title": "Publicités", + "pHeading": "Gérer les publicités", + "activeAds": "Campagnes actives", + "archievedAds": "Campagnes terminées", + "pMessage": "Aucune publicité n'est présente pour cette campagne.", + "delete": "Supprimer" + }, "userChat": { "chat": "Chat", "search": "Recherche", diff --git a/public/locales/hi.json b/public/locales/hi.json index 490b5b5d04..d952ff7f30 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -646,6 +646,14 @@ "event": "आयोजन", "organization": "संगठन" }, + "advertisement": { + "title": "विज्ञापन", + "pHeading": "विज्ञापन प्रबंधन", + "activeAds": "सक्रिय अभियान", + "archievedAds": "संपन्न अभियान", + "pMessage": "इस अभियान के लिए कोई विज्ञापन नहीं हैं।", + "delete": "हटाएँ" + }, "userChat": { "chat": "बात", "search": "खोज", diff --git a/public/locales/sp.json b/public/locales/sp.json index 33cb77ed64..9b107afd44 100644 --- a/public/locales/sp.json +++ b/public/locales/sp.json @@ -646,6 +646,14 @@ "event": "Evento", "organization": "Organización" }, + "advertisement": { + "title": "Anuncios", + "pHeading": "Gestionar anuncios", + "activeAds": "Campañas activas", + "archievedAds": "Campañas completadas", + "pMessage": "No hay anuncios disponibles para esta campaña.", + "delete": "Eliminar" + }, "userChat": { "chat": "Charlar", "search": "Buscar", diff --git a/public/locales/zh.json b/public/locales/zh.json index 8de1d7c5d9..92e8c14c83 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -646,6 +646,14 @@ "event": "事件", "organization": "組織" }, + "advertisement": { + "title": "广告", + "pHeading": "管理广告", + "activeAds": "活动广告", + "archievedAds": "已完成的广告活动", + "pMessage": "此广告活动没有相关广告。", + "delete": "删除" + }, "userChat": { "chat": "聊天", "search": "搜尋", diff --git a/schema.graphql b/schema.graphql new file mode 100644 index 0000000000..d200ee7349 --- /dev/null +++ b/schema.graphql @@ -0,0 +1,1152 @@ +directive @auth on FIELD_DEFINITION + +directive @role(requires: UserType) on FIELD_DEFINITION + +type Advertisement { + _id: ID + endDate: Date! + link: String! + name: String! + orgId: ID + startDate: Date! + type: String! +} + +type AggregatePost { + count: Int! +} + +type AggregateUser { + count: Int! +} + +type AndroidFirebaseOptions { + apiKey: String + appId: String + messagingSenderId: String + projectId: String + storageBucket: String +} + +type AuthData { + accessToken: String! + androidFirebaseOptions: AndroidFirebaseOptions! + iosFirebaseOptions: IOSFirebaseOptions! + refreshToken: String! + user: User! +} + +type CheckIn { + _id: ID! + allotedRoom: String + allotedSeat: String + event: Event! + feedbackSubmitted: Boolean! + time: DateTime! + user: User! +} + +input CheckInInput { + allotedRoom: String + allotedSeat: String + eventId: ID! + userId: ID! +} + +type CheckInStatus { + _id: ID! + checkIn: CheckIn + user: User! +} + +type Comment { + _id: ID + createdAt: DateTime + creator: User! + likeCount: Int + likedBy: [User] + post: Post! + text: String! +} + +input CommentInput { + text: String! +} + +union ConnectionError = InvalidCursor | MaximumValueError + +type ConnectionPageInfo { + endCursor: String + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String +} + +input CreateUserTagInput { + name: String! + organizationId: ID! + parentTagId: ID +} + +input CursorPaginationInput { + cursor: String + direction: PaginationDirection! + limit: PositiveInt! +} + +scalar Date + +scalar DateTime + +type DeletePayload { + success: Boolean! +} + +type DirectChat { + _id: ID! + creator: User! + messages: [DirectChatMessage] + organization: Organization! + users: [User!]! +} + +type DirectChatMessage { + _id: ID! + createdAt: DateTime! + directChatMessageBelongsTo: DirectChat! + messageContent: String! + receiver: User! + sender: User! +} + +type Donation { + _id: ID! + amount: Float! + nameOfOrg: String! + nameOfUser: String! + orgId: ID! + payPalId: String! + userId: ID! +} + +input DonationWhereInput { + id: ID + id_contains: ID + id_in: [ID!] + id_not: ID + id_not_in: [ID!] + id_starts_with: ID + name_of_user: String + name_of_user_contains: String + name_of_user_in: [String!] + name_of_user_not: String + name_of_user_not_in: [String!] + name_of_user_starts_with: String +} + +scalar EmailAddress + +interface Error { + message: String! +} + +type Event { + _id: ID! + admins(adminId: ID): [User] + allDay: Boolean! + attendees: [User!]! + attendeesCheckInStatus: [CheckInStatus!]! + averageFeedbackScore: Float + creator: User! + description: String! + endDate: Date! + endTime: Time + feedback: [Feedback!]! + isPublic: Boolean! + isRegisterable: Boolean! + latitude: Latitude + location: String + longitude: Longitude + organization: Organization + projects: [EventProject] + recurrance: Recurrance + recurring: Boolean! + startDate: Date! + startTime: Time + status: Status! + title: String! +} + +input EventAttendeeInput { + eventId: ID! + userId: ID! +} + +input EventInput { + allDay: Boolean! + description: String! + endDate: Date + endTime: Time + isPublic: Boolean! + isRegisterable: Boolean! + latitude: Latitude + location: String + longitude: Longitude + organizationId: ID! + recurrance: Recurrance + recurring: Boolean! + startDate: Date! + startTime: Time + title: String! +} + +enum EventOrderByInput { + allDay_ASC + allDay_DESC + description_ASC + description_DESC + endDate_ASC + endDate_DESC + endTime_ASC + endTime_DESC + id_ASC + id_DESC + location_ASC + location_DESC + recurrance_ASC + recurrance_DESC + startDate_ASC + startDate_DESC + startTime_ASC + startTime_DESC + title_ASC + title_DESC +} + +type EventProject { + _id: ID! + description: String! + event: Event! + tasks: [Task] + title: String! +} + +input EventProjectInput { + description: String! + eventId: ID! + title: String! +} + +input EventWhereInput { + description: String + description_contains: String + description_in: [String!] + description_not: String + description_not_in: [String!] + description_starts_with: String + id: ID + id_contains: ID + id_in: [ID!] + id_not: ID + id_not_in: [ID!] + id_starts_with: ID + location: String + location_contains: String + location_in: [String!] + location_not: String + location_not_in: [String!] + location_starts_with: String + organization_id: ID + title: String + title_contains: String + title_in: [String!] + title_not: String + title_not_in: [String!] + title_starts_with: String +} + +type ExtendSession { + accessToken: String! + refreshToken: String! +} + +type Feedback { + _id: ID! + event: Event! + rating: Int! + review: String +} + +input FeedbackInput { + eventId: ID! + rating: Int! + review: String +} + +interface FieldError { + message: String! + path: [String!]! +} + +input ForgotPasswordData { + newPassword: String! + otpToken: String! + userOtp: String! +} + +type Group { + _id: ID + admins: [User] + createdAt: DateTime + description: String + organization: Organization! + title: String +} + +type GroupChat { + _id: ID! + creator: User! + messages: [GroupChatMessage] + organization: Organization! + users: [User!]! +} + +type GroupChatMessage { + _id: ID! + createdAt: DateTime! + groupChatMessageBelongsTo: GroupChat! + messageContent: String! + sender: User! +} + +type IOSFirebaseOptions { + apiKey: String + appId: String + iosBundleId: String + iosClientId: String + messagingSenderId: String + projectId: String + storageBucket: String +} + +type InvalidCursor implements FieldError { + message: String! + path: [String!]! +} + +type Language { + _id: ID! + createdAt: String! + en: String! + translation: [LanguageModel] +} + +input LanguageInput { + en_value: String! + translation_lang_code: String! + translation_value: String! +} + +type LanguageModel { + _id: ID! + createdAt: DateTime! + lang_code: String! + value: String! + verified: Boolean! +} + +scalar Latitude + +input LoginInput { + email: EmailAddress! + password: String! +} + +scalar Longitude + +type MaximumLengthError implements FieldError { + message: String! + path: [String!]! +} + +type MaximumValueError implements FieldError { + limit: Int! + message: String! + path: [String!]! +} + +type MembershipRequest { + _id: ID! + organization: Organization! + user: User! +} + +type Message { + _id: ID! + createdAt: DateTime + creator: User + imageUrl: URL + text: String + videoUrl: URL +} + +type MessageChat { + _id: ID! + createdAt: DateTime! + languageBarrier: Boolean + message: String! + receiver: User! + sender: User! +} + +input MessageChatInput { + message: String! + receiver: ID! +} + +type MinimumLengthError implements FieldError { + limit: Int! + message: String! + path: [String!]! +} + +type MinimumValueError implements FieldError { + message: String! + path: [String!]! +} + +type Mutation { + acceptAdmin(id: ID!): Boolean! + acceptMembershipRequest(membershipRequestId: ID!): MembershipRequest! + addEventAttendee(data: EventAttendeeInput!): User! + addFeedback(data: FeedbackInput!): Feedback! + addLanguageTranslation(data: LanguageInput!): Language! + addOrganizationImage(file: String!, organizationId: String!): Organization! + addUserImage(file: String!): User! + addUserToGroupChat(chatId: ID!, userId: ID!): GroupChat! + adminRemoveEvent(eventId: ID!): Event! + adminRemoveGroup(groupId: ID!): GroupChat! + assignUserTag(input: ToggleUserTagAssignInput!): User + blockPluginCreationBySuperadmin(blockUser: Boolean!, userId: ID!): User! + blockUser(organizationId: ID!, userId: ID!): User! + cancelMembershipRequest(membershipRequestId: ID!): MembershipRequest! + checkIn(data: CheckInInput!): CheckIn! + createAdmin(data: UserAndOrganizationInput!): User! + createAdvertisement( + endDate: Date! + link: String! + name: String! + orgId: ID! + startDate: Date! + type: String! + ): Advertisement! + createComment(data: CommentInput!, postId: ID!): Comment + createDirectChat(data: createChatInput!): DirectChat! + createDonation( + amount: Float! + nameOfOrg: String! + nameOfUser: String! + orgId: ID! + payPalId: ID! + userId: ID! + ): Donation! + createEvent(data: EventInput): Event! + createEventProject(data: EventProjectInput!): EventProject! + createGroupChat(data: createGroupChatInput!): GroupChat! + createMember(input: UserAndOrganizationInput!): Organization! + createMessageChat(data: MessageChatInput!): MessageChat! + createOrganization(data: OrganizationInput, file: String): Organization! + createPlugin( + pluginCreatedBy: String! + pluginDesc: String! + pluginName: String! + uninstalledOrgs: [ID!] + ): Plugin! + createPost(data: PostInput!, file: String): Post + createTask(data: TaskInput!, eventProjectId: ID!): Task! + createUserTag(input: CreateUserTagInput!): UserTag + deleteAdvertisementById(id: ID!): DeletePayload! + deleteDonationById(id: ID!): DeletePayload! + forgotPassword(data: ForgotPasswordData!): Boolean! + joinPublicOrganization(organizationId: ID!): User! + leaveOrganization(organizationId: ID!): User! + likeComment(id: ID!): Comment + likePost(id: ID!): Post + login(data: LoginInput!): AuthData! + logout: Boolean! + otp(data: OTPInput!): OtpData! + recaptcha(data: RecaptchaVerification!): Boolean! + refreshToken(refreshToken: String!): ExtendSession! + registerForEvent(id: ID!): Event! + rejectAdmin(id: ID!): Boolean! + rejectMembershipRequest(membershipRequestId: ID!): MembershipRequest! + removeAdmin(data: UserAndOrganizationInput!): User! + removeAdvertisement(id: ID!): Advertisement + removeComment(id: ID!): Comment + removeDirectChat(chatId: ID!, organizationId: ID!): DirectChat! + removeEvent(id: ID!): Event! + removeEventAttendee(data: EventAttendeeInput!): User! + removeEventProject(id: ID!): EventProject! + removeGroupChat(chatId: ID!): GroupChat! + removeMember(data: UserAndOrganizationInput!): Organization! + removeOrganization(id: ID!): User! + removeOrganizationImage(organizationId: String!): Organization! + removePost(id: ID!): Post + removeTask(id: ID!): Task + removeUserFromGroupChat(chatId: ID!, userId: ID!): GroupChat! + removeUserImage: User! + removeUserTag(id: ID!): UserTag + revokeRefreshTokenForUser(userId: String!): Boolean! + saveFcmToken(token: String): Boolean! + sendMembershipRequest(organizationId: ID!): MembershipRequest! + sendMessageToDirectChat( + chatId: ID! + messageContent: String! + ): DirectChatMessage! + sendMessageToGroupChat( + chatId: ID! + messageContent: String! + ): GroupChatMessage! + setTaskVolunteers(id: ID!, volunteers: [ID]!): Task + signUp(data: UserInput!, file: String): AuthData! + togglePostPin(id: ID!): Post! + unassignUserTag(input: ToggleUserTagAssignInput!): User + unblockUser(organizationId: ID!, userId: ID!): User! + unlikeComment(id: ID!): Comment + unlikePost(id: ID!): Post + unregisterForEventByUser(id: ID!): Event! + updateEvent(data: UpdateEventInput, id: ID!): Event! + updateEventProject(data: UpdateEventProjectInput!, id: ID!): EventProject! + updateLanguage(languageCode: String!): User! + updateOrganization( + data: UpdateOrganizationInput + file: String + id: ID! + ): Organization! + updatePluginStatus(id: ID!, orgId: ID!): Plugin! + updatePost(data: PostUpdateInput, id: ID!): Post! + updateTask(data: UpdateTaskInput!, id: ID!): Task + updateUserPassword(data: UpdateUserPasswordInput!): User! + updateUserProfile(data: UpdateUserInput, file: String): User! + updateUserTag(input: UpdateUserTagInput!): UserTag + updateUserType(data: UpdateUserTypeInput!): Boolean! +} + +input OTPInput { + email: EmailAddress! +} + +type Organization { + _id: ID! + admins(adminId: ID): [User] + apiUrl: URL! + blockedUsers: [User] + createdAt: DateTime + creator: User! + description: String! + image: String + isPublic: Boolean! + location: String + members: [User] + membershipRequests: [MembershipRequest] + name: String! + pinnedPosts: [Post] + userTags( + after: String + before: String + first: PositiveInt + last: PositiveInt + ): UserTagsConnection + visibleInSearch: Boolean! +} + +type OrganizationInfoNode { + _id: ID! + apiUrl: URL! + creator: User! + description: String! + image: String + isPublic: Boolean! + name: String! + visibleInSearch: Boolean! +} + +input OrganizationInput { + apiUrl: URL + attendees: String + description: String! + image: String + isPublic: Boolean! + location: String + name: String! + visibleInSearch: Boolean! +} + +enum OrganizationOrderByInput { + apiUrl_ASC + apiUrl_DESC + description_ASC + description_DESC + id_ASC + id_DESC + name_ASC + name_DESC +} + +input OrganizationWhereInput { + apiUrl: URL + apiUrl_contains: URL + apiUrl_in: [URL!] + apiUrl_not: URL + apiUrl_not_in: [URL!] + apiUrl_starts_with: URL + description: String + description_contains: String + description_in: [String!] + description_not: String + description_not_in: [String!] + description_starts_with: String + id: ID + id_contains: ID + id_in: [ID!] + id_not: ID + id_not_in: [ID!] + id_starts_with: ID + isPublic: Boolean + name: String + name_contains: String + name_in: [String!] + name_not: String + name_not_in: [String!] + name_starts_with: String + visibleInSearch: Boolean +} + +type OtpData { + otpToken: String! +} + +""" +Information about pagination in a connection. +""" +type PageInfo { + currPageNo: Int + + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + nextPageNo: Int + prevPageNo: Int + totalPages: Int +} + +enum PaginationDirection { + BACKWARD + FORWARD +} + +scalar PhoneNumber + +type Plugin { + _id: ID! + pluginCreatedBy: String! + pluginDesc: String! + pluginName: String! + uninstalledOrgs: [ID!]! +} + +type PluginField { + createdAt: DateTime + key: String! + status: Status! + value: String! +} + +input PluginFieldInput { + key: String! + value: String! +} + +input PluginInput { + fields: [PluginFieldInput] + orgId: ID! + pluginKey: String + pluginName: String! + pluginType: Type +} + +scalar PositiveInt + +type Post { + _id: ID + commentCount: Int + comments: [Comment] + createdAt: DateTime + creator: User! + imageUrl: URL + likeCount: Int + likedBy: [User] + organization: Organization! + pinned: Boolean + text: String! + title: String + videoUrl: URL +} + +""" +A connection to a list of items. +""" +type PostConnection { + aggregate: AggregatePost! + + """ + A list of edges. + """ + edges: [Post]! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! +} + +input PostInput { + _id: ID + imageUrl: URL + organizationId: ID! + pinned: Boolean + text: String! + title: String + videoUrl: URL +} + +enum PostOrderByInput { + commentCount_ASC + commentCount_DESC + createdAt_ASC + createdAt_DESC + id_ASC + id_DESC + imageUrl_ASC + imageUrl_DESC + likeCount_ASC + likeCount_DESC + text_ASC + text_DESC + title_ASC + title_DESC + videoUrl_ASC + videoUrl_DESC +} + +input PostUpdateInput { + imageUrl: String + text: String + title: String + videoUrl: String +} + +input PostWhereInput { + id: ID + id_contains: ID + id_in: [ID!] + id_not: ID + id_not_in: [ID!] + id_starts_with: ID + text: String + text_contains: String + text_in: [String!] + text_not: String + text_not_in: [String!] + text_starts_with: String + title: String + title_contains: String + title_in: [String!] + title_not: String + title_not_in: [String!] + title_starts_with: String +} + +type Query { + adminPlugin(orgId: ID!): [Plugin] + checkAuth: User! + directChatsByUserID(id: ID!): [DirectChat] + directChatsMessagesByChatID(id: ID!): [DirectChatMessage] + event(id: ID!): Event + eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event] + eventsByOrganizationConnection( + first: Int + orderBy: EventOrderByInput + skip: Int + where: EventWhereInput + ): [Event!]! + getAdvertisements: [Advertisement] + getDonationById(id: ID!): Donation! + getDonationByOrgId(orgId: ID!): [Donation] + getDonationByOrgIdConnection( + first: Int + orgId: ID! + skip: Int + where: DonationWhereInput + ): [Donation!]! + getPlugins: [Plugin] + getlanguage(lang_code: String!): [Translation] + hasSubmittedFeedback(eventId: ID!, userId: ID!): Boolean + joinedOrganizations(id: ID): [Organization] + me: User! + myLanguage: String + organizations(id: ID, orderBy: OrganizationOrderByInput): [Organization] + organizationsConnection( + first: Int + orderBy: OrganizationOrderByInput + skip: Int + where: OrganizationWhereInput + ): [Organization]! + organizationsMemberConnection( + first: Int + orderBy: UserOrderByInput + orgId: ID! + skip: Int + where: UserWhereInput + ): UserConnection! + plugin(orgId: ID!): [Plugin] + post(id: ID!): Post + postsByOrganization(id: ID!, orderBy: PostOrderByInput): [Post] + postsByOrganizationConnection( + first: Int + id: ID! + orderBy: PostOrderByInput + skip: Int + where: PostWhereInput + ): PostConnection + registeredEventsByUser(id: ID, orderBy: EventOrderByInput): [Event] + registrantsByEvent(id: ID!): [User] + user(id: ID!): User! + userLanguage(userId: ID!): String + users(orderBy: UserOrderByInput, where: UserWhereInput): [User] + usersConnection( + first: Int + orderBy: UserOrderByInput + skip: Int + where: UserWhereInput + ): [User]! +} + +input RecaptchaVerification { + recaptchaToken: String! +} + +enum Recurrance { + DAILY + MONTHLY + ONCE + WEEKLY + YEARLY +} + +enum Status { + ACTIVE + BLOCKED + DELETED +} + +type Subscription { + directMessageChat: MessageChat + messageSentToDirectChat: DirectChatMessage + messageSentToGroupChat: GroupChatMessage + onPluginUpdate: Plugin +} + +type Task { + _id: ID! + completed: Boolean + createdAt: DateTime! + creator: User! + deadline: DateTime + description: String + event: Event! + title: String! + volunteers: [User] +} + +input TaskInput { + deadline: DateTime! + description: String! + title: String! +} + +enum TaskOrderByInput { + createdAt_ASC + createdAt_DESC + deadline_ASC + deadline_DESC + description_ASC + description_DESC + id_ASC + id_DESC + title_ASC + title_DESC +} + +scalar Time + +input ToggleUserTagAssignInput { + tagId: ID! + userId: ID! +} + +type Translation { + en_value: String + lang_code: String + translation: String + verified: Boolean +} + +enum Type { + PRIVATE + UNIVERSAL +} + +scalar URL + +type UnauthenticatedError implements Error { + message: String! +} + +type UnauthorizedError implements Error { + message: String! +} + +input UpdateEventInput { + allDay: Boolean + description: String + endDate: Date + endTime: Time + isPublic: Boolean + isRegisterable: Boolean + latitude: Latitude + location: String + longitude: Longitude + recurrance: Recurrance + recurring: Boolean + startDate: Date + startTime: Time + title: String +} + +input UpdateEventProjectInput { + description: String + title: String +} + +input UpdateOrganizationInput { + description: String + isPublic: Boolean + location: String + name: String + visibleInSearch: Boolean +} + +input UpdateTaskInput { + completed: Boolean + deadline: DateTime + description: String + title: String +} + +input UpdateUserInput { + email: EmailAddress + firstName: String + lastName: String +} + +input UpdateUserPasswordInput { + confirmNewPassword: String! + newPassword: String! + previousPassword: String! +} + +input UpdateUserTagInput { + _id: ID! + name: String! +} + +input UpdateUserTypeInput { + id: ID + userType: String +} + +scalar Upload + +type User { + _id: ID! + adminApproved: Boolean + adminFor: [Organization] + appLanguageCode: String! + assignedTasks: [Task] + createdAt: DateTime + createdEvents: [Event] + createdOrganizations: [Organization] + email: EmailAddress! + eventAdmin: [Event] + firstName: String! + image: String + joinedOrganizations: [Organization] + lastName: String! + membershipRequests: [MembershipRequest] + organizationUserBelongsTo: Organization + organizationsBlockedBy: [Organization] + pluginCreationAllowed: Boolean + registeredEvents: [Event] + tagsAssignedWith( + after: String + before: String + first: PositiveInt + last: PositiveInt + organizationId: ID + ): UserTagsConnection + tokenVersion: Int! + userType: String +} + +input UserAndOrganizationInput { + organizationId: ID! + userId: ID! +} + +type UserConnection { + aggregate: AggregateUser! + edges: [User]! + pageInfo: PageInfo! +} + +type UserEdge { + cursor: String! + node: User! +} + +input UserInput { + appLanguageCode: String + email: EmailAddress! + firstName: String! + lastName: String! + organizationUserBelongsToId: ID + password: String! +} + +enum UserOrderByInput { + appLanguageCode_ASC + appLanguageCode_DESC + email_ASC + email_DESC + firstName_ASC + firstName_DESC + id_ASC + id_DESC + lastName_ASC + lastName_DESC +} + +type UserTag { + _id: ID! + childTags(input: UserTagsConnectionInput!): UserTagsConnectionResult! + name: String! + organization: Organization + parentTag: UserTag + usersAssignedTo(input: UsersConnectionInput!): UsersConnectionResult! +} + +type UserTagEdge { + cursor: String! + node: UserTag! +} + +type UserTagsConnection { + edges: [UserTagEdge!]! + pageInfo: ConnectionPageInfo! +} + +input UserTagsConnectionInput { + cursor: String + direction: PaginationDirection! + limit: PositiveInt! +} + +type UserTagsConnectionResult { + data: UserTagsConnection + errors: [ConnectionError!]! +} + +enum UserType { + ADMIN + SUPERADMIN + USER +} + +input UserWhereInput { + admin_for: ID + appLanguageCode: String + appLanguageCode_contains: String + appLanguageCode_in: [String!] + appLanguageCode_not: String + appLanguageCode_not_in: [String!] + appLanguageCode_starts_with: String + email: EmailAddress + email_contains: EmailAddress + email_in: [EmailAddress!] + email_not: EmailAddress + email_not_in: [EmailAddress!] + email_starts_with: EmailAddress + event_title_contains: String + firstName: String + firstName_contains: String + firstName_in: [String!] + firstName_not: String + firstName_not_in: [String!] + firstName_starts_with: String + id: ID + id_contains: ID + id_in: [ID!] + id_not: ID + id_not_in: [ID!] + id_starts_with: ID + lastName: String + lastName_contains: String + lastName_in: [String!] + lastName_not: String + lastName_not_in: [String!] + lastName_starts_with: String +} + +type UsersConnection { + edges: [UserEdge!]! + pageInfo: ConnectionPageInfo! +} + +input UsersConnectionInput { + cursor: String + direction: PaginationDirection! + limit: PositiveInt! +} + +type UsersConnectionResult { + data: UsersConnection + errors: [ConnectionError!]! +} + +input createChatInput { + organizationId: ID! + userIds: [ID!]! +} + +input createGroupChatInput { + organizationId: ID! + title: String! + userIds: [ID!]! +} diff --git a/src/App.tsx b/src/App.tsx index cf20c722e5..1c69256c32 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -32,6 +32,7 @@ import Settings from 'screens/UserPortal/Settings/Settings'; import Donate from 'screens/UserPortal/Donate/Donate'; import Events from 'screens/UserPortal/Events/Events'; import Tasks from 'screens/UserPortal/Tasks/Tasks'; +import Advertisements from 'components/Advertisements/Advertisements'; import Chat from 'screens/UserPortal/Chat/Chat'; function app(): JSX.Element { @@ -109,6 +110,7 @@ function app(): JSX.Element { + diff --git a/src/GraphQl/Mutations/mutations.ts b/src/GraphQl/Mutations/mutations.ts index dca9e713c3..34abda2b26 100644 --- a/src/GraphQl/Mutations/mutations.ts +++ b/src/GraphQl/Mutations/mutations.ts @@ -400,7 +400,34 @@ export const ADD_PLUGIN_MUTATION = gql` } } `; - +export const ADD_ADVERTISEMENT_MUTATION = gql` + mutation ( + $orgId: ID! + $name: String! + $link: String! + $type: String! + $startDate: Date! + $endDate: Date! + ) { + createAdvertisement( + orgId: $orgId + name: $name + link: $link + type: $type + startDate: $startDate + endDate: $endDate + ) { + _id + } + } +`; +export const DELETE_ADVERTISEMENT_BY_ID = gql` + mutation ($id: ID!) { + deleteAdvertisementById(id: $id) { + success + } + } +`; export const UPDATE_POST_MUTATION = gql` mutation UpdatePost( $id: ID! diff --git a/src/GraphQl/Queries/Queries.ts b/src/GraphQl/Queries/Queries.ts index 500dde6192..7bf99d48ee 100644 --- a/src/GraphQl/Queries/Queries.ts +++ b/src/GraphQl/Queries/Queries.ts @@ -704,7 +704,19 @@ export const PLUGIN_GET = gql` } } `; - +export const ADVERTISEMENTS_GET = gql` + query getAdvertisement { + getAdvertisements { + _id + name + type + orgId + link + endDate + startDate + } + } +`; export const ORGANIZATION_EVENTS_CONNECTION = gql` query EventsByOrganizationConnection( $organization_id: ID! diff --git a/src/components/AddOn/core/AddOnStore/AddOnStore.tsx b/src/components/AddOn/core/AddOnStore/AddOnStore.tsx index 2e3e511149..d81ce6fa4c 100644 --- a/src/components/AddOn/core/AddOnStore/AddOnStore.tsx +++ b/src/components/AddOn/core/AddOnStore/AddOnStore.tsx @@ -160,7 +160,7 @@ function addOnStore(): JSX.Element { Search results for {searchText}

) : null} - + = new ApolloClient({ + cache: new InMemoryCache(), + link: ApolloLink.from([httpLink]), +}); +describe('Testing Advertisement Component', () => { + test('Temporary test for Advertisement', () => { + expect(true).toBe(true); + const { getByTestId } = render( + + + + + {} + + + + + ); + expect(getByTestId('AdEntryStore')).toBeInTheDocument(); + }); + + test('renders advertisement data', async () => { + const mocks = [ + { + request: { + query: ADVERTISEMENTS_GET, + variables: { + name: 'Test', + }, + }, + result: { + data: { + getAdvertisements: [ + { + _id: '1', + name: 'Advertisement', + type: 'POPUP', + orgId: 'org1', + link: 'http://example.com', + endDate: new Date(), + startDate: new Date(), + }, + // Add more mock data if needed + ], + }, + loading: false, + }, + }, + ]; + + const { getByTestId } = render( + + + + + + + + + + + + ); + + expect(getByTestId('AdEntryStore')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Advertisements/Advertisements.tsx b/src/components/Advertisements/Advertisements.tsx new file mode 100644 index 0000000000..2d9d936d1e --- /dev/null +++ b/src/components/Advertisements/Advertisements.tsx @@ -0,0 +1,197 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'react'; +import styles from './Advertisement.module.css'; +import { useQuery } from '@apollo/client'; +import { ADVERTISEMENTS_GET, PLUGIN_GET } from 'GraphQl/Queries/Queries'; // PLUGIN_LIST +import { useSelector } from 'react-redux'; +import type { RootState } from '../../state/reducers'; +import { Col, Form, Row, Tab, Tabs } from 'react-bootstrap'; +import PluginHelper from 'components/AddOn/support/services/Plugin.helper'; +import { store } from 'state/store'; +import { useTranslation } from 'react-i18next'; +import Loader from 'components/Loader/Loader'; +import OrganizationScreen from 'components/OrganizationScreen/OrganizationScreen'; +import AdvertisementEntry from './core/AdvertisementEntry/AdvertisementEntry'; +import AdvertisementRegister from './core/AdvertisementRegister/AdvertisementRegister'; +import AddOnRegister from 'components/AddOn/core/AddOnRegister/AddOnRegister'; +export default function advertisements(): JSX.Element { + const { + data: data2, + loading: loading2, + error: error2, + } = useQuery(ADVERTISEMENTS_GET); + const currentOrgId = window.location.href.split('/id=')[1] + ''; + const { t } = useTranslation('translation', { keyPrefix: 'advertisement' }); + document.title = t('title'); + + const [isStore, setIsStore] = useState(true); + const [showEnabled, setShowEnabled] = useState(true); + const [searchText, setSearchText] = useState(''); + const [dataList, setDataList] = useState([]); + + const [render, setRender] = useState(true); + const appRoutes = useSelector((state: RootState) => state.appRoutes); + const { targets, configUrl } = appRoutes; + + const plugins = useSelector((state: RootState) => state.plugins); + const { installed, addonStore } = plugins; + const { data, loading, error } = useQuery(PLUGIN_GET); + /* istanbul ignore next */ + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + const getStorePlugins = async () => { + let plugins = await new PluginHelper().fetchStore(); + const installIds = (await new PluginHelper().fetchInstalled()).map( + (plugin: any) => plugin.id + ); + plugins = plugins.map((plugin: any) => { + plugin.installed = installIds.includes(plugin.id); + return plugin; + }); + store.dispatch({ type: 'UPDATE_STORE', payload: plugins }); + }; + + /* istanbul ignore next */ + const getInstalledPlugins: () => any = () => { + setDataList(data); + }; + // const getAdvertisements: () => any = ()=> { + // return + // } + + /* istanbul ignore next */ + const updateLinks = async (links: any[]): Promise => { + store.dispatch({ type: 'UPDATE_P_TARGETS', payload: links }); + }; + // /* istanbul ignore next */ + const pluginModified = (): void => { + return getInstalledPlugins(); + // .then((installedPlugins) => { + // getStorePlugins(); + // return installedPlugins; + // }); + }; + + const updateSelectedTab = (tab: any): void => { + setIsStore(tab === 'activeAds'); + isStore ? getStorePlugins() : getInstalledPlugins(); + }; + + const filterChange = (ev: any): void => { + setShowEnabled(ev.target.value === 'enabled'); + }; + + /* istanbul ignore next */ + if (loading) { + return ( + <> +
+ + ); + } + + return ( + <> + + + +
+

{t('pHeading')}

+ + + + + {data2?.getAdvertisements + .filter((ad: any) => ad.orgId == currentOrgId) + .filter((ad: any) => new Date(ad.endDate) > new Date()) + .length == 0 ? ( +

{t('pMessage')}

// eslint-disable-line + ) : ( + data2?.getAdvertisements + .filter((ad: any) => ad.orgId == currentOrgId) + .filter((ad: any) => new Date(ad.endDate) > new Date()) + .map( + ( + ad: { + _id: string; + name: string | undefined; + type: string | undefined; + orgId: string; + link: string; + endDate: Date; + startDate: Date; + }, + i: React.Key | null | undefined + ): JSX.Element => ( + + ) + ) + )} +
+ + {data2?.getAdvertisements + .filter((ad: any) => ad.orgId == currentOrgId) + .filter((ad: any) => new Date(ad.endDate) < new Date()) + .length == 0 ? ( +

{t('pMessage')}

// eslint-disable-line + ) : ( + data2?.getAdvertisements + .filter((ad: any) => ad.orgId == currentOrgId) + .filter((ad: any) => new Date(ad.endDate) < new Date()) + .map( + ( + ad: { + _id: string; + name: string | undefined; + type: string | undefined; + orgId: string; + link: string; + endDate: Date; + startDate: Date; + }, + i: React.Key | null | undefined + ): JSX.Element => ( + + ) + ) + )} +
+
+
+ +
+
+ + ); +} + +advertisements.defaultProps = {}; + +advertisements.propTypes = {}; diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.module.css b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.module.css new file mode 100644 index 0000000000..1f1ea89996 --- /dev/null +++ b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.module.css @@ -0,0 +1,20 @@ +.entrytoggle { + margin: 24px 24px 0 auto; + width: fit-content; +} + +.entryaction { + margin-left: auto; + display: flex !important; + align-items: center; +} + +.entryaction i { + margin-right: 8px; +} + +.entryaction .spinner-grow { + height: 1rem; + width: 1rem; + margin-right: 8px; +} diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx new file mode 100644 index 0000000000..896c61e5e1 --- /dev/null +++ b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.test.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { + ApolloClient, + ApolloProvider, + InMemoryCache, + ApolloLink, + HttpLink, +} from '@apollo/client'; + +import type { NormalizedCacheObject } from '@apollo/client'; +import { BrowserRouter } from 'react-router-dom'; +import AdvertisementEntry from './AdvertisementEntry'; +import { Provider } from 'react-redux'; +import { store } from 'state/store'; +import { BACKEND_URL } from 'Constant/constant'; +import i18nForTest from 'utils/i18nForTest'; +import { I18nextProvider } from 'react-i18next'; + +const httpLink = new HttpLink({ + uri: BACKEND_URL, + headers: { + authorization: 'Bearer ' + localStorage.getItem('token') || '', + }, +}); + +const client: ApolloClient = new ApolloClient({ + cache: new InMemoryCache(), + link: ApolloLink.from([httpLink]), +}); +describe('Testing Advertisement Entry Component', () => { + test('Temporary test for Advertisement Entry', () => { + const { getByTestId, getAllByText } = render( + + + + + { + + } + + + + + ); + expect(getByTestId('AdEntry')).toBeInTheDocument(); + expect(getAllByText('POPUP')[0]).toBeInTheDocument(); + expect(getAllByText('Advert1')[0]).toBeInTheDocument(); + }); +}); diff --git a/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx new file mode 100644 index 0000000000..36d99004fc --- /dev/null +++ b/src/components/Advertisements/core/AdvertisementEntry/AdvertisementEntry.tsx @@ -0,0 +1,103 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import styles from './AdvertisementEntry.module.css'; +import { Button, Card, Col, Row, Spinner } from 'react-bootstrap'; +import { DELETE_ADVERTISEMENT_BY_ID } from 'GraphQl/Mutations/mutations'; +import { useMutation } from '@apollo/client'; +import { useTranslation } from 'react-i18next'; +interface InterfaceAddOnEntryProps { + id: string; + name: string; + link: string; + type: string; + orgId: string; + startDate: Date; + endDate: Date; +} +function advertisementEntry({ + id, + name, + type, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + orgId, + link, + endDate, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + startDate, +}: InterfaceAddOnEntryProps): JSX.Element { + const { t } = useTranslation('translation', { keyPrefix: 'advertisement' }); + const [buttonLoading, setButtonLoading] = useState(false); + const [deleteAdById] = useMutation(DELETE_ADVERTISEMENT_BY_ID); + + const onDelete = async (): Promise => { + setButtonLoading(true); + await deleteAdById({ + variables: { + id: id.toString(), + }, + }); + setButtonLoading(false); + }; + return ( + <> + + {Array.from({ length: 4 }).map((_, idx) => ( + + + + + {name} + Ends on {endDate?.toDateString()} + + {type} + + {link} + + + + + ))} + +
+ + ); +} + +advertisementEntry.propTypes = { + name: PropTypes.string, + type: PropTypes.string, + orgId: PropTypes.string, + link: PropTypes.string, + endDate: PropTypes.instanceOf(Date), + startDate: PropTypes.instanceOf(Date), +}; + +advertisementEntry.defaultProps = { + name: '', + type: '', + orgId: '', + link: '', + endDate: new Date(), + startDate: new Date(), +}; +export default advertisementEntry; diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.module.css b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.module.css new file mode 100644 index 0000000000..c122d386fa --- /dev/null +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.module.css @@ -0,0 +1,9 @@ +.modalbtn { + display: flex !important; + margin-left: auto; + align-items: center; +} + +.modalbtn i { + margin-right: 8px; +} diff --git a/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx new file mode 100644 index 0000000000..e205d50598 --- /dev/null +++ b/src/components/Advertisements/core/AdvertisementRegister/AdvertisementRegister.tsx @@ -0,0 +1,207 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import styles from './AdvertisementRegister.module.css'; +import { Button, Form, Modal } from 'react-bootstrap'; +import { useMutation } from '@apollo/client'; +import { ADD_ADVERTISEMENT_MUTATION } from 'GraphQl/Mutations/mutations'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import dayjs from 'dayjs'; + +interface InterfaceAddOnRegisterProps { + id?: string; // OrgId + createdBy?: string; // User +} +interface InterfaceFormStateTypes { + name: string; + link: string; + type: string; + startDate: Date; + endDate: Date; + orgId: string; +} +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function advertisementRegister({ + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + createdBy, +}: InterfaceAddOnRegisterProps): JSX.Element { + const { t } = useTranslation('translation', { keyPrefix: 'advertisement' }); + + const [show, setShow] = useState(false); + + const handleClose = (): void => setShow(false); + const handleShow = (): void => setShow(true); + const [create] = useMutation(ADD_ADVERTISEMENT_MUTATION); + + //getting orgId from URL + const currentOrg = window.location.href.split('/id=')[1] + ''; + const [formState, setFormState] = useState({ + name: '', + link: '', + type: 'BANNER', + startDate: new Date(), + endDate: new Date(), + orgId: currentOrg, + }); + const handleRegister = async (): Promise => { + try { + console.log('At handle register', formState); + const { data } = await create({ + variables: { + orgId: currentOrg, + name: formState.name as string, + link: formState.link as string, + type: formState.type as string, + startDate: dayjs(formState.startDate).format('YYYY-MM-DD'), + endDate: dayjs(formState.endDate).format('YYYY-MM-DD'), + }, + }); + + if (data) { + toast.success('Advertisement created successfully'); + setTimeout(() => { + window.location.reload(); + }, 2000); + } + } catch (error) { + console.log('error occured', error); + } + }; + return ( + <> + + + + + {t('RClose')} + + +
+ + {t('Rname')} + { + setFormState({ + ...formState, + name: e.target.value, + }); + }} + /> + + + {t('Rlink')} + { + setFormState({ + ...formState, + link: e.target.value, + }); + }} + /> + + + {t('Rtype')} + { + setFormState({ + ...formState, + type: e.target.value, + }); + console.log(e.target, e.target.value, typeof e.target.value); + }} + > + + + + + + + {t('RstartDate')} + { + setFormState({ + ...formState, + startDate: new Date(e.target.value), + }); + }} + /> + + + + {t('RendDate')} + { + setFormState({ + ...formState, + endDate: new Date(e.target.value), + }); + }} + /> + +
+
+ + + + +
+ + ); +} + +advertisementRegister.defaultProps = { + name: '', + link: '', + type: 'BANNER', + startDate: new Date(), + endDate: new Date(), + orgId: '', +}; + +advertisementRegister.propTypes = { + name: PropTypes.string, + link: PropTypes.string, + type: PropTypes.string, + startDate: PropTypes.instanceOf(Date), + endDate: PropTypes.instanceOf(Date), + orgId: PropTypes.string, +}; + +export default advertisementRegister; diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index 8504b13d2a..4c490c9c94 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -88,6 +88,8 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { stroke={props.fill} /> ); + case 'Advertisement': + return ; default: return ( state.appRoutes); const { targets, configUrl } = appRoutes; - return ( <> + + +
+ + {'Promoted Content'} +
+
+ + {props.title} + {props.title} + {props.image && ( + + )} + +
+ + ); +} diff --git a/src/screens/UserPortal/Home/Home.tsx b/src/screens/UserPortal/Home/Home.tsx index b657fc0c02..ad02e1b26c 100644 --- a/src/screens/UserPortal/Home/Home.tsx +++ b/src/screens/UserPortal/Home/Home.tsx @@ -11,13 +11,17 @@ import getOrganizationId from 'utils/getOrganizationId'; import SendIcon from '@mui/icons-material/Send'; import PostCard from 'components/UserPortal/PostCard/PostCard'; import { useMutation, useQuery } from '@apollo/client'; -import { ORGANIZATION_POST_CONNECTION_LIST } from 'GraphQl/Queries/Queries'; +import { + ADVERTISEMENTS_GET, + ORGANIZATION_POST_CONNECTION_LIST, +} from 'GraphQl/Queries/Queries'; import { CREATE_POST_MUTATION } from 'GraphQl/Mutations/mutations'; import { errorHandler } from 'utils/errorHandler'; import { useTranslation } from 'react-i18next'; import convertToBase64 from 'utils/convertToBase64'; import { toast } from 'react-toastify'; import HourglassBottomIcon from '@mui/icons-material/HourglassBottom'; +import PromotedPost from 'components/UserPortal/PromotedPost/PromotedPost'; interface InterfacePostCardProps { id: string; @@ -60,11 +64,19 @@ export default function home(): JSX.Element { const [posts, setPosts] = React.useState([]); const [postContent, setPostContent] = React.useState(''); const [postImage, setPostImage] = React.useState(''); + const currentOrgId = window.location.href.split('/id=')[1] + ''; + const [adContent, setAdContent] = React.useState([]); const navbarProps = { currentPage: 'home', }; - + const { + data: promotedPostsData, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + refetch: _promotedPostsRefetch, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loading: promotedPostsLoading, + } = useQuery(ADVERTISEMENTS_GET); const { data, refetch, @@ -116,6 +128,12 @@ export default function home(): JSX.Element { } }, [data]); + React.useEffect(() => { + if (promotedPostsData) { + setAdContent(promotedPostsData.getAdvertisements); + } + }, [data]); + return ( <> @@ -184,6 +202,21 @@ export default function home(): JSX.Element { + {adContent + .filter((ad: any) => ad.orgId == currentOrgId) + .filter((ad: any) => new Date(ad.endDate) > new Date()).length == 0 + ? '' + : adContent + .filter((ad: any) => ad.orgId == currentOrgId) + .filter((ad: any) => new Date(ad.endDate) > new Date()) + .map((post: any) => ( + + ))} {loadingPosts ? (
Loading... diff --git a/src/state/reducers/routesReducer.test.ts b/src/state/reducers/routesReducer.test.ts index 89743a8508..765920e6f3 100644 --- a/src/state/reducers/routesReducer.test.ts +++ b/src/state/reducers/routesReducer.test.ts @@ -18,6 +18,7 @@ describe('Testing Routes reducer', () => { name: 'Block/Unblock', url: '/blockuser/id=undefined', }, + { name: 'Advertisement', url: '/orgads/id=undefined' }, { name: 'Plugins', subTargets: [ @@ -50,6 +51,11 @@ describe('Testing Routes reducer', () => { }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, + { + name: 'Advertisement', + comp_id: 'orgads', + component: 'Advertisements', + }, { name: 'Plugins', comp_id: null, @@ -83,6 +89,7 @@ describe('Testing Routes reducer', () => { { name: 'Events', url: '/orgevents/id=undefined' }, { name: 'Posts', url: '/orgpost/id=undefined' }, { name: 'Block/Unblock', url: '/blockuser/id=undefined' }, + { name: 'Advertisement', url: '/orgads/id=undefined' }, { name: 'Plugins', subTargets: [ @@ -116,6 +123,11 @@ describe('Testing Routes reducer', () => { }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, + { + name: 'Advertisement', + comp_id: 'orgads', + component: 'Advertisements', + }, { name: 'Plugins', comp_id: null, @@ -152,6 +164,7 @@ describe('Testing Routes reducer', () => { name: 'Block/Unblock', url: '/blockuser/id=undefined', }, + { name: 'Advertisement', url: '/orgads/id=undefined' }, { name: 'Settings', url: '/orgsetting/id=undefined' }, { name: 'All Organizations', url: '/orglist/id=undefined' }, { @@ -185,8 +198,14 @@ describe('Testing Routes reducer', () => { comp_id: 'orgevents', component: 'OrganizationEvents', }, + { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, + { + name: 'Advertisement', + comp_id: 'orgads', + component: 'Advertisements', + }, { name: 'Plugins', comp_id: null, diff --git a/src/state/reducers/routesReducer.ts b/src/state/reducers/routesReducer.ts index d0d5a7ab26..4fafbd1ebb 100644 --- a/src/state/reducers/routesReducer.ts +++ b/src/state/reducers/routesReducer.ts @@ -68,6 +68,7 @@ const components: ComponentType[] = [ { name: 'Events', comp_id: 'orgevents', component: 'OrganizationEvents' }, { name: 'Posts', comp_id: 'orgpost', component: 'OrgPost' }, { name: 'Block/Unblock', comp_id: 'blockuser', component: 'BlockUser' }, + { name: 'Advertisement', comp_id: 'orgads', component: 'Advertisements' }, { name: 'Plugins', comp_id: null, @@ -81,6 +82,7 @@ const components: ComponentType[] = [ }, ], }, + { name: 'Settings', comp_id: 'orgsetting', component: 'OrgSettings' }, { name: 'All Organizations', comp_id: 'orglist', component: 'OrgList' }, { name: '', comp_id: 'member', component: 'MemberDetail' },