• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / d4d9f278-d845-4992-8c81-4f3757c427a1

08 Sep 2025 02:07PM UTC coverage: 86.303% (-1.8%) from 88.121%
d4d9f278-d845-4992-8c81-4f3757c427a1

Pull #5842

circleci

joeherm
fix(deploy): Update CircleCI to use common Dockerfile for building frontend
Pull Request #5842: fix(deploy): Unify Dockerfiles

2744 of 3951 branches covered (69.45%)

Branch coverage included in aggregate %.

17910 of 19981 relevant lines covered (89.64%)

9.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.96
/frontend/src/components/phones/dashboard/PhoneDashboard.tsx
1
import { useToggleState } from "react-stately";
1✔
2
import { useToggleButton } from "react-aria";
1✔
3
import { toast } from "react-toastify";
1✔
4
import { useRelayNumber } from "../../../hooks/api/relayNumber";
1✔
5
import styles from "./PhoneDashboard.module.scss";
1✔
6
import {
7
  CopyIcon,
8
  ForwardIcon,
9
  BlockIcon,
10
  ChevronRightIcon,
11
} from "../../Icons";
1✔
12
import { MouseEventHandler, useRef, useState } from "react";
1✔
13
import { VerifiedPhone } from "../../../hooks/api/realPhone";
14
import { useInboundContact } from "../../../hooks/api/inboundContact";
1✔
15
import { ProfileData } from "../../../hooks/api/profile";
16
import { SendersPanelView } from "./SendersPanelView";
1✔
17
import { formatPhone } from "../../../functions/formatPhone";
1✔
18
import { getLocale } from "../../../functions/getLocale";
1✔
19
import { parseDate } from "../../../functions/parseDate";
1✔
20
import { Tips } from "../../dashboard/tips/Tips";
1✔
21
import { RuntimeData } from "../../../hooks/api/types";
22
import { DismissalData } from "../../../hooks/localDismissal";
23
import { Banner } from "../../Banner";
1✔
24
import { useL10n } from "../../../hooks/l10n";
1✔
25

26
export type Props = {
27
  profile: ProfileData;
28
  runtimeData: RuntimeData;
29
  realPhone: VerifiedPhone;
30
  dismissal: {
31
    resendSMS: DismissalData;
32
  };
33
  onRequestContactCard: () => Promise<Response>;
34
};
35

