Files
growstuff/app/javascript/components/PlantedDate.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

103 lines
3.3 KiB
React

import React, {useEffect, useRef, useState} from 'react';
import {patchJson} from '../api';
import {formatDate} from '../dates';
function errorMessage(status, data) {
if (status === 422 && data && data.errors) {
const [field, messages] = Object.entries(data.errors)[0] || [];
if (field) return `${field.replace(/_/g, ' ').replace(/^./, (c) => c.toUpperCase())} ${messages[0]}`;
}
return 'Couldn\'t save the date. Please try again.';
}
// "Planted 12 Sep". When the viewer owns the planting it is a button with a
// pencil that opens the browser's own date picker, saved with the tick (or
// Enter) and dropped with the cross (or Escape). `onUpdated` gets the garden's
// refreshed card, so the row's predictions follow the new date.
export default function PlantedDate({planting, onUpdated}) {
const {url, planted_at: plantedAt, can_edit: canEdit} = planting;
const [editing, setEditing] = useState(false);
const [value, setValue] = useState(plantedAt || '');
const [saving, setSaving] = useState(false);
const [error, setError] = useState(null);
const input = useRef(null);
useEffect(() => {
if (editing && input.current) input.current.focus();
}, [editing]);
if (!canEdit) return plantedAt ? <span className="planting-row-planted">Planted {formatDate(plantedAt)}</span> : null;
function open() {
setValue(plantedAt || '');
setError(null);
setEditing(true);
}
function cancel() {
setEditing(false);
setError(null);
}
async function save() {
if (!value || value === plantedAt) return cancel();
setSaving(true);
setError(null);
try {
const {ok, status, data} = await patchJson(url, {planting: {planted_at: value}});
if (ok) {
setSaving(false);
setEditing(false);
onUpdated(data.garden);
return;
}
setError(errorMessage(status, data));
} catch (failure) {
setError('Couldn\'t reach the server. Please try again.');
}
setSaving(false);
}
function onKeyDown(event) {
if (event.key === 'Enter') {
event.preventDefault();
save();
} else if (event.key === 'Escape') {
cancel();
}
}
if (!editing) {
return (
<button type="button" className="planting-row-planted planting-date-button" onClick={open} title="Change the planted date">
{plantedAt ? `Planted ${formatDate(plantedAt)}` : 'Set planted date'}
<i className="fas fa-pencil" aria-hidden="true" />
</button>
);
}
return (
<span className="planting-date-editor">
<input
ref={input}
type="date"
className="form-control form-control-sm"
aria-label="Planted date"
value={value}
disabled={saving}
onChange={(event) => setValue(event.target.value)}
onKeyDown={onKeyDown}
/>
<button type="button" className="btn btn-sm btn-link" onClick={save} disabled={saving || !value} aria-label="Save planted date">
<i className="fas fa-check" aria-hidden="true" />
</button>
<button type="button" className="btn btn-sm btn-link" onClick={cancel} disabled={saving} aria-label="Cancel">
<i className="fas fa-times" aria-hidden="true" />
</button>
{error && <small className="planting-date-error" role="alert">{error}</small>}
</span>
);
}