Skip to content

Commit b6ecf06

Browse files
authored
Merge pull request #1935 from codeforboston/main
Deploy to PROD 9/2/25
2 parents 180b784 + cd7c394 commit b6ecf06

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1676
-334
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Deploy Python Firebase functions to development environment
2+
on:
3+
push:
4+
branches:
5+
- main
6+
paths:
7+
- llm/**
8+
- firestore.rules
9+
- firestore.indexes.json
10+
- firebase.json
11+
- storage.rules
12+
workflow_dispatch:
13+
14+
jobs:
15+
build_and_deploy:
16+
# Don't deploy forks, even if actions are enabled
17+
if: github.repository_owner == 'codeforboston'
18+
runs-on: ubuntu-latest
19+
environment: dev
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v5
23+
- name: Install Python 3.11
24+
run: |
25+
sudo add-apt-repository ppa:deadsnakes/ppa -y
26+
sudo apt update
27+
sudo apt install python3.11 python3.11-venv python3-pip
28+
- name: Build and deploy to Firebase
29+
uses: w9jds/[email protected]
30+
with:
31+
args: deploy --force --only functions:maple-llm
32+
env:
33+
GCP_SA_KEY: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
34+
ASSEMBLY_API_KEY: ${{ secrets.ASSEMBLY_API_KEY }}
35+
PROJECT_ID: digital-testimony-dev

components/EditProfilePage/EditProfilePage.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Router from "next/router"
33
import { useState } from "react"
44
import { TabPane } from "react-bootstrap"
55
import TabContainer from "react-bootstrap/TabContainer"
6-
import { useAuth } from "../auth"
6+
import { Frequency, useAuth } from "../auth"
77
import { Container, Row, Spinner } from "../bootstrap"
88
import {
99
Profile,
@@ -27,8 +27,9 @@ import { TestimoniesTab } from "./TestimoniesTab"
2727
import { useFlags } from "components/featureFlags"
2828
import LoginPage from "components/Login/Login"
2929
import { PendingUpgradeBanner } from "components/PendingUpgradeBanner"
30+
import { FollowersTab } from "./FollowersTab"
3031

31-
const tabTitle = ["about-you", "testimonies", "following"] as const
32+
const tabTitle = ["about-you", "testimonies", "following", "followers"] as const
3233
export type TabTitles = (typeof tabTitle)[number]
3334

3435
export default function EditProfile({
@@ -86,16 +87,16 @@ export function EditProfileForm({
8687

8788
const [formUpdated, setFormUpdated] = useState(false)
8889
const [settingsModal, setSettingsModal] = useState<"show" | null>(null)
89-
const [notifications, setNotifications] = useState<
90-
"Weekly" | "Monthly" | "None"
91-
>(notificationFrequency ? notificationFrequency : "Monthly")
90+
const [notifications, setNotifications] = useState<Frequency>(
91+
notificationFrequency || "Weekly"
92+
)
9293
const [isProfilePublic, setIsProfilePublic] = useState<false | true>(
9394
isPublic ? isPublic : false
9495
)
9596

9697
const onSettingsModalOpen = () => {
9798
setSettingsModal("show")
98-
setNotifications(notificationFrequency ? notificationFrequency : "Monthly")
99+
setNotifications(notificationFrequency || "Weekly")
99100
setIsProfilePublic(isPublic ? isPublic : false)
100101
}
101102

@@ -115,6 +116,7 @@ export function EditProfileForm({
115116
isOrg = isOrg || isPendingUpgrade
116117

117118
const { t } = useTranslation("editProfile")
119+
const [followerCount, setFollowerCount] = useState<number | null>(null)
118120

119121
const tabs = [
120122
{
@@ -147,6 +149,18 @@ export function EditProfileForm({
147149
title: t("tabs.following"),
148150
eventKey: "following",
149151
content: <FollowingTab className="mt-3 mb-4" />
152+
},
153+
{
154+
title: followerCount
155+
? t("tabs.followersWithCount", { count: followerCount })
156+
: t("tabs.followers"),
157+
eventKey: "followers",
158+
content: (
159+
<FollowersTab
160+
className="mt-3 mb-4"
161+
setFollowerCount={setFollowerCount}
162+
/>
163+
)
150164
}
151165
]
152166

@@ -174,9 +188,7 @@ export function EditProfileForm({
174188
>
175189
<TabNavWrapper>
176190
{tabs.map((t, i) => (
177-
<>
178-
<TabNavItem tab={t} i={i} />
179-
</>
191+
<TabNavItem key={i} tab={t} i={i} />
180192
))}
181193
</TabNavWrapper>
182194
<StyledTabContent>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { functions } from "components/firebase"
2+
import { httpsCallable } from "firebase/functions"
3+
import { useTranslation } from "next-i18next"
4+
import { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react"
5+
import { useAuth } from "../auth"
6+
import { usePublicProfile } from "components/db"
7+
import { Internal } from "components/links"
8+
import { FollowUserButton } from "components/shared/FollowButton"
9+
import React from "react"
10+
import { Col, Row, Spinner, Stack, Alert } from "../bootstrap"
11+
import { TitledSectionCard } from "../shared"
12+
import { OrgIconSmall } from "./StyledEditProfileComponents"
13+
14+
export const FollowersTab = ({
15+
className,
16+
setFollowerCount
17+
}: {
18+
className?: string
19+
setFollowerCount: Dispatch<SetStateAction<number | null>>
20+
}) => {
21+
const uid = useAuth().user?.uid
22+
const [followerIds, setFollowerIds] = useState<string[]>([])
23+
const [loading, setLoading] = useState(true)
24+
const [error, setError] = useState<string | null>(null)
25+
const { t } = useTranslation("editProfile")
26+
27+
useEffect(() => {
28+
const fetchFollowers = async () => {
29+
try {
30+
const { data: followerIds } = await httpsCallable<void, string[]>(
31+
functions,
32+
"getFollowers"
33+
)()
34+
setFollowerIds(followerIds)
35+
setFollowerCount(followerIds.length)
36+
setLoading(false)
37+
} catch (err) {
38+
console.error("Error fetching followerIds", err)
39+
setError("Error fetching followers.")
40+
setLoading(false)
41+
return
42+
}
43+
}
44+
if (uid) fetchFollowers()
45+
}, [uid])
46+
return (
47+
<TitledSectionCard className={className}>
48+
<div className="mx-4 mt-3 d-flex flex-column gap-3">
49+
<Stack>
50+
<h2>{t("follow.your_followers")}</h2>
51+
<p className="mt-0 text-muted">
52+
{t("follow.follower_info_disclaimer")}
53+
</p>
54+
<div className="mt-3">
55+
{error ? (
56+
<Alert variant="danger">{error}</Alert>
57+
) : loading ? (
58+
<Spinner animation="border" className="mx-auto" />
59+
) : (
60+
followerIds.map((profileId, i) => (
61+
<FollowerCard key={i} profileId={profileId} />
62+
))
63+
)}
64+
</div>
65+
</Stack>
66+
</div>
67+
</TitledSectionCard>
68+
)
69+
}
70+
71+
const FollowerCard = ({ profileId }: { profileId: string }) => {
72+
const { result: profile, loading } = usePublicProfile(profileId)
73+
const { t } = useTranslation("profile")
74+
if (loading) {
75+
return (
76+
<FollowerCardWrapper>
77+
<Spinner animation="border" className="mx-auto" />
78+
</FollowerCardWrapper>
79+
)
80+
}
81+
const { fullName, profileImage, public: isPublic } = profile || {}
82+
const displayName = isPublic && fullName ? fullName : t("anonymousUser")
83+
return (
84+
<FollowerCardWrapper>
85+
<Col className="d-flex align-items-center flex-grow-1 p-0 text-start">
86+
<OrgIconSmall
87+
className="mr-4 mt-0 mb-0 ms-0"
88+
profileImage={profileImage}
89+
/>
90+
{isPublic ? (
91+
<Internal href={`/profile?id=${profileId}`}>{displayName}</Internal>
92+
) : (
93+
<span>{displayName}</span>
94+
)}
95+
</Col>
96+
{isPublic ? (
97+
<Col
98+
xs="auto"
99+
className="d-flex justify-content-end ms-auto text-end p-0"
100+
>
101+
<FollowUserButton profileId={profileId} />
102+
</Col>
103+
) : (
104+
<></>
105+
)}
106+
</FollowerCardWrapper>
107+
)
108+
}
109+
110+
const FollowerCardWrapper = ({ children }: { children: ReactNode }) => (
111+
<div className={`fs-3 lh-lg`}>
112+
<Row className="align-items-center justify-content-between g-0 w-100">
113+
{children}
114+
</Row>
115+
<hr className={`mt-3`} />
116+
</div>
117+
)

components/EditProfilePage/ProfileSettingsModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Props = Pick<ModalProps, "show" | "onHide"> & {
1515
role: Role
1616
setIsProfilePublic: Dispatch<SetStateAction<false | true>>
1717
notifications: Frequency
18-
setNotifications: Dispatch<SetStateAction<"Weekly" | "Monthly" | "None">>
18+
setNotifications: Dispatch<SetStateAction<Frequency>>
1919
onSettingsModalClose: () => void
2020
}
2121

components/EditProfilePage/StyledEditProfileComponents.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const StyledTabNav = styled(Nav).attrs(props => ({
4242
export const TabNavWrapper = ({ children, className, ...props }: NavProps) => {
4343
return (
4444
<Nav
45-
className={`d-flex mb-3 text-center h3 color-dark ${className}`}
45+
className={`d-flex w-100 flex-column flex-lg-row flex-lg-nowrap mb-3 text-center h3 color-dark ${className}`}
4646
{...props}
4747
>
4848
{children}
@@ -68,12 +68,11 @@ export const TabNavItem = ({
6868
className?: string
6969
}) => {
7070
return (
71-
<Nav.Item
72-
className={`flex-grow-1 col-12 col-md-auto ${className}`}
73-
key={tab.eventKey}
74-
>
71+
<Nav.Item className={`flex-lg-fill ${className}`} key={tab.eventKey}>
7572
<TabNavLink eventKey={tab.eventKey} className={`rounded-top m-0 p-0`}>
76-
<p className={`my-0 ${i === 0 ? "" : "mx-4"}`}>{tab.title}</p>
73+
<p className={`my-0 text-nowrap ${i === 0 ? "" : "mx-4"}`}>
74+
{tab.title}
75+
</p>
7776
<hr className={`my-0`} />
7877
</TabNavLink>
7978
</Nav.Item>

components/Newsfeed/Newsfeed.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ErrorPage from "next/error"
22
import { Timestamp } from "firebase/firestore"
33
import { useTranslation } from "next-i18next"
44
import { useEffect, useState } from "react"
5-
import { useAuth } from "../auth"
5+
import { Frequency, useAuth } from "../auth"
66
import { Col, Row, Spinner } from "../bootstrap"
77
import { Profile, useProfile, usePublicProfile } from "../db"
88
import { NotificationProps, Notifications } from "./NotificationProps"
@@ -142,9 +142,9 @@ export default function Newsfeed() {
142142
}: Profile = profile
143143

144144
const [settingsModal, setSettingsModal] = useState<"show" | null>(null)
145-
const [notifications, setNotifications] = useState<
146-
"Weekly" | "Monthly" | "None"
147-
>(notificationFrequency ? notificationFrequency : "Weekly")
145+
const [notifications, setNotifications] = useState<Frequency>(
146+
notificationFrequency ? notificationFrequency : "Weekly"
147+
)
148148
const [isProfilePublic, setIsProfilePublic] = useState<false | true>(
149149
isPublic ? isPublic : false
150150
)

components/ProfilePage/ProfileHeader.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useTranslation } from "next-i18next"
22
import { useContext, useState } from "react"
33
import { useMediaQuery } from "usehooks-ts"
4-
import { useAuth } from "../auth"
4+
import { Frequency, useAuth } from "../auth"
55
import { Profile, useProfile } from "../db"
66
import { EditProfileButton, ProfileButtons } from "./ProfileButtons"
77
import { Header, ProfileDisplayName } from "./StyledProfileComponents"
@@ -30,16 +30,16 @@ export const ProfileHeader = ({
3030
}: Profile = profile
3131

3232
const [settingsModal, setSettingsModal] = useState<"show" | null>(null)
33-
const [notifications, setNotifications] = useState<
34-
"Weekly" | "Monthly" | "None"
35-
>(notificationFrequency ? notificationFrequency : "Monthly")
33+
const [notifications, setNotifications] = useState<Frequency>(
34+
notificationFrequency || "Weekly"
35+
)
3636
const [isProfilePublic, setIsProfilePublic] = useState<false | true>(
3737
isPublic ? isPublic : false
3838
)
3939

4040
const onSettingsModalOpen = () => {
4141
setSettingsModal("show")
42-
setNotifications(notificationFrequency ? notificationFrequency : "Monthly")
42+
setNotifications(notificationFrequency || "Weekly")
4343
setIsProfilePublic(isPublic ? isPublic : false)
4444
}
4545

components/auth/Provider.tsx

Lines changed: 0 additions & 21 deletions
This file was deleted.

components/auth/hooks.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe.skip("useCreateUserWithEmailAndPassword", () => {
2626
fullName: info.fullName,
2727
role: "user",
2828
public: false,
29-
notificationFrequency: "Monthly",
29+
notificationFrequency: "Weekly",
3030
email: info.email
3131
})
3232
})

components/auth/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@ export * from "./service"
33
export { default as SignInWithButton } from "./SignInWithButton"
44
export { default as SignOut } from "./SignOut"
55
export * from "./types"
6-
export { Provider } from "./Provider"

0 commit comments

Comments
 (0)