feat: add race cover image extraction
Some checks failed
CI / build-and-test (pull_request) Has been cancelled

This commit is contained in:
Vaka.pro
2026-04-27 22:56:41 +03:00
parent 0b7ad23252
commit 0153f223f2
15 changed files with 295 additions and 8 deletions

View File

@@ -23,6 +23,7 @@ function normalizeRace(input: unknown): Race {
race?.status === "registered" ||
race?.status === "completed") &&
isNullableString(race?.officialUrl) &&
isNullableString(race?.coverImageUrl) &&
isNullableString(race?.startTime) &&
isNullableString(race?.clusterSchedule) &&
isNullableString(race?.bibPickup) &&
@@ -48,6 +49,7 @@ function normalizeRace(input: unknown): Race {
distanceKm: race.distanceKm,
status: race.status,
officialUrl: race.officialUrl,
coverImageUrl: race.coverImageUrl,
startTime: race.startTime,
clusterSchedule: race.clusterSchedule,
bibPickup: race.bibPickup,

View File

@@ -7,6 +7,7 @@ export interface Race {
distanceKm: number;
status: RaceStatus | null;
officialUrl: string | null;
coverImageUrl: string | null;
startTime: string | null;
clusterSchedule: string | null;
bibPickup: string | null;
@@ -30,6 +31,7 @@ export interface CreateRacePayload {
distanceKm: number;
status?: RaceStatus | null;
officialUrl?: string | null;
coverImageUrl?: string | null;
startTime?: string | null;
clusterSchedule?: string | null;
bibPickup?: string | null;

View File

@@ -187,6 +187,15 @@ function getFallbackRaceVisual(race: Race): RaceVisual {
export function getRaceVisual(race: Race): RaceVisual {
const fallback = getFallbackRaceVisual(race);
if (race.coverImageUrl) {
return {
...fallback,
imageSrc: race.coverImageUrl,
fallbackSrc: fallback.imageSrc,
};
}
const title = normalizeTitle(race.title);
const official = OFFICIAL_VISUALS.find((visual) =>
visual.keywords.some((keyword) => title.includes(normalizeTitle(keyword))),

View File

@@ -31,6 +31,7 @@ interface FormData {
distanceKm: string;
status: string;
officialUrl: string;
coverImageUrl: string;
startTime: string;
clusterSchedule: string;
bibPickup: string;
@@ -46,6 +47,7 @@ const EMPTY_FORM: FormData = {
distanceKm: "",
status: "planned",
officialUrl: "",
coverImageUrl: "",
startTime: "",
clusterSchedule: "",
bibPickup: "",
@@ -63,6 +65,7 @@ function raceToFormData(race: Race): FormData {
distanceKm: String(race.distanceKm),
status: race.status ?? "",
officialUrl: race.officialUrl ?? "",
coverImageUrl: race.coverImageUrl ?? "",
startTime: race.startTime ?? "",
clusterSchedule: race.clusterSchedule ?? "",
bibPickup: race.bibPickup ?? "",
@@ -197,6 +200,7 @@ export function RaceFormPage(): JSX.Element {
distanceKm: parseFloat(form.distanceKm),
status: statusValue,
officialUrl: emptyToNull(form.officialUrl),
coverImageUrl: emptyToNull(form.coverImageUrl),
startTime: emptyToNull(form.startTime),
clusterSchedule: emptyToNull(form.clusterSchedule),
bibPickup: emptyToNull(form.bibPickup),
@@ -217,6 +221,7 @@ export function RaceFormPage(): JSX.Element {
distanceKm: parseFloat(form.distanceKm),
status: statusValue,
officialUrl: emptyToNull(form.officialUrl),
coverImageUrl: emptyToNull(form.coverImageUrl),
startTime: emptyToNull(form.startTime),
clusterSchedule: emptyToNull(form.clusterSchedule),
bibPickup: emptyToNull(form.bibPickup),
@@ -346,6 +351,18 @@ export function RaceFormPage(): JSX.Element {
</label>
)}
<label className="race-form__field">
<span className="race-form__label">URL обложки</span>
<input
className="race-form__input"
type="url"
name="coverImageUrl"
value={form.coverImageUrl}
onChange={handleChange}
placeholder="https://…"
/>
</label>
{hideOrgScheduleFields ? null : (
<div className="race-form__field">
<span className="race-form__label">Время старта</span>