From f243236d00ad641357b92e5e22591f0fca9fdec5 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Tue, 28 Nov 2023 11:17:46 -0300 Subject: [PATCH 01/24] change: create ProfileClass implementing Profile --- src/types/Profile.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/types/Profile.ts b/src/types/Profile.ts index 0acf5be5..9495c2ca 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -12,3 +12,16 @@ export type Profile = { bio: string | null; creationDate: string; } + +export class ProfileClass implements Profile { + constructor(private profile: Profile) {} + + get id() { return this.profile.id } + get user() { return this.profile.user } + get firstname() { return this.profile.firstname } + get lastname() { return this.profile.lastname } + get bio() { return this.profile.bio } + get gender() { return this.profile.gender } + get image() { return this.profile.image } + get creationDate() { return this.profile.creationDate } +} From 94b9b52528e818f88c499ce6c9513f88b2c35b73 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Tue, 28 Nov 2023 11:23:11 -0300 Subject: [PATCH 02/24] change: ProfileClass setters --- src/types/Profile.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/types/Profile.ts b/src/types/Profile.ts index 9495c2ca..809ba09f 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -1,4 +1,5 @@ import type { User } from "@/types/User"; +import { type Nullable } from "@/types/utils"; export type Gender = "M" | "F" | "O"; @@ -17,11 +18,25 @@ export class ProfileClass implements Profile { constructor(private profile: Profile) {} get id() { return this.profile.id } + set id(id: string) { this.profile.id = id } + get user() { return this.profile.user } + set user(user: User) { this.profile.user = user } + get firstname() { return this.profile.firstname } + set firstname(firstname: Nullable) { this.profile.firstname = firstname } + get lastname() { return this.profile.lastname } + set lastname(lastname: Nullable) { this.profile.lastname = lastname } + get bio() { return this.profile.bio } + set bio(bio: Nullable) { this.profile.bio = bio } + get gender() { return this.profile.gender } + set gender(gender: Nullable) { this.profile.gender = gender } + get image() { return this.profile.image } + set image(image: Nullable) { this.profile.image = image } + get creationDate() { return this.profile.creationDate } } From 8ebf492e585f99a186d4e8d2d14fe49de335383f Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Tue, 28 Nov 2023 13:38:03 -0300 Subject: [PATCH 03/24] change: profileUtils functions as ProfileClass Methods --- src/types/Profile.ts | 69 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/types/Profile.ts b/src/types/Profile.ts index 809ba09f..2b98113a 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -2,6 +2,11 @@ import type { User } from "@/types/User"; import { type Nullable } from "@/types/utils"; export type Gender = "M" | "F" | "O"; +export const GenderOptions: {[k in Gender]: string} = { + M: "Masculino", + F: "Feminino", + O: "Outro", +}; export type Profile = { id: string; @@ -17,6 +22,70 @@ export type Profile = { export class ProfileClass implements Profile { constructor(private profile: Profile) {} + /** + * Builds the full name of the profile. + * + * @returns {(string | null)} The concatenation of the first and last names, with a space in between + * or `null` if both are null. + */ + get fullname(): Nullable { + const hasFirst = this.firstname && this.firstname.length > 0; + const hasLast = this.lastname && this.lastname.length > 0; + + if (!hasFirst && !hasLast) + return null; + + return ( this.firstname ?? "" ) + + ( hasFirst && hasLast ? " " : "" ) + + ( this.lastname ?? "" ); + } + + /** + * User readable gender name, instead of the API value. + */ + get genderName() { + if (this.gender) + return GenderOptions[this.gender]; + + // todo: use a constant with this value + return "Não informado"; + } + + /** + * Image URL ready to be used on an ``. + */ + get imageUrl() { + if (this.image === null) + return null; + + return import.meta.env.VITE_UNIVERSIME_API + "/profile/image/" + this.id; + } + + /** + * Separates a full name into a first name and a last name. + * + * @param {string} fullname The full name to be separated. + * @returns {[string, string]} An 2 element array, where the first element is + * the first name and the second is the last name or `undefined`, if `fullname` + * doesn't have a last name. + */ + public static separateFullname(fullname: string): [string, string | undefined] { + fullname = fullname.trim(); + const spaceIndex = fullname.indexOf(" "); + + if (fullname.length === 0 || spaceIndex < 0) + return [fullname, undefined]; + + const firstname = fullname.slice(0, spaceIndex); + const lastname = fullname.slice(spaceIndex + 1); + + return [ + firstname, + lastname, + ]; + } + + /* Profile type getters and setters */ get id() { return this.profile.id } set id(id: string) { this.profile.id = id } From fd36cbec842bec36cdca6d9b51223f2c2fa2b468 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Wed, 29 Nov 2023 09:43:51 -0300 Subject: [PATCH 04/24] change: imageUrl returns default when null --- src/types/Profile.ts | 3 ++- src/utils/assets.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/types/Profile.ts b/src/types/Profile.ts index 2b98113a..ec8b5e7a 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -1,5 +1,6 @@ import type { User } from "@/types/User"; import { type Nullable } from "@/types/utils"; +import { IMG_DEFAULT_PROFILE } from "@/utils/assets"; export type Gender = "M" | "F" | "O"; export const GenderOptions: {[k in Gender]: string} = { @@ -56,7 +57,7 @@ export class ProfileClass implements Profile { */ get imageUrl() { if (this.image === null) - return null; + return IMG_DEFAULT_PROFILE; return import.meta.env.VITE_UNIVERSIME_API + "/profile/image/" + this.id; } diff --git a/src/utils/assets.ts b/src/utils/assets.ts index 05c11815..15d27b19 100644 --- a/src/utils/assets.ts +++ b/src/utils/assets.ts @@ -1,5 +1,6 @@ -export const IMG_UNIVERSI_LOGO = "/assets/imgs/universi-me2.png"; -export const IMG_DCX_LOGO = "/assets/imgs/dcx-png 1.png"; +export const IMG_UNIVERSI_LOGO = "/assets/imgs/universi-me2.png"; +export const IMG_DCX_LOGO = "/assets/imgs/dcx-png 1.png"; +export const IMG_DEFAULT_PROFILE = "/assets/imgs/default_avatar.png"; export const ICON_CHEVRON_DOWN = "/assets/icons/chevron-down-1.svg"; export const ICON_CHEVRON_UP_BLACK = "/assets/icons/chevron-up-black.svg"; From d2bb7a312c2cdc25418fd422389c06bd6d8cae2c Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Wed, 29 Nov 2023 09:53:19 -0300 Subject: [PATCH 05/24] change: creationDate as Date instead of string --- src/types/Profile.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/types/Profile.ts b/src/types/Profile.ts index ec8b5e7a..5103f3ed 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -62,6 +62,13 @@ export class ProfileClass implements Profile { return import.meta.env.VITE_UNIVERSIME_API + "/profile/image/" + this.id; } + /** + * Created date as `Date` instead of string; + */ + get createdAt() { + return new Date(this.creationDate); + } + /** * Separates a full name into a first name and a last name. * From cd22433c28e15aaa6ddfe3c046ab855769a3f46e Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 21:26:11 -0300 Subject: [PATCH 06/24] change: GENDER_OPTIONS instead of GenderOptions --- src/types/Profile.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/Profile.ts b/src/types/Profile.ts index 5103f3ed..9a55311e 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -3,7 +3,7 @@ import { type Nullable } from "@/types/utils"; import { IMG_DEFAULT_PROFILE } from "@/utils/assets"; export type Gender = "M" | "F" | "O"; -export const GenderOptions: {[k in Gender]: string} = { +export const GENDER_OPTIONS: {[k in Gender]: string} = { M: "Masculino", F: "Feminino", O: "Outro", @@ -46,7 +46,7 @@ export class ProfileClass implements Profile { */ get genderName() { if (this.gender) - return GenderOptions[this.gender]; + return GENDER_OPTIONS[this.gender]; // todo: use a constant with this value return "Não informado"; From 4b8479f8ede9e06baf18a7d6d033b40e1714d03e Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 21:29:12 -0300 Subject: [PATCH 07/24] change: use new GENDER_OPTIONS --- src/pages/ManageProfile/loader.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/ManageProfile/loader.ts b/src/pages/ManageProfile/loader.ts index 5a36ada4..a5179a8d 100644 --- a/src/pages/ManageProfile/loader.ts +++ b/src/pages/ManageProfile/loader.ts @@ -1,6 +1,5 @@ import UniversimeApi from "@/services/UniversimeApi"; -import { GENDER_OPTIONS } from "@/utils/profileUtils"; -import { Gender, Profile } from "@/types/Profile"; +import { type Gender, type Profile, GENDER_OPTIONS } from "@/types/Profile"; import { Link, TypeLink, TypeLinkToLabel } from "@/types/Link"; export type ManageProfileLoaderResponse = { From 9a12b5aa31f27af5473fa07cb4ddc9c287ef6f36 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 21:51:11 -0300 Subject: [PATCH 08/24] change: use ProfileClass on RolesPage --- .../ProfileSettings/ProfileSettings.tsx | 3 +- src/pages/Settings/RolesPage/RolesPage.tsx | 40 ++++++++++--------- src/types/Profile.ts | 7 ++++ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/pages/Profile/ProfileSettings/ProfileSettings.tsx b/src/pages/Profile/ProfileSettings/ProfileSettings.tsx index 76bbd3b5..e2423dd4 100644 --- a/src/pages/Profile/ProfileSettings/ProfileSettings.tsx +++ b/src/pages/Profile/ProfileSettings/ProfileSettings.tsx @@ -1,7 +1,8 @@ import { ChangeEvent, MouseEvent, useContext, useMemo, useState } from 'react'; import { ProfileContext } from '@/pages/Profile'; import { Link, TypeLink, TypeLinkToBootstrapIcon, TypeLinkToLabel } from '@/types/Link'; -import { getFullName, separateFullName, GENDER_OPTIONS } from '@/utils/profileUtils'; +import { getFullName, separateFullName } from '@/utils/profileUtils'; +import { GENDER_OPTIONS } from "@/types/Profile"; import { UniversimeApi } from '@/services/UniversimeApi'; import './ProfileSettings.css' diff --git a/src/pages/Settings/RolesPage/RolesPage.tsx b/src/pages/Settings/RolesPage/RolesPage.tsx index 3991ce8f..d6c7e47c 100644 --- a/src/pages/Settings/RolesPage/RolesPage.tsx +++ b/src/pages/Settings/RolesPage/RolesPage.tsx @@ -7,11 +7,10 @@ import { AuthContext } from "@/contexts/Auth"; import { SettingsTitle, type RolesPageLoaderResponse, RolesPageFetch, SettingsDescription } from "@/pages/Settings"; import { ProfileImage } from "@/components/ProfileImage/ProfileImage"; import { setStateAsValue } from "@/utils/tsxUtils"; -import { getFullName, getProfileImageUrl } from "@/utils/profileUtils"; import { type OptionInMenu, renderOption } from "@/utils/dropdownMenuUtils"; import * as SwalUtils from "@/utils/sweetalertUtils"; -import { type Profile } from "@/types/Profile"; +import { ProfileClass, type Profile } from "@/types/Profile"; import { UserAccessLevelLabel, type UserAccessLevel, compareAccessLevel } from "@/types/User"; import { type Optional } from "@/types/utils"; import "./RolesPage.less"; @@ -21,7 +20,7 @@ export function RolesPage() { const navigate = useNavigate(); const auth = useContext(AuthContext); - const [participants, participantsDispatch] = useReducer(participantsReducer, data.success ? data.participants : undefined); + const [participants, participantsDispatch] = useReducer(participantsReducer, data.success ? data.participants.map(ProfileClass.new) : undefined); const [filter, setFilter] = useState(""); if (!participants) { @@ -42,7 +41,7 @@ export function RolesPage() { return compareAccessLevel(a.user.accessLevel!, b.user.accessLevel!); } - return getFullName(a).localeCompare(getFullName(b)); + return (a.fullname ?? "").localeCompare(b.fullname ?? ""); }); const CHANGE_ROLE_OPTIONS: OptionInMenu[] = Object.entries(UserAccessLevelLabel).map(([role, label]) => ({ @@ -71,9 +70,9 @@ export function RolesPage() { const isOwnProfile = auth.profile!.id === profile.id; return
- +
-

{getFullName(profile)}

+

{profile.fullname}

{profile.bio}

@@ -105,18 +104,11 @@ export function RolesPage() { if (p.id !== action.profileId) return p; - const unchanging = data.participants + const originalRole = data.participants .find(p => p.id === action.profileId)! - .user.accessLevel === action.setRole; - - return { - ...p, - changed: unchanging ? undefined : true, - user: { - ...p.user, - accessLevel: action.setRole, - } - } + .user.accessLevel!; + + return new ProfileOnList(p, originalRole, action.setRole); }); } @@ -124,7 +116,7 @@ export function RolesPage() { const response = await RolesPageFetch(auth.organization!.id); participantsDispatch({ type: "SET_ALL", - setParticipants: response.success ? response.participants : undefined, + setParticipants: response.success ? response.participants.map(ProfileClass.new) : undefined, }); } @@ -154,7 +146,17 @@ export function RolesPage() { } } -type ProfileOnList = Profile & { changed?: true }; +class ProfileOnList extends ProfileClass { + public changed?: true; + + constructor(profile: Profile, originalRole: UserAccessLevel, newRole: UserAccessLevel) { + super(profile); + this.user.accessLevel = newRole; + this.changed = originalRole === newRole + ? true + : undefined; + } +} type ParticipantsReducerAction = { type: "SET_ROLE"; diff --git a/src/types/Profile.ts b/src/types/Profile.ts index 9a55311e..21dddedf 100644 --- a/src/types/Profile.ts +++ b/src/types/Profile.ts @@ -93,6 +93,13 @@ export class ProfileClass implements Profile { ]; } + /** + * The same as `new ProfileClass(profile)`, but can be used as a callback function. + */ + public static new(profile: Profile) { + return new ProfileClass(profile); + } + /* Profile type getters and setters */ get id() { return this.profile.id } set id(id: string) { this.profile.id = id } From 54794dc38f13c9da79e729b83139f2350b31ece7 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 21:56:55 -0300 Subject: [PATCH 09/24] change: use ProfileClass on ProfilePage --- src/pages/Profile/ProfileContext.ts | 4 ++-- src/pages/Profile/ProfilePage.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/Profile/ProfileContext.ts b/src/pages/Profile/ProfileContext.ts index 4162d896..0bfafc92 100644 --- a/src/pages/Profile/ProfileContext.ts +++ b/src/pages/Profile/ProfileContext.ts @@ -1,5 +1,5 @@ import { createContext } from "react" -import type { Profile } from "@/types/Profile"; +import type { ProfileClass } from "@/types/Profile"; import type { Group } from "@/types/Group"; import type { Competence, CompetenceType, Level } from "@/types/Competence"; import type { Recommendation } from "@/types/Recommendation"; @@ -10,7 +10,7 @@ import type { Folder } from "@/types/Capacity"; export type ProfileContextType = null | { accessingLoggedUser: boolean; - profile: Profile; + profile: ProfileClass; editCompetence: Competence | null; allCompetenceTypes: CompetenceType[]; diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx index 94d2549d..6d71684a 100644 --- a/src/pages/Profile/ProfilePage.tsx +++ b/src/pages/Profile/ProfilePage.tsx @@ -12,6 +12,7 @@ import * as SwalUtils from "@/utils/sweetalertUtils"; import { AuthContext } from "@/contexts/Auth"; import { SelectionBar } from "./SelectionBar/SelectionBar"; +import { ProfileClass } from "@/types/Profile"; import './Profile.css'; export function ProfilePage() { @@ -27,7 +28,7 @@ export function ProfilePage() { accessingLoggedUser: loaderData.accessingLoggedUser, allCompetenceTypes: loaderData.allCompetenceTypes, editCompetence: null, - profile: loaderData.profile!, + profile: new ProfileClass(loaderData.profile!), profileListData: { achievements: loaderData.profileListData.achievements, competences: loaderData.profileListData.competences, From 451b7b30ed130b18707ca3c7d75e05b26660c095 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:07:32 -0300 Subject: [PATCH 10/24] change: ProfileClass on ProfileSettings --- src/pages/Profile/ProfileSettings/ProfileSettings.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/Profile/ProfileSettings/ProfileSettings.tsx b/src/pages/Profile/ProfileSettings/ProfileSettings.tsx index e2423dd4..94ff9f77 100644 --- a/src/pages/Profile/ProfileSettings/ProfileSettings.tsx +++ b/src/pages/Profile/ProfileSettings/ProfileSettings.tsx @@ -1,8 +1,7 @@ import { ChangeEvent, MouseEvent, useContext, useMemo, useState } from 'react'; import { ProfileContext } from '@/pages/Profile'; import { Link, TypeLink, TypeLinkToBootstrapIcon, TypeLinkToLabel } from '@/types/Link'; -import { getFullName, separateFullName } from '@/utils/profileUtils'; -import { GENDER_OPTIONS } from "@/types/Profile"; +import { GENDER_OPTIONS, ProfileClass } from "@/types/Profile"; import { UniversimeApi } from '@/services/UniversimeApi'; import './ProfileSettings.css' @@ -32,7 +31,7 @@ export function ProfileSettings(props: ProfileSettingsProps) {

Nome

- +
@@ -184,7 +183,7 @@ export function ProfileSettings(props: ProfileSettingsProps) { ? (genderElement as HTMLSelectElement).value : ''; - const [name, lastname] = separateFullName(fullname); + const [name, lastname] = ProfileClass.separateFullname(fullname); UniversimeApi.Profile.edit({ profileId: profileContext.profile.id, From 1cb7aef7bd9016fb95486641383bbd475ddc7ab6 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:09:06 -0300 Subject: [PATCH 11/24] change: use ProfileClass on ProfileLastRecommendations --- .../ProfileLastRecommendations.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/pages/Profile/ProfileLastRecommendations/ProfileLastRecommendations.tsx b/src/pages/Profile/ProfileLastRecommendations/ProfileLastRecommendations.tsx index d4d8818e..b8fece31 100644 --- a/src/pages/Profile/ProfileLastRecommendations/ProfileLastRecommendations.tsx +++ b/src/pages/Profile/ProfileLastRecommendations/ProfileLastRecommendations.tsx @@ -1,9 +1,9 @@ import { useContext } from 'react'; import { Link } from 'react-router-dom'; import { ProfileContext } from '@/pages/Profile'; -import { getFullName, getProfileImageUrl } from '@/utils/profileUtils'; import { ProfileImage } from '@/components/ProfileImage/ProfileImage'; import './ProfileLastRecommendations.css' +import { ProfileClass } from '@/types/Profile'; const MAX_RECOMMENDATIONS_QUANTITY = 3; @@ -12,13 +12,15 @@ export function ProfileLastRecommendations() { if (profileContext === null) return null; + const recommendationsReceived = profileContext.profileListData.recommendationsReceived.map(r => ({ ...r, origin: new ProfileClass(r.origin), destiny: new ProfileClass(r.destiny) })); + return (

Últimas Recomendações

- { profileContext.profileListData.recommendationsReceived.length > 0 ? + { recommendationsReceived.length > 0 ?
{ - profileContext.profileListData.recommendationsReceived.map((recommendation, i) => { + recommendationsReceived.map((recommendation, i) => { if (i >= MAX_RECOMMENDATIONS_QUANTITY) return null; @@ -26,11 +28,11 @@ export function ProfileLastRecommendations() { return (
- +
- {getFullName(recommendation.origin)} + {recommendation.origin.fullname}

Recomendou pela competência:

{recommendation.competenceType.name}

From e6a96fe181a952de7a991c55b072fea4d9a4a6fd Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:11:20 -0300 Subject: [PATCH 12/24] change: use ProfileClass on ProfileBio --- src/components/ProfileInfo/ProfileBio/ProfileBio.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/ProfileInfo/ProfileBio/ProfileBio.tsx b/src/components/ProfileInfo/ProfileBio/ProfileBio.tsx index 7607ea51..28ff716d 100644 --- a/src/components/ProfileInfo/ProfileBio/ProfileBio.tsx +++ b/src/components/ProfileInfo/ProfileBio/ProfileBio.tsx @@ -1,11 +1,10 @@ import { Link } from 'react-router-dom'; import { ProfileImage } from '@/components/ProfileImage/ProfileImage'; -import { getFullName, getProfileImageUrl } from '@/utils/profileUtils'; import { ICON_EDIT_WHITE } from '@/utils/assets'; import { groupBannerUrl } from '@/utils/apiUtils'; -import type { Profile } from '@/types/Profile'; +import { type Profile, ProfileClass } from '@/types/Profile'; import { TypeLinkToBootstrapIcon, type Link as Link_API } from '@/types/Link'; import type { Group } from '@/types/Group'; import './ProfileBio.less'; @@ -26,6 +25,8 @@ export function ProfileBio(props: ProfileBioProps) { ? { backgroundImage: `url(${groupBannerUrl(props.organization)})` } : { backgroundColor: "var(--primary-color)" } + const profile = new ProfileClass(props.profile); + return (
@@ -42,11 +43,11 @@ export function ProfileBio(props: ProfileBioProps) {
- + { isOnOwnProfile - ?

{ getFullName(props.profile) }

- : { getFullName(props.profile) } + ?

{ profile.fullname }

+ : { profile.fullname } } { props.profile.bio === null || props.profile.bio.length === 0 From 949a8ab3a601d2b41be676e43d36afddcade8ccd Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:13:37 -0300 Subject: [PATCH 13/24] change: use ProfileClass on GroupContext --- src/pages/Group/Group.tsx | 5 +++-- src/pages/Group/GroupContext.tsx | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/Group/Group.tsx b/src/pages/Group/Group.tsx index f1fdff8d..9075aeb6 100644 --- a/src/pages/Group/Group.tsx +++ b/src/pages/Group/Group.tsx @@ -5,6 +5,7 @@ import { GroupContext, GroupIntro, GroupTabRenderer, GroupTabs, fetchGroupPageDa import { ProfileInfo } from "@/components/ProfileInfo/ProfileInfo"; import { AuthContext } from "@/contexts/Auth"; import "./Group.less"; +import { ProfileClass } from "@/types/Profile"; export function GroupPage() { const page = useLoaderData() as GroupPageLoaderResponse; @@ -72,11 +73,11 @@ export function GroupPage() { group: data.group!, loggedData: { isParticipant: data.loggedData?.isParticipant!, - profile: data.loggedData?.profile!, + profile: new ProfileClass(data.loggedData?.profile!), links: data.loggedData?.links ?? [], groups: data.loggedData?.groups ?? [], }, - participants: data.participants, + participants: data.participants.map(ProfileClass.new), subgroups: data.subGroups, currentContent: undefined, diff --git a/src/pages/Group/GroupContext.tsx b/src/pages/Group/GroupContext.tsx index 61fbe989..0d5730b8 100644 --- a/src/pages/Group/GroupContext.tsx +++ b/src/pages/Group/GroupContext.tsx @@ -1,13 +1,13 @@ import { createContext } from "react"; import { Group } from "@/types/Group"; -import { Profile } from "@/types/Profile"; +import { type ProfileClass } from "@/types/Profile"; import type { Content, Folder } from "@/types/Capacity"; import { Link } from "@/types/Link"; export type GroupContextType = null | { group: Group; subgroups: Group[]; - participants: Profile[]; + participants: ProfileClass[]; folders: Folder[]; currentContent: Folder | undefined; @@ -33,7 +33,7 @@ export type GroupContextType = null | { loggedData: { isParticipant: boolean; - profile: Profile; + profile: ProfileClass; links: Link[]; groups: Group[]; }; From 0190028377f64808ac6561a7154da18f8869724c Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:15:10 -0300 Subject: [PATCH 14/24] change: use ProfileClass on GroupPeople --- src/pages/Group/GroupTabs/GroupPeople/GroupPeople.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/Group/GroupTabs/GroupPeople/GroupPeople.tsx b/src/pages/Group/GroupTabs/GroupPeople/GroupPeople.tsx index c69aac12..b68ad1e9 100644 --- a/src/pages/Group/GroupTabs/GroupPeople/GroupPeople.tsx +++ b/src/pages/Group/GroupTabs/GroupPeople/GroupPeople.tsx @@ -3,9 +3,8 @@ import { Link } from "react-router-dom"; import { EMPTY_LIST_CLASS, GroupContext } from "@/pages/Group"; import { setStateAsValue } from "@/utils/tsxUtils"; -import { Profile } from "@/types/Profile"; +import { ProfileClass } from "@/types/Profile"; import { ProfileImage } from "@/components/ProfileImage/ProfileImage"; -import { getFullName } from "@/utils/profileUtils"; import "./GroupPeople.less"; import { Filter } from "@/components/Filter/Filter"; @@ -33,7 +32,7 @@ export function GroupPeople() { ); } -function makePeopleList(people: Profile[], filter: string) { +function makePeopleList(people: ProfileClass[], filter: string) { if (people.length === 0) { return

Esse grupo não possui participantes.

} @@ -41,7 +40,7 @@ function makePeopleList(people: Profile[], filter: string) { const lowercaseFilter = filter.toLowerCase(); const filteredPeople = filter.length === 0 ? people - : people.filter(p => (getFullName(p)).toLowerCase().includes(lowercaseFilter)); + : people.filter(p => (p.fullname ?? "").toLowerCase().includes(lowercaseFilter)); if (filteredPeople.length === 0) { return

Nenhum participante encontrado com a pesquisa.

@@ -52,7 +51,7 @@ function makePeopleList(people: Profile[], filter: string) { .map(renderPerson); } -function renderPerson(person: Profile) { +function renderPerson(person: ProfileClass) { const linkToProfile = `/profile/${person.user.name}`; const imageUrl = person.image?.startsWith("/") @@ -66,7 +65,7 @@ function renderPerson(person: Profile) {
- {getFullName(person)} + {person.fullname}

{person.bio}

From 0c60c4cc25bbb4a4ec2e6242beeca02b7107681e Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:20:09 -0300 Subject: [PATCH 15/24] change: use ProfileClass on ManageProfile --- src/pages/ManageProfile/ManageProfile.tsx | 3 +-- src/pages/ManageProfile/loader.ts | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/ManageProfile/ManageProfile.tsx b/src/pages/ManageProfile/ManageProfile.tsx index b059d938..b645f765 100644 --- a/src/pages/ManageProfile/ManageProfile.tsx +++ b/src/pages/ManageProfile/ManageProfile.tsx @@ -4,7 +4,6 @@ import { Navigate, useLoaderData, useNavigate } from "react-router-dom"; import UniversimeApi from "@/services/UniversimeApi"; import { ManageProfileLinks, ManageProfileLoaderResponse, ManageProfilePassword, ManageProfileImage, getManageLinks, getProfileImage } from "@/pages/ManageProfile"; import { setStateAsValue } from "@/utils/tsxUtils"; -import { getProfileImageUrl } from "@/utils/profileUtils"; import { AuthContext } from "@/contexts/Auth"; import * as SwalUtils from "@/utils/sweetalertUtils"; @@ -38,7 +37,7 @@ export function ManageProfilePage() {
- +
Altere seu nome diff --git a/src/pages/ManageProfile/loader.ts b/src/pages/ManageProfile/loader.ts index a5179a8d..25374cde 100644 --- a/src/pages/ManageProfile/loader.ts +++ b/src/pages/ManageProfile/loader.ts @@ -1,9 +1,9 @@ import UniversimeApi from "@/services/UniversimeApi"; -import { type Gender, type Profile, GENDER_OPTIONS } from "@/types/Profile"; +import { type Gender, ProfileClass, GENDER_OPTIONS } from "@/types/Profile"; import { Link, TypeLink, TypeLinkToLabel } from "@/types/Link"; export type ManageProfileLoaderResponse = { - profile: Profile | null; + profile: ProfileClass | null; links: Link[]; genderOptions: { @@ -45,7 +45,7 @@ export async function ManageProfileLoader(): Promise Date: Fri, 1 Dec 2023 22:20:45 -0300 Subject: [PATCH 16/24] change: use ProfileClass on AuthContext --- .../components/WelcomeUser/WelcomeUser.tsx | 3 +-- src/contexts/Auth/AuthContext.tsx | 10 +++++----- src/contexts/Auth/AuthProvider.tsx | 10 +++++++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/UniversiHeader/components/WelcomeUser/WelcomeUser.tsx b/src/components/UniversiHeader/components/WelcomeUser/WelcomeUser.tsx index 49f7c145..93dc7485 100644 --- a/src/components/UniversiHeader/components/WelcomeUser/WelcomeUser.tsx +++ b/src/components/UniversiHeader/components/WelcomeUser/WelcomeUser.tsx @@ -3,7 +3,6 @@ import { Link, redirect, useNavigate } from "react-router-dom"; import { ProfileImage } from "@/components/ProfileImage/ProfileImage"; import { AuthContext } from "@/contexts/Auth"; import "./WelcomeUser.less" -import { getProfileImageUrl } from "@/utils/profileUtils"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu" export function WelcomeUser() { @@ -20,7 +19,7 @@ export function WelcomeUser() { return( !isLogged ? null :
- {setProfileClicked(!profileClicked)}}/> + {setProfileClicked(!profileClicked)}}/> { profileClicked ? diff --git a/src/contexts/Auth/AuthContext.tsx b/src/contexts/Auth/AuthContext.tsx index 3089fcbf..762db9fa 100644 --- a/src/contexts/Auth/AuthContext.tsx +++ b/src/contexts/Auth/AuthContext.tsx @@ -1,18 +1,18 @@ import { createContext } from "react"; import { User } from "@/types/User"; -import { Profile } from "@/types/Profile"; +import { type ProfileClass } from "@/types/Profile"; import type { Group } from "@/types/Group"; export type AuthContextType = { user : User | null; - profile: Profile | null; + profile: ProfileClass | null; organization: Group | null; - signin: (email : string, password: string, recaptchaToken: string | null) => Promise; - signinGoogle: () => Promise; + signin: (email : string, password: string, recaptchaToken: string | null) => Promise; + signinGoogle: () => Promise; signout: () => Promise; - updateLoggedUser: () => Promise; + updateLoggedUser: () => Promise; } export const AuthContext = createContext(null!); diff --git a/src/contexts/Auth/AuthProvider.tsx b/src/contexts/Auth/AuthProvider.tsx index f2442bf4..926f7b0c 100644 --- a/src/contexts/Auth/AuthProvider.tsx +++ b/src/contexts/Auth/AuthProvider.tsx @@ -1,12 +1,12 @@ import { ReactNode, useEffect, useState } from "react"; import { AuthContext } from "./AuthContext"; -import { Profile } from "@/types/Profile"; +import { ProfileClass } from "@/types/Profile"; import { UniversimeApi } from "@/services/UniversimeApi"; import { goTo } from "@/services/routes"; import type { Group } from "@/types/Group"; export const AuthProvider = ({ children }: { children: ReactNode }) => { - const [profile, setProfile] = useState(null); + const [profile, setProfile] = useState(null); const [organization, setOrganization] = useState(null); const [finishedLogin, setFinishedLogin] = useState(false); const user = profile?.user ?? null; @@ -90,5 +90,9 @@ async function getLoggedProfile() { if(!await UniversimeApi.Auth.validateToken()) { return null; } - return (await UniversimeApi.Profile.profile()).body?.profile ?? null; + + const responseProfile = (await UniversimeApi.Profile.profile()).body?.profile; + return responseProfile + ? new ProfileClass(responseProfile) + : null; } From 4aebbf991ea0d3a2aae432d178e27f299917f917 Mon Sep 17 00:00:00 2001 From: Douglas Sebastian Date: Fri, 1 Dec 2023 22:22:03 -0300 Subject: [PATCH 17/24] change: delete profileUtils.ts --- src/utils/profileUtils.ts | 40 --------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 src/utils/profileUtils.ts diff --git a/src/utils/profileUtils.ts b/src/utils/profileUtils.ts deleted file mode 100644 index 6c792698..00000000 --- a/src/utils/profileUtils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Profile, Gender } from "@/types/Profile" - -export const GENDER_OPTIONS = { - "M": "Masculino", - "F": "Feminino", - "O": "Outro", -} - -export function getFullName(profile: Profile): string { - const first = profile.firstname ?? ""; - const last = profile.lastname ?? ""; - - return `${first}${first != "" ? " " : ""}${last}` -} - -export function separateFullName(fullname: string): [string, string] { - const names = fullname.split(' '); - - if (names.length <= 0) - return ['', '']; - - return [ - names[0], - names.slice(1).join(' ') - ] -} - -export function getGenderName(gender: Gender | null | undefined): string { - return gender ? GENDER_OPTIONS[gender] : 'Não informado'; -} - -export function getProfileImageUrl(profile: Profile): string | null { - if (!profile.image) { - return "/assets/imgs/default_avatar.png"; - } - - return profile.image.startsWith("/") - ? `${import.meta.env.VITE_UNIVERSIME_API}${profile.image}` - : profile.image; -} From bde381384596a0bea3f9b27e13aa33e1db3cfd76 Mon Sep 17 00:00:00 2001 From: kassioL2L Date: Wed, 6 Dec 2023 20:13:05 -0300 Subject: [PATCH 18/24] parte inicial --- .../Group/GroupTabs/GroupFeed/GroupFeed.less | 0 .../Group/GroupTabs/GroupFeed/GroupFeed.tsx | 0 src/pages/Group/GroupTabs/GroupTabs.tsx | 9 +++- src/pages/Group/GroupTabs/index.ts | 1 + src/services/UniversimeApi/Feed.ts | 41 +++++++++++++++++++ src/types/Feed.ts | 0 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less create mode 100644 src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx create mode 100644 src/services/UniversimeApi/Feed.ts create mode 100644 src/types/Feed.ts diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/Group/GroupTabs/GroupTabs.tsx b/src/pages/Group/GroupTabs/GroupTabs.tsx index c13ecdc5..dfd838b8 100644 --- a/src/pages/Group/GroupTabs/GroupTabs.tsx +++ b/src/pages/Group/GroupTabs/GroupTabs.tsx @@ -1,11 +1,11 @@ import { useContext, type ReactElement, useState, useEffect } from "react"; -import { GroupContents, GroupContext, GroupGroups, GroupPeople } from "@/pages/Group"; +import { GroupContents, GroupContext, GroupGroups, GroupPeople, GroupFeed} from "@/pages/Group"; import "./GroupTabs.less"; import UniversimeApi from "@/services/UniversimeApi"; import { AuthContext } from "@/contexts/Auth"; import { GroupSubmenu } from "../GroupSubmenu/GroupSubmenu"; -export type AvailableTabs = "contents" | "files" | "groups" | "people"; +export type AvailableTabs = "feed" | "contents" | "files" | "groups" | "people"; export type GroupTabDefinition = { name: string, @@ -89,6 +89,11 @@ export function GroupTabRenderer({tab}: { tab: AvailableTabs }) { export const EMPTY_LIST_CLASS = "empty-text"; const TABS: GroupTabDefinition[] = [ + { + name: 'Feed', + value: 'feed', + renderer: GroupFeed, + }, { name: 'Conteúdos', value: "contents", diff --git a/src/pages/Group/GroupTabs/index.ts b/src/pages/Group/GroupTabs/index.ts index f5133b2b..4117c82c 100644 --- a/src/pages/Group/GroupTabs/index.ts +++ b/src/pages/Group/GroupTabs/index.ts @@ -3,3 +3,4 @@ export * from "./GroupTabs"; export * from "./GroupContents"; export * from "./GroupPeople/GroupPeople"; export * from "./GroupGroups/GroupGroups"; +export * from "./GroupFeed/GroupFeed"; diff --git a/src/services/UniversimeApi/Feed.ts b/src/services/UniversimeApi/Feed.ts new file mode 100644 index 00000000..5fe51cb3 --- /dev/null +++ b/src/services/UniversimeApi/Feed.ts @@ -0,0 +1,41 @@ +import { ApiResponse } from "@/types/UniversimeApi"; +import { api } from "./api"; +import {GroupPost} from "@/types/Feed" + +export type CreateGroupPostRequestDTO = { + content: string; +}; + + +export type CreateGroupPostResponseDTO = ApiResponse; +export type GetGroupPostsResponseDTO = ApiResponse; + +export async function getGroupPosts(groupId: string): Promise { + try { + const response = await api.get(`/api/feed/groups/${groupId}/posts`); + return response.data; + } catch (error) { + console.error("Erro ao obter posts do grupo:", error); + throw error; + } +} + +export async function createGroupPost(groupId: string, body: CreateGroupPostRequestDTO): Promise { + try { + const response = await api.post(`/api/feed/groups/${groupId}/posts`, body); + return response.data; + } catch (error) { + console.error("Erro ao criar post no grupo:", error); + throw error; + } +} + +export async function deleteGroupPost(groupId: string, postId: string): Promise> { + try { + const response = await api.delete>(`/api/feed/groups/${groupId}/posts/${postId}`); + return response.data; + } catch (error) { + console.error("Erro ao excluir post do grupo:", error); + throw error; + } +} diff --git a/src/types/Feed.ts b/src/types/Feed.ts new file mode 100644 index 00000000..e69de29b From 6294ca09577e7629f182a849efb64abf135b0351 Mon Sep 17 00:00:00 2001 From: 710lucas Date: Fri, 8 Dec 2023 17:24:54 -0300 Subject: [PATCH 19/24] =?UTF-8?q?Requis=C3=A7=C3=B5es=20de=20Feed=20Carreg?= =?UTF-8?q?amento=20de=20p=C3=A1gina?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Group/Group.tsx | 6 ++ src/pages/Group/GroupContext.tsx | 11 ++ .../Group/GroupTabs/GroupFeed/GroupFeed.tsx | 102 ++++++++++++++++++ src/pages/Group/GroupTabs/GroupTabs.tsx | 12 +-- src/pages/Group/loader.ts | 9 +- src/services/UniversimeApi/Feed.ts | 24 ++--- src/services/UniversimeApi/index.ts | 2 + src/types/Feed.ts | 5 + 8 files changed, 150 insertions(+), 21 deletions(-) diff --git a/src/pages/Group/Group.tsx b/src/pages/Group/Group.tsx index 13809cb3..4218acd5 100644 --- a/src/pages/Group/Group.tsx +++ b/src/pages/Group/Group.tsx @@ -69,6 +69,7 @@ export function GroupPage() { return { folders: data.folders, + posts: data.posts, group: data.group!, loggedData: { isParticipant: data.loggedData?.isParticipant!, @@ -99,6 +100,11 @@ export function GroupPage() { setContext({...this, editGroup: c}); }, + editPost: undefined, + setEditPost(c){ + setContext({...this, editPost: c}) + }, + refreshData: refreshGroupData, }; } diff --git a/src/pages/Group/GroupContext.tsx b/src/pages/Group/GroupContext.tsx index 55847c28..8379fdf7 100644 --- a/src/pages/Group/GroupContext.tsx +++ b/src/pages/Group/GroupContext.tsx @@ -3,12 +3,14 @@ import { Group } from "@/types/Group"; import { Profile } from "@/types/Profile"; import type { Content, Folder } from "@/types/Capacity"; import { Link } from "@/types/Link"; +import { GroupPost } from "@/types/Feed"; export type GroupContextType = null | { group: Group; subgroups: Group[]; participants: Profile[]; folders: Folder[]; + posts: GroupPost[]; currentContent: Folder | undefined; setCurrentContent(content: Folder | undefined): any; @@ -40,6 +42,15 @@ export type GroupContextType = null | { editGroup: Group | null | undefined; setEditGroup(group: Group | null | undefined) : any; + /** + * The post being edited/created. + * + * If `null`, should handle creation of a post. If has a value, should handle + * post edit. If `undefined`, no post is being edited nor created. + */ + editPost: GroupPost | null | undefined; + setEditPost(post: GroupPost | null | undefined) : any; + loggedData: { isParticipant: boolean; profile: Profile; diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx index e69de29b..4f8e5c08 100644 --- a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx +++ b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx @@ -0,0 +1,102 @@ +import { ActionButton } from "@/components/ActionButton/ActionButton"; +import { Filter } from "@/components/Filter/Filter"; +import { FormInputs, UniversiForm } from "@/components/UniversiForm/UniversiForm"; +import { RequiredValidation } from "@/components/UniversiForm/Validation/RequiredValidation"; +import { TextValidation } from "@/components/UniversiForm/Validation/TextValidation"; +import { ValidationComposite } from "@/components/UniversiForm/Validation/ValidationComposite"; +import UniversimeApi from "@/services/UniversimeApi"; +import { GroupPost } from "@/types/Feed"; +import { useContext, useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { GroupContext } from "../../GroupContext"; + +export function GroupFeed(){ + + const [filterPosts, setFilterPosts] = useState(""); + const groupContext = useContext(GroupContext); + + console.log(groupContext) + + if(groupContext == null) + return <> + + return( +
+
+
+ + { + + } +
+
+ +
+ { + groupContext.posts.map(renderPost) + } +
+ { + groupContext.editPost !== undefined ? + + ().addValidation(new RequiredValidation()).addValidation(new TextValidation()) + }, { + DTOName: "content", label: "Mensagem do post", type: FormInputs.LONG_TEXT, validation: new ValidationComposite().addValidation(new RequiredValidation()).addValidation(new TextValidation()) + } + ]} + requisition={groupContext.editPost ? UniversimeApi.Feed.createGroupPost : UniversimeApi.Feed.createGroupPost} + callback={()=>{groupContext.setEditGroup(undefined); groupContext.refreshData()}} + /> + : + <> + } +
+ ) + + // function makePostList(){ + // if(groupContext?.group == undefined) + // return <> + // UniversimeApi.Feed.getGroupPosts({groupId : groupContext?.group.id}) + // .then((response)=>{ + // if(response.success){ + // setGroupPosts(response.body); + // } + // }) + + // return renderPost({content: "Testando uma postagem de um post no universi.me, teoricamente isso seria uma request da API", + // title: "Teste de post", + // author: groupContext.loggedData.profile}) + // } + + function renderPosts(posts: GroupPost[]){ + return(posts.map(renderPost)) + } + + function renderPost(post : GroupPost){ + // const linkToProfile = `/profile/${post.author.user}` + console.log("renderPost") + + if(filterPosts != "" && + !post.content.toLowerCase().includes(filterPosts.toLowerCase())) + return <> + + + return( +
+ + + + +
+

{post.content}

+
+
+ ) + } +} \ No newline at end of file diff --git a/src/pages/Group/GroupTabs/GroupTabs.tsx b/src/pages/Group/GroupTabs/GroupTabs.tsx index dfd838b8..7573506d 100644 --- a/src/pages/Group/GroupTabs/GroupTabs.tsx +++ b/src/pages/Group/GroupTabs/GroupTabs.tsx @@ -1,5 +1,5 @@ import { useContext, type ReactElement, useState, useEffect } from "react"; -import { GroupContents, GroupContext, GroupGroups, GroupPeople, GroupFeed} from "@/pages/Group"; +import { GroupContents, GroupContext, GroupGroups, GroupPeople,GroupFeed} from "@/pages/Group"; import "./GroupTabs.less"; import UniversimeApi from "@/services/UniversimeApi"; import { AuthContext } from "@/contexts/Auth"; @@ -89,11 +89,6 @@ export function GroupTabRenderer({tab}: { tab: AvailableTabs }) { export const EMPTY_LIST_CLASS = "empty-text"; const TABS: GroupTabDefinition[] = [ - { - name: 'Feed', - value: 'feed', - renderer: GroupFeed, - }, { name: 'Conteúdos', value: "contents", @@ -114,4 +109,9 @@ const TABS: GroupTabDefinition[] = [ value: "people", renderer: GroupPeople, }, + { + name: 'Feed', + value: 'feed', + renderer: GroupFeed, + }, ]; diff --git a/src/pages/Group/loader.ts b/src/pages/Group/loader.ts index 77fe6e8b..21863143 100644 --- a/src/pages/Group/loader.ts +++ b/src/pages/Group/loader.ts @@ -5,12 +5,14 @@ import type { Profile } from "@/types/Profile"; import type { Group } from "@/types/Group"; import type { Folder } from "@/types/Capacity"; import { Link } from "@/types/Link"; +import { GroupPost } from "@/types/Feed"; export type GroupPageLoaderResponse = { group: Group | undefined; subGroups: Group[]; participants: Profile[]; folders: Folder[]; + posts: GroupPost[]; loggedData: undefined | { profile: Profile; @@ -32,12 +34,13 @@ export async function fetchGroupPageData(props: {groupPath: string | undefined}) const group = groupRes.body.group; const profile = profileRes.body.profile; - const [subgroupsRes, participantsRes, foldersRes, profileGroupsRes, profileLinksRes] = await Promise.all([ + const [subgroupsRes, participantsRes, foldersRes, profileGroupsRes, profileLinksRes, groupPostsRes] = await Promise.all([ UniversimeApi.Group.subgroups({groupId: group.id}), UniversimeApi.Group.participants({groupId: group.id}), UniversimeApi.Group.folders({groupId: group.id}), UniversimeApi.Profile.groups({profileId: profile.id}), UniversimeApi.Profile.links({profileId: profile.id}), + UniversimeApi.Feed.getGroupPosts({groupId: group.id}), ]); return { @@ -45,6 +48,7 @@ export async function fetchGroupPageData(props: {groupPath: string | undefined}) folders: foldersRes.body?.folders ?? [], participants: participantsRes.body?.participants ?? [], subGroups: subgroupsRes.body?.subgroups ?? [], + posts: groupPostsRes.body?.posts ?? [], loggedData: { profile: profile, groups: profileGroupsRes.body?.groups ?? [], @@ -65,5 +69,6 @@ const FAILED_TO_LOAD: GroupPageLoaderResponse = { loggedData: undefined, folders: [], participants: [], - subGroups: [] + subGroups: [], + posts: [] }; diff --git a/src/services/UniversimeApi/Feed.ts b/src/services/UniversimeApi/Feed.ts index 5fe51cb3..0e5b5b5e 100644 --- a/src/services/UniversimeApi/Feed.ts +++ b/src/services/UniversimeApi/Feed.ts @@ -2,28 +2,26 @@ import { ApiResponse } from "@/types/UniversimeApi"; import { api } from "./api"; import {GroupPost} from "@/types/Feed" -export type CreateGroupPostRequestDTO = { +export type CreateGroupPost_RequestDTO = { content: string; + groupId : string }; +export type GetGroupPost_RequestDTO = { + groupId : string; +} + export type CreateGroupPostResponseDTO = ApiResponse; -export type GetGroupPostsResponseDTO = ApiResponse; +export type GetGroupPostsResponseDTO = ApiResponse<{posts: GroupPost[]}>; -export async function getGroupPosts(groupId: string): Promise { - try { - const response = await api.get(`/api/feed/groups/${groupId}/posts`); - return response.data; - } catch (error) { - console.error("Erro ao obter posts do grupo:", error); - throw error; - } +export async function getGroupPosts(body : GetGroupPost_RequestDTO): Promise { + return (await api.get(`/feed/groups/${body.groupId}/posts`)).data; } -export async function createGroupPost(groupId: string, body: CreateGroupPostRequestDTO): Promise { +export async function createGroupPost(body: CreateGroupPost_RequestDTO): Promise { try { - const response = await api.post(`/api/feed/groups/${groupId}/posts`, body); - return response.data; + return await (await api.post(`/feed/groups/${body.groupId}/posts`, body)).data; } catch (error) { console.error("Erro ao criar post no grupo:", error); throw error; diff --git a/src/services/UniversimeApi/index.ts b/src/services/UniversimeApi/index.ts index 2092176b..8457d364 100644 --- a/src/services/UniversimeApi/index.ts +++ b/src/services/UniversimeApi/index.ts @@ -9,6 +9,7 @@ import * as Capacity from "./Capacity" import * as User from "./User" import * as Image from "./Image" import * as Admin from "./Admin" +import * as Feed from "./Feed" export const UniversimeApi = { api, @@ -22,6 +23,7 @@ export const UniversimeApi = { User, Image, Admin, + Feed, }; export default UniversimeApi; diff --git a/src/types/Feed.ts b/src/types/Feed.ts index e69de29b..2ec6251e 100644 --- a/src/types/Feed.ts +++ b/src/types/Feed.ts @@ -0,0 +1,5 @@ +export type GroupPost = { + id: string; + groupId: string; + content : string; +} \ No newline at end of file From f23e631ed5f0f2420620b1e71dc38fa89de9700f Mon Sep 17 00:00:00 2001 From: 710lucas Date: Fri, 8 Dec 2023 19:08:54 -0300 Subject: [PATCH 20/24] =?UTF-8?q?Finaliza=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Group/Group.less | 5 +- src/pages/Group/Group.tsx | 4 +- .../Group/GroupTabs/GroupFeed/GroupFeed.less | 129 ++++++++++++++++++ .../Group/GroupTabs/GroupFeed/GroupFeed.tsx | 54 +++----- src/pages/Group/GroupTabs/GroupTabs.tsx | 10 +- src/services/UniversimeApi/Feed.ts | 3 +- src/types/Feed.ts | 4 + 7 files changed, 168 insertions(+), 41 deletions(-) diff --git a/src/pages/Group/Group.less b/src/pages/Group/Group.less index e2b1612a..c5927f97 100644 --- a/src/pages/Group/Group.less +++ b/src/pages/Group/Group.less @@ -12,7 +12,10 @@ max-width: 1920px; .tab-item{ - border: 1px solid var(--secondary-color); + img{ + border: none !important; + } + border: none !important; padding: 0.8rem; border-radius: var(--border-radius); filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); diff --git a/src/pages/Group/Group.tsx b/src/pages/Group/Group.tsx index 4218acd5..2682ffcd 100644 --- a/src/pages/Group/Group.tsx +++ b/src/pages/Group/Group.tsx @@ -9,12 +9,12 @@ import "./Group.less"; export function GroupPage() { const page = useLoaderData() as GroupPageLoaderResponse; const authContext = useContext(AuthContext); - const [currentTab, setCurrentTab] = useState("contents"); + const [currentTab, setCurrentTab] = useState("feed"); const [context, setContext] = useState(makeContext(page)); useEffect(() => { setContext(makeContext(page)); - setCurrentTab("contents"); + setCurrentTab("feed"); }, [page]); if (!page.loggedData || !page.group) { diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less index e69de29b..db1d248a 100644 --- a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less +++ b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less @@ -0,0 +1,129 @@ + +@import url(/src/layouts/colors.less); +@import url(/src/layouts/fonts.less); + +#group-page #feed { + .feed-list { + .feed-item { + display: flex; + flex-direction: row; + align-items: start; + + height: fit-content; + + width: 100%; + + &:not(:last-of-type) { + margin-bottom: 1.5rem; + } + + .feed-image { + @side: 8rem !important; + width: @side; + height: @side; + aspect-ratio: 1; + + border-radius: 100%; + object-fit: contain; + } + + .info { + margin-left: 3rem; + + color: @font-color-v2; + .group-name { + color: inherit; + text-decoration: none; + font-weight: @font-weight-semibold; + font-size: 1.5rem; + } + + .group-description { + margin-top: .75rem; + text-align: justify; + } + } + .group-options-button { + margin-right: 1rem; + margin-left: auto; + + display: flex; + padding-bottom: 3px; + align-items: center; + justify-content: center; + + border: none; + border-radius: 50%; + background-color: transparent; + + font-size: 1.5rem; + height: 2rem; + aspect-ratio: 1; + + will-change: background-color; + cursor: pointer; + + &:hover { + background-color: #C1C1C1; + } + } + + .group-options { + background-color: @card-background-color; + border-radius: .625rem; + padding: .25rem .5rem; + + min-width: 7rem; + + .group-options-arrow { + fill: @card-background-color; + } + + .dropdown-options-item { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + + padding: .25rem .5rem; + border-radius: .625rem; + + font-size: .9rem; + + cursor: pointer; + + &:hover { + background-color: @button-hover-color; + outline: none; + + &.delete { + background-color: @alert-color; + color: @font-color-v1; + } + } + + &[data-disabled] { + color: @font-disabled-color; + + &:hover { + background-color: transparent; + } + } + + &:not(:last-child) { + margin-bottom: .25rem; + } + + .right-slot { + width: fit-content; + margin-left: auto; + + &.bi { + font-size: 1.2em; + } + } + } + } + } + } +} diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx index 4f8e5c08..974b67bc 100644 --- a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx +++ b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx @@ -1,39 +1,45 @@ import { ActionButton } from "@/components/ActionButton/ActionButton"; import { Filter } from "@/components/Filter/Filter"; +import { ProfileImage } from "@/components/ProfileImage/ProfileImage"; import { FormInputs, UniversiForm } from "@/components/UniversiForm/UniversiForm"; import { RequiredValidation } from "@/components/UniversiForm/Validation/RequiredValidation"; import { TextValidation } from "@/components/UniversiForm/Validation/TextValidation"; import { ValidationComposite } from "@/components/UniversiForm/Validation/ValidationComposite"; import UniversimeApi from "@/services/UniversimeApi"; import { GroupPost } from "@/types/Feed"; +import { getProfileImageUrl } from "@/utils/profileUtils"; import { useContext, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { GroupContext } from "../../GroupContext"; +import "./GroupFeed.less"; export function GroupFeed(){ const [filterPosts, setFilterPosts] = useState(""); const groupContext = useContext(GroupContext); - console.log(groupContext) if(groupContext == null) return <> return( -
+
{ - + groupContext.participants.some(p => p.id == groupContext.loggedData.profile.id) + ? + + : + <> }
-
+
{ - groupContext.posts.map(renderPost) + groupContext.posts.slice().reverse().map(renderPost) }
{ @@ -45,13 +51,13 @@ export function GroupFeed(){ { DTOName: "groupId", label: "", type: FormInputs.HIDDEN, value: groupContext.group.id }, { - DTOName: "title", label: "Título do post", type: FormInputs.TEXT, validation: new ValidationComposite().addValidation(new RequiredValidation()).addValidation(new TextValidation()) + DTOName: "authorId", label: "", type: FormInputs.HIDDEN, value: groupContext.loggedData.profile.id }, { DTOName: "content", label: "Mensagem do post", type: FormInputs.LONG_TEXT, validation: new ValidationComposite().addValidation(new RequiredValidation()).addValidation(new TextValidation()) } ]} requisition={groupContext.editPost ? UniversimeApi.Feed.createGroupPost : UniversimeApi.Feed.createGroupPost} - callback={()=>{groupContext.setEditGroup(undefined); groupContext.refreshData()}} + callback={groupContext.refreshData} /> : <> @@ -59,28 +65,7 @@ export function GroupFeed(){
) - // function makePostList(){ - // if(groupContext?.group == undefined) - // return <> - // UniversimeApi.Feed.getGroupPosts({groupId : groupContext?.group.id}) - // .then((response)=>{ - // if(response.success){ - // setGroupPosts(response.body); - // } - // }) - - // return renderPost({content: "Testando uma postagem de um post no universi.me, teoricamente isso seria uma request da API", - // title: "Teste de post", - // author: groupContext.loggedData.profile}) - // } - - function renderPosts(posts: GroupPost[]){ - return(posts.map(renderPost)) - } - function renderPost(post : GroupPost){ - // const linkToProfile = `/profile/${post.author.user}` - console.log("renderPost") if(filterPosts != "" && !post.content.toLowerCase().includes(filterPosts.toLowerCase())) @@ -88,10 +73,15 @@ export function GroupFeed(){ return( -
- - - +
+ { + post.author + ? + + + + : <> + }

{post.content}

diff --git a/src/pages/Group/GroupTabs/GroupTabs.tsx b/src/pages/Group/GroupTabs/GroupTabs.tsx index 7573506d..dde93bb2 100644 --- a/src/pages/Group/GroupTabs/GroupTabs.tsx +++ b/src/pages/Group/GroupTabs/GroupTabs.tsx @@ -89,6 +89,11 @@ export function GroupTabRenderer({tab}: { tab: AvailableTabs }) { export const EMPTY_LIST_CLASS = "empty-text"; const TABS: GroupTabDefinition[] = [ + { + name: 'Feed', + value: 'feed', + renderer: GroupFeed, + }, { name: 'Conteúdos', value: "contents", @@ -109,9 +114,4 @@ const TABS: GroupTabDefinition[] = [ value: "people", renderer: GroupPeople, }, - { - name: 'Feed', - value: 'feed', - renderer: GroupFeed, - }, ]; diff --git a/src/services/UniversimeApi/Feed.ts b/src/services/UniversimeApi/Feed.ts index 0e5b5b5e..477d7897 100644 --- a/src/services/UniversimeApi/Feed.ts +++ b/src/services/UniversimeApi/Feed.ts @@ -4,7 +4,8 @@ import {GroupPost} from "@/types/Feed" export type CreateGroupPost_RequestDTO = { content: string; - groupId : string + groupId : string; + authorId : string; }; export type GetGroupPost_RequestDTO = { diff --git a/src/types/Feed.ts b/src/types/Feed.ts index 2ec6251e..12d9a1d2 100644 --- a/src/types/Feed.ts +++ b/src/types/Feed.ts @@ -1,5 +1,9 @@ +import { Profile } from "./Profile"; + export type GroupPost = { id: string; groupId: string; content : string; + authorId : string; + author : Profile | undefined; } \ No newline at end of file From 74b2c189e4a80f82c6989610a96dcc5a762c8ed7 Mon Sep 17 00:00:00 2001 From: 710lucas Date: Fri, 8 Dec 2023 19:19:22 -0300 Subject: [PATCH 21/24] =?UTF-8?q?altera=C3=A7=C3=A3o=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less index db1d248a..2d442528 100644 --- a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less +++ b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.less @@ -24,7 +24,7 @@ aspect-ratio: 1; border-radius: 100%; - object-fit: contain; + object-fit: cover; } .info { From 522208ac31a630322600edcc08de1bf000334dea Mon Sep 17 00:00:00 2001 From: 710lucas Date: Sat, 9 Dec 2023 15:33:58 -0300 Subject: [PATCH 22/24] Implementando profileClass --- .../Group/GroupTabs/GroupFeed/GroupFeed.tsx | 16 ++++++++++------ src/types/Feed.ts | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx index 974b67bc..e3fa924c 100644 --- a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx +++ b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx @@ -1,14 +1,13 @@ import { ActionButton } from "@/components/ActionButton/ActionButton"; import { Filter } from "@/components/Filter/Filter"; -import { ProfileImage } from "@/components/ProfileImage/ProfileImage"; import { FormInputs, UniversiForm } from "@/components/UniversiForm/UniversiForm"; import { RequiredValidation } from "@/components/UniversiForm/Validation/RequiredValidation"; import { TextValidation } from "@/components/UniversiForm/Validation/TextValidation"; import { ValidationComposite } from "@/components/UniversiForm/Validation/ValidationComposite"; import UniversimeApi from "@/services/UniversimeApi"; import { GroupPost } from "@/types/Feed"; -import { getProfileImageUrl } from "@/utils/profileUtils"; -import { useContext, useEffect, useState } from "react"; +import { ProfileClass } from "@/types/Profile"; +import { useContext, useState } from "react"; import { Link } from "react-router-dom"; import { GroupContext } from "../../GroupContext"; import "./GroupFeed.less"; @@ -70,15 +69,20 @@ export function GroupFeed(){ if(filterPosts != "" && !post.content.toLowerCase().includes(filterPosts.toLowerCase())) return <> + + if(!post.author) + return <> + + const author : ProfileClass = new ProfileClass(post.author); return(
{ - post.author + author ? - - + + : <> } diff --git a/src/types/Feed.ts b/src/types/Feed.ts index 12d9a1d2..c20e9333 100644 --- a/src/types/Feed.ts +++ b/src/types/Feed.ts @@ -1,9 +1,9 @@ -import { Profile } from "./Profile"; +import { ProfileClass } from "./Profile"; export type GroupPost = { id: string; groupId: string; content : string; authorId : string; - author : Profile | undefined; + author : ProfileClass | undefined; } \ No newline at end of file From d38d787a35b7f65443296b47df0330085ecb2e19 Mon Sep 17 00:00:00 2001 From: kassioL2L Date: Tue, 12 Dec 2023 15:25:17 -0300 Subject: [PATCH 23/24] tirando console.log --- src/services/UniversimeApi/Feed.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/services/UniversimeApi/Feed.ts b/src/services/UniversimeApi/Feed.ts index 477d7897..97ba0b6d 100644 --- a/src/services/UniversimeApi/Feed.ts +++ b/src/services/UniversimeApi/Feed.ts @@ -21,20 +21,10 @@ export async function getGroupPosts(body : GetGroupPost_RequestDTO): Promise { - try { return await (await api.post(`/feed/groups/${body.groupId}/posts`, body)).data; - } catch (error) { - console.error("Erro ao criar post no grupo:", error); - throw error; - } } export async function deleteGroupPost(groupId: string, postId: string): Promise> { - try { const response = await api.delete>(`/api/feed/groups/${groupId}/posts/${postId}`); return response.data; - } catch (error) { - console.error("Erro ao excluir post do grupo:", error); - throw error; - } } From d5c52ad246f365b64f888c243002ba45442e18d1 Mon Sep 17 00:00:00 2001 From: 710lucas Date: Tue, 12 Dec 2023 15:44:59 -0300 Subject: [PATCH 24/24] =?UTF-8?q?Ajeitando=20indenta=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx index e3fa924c..7125f49c 100644 --- a/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx +++ b/src/pages/Group/GroupTabs/GroupFeed/GroupFeed.tsx @@ -29,9 +29,9 @@ export function GroupFeed(){ { groupContext.participants.some(p => p.id == groupContext.loggedData.profile.id) ? - + : - <> + <> }