Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add client feedback copy updates [#793](https://github.com/azavea/echo-locator/pull/793)
- Add transit routing architecture ADR [#804](https://github.com/azavea/echo-locator/pull/804)
- Add neighborhood csv submission instructions [#801](https://github.com/azavea/echo-locator/pull/801)
- Add Spanish and Mandarin translations [#807](https://github.com/azavea/echo-locator/pull/807)

### Changed

Expand Down
27 changes: 21 additions & 6 deletions src/frontend/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
"otherTitle": "Other recommended neighborhoods",
"otherTitleLegend": "Other",
"tooFarTitle": "Not a match",
"tooFarDisclaimer": "These neighborhoods are out of reach with your selected transit option, meaning commute times exceeding 1 hour"
"tooFarDisclaimer": "These neighborhoods are excluded by the filters set in your profile",
"groupedCountZipCodes": "zip codes",
"groupedLoadMore": "Load more",
"eccBenefits": "ECC Benefits",
"min": "min",
"over2Hours": "Over 2 hr"
},
"photoCredit": "Photo by {{artist}}. ",
"photoSource": "Source.",
Expand All @@ -62,8 +67,14 @@
"schoolChoice": "School choice options",
"schoolsSafetyCard": {
"header": "Schools & Safety",
"schoolsLabel": "Schools",
"safetyLabel": "Safety"
"schools": "Schools",
"safety": "Safety",
"commute": "Commute",
"high": "High",
"above": "Above Avg",
"average": "Average",
"below": "Below Avg",
"low": "Low"
},
"findUnitsLink": "Go to <strong>Find Units</strong> to learn more",
"aboutAreaSection": {
Expand Down Expand Up @@ -136,7 +147,7 @@
"header": "Your Profile",
"stepBedroom": {
"question": "How many bedrooms does your voucher have?",
"description": "Reach out to your BHA Housing Coordinator if you're unsure."
"description": "Reach out to your BHA Housing Specialist if you're unsure."
},
"stepImportance": {
"question": "What is important to you?",
Expand Down Expand Up @@ -188,7 +199,10 @@
"bodyText": "Edit your settings and add filters using the buttons at the top",
"showMyRecommendations": "Show my recommendations",
"listNeighborhoods": "List all neighborhoods",
"showTopTen": "Show Top 10"
"showTopTen": "Show Top 10",
"next": "Next",
"details": "Details",
"prev": "Prev"
},
"filterModal": {
"dialogHeading": "Filter recommendations",
Expand All @@ -201,7 +215,8 @@
"regionMapCaption": "Based on a map by",
"echoLink": "Learn more about ECHO",
"regionsFilterLabel": "Filter by region selections",
"eccFilter": "Only recommend Expanded Choice Communities (ECC)"
"eccFilter": "Only recommend Expanded Choice Communities (ECC)",
"eccFilterDescription": "Moving to an ECC area could qualify you for financial assistance to help with your security deposit, broker fees, and moving expenses."
},
"editTripsModal": {
"dialogHeading": "Choose a trip",
Expand Down
271 changes: 143 additions & 128 deletions src/frontend/public/locales/es/translation.json

Large diffs are not rendered by default.

255 changes: 135 additions & 120 deletions src/frontend/public/locales/zh/translation.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/frontend/src/components/EditFiltersModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const EditFiltersModal = ({ isMobile = false }) => {
customImageClassName="w-full"
customCaption={
<>
CC BY-SA 3.0. $
CC BY-SA 3.0.{" "}
{t("filterModal.regionMapCaption")}{" "}
<Link
href={PROTONK_URL}
Expand Down Expand Up @@ -213,7 +213,9 @@ const EditFiltersModal = ({ isMobile = false }) => {
ecc: isSelected,
})
}
description={`Select to only recommend neighborhoods that are Expanded Choice Communities (ECC)`}
description={t(
"filterModal.eccFilterDescription"
)}
footer={
<Link
href={BHA_URL}
Expand Down
5 changes: 4 additions & 1 deletion src/frontend/src/components/EditWizard/StepTrips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ const StepTrips = ({
>
<div>
<h2 className="text-xl font-bold text-gray-900">
{destination.purpose}
{t([
"destinationPurposes." +
destination.purpose,
])}
</h2>
<p className="max-w-40 flex flex-wrap font-normal text-gray-600 text-sm">
{destination.location.label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export const cardStyles = tv({
zip: "text-rg text-gray-500",
tagsContainer: "flex flex-wrap gap-2",
tag: " h-[21px] flex items-center gap-2 rounded-[var(--spacing-2)] border border-gray-300 px-2 py-1 text-xs font-bold text-[#006512]",
statsContainer: "flex flex-start gap-5 self-stretch -mt-2",
statItem: "flex flex-1 flex-col px-0 py-3 align-start",
statsContainer:
"flex flex-start gap-5 self-stretch -mt-2 overflow-hidden",
statItem: "flex flex-1 max-w-[33.33%] flex-col px-0 py-3 align-start",
navContainer:
"flex align-center self-stretch gap-3 border-t border-gray-300 p-3 bg-gray-100",
},
Expand All @@ -37,5 +38,13 @@ export const cardStyles = tv({
root: "relative",
},
},
isPopup: {
true: {
root: "w-fit",
},
false: {
root: "w-auto",
},
},
},
});
20 changes: 14 additions & 6 deletions src/frontend/src/components/NeighborhoodCard/NeighborhoodCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Button from "components/base/Button/Button";
import Meter from "components/base/Meter/Meter";
import Range from "components/base/Range/Range";
import SchoolMeter from "components/SchoolMeter";
import { useTranslation } from "react-i18next";
import type { Stats } from "src/libs/formatNeighborhoodDataByCard";
import { cardStyles } from "./NeighborhoodCard.styles";

Expand Down Expand Up @@ -57,7 +58,12 @@ const NeighborhoodCard = ({
statsContainer,
statItem,
navContainer,
} = cardStyles({ hasImage: !!imageUrl, ...listViewStyling });
} = cardStyles({
hasImage: !!imageUrl,
isPopup: isPopup,
...listViewStyling,
});
const { t } = useTranslation();

const hasTag = isTopTen || hasECC;
const { favorites } = useAppSelector(selectUserProfile);
Expand Down Expand Up @@ -106,13 +112,15 @@ const NeighborhoodCard = ({
{isTopTen && (
<div className={tag()}>
<FlagIcon className="h-4 w-4 fill-[#50935D]" />
<span>Top 10</span>
<span>{t("discoverNeighborhoods.topTen")}</span>
</div>
)}
{hasECC && (
<div className={tag()}>
<SquareDollarIcon className="h-[13px] w-[13px] -ml-[1px] mr-[1px] fill-[#50935D]" />
<span>ECC Benefits</span>
<span>
{t("discoverNeighborhoods.eccBenefits")}
</span>
</div>
)}
</div>
Expand Down Expand Up @@ -142,7 +150,7 @@ const NeighborhoodCard = ({
<ArrowLeftIcon className="font-normal h-[14px] w-[14px] text-black" />
}
>
Prev
{t("top10Tour.prev")}
</Button>
)}
{onDetails && (
Expand All @@ -151,7 +159,7 @@ const NeighborhoodCard = ({
className="flex-1"
onPress={onDetails}
>
Details
{t("top10Tour.details")}
</Button>
)}
{onNext && (
Expand All @@ -163,7 +171,7 @@ const NeighborhoodCard = ({
<ArrowRightIcon className="font-normal h-[14px] w-[14px] text-black" />
}
>
Next
{t("top10Tour.next")}
</Button>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useState } from "react";

import type { NeighborhoodDetails } from "reducers/neighborhoods/types";
import formatNeighborhoodDataByViewType from "libs/formatNeighborhoodDataByCard";
import Button from "components/base/Button/Button";
import formatNeighborhoodDataByViewType from "libs/formatNeighborhoodDataByCard";
import type { NeighborhoodDetails } from "reducers/neighborhoods/types";

import { listStyles } from "./NeighborhoodsList.styles";
import { useTranslation } from "react-i18next";
import ClickableNeighborhoodCard from "../NeighborhoodCard/ClickableNeighborhoodCard";
import { listStyles } from "./NeighborhoodsList.styles";

interface Props {
neighborhoodDetailsMap: NeighborhoodDetails;
Expand Down Expand Up @@ -34,6 +35,7 @@ const NeighborhoodList = ({
groupCount,
button,
} = listStyles({ isMobile: isMobile, isGroup: isGroup });
const { t } = useTranslation();

// Display 10 Neighborhood cards per page.
// If it's a grouped-by-neighborhood-name list,
Expand All @@ -58,7 +60,8 @@ const NeighborhoodList = ({
<div className={groupContainer()}>
<p className={groupTitle()}>{groupName}</p>
<p className={groupCount()}>
{neighborhoodList.length} zip codes
{neighborhoodList.length}{" "}
{t("discoverNeighborhoods.groupedCountZipCodes")}
</p>
</div>
)}
Expand Down Expand Up @@ -99,7 +102,7 @@ const NeighborhoodList = ({
className={button()}
onClick={handleLoadMore}
>
Load More
{t("discoverNeighborhoods.groupedLoadMore")}
</Button>
)}
</div>
Expand Down
7 changes: 6 additions & 1 deletion src/frontend/src/components/SchoolMeter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ const SchoolMeter = ({
return isSchoolChoice ? (
<div className="flex flex-col gap-2 w-full">
<div className={labelContainer()}>
<AriaLabel className={mainLabel()}>{props.label}</AriaLabel>
<AriaLabel className={mainLabel()}>
{t([
"neighborhoodDetail.schoolsSafetyCard." +
props.labelKey,
])}
</AriaLabel>
</div>
<Link
href={schoolChoiceLink}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/components/YourTrips/YourTrips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const YourTrips = ({
(tripsFromDest: TripType[], dest: Destination) => [
...tripsFromDest,
{
title: dest.purpose,
title: t(["destinationPurposes." + dest.purpose]),
subtitle: dest.location.label,
neighborhoodZipcode: activeNeighborhood ?? "",
destination: dest,
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/src/components/base/Meter/Meter.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { tv } from "tailwind-variants";
export const meterStyles = tv({
slots: {
root: "flex flex-col gap-3 w-full ",
labelContainer: "flex flex-col",
labelContainer: "flex flex-col w-full",
mainLabel: "text-sm text-gray-600",
valueLabel: "text-sm font-bold text-black -mt-1 mb-[1px] text-nowrap",
valueLabel:
"text-sm font-bold text-black -mt-1 mb-[1px] text-nowrap truncate",
track: "absolute top-1 h-1 w-full rounded-[var(--spacing-1)] bg-gray-300",
fill: "absolute h-[6px] rounded-[var(--spacing-1)]",
thumb: "absolute rounded-[var(--spacing-1)] top-[3px] h-4 w-2 -translate-y-1/2 left-[calc(50%-2px)] bg-gray-500 border border-white",
Expand Down
23 changes: 18 additions & 5 deletions src/frontend/src/components/base/Meter/Meter.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {
Meter as AriaMeter,
Label as AriaLabel,
Meter as AriaMeter,
type MeterProps as AriaMeterProps,
} from "react-aria-components";

import { useTranslation } from "react-i18next";
import { meterStyles } from "./Meter.styles";

type MeterStatus = "Low" | "Below Avg" | "Average" | "Above Avg" | "High";

export interface MeterProps extends AriaMeterProps {
label: string;
labelKey: string;
showCategory?: boolean;
}

Expand All @@ -21,20 +22,32 @@ const getStatusFromValue = (value: number): MeterStatus => {
return "High";
};

const Meter = ({ label, showCategory = false, ...props }: MeterProps) => {
const Meter = ({ labelKey, showCategory = false, ...props }: MeterProps) => {
const { t } = useTranslation();
const status = getStatusFromValue(props.value ?? 0);
const { root, labelContainer, mainLabel, valueLabel, track, fill, thumb } =
meterStyles({ status });

const translatableLabelKey = status.split(" ")[0].toLowerCase();

return (
<AriaMeter {...props} className={root()}>
{({ percentage }) => (
<>
<div className={labelContainer()}>
<AriaLabel className={mainLabel()}>{label}</AriaLabel>
<AriaLabel className={mainLabel()}>
{t([
"neighborhoodDetail.schoolsSafetyCard." +
labelKey,
])}
</AriaLabel>
{showCategory && (
<AriaLabel className={valueLabel()}>
{status}
{t([
"neighborhoodDetail." +
"schoolsSafetyCard." +
translatableLabelKey,
])}
</AriaLabel>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/components/base/Range/Range.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const rangeStyles = tv({
slots: {
root: "flex flex-col gap-3 w-full",
labelContainer: "flex flex-col max-w-[80px]",
mainLabel: "text-sm text-gray-600",
mainLabel: "text-sm text-gray-600 truncate",
rangeLabel: "text-sm font-bold text-black truncate",
track: "absolute top-1 h-1 w-full rounded-[var(--spacing-1)] bg-gray-300",
fill: "flex flex-row justify-end absolute h-[6px] rounded-[var(--spacing-1)] bg-gray-500",
Expand Down
22 changes: 15 additions & 7 deletions src/frontend/src/components/base/Range/Range.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import DotIcon from "assets/icons/dots.svg?react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { rangeStyles } from "./Range.styles";
import DotIcon from "assets/icons/dots.svg?react";

const MIN_DEFAULT = 0;
const MAX_DEFAULT = 120;

export interface RangeProps {
label?: string;
labelKey?: string;
start: number;
end: number;
className?: string;
Expand All @@ -15,13 +16,14 @@ export interface RangeProps {
}

const Range = ({
label,
labelKey,
start,
end,
className,
min = MIN_DEFAULT,
max = MAX_DEFAULT,
}: RangeProps) => {
const { t } = useTranslation();
const { root, labelContainer, mainLabel, rangeLabel, track, fill, dots } =
rangeStyles();

Expand All @@ -32,15 +34,21 @@ const Range = ({
((Math.min(end, MAX_DEFAULT) - start) / totalRange) * 100 || 8;

const rangeText = useMemo(() => {
if (start >= MAX_DEFAULT) return "Over 2 hr";
if (isPoint) return `${start} min`;
return `${start}-${end > MAX_DEFAULT ? `${MAX_DEFAULT}+` : end} min`;
if (start >= MAX_DEFAULT) return t("discoverNeighborhoods.over2Hours");
if (isPoint) return `${start} ${t("discoverNeighborhoods.min")}`;
return `${start}-${end > MAX_DEFAULT ? `${MAX_DEFAULT}+` : end} ${t("discoverNeighborhoods.min")}`;
}, [start, end]);

return (
<div className={root({ className })}>
<div className={labelContainer()}>
{label && <span className={mainLabel()}>{label}</span>}
{labelKey && (
<span className={mainLabel()}>
{t([
"neighborhoodDetail.schoolsSafetyCard." + labelKey,
])}
</span>
)}
<span className={rangeLabel()}>{rangeText}</span>
</div>
<div className="relative">
Expand Down
Loading
Loading