Skip to content

Commit 8cc0275

Browse files
Merge pull request #448 from DemocracyLab/about_team_cleanup
About Us Team section reorganization
2 parents bbed966 + 98b6f74 commit 8cc0275

File tree

12 files changed

+316
-196
lines changed

12 files changed

+316
-196
lines changed

civictechprojects/helpers/context_preload.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ def about_project_preload(context, query_args):
1717
return context
1818

1919

20+
def about_us_preload(context, query_args):
21+
context = default_preload(context, query_args)
22+
context['title'] = 'DemocracyLab | About'
23+
context['description'] = 'Learn About democracyLab, the nonprofit connecting skilled individuals to tech-for-good projects.'
24+
return context
25+
26+
27+
def edit_profile_preload(context, query_args):
28+
context = default_preload(context, query_args)
29+
context['title'] = 'Update User Profile | DemocracyLab'
30+
context['description'] = 'Update User Profile page'
31+
return context
32+
33+
2034
def default_preload(context, query_args):
2135
context['title'] = 'DemocracyLab'
2236
context['description'] = 'Everyone has something to contribute to the technical solutions society needs. ' \
@@ -27,7 +41,9 @@ def default_preload(context, query_args):
2741

2842

2943
preload_urls = [
30-
{'section': FrontEndSection.AboutProject.value, 'handler': about_project_preload}
44+
{'section': FrontEndSection.AboutProject.value, 'handler': about_project_preload},
45+
{'section': FrontEndSection.EditProfile.value, 'handler': edit_profile_preload},
46+
{'section': FrontEndSection.AboutUs.value, 'handler': about_us_preload}
3147
]
3248

3349

civictechprojects/migrations/data_migrations/migrate_location.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,16 @@
7878

7979

8080
def migrate_locations_from_city_list(*args):
81-
for project in Project.objects.all():
82-
if project.project_location in city_data:
83-
print('Migrating location data for ' + str(project))
84-
data = city_data[project.project_location]
85-
project.project_location = data["location_id"]
86-
project.project_country = data["country"]
87-
project.project_state = data["state"]
88-
project.project_city = data["city"]
89-
project.project_location_coords = Point(data['longitude'], data['latitude'])
90-
project.save()
81+
if Project.objects.count() > 0:
82+
for project in Project.objects.all():
83+
if project.project_location in city_data:
84+
print('Migrating location data for ' + str(project))
85+
data = city_data[project.project_location]
86+
project.project_location = data["location_id"]
87+
project.project_country = data["country"]
88+
project.project_state = data["state"]
89+
project.project_city = data["city"]
90+
project.project_location_coords = Point(data['longitude'], data['latitude'])
91+
project.save()
9192

9293

common/components/componentsBySection/AboutUs/BioPersonData.jsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
// @flow
22

33
import type {VolunteerUserData, VolunteerDetailsAPIData} from "../../utils/ProjectAPIUtils.js";
4+
import type {Dictionary} from "../../types/Generics.jsx";
45

56
export type BioPersonData = {|
67
first_name: string,
78
last_name: string,
89
title: ?$ReadOnlyArray<string>,
10+
title_tag: ?string,
911
user_thumbnail: ?string,
1012
bio_text: ?string,
1113
profile_id: ?number
1214
|};
1315

