mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-09-29 01:15:00 -04:00
* 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>
103 lines
3.3 KiB
React
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>
|
|
);
|
|
}
|