Files
growstuff/app/javascript/components/GardenCard.jsx
T
Brenda WallaceandClaude Opus 5 7e181982ff Upgrade Font Awesome to 6.7.2 (#4846)
* Restore the Ruby 4.0.6 upgrade

PR #4831 committed a stale Gemfile.lock over the newer one, taking
.ruby-version back to 3.2.3 and rolling back active_median, dalli,
parallel, rbs and selenium-webdriver. Dockerfile and
.devcontainer/Dockerfile still build on Ruby 4, so the Gemfile's version
check fails against the image.

Restores both files to what they were before that merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Upgrade Font Awesome from 5.15.1 to 6.7.2

Font names have moved on twice since this was pinned: the codebase still
carried Font Awesome 4 names that render as an empty box, and FA6 icons
such as fa-ellipsis-vertical were unavailable.

The 6.x gem no longer ships _font-awesome-sprockets.scss, but its
@font-face rules still call fa-font-path(), so application.scss defines
that against Sprockets' font-path helper. Its fonts live under
assets/fonts/font-awesome, which is already in the precompile list.

Icon names:
- The bare "fa" prefix is not a style in 6. Every "fa fa-<name>" becomes
  "fas fa-<name>" (38 call sites, all solid; the one brands icon already
  used fab).
- fa-sun-o was a Font Awesome 4 name and had been rendering as a box;
  now fa-sun.
- fa-pencil was likewise broken under 5, where it was fa-pencil-alt. It
  exists again in 6, so the existing name now works.
- activity_icon asked for 'fa-truck-pickup' through a helper that already
  adds the prefix, so it emitted "fa-fa-truck-pickup".

Verified that the solid, regular and brands fonts all resolve through
the asset pipeline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* couple more places to upgrade font awesome

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 13:39:38 +09:30

114 lines
4.6 KiB
React

import React from 'react';
import {isPlainClick} from '../events';
import ActionsMenu from './ActionsMenu';
import PlantingChip from './PlantingChip';
import PlantingRow from './PlantingRow';
// One garden: its name and actions menu (top right), a picture, then what is
// planted, as perennials (just names) and annuals (each with its progress).
// Matches gardens/_card.
export default function GardenCard({garden, defaultIconUrl, onPlant, highlightedId, highlightKind, onPlantingUpdated, onEditPlanting, onHarvestPlanting, onSaveSeedsPlanting, onAddPhoto, onFinishPlanting, onEditGarden}) {
const {id, name, url, image_url: imageUrl, owner, actions, plant_url: plantUrl, perennials, annuals} = garden;
const empty = perennials.length === 0 && annuals.length === 0;
// What each planting's menu items open, for annuals and perennials alike.
const handlers = {
edit: onEditPlanting,
harvest: onHarvestPlanting,
seeds: onSaveSeedsPlanting,
finish: onFinishPlanting,
photo: onAddPhoto && ((planting, action) => onAddPhoto({label: planting.crop.name, href: action.href})),
};
function plant(event) {
if (onPlant && isPlainClick(event)) {
event.preventDefault();
onPlant(garden);
}
}
return (
<div className="card garden-card" data-garden-id={id}>
<div className="card-header garden-card-header">
<div>
<h2 className="garden-card-title"><a href={url} name={`garden-${id}`}>{name}</a></h2>
{owner && <div className="garden-card-owner">owner: <a href={owner.url}>{owner.login_name}</a></div>}
</div>
<div className="garden-card-header-actions">
{plantUrl && (
<a href={plantUrl} className="btn btn-success btn-sm garden-add-planting" onClick={plant}>
<i className="fas fa-plus" aria-hidden="true" /> Add planting
<span className="visually-hidden"> to {name}</span>
</a>
)}
<ActionsMenu
id={`garden-${id}`}
actions={actions}
label={<i className="fas fa-ellipsis-v" aria-hidden="true" />}
ariaLabel={`Actions for ${name}`}
className="btn btn-sm btn-link actions-toggle-dots"
onSelect={(action, event) => {
if (!isPlainClick(event)) return;
if (action.key === 'photo' && onAddPhoto) {
event.preventDefault();
onAddPhoto({label: name, href: action.href});
} else if (action.key === 'edit' && onEditGarden) {
event.preventDefault();
onEditGarden(garden);
}
}}
/>
</div>
</div>
<div className="card-body garden-card-body">
<img src={imageUrl} alt={name} className="garden-card-image" />
<div className="garden-card-content">
{empty ? (
<p className="garden-card-none">Nothing planted here yet.</p>
) : (
<>
<section className="garden-card-section">
<h3 className="garden-card-heading">Perennials</h3>
{perennials.length > 0 ? (
<div className="garden-card-chips">
{perennials.map((planting) => (
<PlantingChip
key={planting.id}
planting={planting}
defaultIconUrl={defaultIconUrl}
highlighted={planting.id === highlightedId}
handlers={handlers}
/>
))}
</div>
) : (
<p className="garden-card-none">None</p>
)}
</section>
<section className="garden-card-section">
<h3 className="garden-card-heading">Annuals</h3>
{annuals.length > 0 ? (
<div className="planting-rows">
{annuals.map((planting) => (
<PlantingRow
key={planting.id}
planting={planting}
defaultIconUrl={defaultIconUrl}
highlighted={planting.id === highlightedId}
highlightKind={highlightKind}
onUpdated={onPlantingUpdated}
handlers={handlers}
/>
))}
</div>
) : (
<p className="garden-card-none">None</p>
)}
</section>
</>
)}
</div>
</div>
</div>
);
}