14-
export function VolunteerUserDataToBioPersonData(v: VolunteerUserData, title: string): BioPersonData {
15-
return {
16-
first_name: v.first_name,
17-
last_name: v.last_name,
18-
title: [title],
19-
user_thumbnail: v.user_thumbnail && v.user_thumbnail.publicUrl,
20-
bio_text: v.about_me,
21-
profile_id: v.id
22-
};
16+
export function VolunteerUserDataToBioPersonData(v: VolunteerUserData, title: string, title_tag: ?string, nameOverrides: ?Dictionary<string>): BioPersonData {
17+
const _title: string = nameOverrides && title_tag in nameOverrides ? nameOverrides[title_tag] : title;
18+
return {
19+
first_name: v.first_name,
20+
last_name: v.last_name,
21+
title: [_title],
22+
title_tag: title_tag,
23+
user_thumbnail: v.user_thumbnail && v.user_thumbnail.publicUrl,
24+
bio_text: v.about_me,
25+
profile_id: v.id
26+
};
2327
}
2428

2529
export function VolunteerDetailsAPIDataEqualsBioPersonData(v: VolunteerDetailsAPIData, b: BioPersonData): boolean {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// @flow
2+
3+
import React from 'react';
4+
import _ from 'lodash';
5+
import type {BioPersonData} from "./BioPersonData.jsx";
6+
import url from "../../utils/url.js";
7+
import Section from "../../enums/Section.js";
8+
import LoadingMessage from "../../chrome/LoadingMessage.jsx";
9+
import type {ProjectDetailsAPIData, TeamAPIData, VolunteerDetailsAPIData} from "../../utils/ProjectAPIUtils.js";
10+
import BioThumbnail from "./BioThumbnail.jsx";
11+
import {VolunteerDetailsAPIDataEqualsBioPersonData, VolunteerUserDataToBioPersonData} from "./BioPersonData.jsx";
12+
import GroupBy from "../../utils/groupBy.js";
13+
import BioModal from "./BioModal.jsx";
14+
import Sort from "../../utils/sort.js";
15+
import array from "../../utils/array.js";
16+
import type {Dictionary} from "../../types/Generics.jsx";
17+
18+
type Props = {|
19+
teamResponse: TeamAPIData
20+
|};
21+
22+
type State = {|
23+
project: ?ProjectDetailsAPIData,
24+
board_of_directors: ?$ReadOnlyArray<BioPersonData>,
25+
project_volunteers: ?{ [key: string]: BioPersonData },
26+
showBiographyModal: boolean,
27+
allowModalUnsafeHtml: boolean,
28+
modalPerson: ?BioPersonData,
29+
personTitle: string
30+
|};
31+
32+
type SectionConfig = {|
33+
title: string,
34+
sectionRoleCategory: string,
35+
topRoles: ?$ReadOnlyArray<string>
36+
|};
37+
38+
const SectionConfigs: $ReadOnlyArray<SectionConfig> = [
39+
{
40+
title: "Development",
41+
sectionRoleCategory: "Software Development",
42+
topRoles: ["technical-director", "technical-lead"]
43+
}, {
44+
title: "Design",
45+
sectionRoleCategory: "Design",
46+
topRoles: ["design-lead", "creative-director"]
47+
}, {
48+
title: "Product Management",
49+
sectionRoleCategory: "Product Management",
50+
topRoles: ["product-manager-lead"]
51+
}, {
52+
title: "Marketing",
53+
sectionRoleCategory: "Marketing",
54+
topRoles: ["marketing-lead"]
55+
}, {
56+
title: "Salesforce Dev/Admin",
57+
sectionRoleCategory: "Operations",
58+
topRoles: ["business-operations-lead"]
59+
}, {
60+
title: "Operations",
61+
sectionRoleCategory: "Business",
62+
topRoles: []
63+
}
64+
];
65+
66+
const roleNameOverride: Dictionary<string> = {
67+
"business-operations-lead": "Salesforce Team Lead"
68+
};
69+
70+
class TeamSections extends React.PureComponent<Props, State> {
71+
constructor(props: Props): void {
72+
super(props);
73+
this.state = {
74+
project: null,
75+
board_of_directors: null,
76+
showBiographyModal: false,
77+
modalPerson: null
78+
};
79+
this.handleShowBio = this.handleShowBio.bind(this);
80+
this.handleClose = this.handleClose.bind(this);
81+
}
82+
83+
componentWillReceiveProps(nextProps: Props): void {
84+
let state: State = { showModal: nextProps.showModal };
85+
state = this.loadTeamDetails(state, nextProps.teamResponse);
86+
this.setState(state);
87+
}
88+
89+
loadTeamDetails(state: State, response: TeamAPIData): State {
90+
state.board_of_directors = response.board_of_directors && JSON.parse(response.board_of_directors);
91+
92+
if(response.project) {
93+
state.project = response.project;
94+
const activeVolunteers: $ReadOnlyArray<VolunteerDetailsAPIData> = response.project.project_volunteers.filter( (pv) => pv.isApproved);
95+
const sortedVolunteers: $ReadOnlyArray<VolunteerDetailsAPIData> = _.sortBy(activeVolunteers, ["platform_date_joined"]);
96+
// Remove board members from volunteer list
97+
const uniqueVolunteers: $ReadOnlyArray<BioPersonData> = _.differenceWith(
98+
sortedVolunteers,
99+
state.board_of_directors,
100+
VolunteerDetailsAPIDataEqualsBioPersonData);
101+
state.project_volunteers = GroupBy.andTransform(
102+
uniqueVolunteers,
103+
(pv: VolunteerDetailsAPIData) => pv.roleTag.subcategory,
104+
(pv: VolunteerDetailsAPIData) => {
105+
return VolunteerUserDataToBioPersonData(pv.user, pv.roleTag.display_name, pv.roleTag.tag_name, roleNameOverride);
106+
});
107+
}
108+
109+
return state;
110+
}
111+
112+
//handlers for biography modal
113+
//show passes information to the modal on whose information to display, close clears that out of state (just in case)
114+
//title is passed separately from the rest because of our data structure for owner and volunteer not matching up
115+
handleShowBio(allowModalUnsafeHtml: boolean, person: BioPersonData) {
116+
this.setState({
117+
modalPerson: person,
118+
showBiographyModal: true,
119+
allowModalUnsafeHtml: allowModalUnsafeHtml
120+
});
121+
}
122+
handleClose() {
123+
this.setState({
124+
modalPerson: null,
125+
showBiographyModal: false,
126+
allowModalUnsafeHtml: false
127+
});
128+
}
129+
130+
render(): ?React$Node {
131+
return (
132+
<React.Fragment>
133+
{this._boardOfDirectors()}
134+
{this._ourTeam()}
135+
{this._renderBioModal()}
136+
</React.Fragment>
137+
);
138+
}
139+
140+
_boardOfDirectors(): ?React$Node {
141+
return (this.state.board_of_directors ?
142+
<div className="about-us-team col">
143+
<h2>Board of Directors</h2>
144+
<p className="about-us-team-description">
145+
Please review our <a href="https://d1agxr2dqkgkuy.cloudfront.net/documents/2019%20DemocracyLab%20Annual%20Report.pdf" >2019 Annual Report</a> to learn about the impact of our programs and platform last year.
146+
</p>
147+
<div className="about-us-team-card-container about-us-board-container">
148+
{this._renderBios(this.state.board_of_directors, true)}
149+
</div>
150+
<hr />
151+
</div> : null);
152+
}
153+
154+
_ourTeam(): ?React$Node {
155+
if(this.state.project) {
156+
const teamSections: $ReadOnlyArray<?React$Node> = SectionConfigs
157+
.filter((sc: SectionConfig) => sc.sectionRoleCategory in this.state.project_volunteers)
158+
.map((sc: SectionConfig) => this._renderTeamSection(sc));
159+
160+
return (
161+
<div className="about-us-team col">
162+
<h2>Our Team</h2>
163+
<p className="about-us-team-description">
164+
We are volunteer engineers, marketers, organizers, strategists, designers, project managers, and citizens
165+
committed to our vision, and driven by our mission.
166+
Please visit our <a href={url.section(Section.AboutProject, {id: this.state.project.project_id})}>project
167+
profile</a> to learn how you can get involved!
168+
</p>
169+
{array.join(teamSections, <hr/>)}
170+
</div>);
171+
} else {
172+
return <div className="about-us-team col"><LoadingMessage message="Loading our team information..."/></div>;
173+
}
174+
}
175+
176+
_renderTeamSection(sectionConfig: SectionConfig): ?React$Node {
177+
if(sectionConfig.sectionRoleCategory in this.state.project_volunteers) {
178+
const team: $ReadOnlyArray<BioPersonData> = this.state.project_volunteers[sectionConfig.sectionRoleCategory];
179+
const sortedTeam: $ReadOnlyArray<BioPersonData> = Sort.byNamedEntries(team, sectionConfig.topRoles, (p: BioPersonData) => p.title_tag);
180+
181+
return (
182+
<React.Fragment>
183+
<h4>{sectionConfig.title}</h4>
184+
<div className="about-us-team-card-container">
185+
{this._renderBios(sortedTeam, false)}
186+
</div>
187+
</React.Fragment>
188+
);
189+
}
190+
}
191+
192+
_renderBioModal(): ?React$Node {
193+
return <BioModal
194+
size="lg"
195+
showModal={this.state.showBiographyModal}
196+
allowUnsafeHtml={this.state.allowModalUnsafeHtml}
197+
handleClose={this.handleClose}
198+
person={this.state.modalPerson}
199+
/>
200+
}
201+
202+
_renderBios(volunteers: $ReadOnlyArray<BioPersonData>, allowUnsafeHtml: boolean): ?React$Node {
203+
return (
204+
volunteers.map((volunteer, i) => {
205+
return (
206+
<BioThumbnail key={i} person={volunteer} handleClick={this.handleShowBio.bind(this, allowUnsafeHtml)}/>
207+
)}
208+
)
209+
);
210+
}
211+
212+
}
213+
214+
export default TeamSections;

0 commit comments

Comments
 (0)