36
export const PhoneDashboard = (props: Props) => {
2✔
37
  const l10n = useL10n();
2✔
38
  const relayNumber = useRelayNumber();
2✔
39
  const relayNumberData = relayNumber.data?.[0];
2✔
40
  const formattedPhoneNumber = formatPhone(props.realPhone.number ?? "", {
2!
41
    withCountryCode: true,
42
  });
43
  const formattedRelayNumber = formatPhone(relayNumberData?.number ?? "", {
2!
44
    withCountryCode: true,
45
  });
46
  const inboundContactData = useInboundContact();
2✔
47
  const inboundArray = inboundContactData.data;
2✔
48

49
  const [justCopiedPhoneNumber, setJustCopiedPhoneNumber] = useState(false);
2✔
50

51
  const resendWelcomeText = !props.dismissal.resendSMS.isDismissed && (
2✔
52
    <div className={styles["banner-wrapper"]}>
53
      <Banner
54
        title={l10n.getString("phone-banner-resend-welcome-sms-title")}
55
        type="info"
56
        cta={{
57
          content: l10n.getString("phone-banner-resend-welcome-sms-cta"),
58
          onClick: async () => {
59
            await props.onRequestContactCard();
×
60
            toast(l10n.getString("phone-banner-resend-welcome-sms-toast-msg"), {
×
61
              type: "success",
62
            });
63
            props.dismissal.resendSMS.dismiss();
×
64
          },
65
          gaViewPing: {
66
            category: "Resend Welcome SMS",
67
            label: "phone-page-banner-resend-welcome",
68
          },
69
        }}
70
        dismissal={{
71
          key: `resend-sms-banner-${props.profile?.id}`,
72
        }}
73
      >
74
        {l10n.getString("phone-banner-resend-welcome-sms-body")}
75
      </Banner>
76
    </div>
77
  );
78

79
  const [showingPrimaryDashboard, toggleDashboardPanel] = useState(true);
2✔
80
  const dateToFormat = props.realPhone.verified_date
2!
81
    ? parseDate(props.realPhone.verified_date)
82
    : new Date();
83
  const dateFormatter = new Intl.DateTimeFormat(getLocale(l10n), {
2✔
84
    dateStyle: "medium",
85
  });
86

87
  const forwardToggleButtonRef = useRef<HTMLButtonElement>(null);
2✔
88
  const forwardToggleState = useToggleState({
2✔
89
    defaultSelected: relayNumberData?.enabled ?? false,
2!
90
    onChange: (isSelected) => {
91
      if (typeof relayNumberData?.id === "number") {
×
92
        relayNumber.setForwardingState(isSelected, relayNumberData.id);
×
93
      }
94
    },
95
  });
96
  const forwardToggleButtonProps = useToggleButton(
2✔
97
    {},
98
    forwardToggleState,
99
    forwardToggleButtonRef,
100
  ).buttonProps;
101

102
  const copyPhoneNumber: MouseEventHandler<HTMLButtonElement> = () => {
2✔
103
    if (relayNumberData?.number) {
×
104
      // removing the + from the number to make it easier to copy
105
      const RelayNumber = relayNumberData.number.replace("+", "");
×
106
      navigator.clipboard.writeText(RelayNumber);
×
107
      setJustCopiedPhoneNumber(true);
×
108
      setTimeout(() => setJustCopiedPhoneNumber(false), 1000);
×
109
    }
110
  };
111

112
  const toggleSendersPanel = () => {
2✔
113
    toggleDashboardPanel(!showingPrimaryDashboard);
×
114
  };
115

116
  //TODO: Add real data to phone stats
117
  const phoneStatistics = (
118
    <div className={styles["phone-statistics-container"]}>
119
      <div className={styles["phone-statistics"]}>
120
        <p className={styles["phone-statistics-title"]}>
121
          {relayNumberData?.remaining_minutes}
122
        </p>
123
        <p className={styles["phone-statistics-body"]}>
124
          {l10n.getString("phone-statistics-remaining-call-minutes")}
125
        </p>
126
      </div>
127

128
      <div className={styles["phone-statistics"]}>
129
        <p className={styles["phone-statistics-title"]}>
130
          {relayNumberData?.remaining_texts}
131
        </p>
132
        <p className={styles["phone-statistics-body"]}>
133
          {l10n.getString("phone-statistics-remaining-texts")}
134
        </p>
135
      </div>
136

137
      <div
138
        className={`${styles["phone-statistics"]} ${
139
          relayNumberData?.enabled ? "" : styles["inactive-statistics"]
2!
140
        }`}
141
      >
142
        <p className={styles["phone-statistics-title"]}>
143
          {relayNumberData?.calls_and_texts_forwarded}
144
        </p>
145
        <p className={styles["phone-statistics-body"]}>
146
          {l10n.getString("phone-statistics-calls-texts-forwarded")}
147
        </p>
148
      </div>
149

150
      <div
151
        className={`${styles["phone-statistics"]} ${
152
          relayNumberData?.enabled ? styles["inactive-statistics"] : ""
2!
153
        }`}
154
      >
155
        <p className={styles["phone-statistics-title"]}>
156
          {relayNumberData?.calls_and_texts_blocked}
157
        </p>
158
        <p className={styles["phone-statistics-body"]}>
159
          {l10n.getString("phone-statistics-calls-texts-blocked")}
160
        </p>
161
      </div>
162
    </div>
163
  );
164

165
  const phoneControls = (
166
    <div className={styles["phone-controls-container"]}>
167
      <div className={styles["phone-controls"]}>
168
        <button
169
          {...forwardToggleButtonProps}
170
          ref={forwardToggleButtonRef}
171
          className={styles["forwarding-toggle"]}
172
          title={
173
            forwardToggleState.isSelected
2!
174
              ? l10n.getString(
175
                  "phone-dashboard-forwarding-toggle-disable-tooltip",
176
                )
177
              : l10n.getString(
178
                  "phone-dashboard-forwarding-toggle-enable-tooltip",
179
                )
180
          }
181
        >
182
          <span
183
            aria-hidden={!forwardToggleState.isSelected}
184
            className={`${styles["forwarding-toggle-state"]} ${styles["forward-state"]}`}
185
          >
186
            <ForwardIcon alt="" width={15} height={15} />
187
            {l10n.getString("phone-dashboard-forwarding-toggle-enable-label")}
188
          </span>
189
          <span
190
            aria-hidden={forwardToggleState.isSelected}
191
            className={`${styles["forwarding-toggle-state"]} ${styles["block-state"]}`}
192
          >
193
            <BlockIcon alt="" width={15} height={15} />
194
            {l10n.getString("phone-dashboard-forwarding-toggle-disable-label")}
195
          </span>
196
        </button>
197
      </div>
198
      <div className={styles["phone-controls-description"]}>
199
        {relayNumberData?.enabled ? (
200
          <span>{l10n.getString("phone-dashboard-forwarding-enabled")}</span>
2✔
201
        ) : (
202
          <span>{l10n.getString("phone-dashboard-forwarding-blocked")}</span>
203
        )}
204
      </div>
205
    </div>
206
  );
207

208
  const phoneMetadata = (
209
    <div className={styles["metadata-container"]}>
210
      <dl>
211
        <div className={`${styles["forward-target"]} ${styles.metadata}`}>
212
          <dt>{l10n.getString("phone-dashboard-metadata-forwarded-to")}</dt>
213
          <dd>{formattedPhoneNumber}</dd>
214
        </div>
215
        <div className={`${styles["date-created"]} ${styles.metadata}`}>
216
          <dt>{l10n.getString("phone-dashboard-metadata-date-created")}</dt>
217
          <dd>{dateFormatter.format(dateToFormat)}</dd>
218
        </div>
219
      </dl>
220
    </div>
221
  );
222

223
  const primaryPanel = (
224
    <div id="primary-panel" className={styles["dashboard-card"]}>
225
      <div className={styles["dashboard-card-header"]}>
226
        <span className={styles["header-phone-number"]}>
227
          <span>{formattedRelayNumber}</span>
228
          <span className={styles["copy-controls"]}>
229
            <span className={styles["copy-button-wrapper"]}>
230
              <button
231
                type="button"
232
                className={styles["copy-button"]}
233
                title="Copied"
234
                onClick={copyPhoneNumber}
235
              >
236
                <CopyIcon
237
                  alt={l10n.getString("setting-api-key-copied-alt")}
238
                  className={styles["copy-icon"]}
239
                  width={20}
240
                  height={20}
241
                />
242
              </button>
243
              <span
244
                aria-hidden={!justCopiedPhoneNumber}
245
                className={`${styles["copied-confirmation"]} ${
246
                  justCopiedPhoneNumber ? styles["is-shown"] : ""
2!
247
                }`}
248
              >
249
                {l10n.getString("phone-dashboard-number-copied")}
250
              </span>
251
            </span>
252
          </span>
253
        </span>
254
        <button
255
          type="button"
256
          className={styles["senders-cta"]}
257
          onClick={toggleSendersPanel}
258
        >
259
          <span>{l10n.getString("phone-dashboard-senders-header")}</span>
260
          <ChevronRightIcon
261
            alt="See Caller and SMS Senders"
262
            className={styles["nav-icon"]}
263
            width={20}
264
            height={20}
265
          />
266
        </button>
267
      </div>
268
      {phoneStatistics}
269
      {phoneControls}
270
      {phoneMetadata}
271
    </div>
272
  );
273

274
  function getSendersPanelType() {
275
    if (props.profile.store_phone_log === false) {
×
276
      return "disabled";
×
277
    }
278
    if (inboundArray && inboundArray.length === 0) {
×
279
      return "empty";
×
280
    }
281
    return "primary";
×
282
  }
283

284
  return (
285
    <div className={styles["main-phone-wrapper"]}>
286
      <main className={styles["content-wrapper"]}>
287
        {resendWelcomeText}
288
        {showingPrimaryDashboard && inboundContactData !== undefined ? (
4✔
289
          // Primary Panel
290
          <>{primaryPanel}</>
2✔
291
        ) : (
292
          // Caller and SMS Senders Panel
293
          <SendersPanelView
294
            data-testid="senders-panel-view"
295
            type={getSendersPanelType()}
296
            back_btn={toggleSendersPanel}
297
          />
298
        )}
299
      </main>
300
      <Tips profile={props.profile} runtimeData={props.runtimeData} />
301
    </div>
302
  );
303
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc