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
20 changes: 10 additions & 10 deletions app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,21 @@ def destroy
end
end

SHIPPING_PAUSE_START = Time.parse("2026-05-10 22:00:00 UTC").freeze
SHIPPING_RESUME = Time.parse("2026-05-11 22:00:00 UTC").freeze
REJECTION_EXCEPTION_START = (SHIPPING_PAUSE_START - 2.days).freeze
# Shipping locked for good when the game ended on July 6th, 2026. Projects
# rejected after the lock may still be re-shipped, so their authors can
# address the rejection.
SHIPPING_LOCKED_AT = Time.parse("2026-07-06 00:00:00 UTC").freeze

def ship
authorize @project

if Time.current >= SHIPPING_PAUSE_START && Time.current < SHIPPING_RESUME
unless recently_rejected_reship? || current_user.admin?
if Time.current >= SHIPPING_LOCKED_AT
unless rejected_after_lock? || current_user.admin?
track_event("project_ship_failed", {
project_id: @project.id,
reason: "shipping_paused"
reason: "shipping_locked"
})
redirect_to manage_project_path(@project), flash: { alert: "Shipping is temporarily paused as reviewers work to review projects of people who are qualifying for the HCTG event. It will be unpaused at 6:00pm ET on May 11th, 2026." }
redirect_to manage_project_path(@project), flash: { alert: "Shipping is locked now that the game has ended. Projects rejected after shipping locked can still be re-shipped." }
return
end
end
Expand Down Expand Up @@ -216,11 +217,10 @@ def allow_reship

private

def recently_rejected_reship?
def rejected_after_lock?
@project.aasm_state == "rejected" &&
@project.rejected_at.present? &&
@project.rejected_at >= REJECTION_EXCEPTION_START &&
@project.rejected_at < SHIPPING_PAUSE_START
@project.rejected_at >= SHIPPING_LOCKED_AT
end

def project_params
Expand Down
42 changes: 20 additions & 22 deletions app/frontend/components/projects/ProjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type { ProjectTag } from "@/interfaces/project_tag";
import type { SharedProps } from "@/types";
import formatTime from "@/utils/formatTime";
import {
isShippingPaused,
isShippingPausedForProject,
SHIPPING_PAUSE_MESSAGE,
} from "@/utils/shippingPause";
isShippingLocked,
isShippingLockedForProject,
SHIPPING_LOCK_MESSAGE,
} from "@/utils/shippingLock";
import { useMemo, useState, useEffect, useRef, useCallback } from "react";
import arrowIcon from "@/assets/icons/arrow.svg";
import clsx from "clsx";
Expand Down Expand Up @@ -274,13 +274,13 @@ export default function ProjectForm({
const disabled = project?.aasm_state === "submitted";
const idvVerified = props.user.verification_status === "verified";
const isAdmin = props.user.is_admin;
const shippingPaused = isAdmin
const shippingLocked = isAdmin
? false
: project
? isShippingPausedForProject(project)
: isShippingPaused();
? isShippingLockedForProject(project)
: isShippingLocked();
const hasRejectionException =
!isAdmin && isShippingPaused() && !shippingPaused;
!isAdmin && isShippingLocked() && !shippingLocked;
const rejectedAwaitingReship = project?.needs_reship_allowance ?? false;
const shipsGloballyDisabled =
!props.user.ships_enabled &&
Expand Down Expand Up @@ -670,20 +670,20 @@ export default function ProjectForm({
<button
className={clsx(
"group flex h-[59px] w-full items-center justify-center gap-3 text-xl font-bold transition-colors",
shippingPaused || shipStopped
shippingLocked || shipStopped
? "cursor-not-allowed bg-gray-300 text-gray-500"
: "cursor-pointer bg-[#fecb0d] text-black hover:bg-[#e5b80b] disabled:cursor-not-allowed disabled:opacity-50",
)}
type="button"
onClick={
shippingPaused || shipStopped
shippingLocked || shipStopped
? undefined
: openShipChecklist
}
disabled={
processing ||
(!isAdmin && !idvVerified) ||
shippingPaused ||
shippingLocked ||
shipStopped
}
>
Expand Down Expand Up @@ -714,31 +714,29 @@ export default function ProjectForm({
Delete
</button>
</div>
{shippingPaused &&
{shippingLocked &&
project.reported_seconds > project.approved_seconds && (
<p className="rounded-lg border border-gray-300 bg-gray-100 px-4 py-3 text-sm text-gray-700">
{SHIPPING_PAUSE_MESSAGE}
{SHIPPING_LOCK_MESSAGE}
</p>
)}
{hasRejectionException &&
project.reported_seconds > project.approved_seconds && (
<p className="rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-800">
Shipping is temporarily paused, but you can re-ship this
project because it was rejected in the two days before the
pause began. Note: if your project is rejected again while
shipping is paused, you will not be able to re-ship it
until the pause ends at 5:00pm ET on May 11th, 2026.
Shipping is locked now that the game has ended, but you
can re-ship this project because it was rejected after
shipping locked.
</p>
)}
{!shippingPaused &&
{!shippingLocked &&
rejectedAwaitingReship &&
project.reported_seconds > project.approved_seconds && (
<p className="rounded-lg border-2 border-red-300 bg-red-50 px-4 py-3 text-base font-semibold text-red-800">
This project was rejected. A reviewer needs to allow it
to be reshipped before you can resubmit it.
This project was rejected. A reviewer needs to allow it to
be reshipped before you can resubmit it.
</p>
)}
{!shippingPaused &&
{!shippingLocked &&
!rejectedAwaitingReship &&
shipsGloballyDisabled &&
project.reported_seconds > project.approved_seconds && (
Expand Down
20 changes: 10 additions & 10 deletions app/frontend/pages/projects/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import IdvVerificationAlert from "@/components/IdvVerificationAlert";
import PageHeading from "@/components/layout/PageHeading";
import ProjectList from "@/components/projects/ProjectList";
import {
isShippingPaused,
isShippingPausedForProject,
SHIPPING_PAUSE_MESSAGE,
} from "@/utils/shippingPause";
isShippingLocked,
isShippingLockedForProject,
SHIPPING_LOCK_MESSAGE,
} from "@/utils/shippingLock";
import { usePage } from "@inertiajs/react";
import type { SharedProps } from "@/types";

Expand All @@ -15,10 +15,10 @@ import type { Project } from "@/interfaces/project";
export default function Index({ projects }: { projects: Project[] }) {
const { props } = usePage<SharedProps>();
const isAdmin = props.user.is_admin;
const allPaused =
const allLocked =
!isAdmin &&
isShippingPaused() &&
projects.every((p) => isShippingPausedForProject(p));
isShippingLocked() &&
projects.every((p) => isShippingLockedForProject(p));

return (
<Layout>
Expand All @@ -29,10 +29,10 @@ export default function Index({ projects }: { projects: Project[] }) {
<div className="mt-8 flex flex-col gap-8 pl-8">
<IdvVerificationAlert />

{allPaused && (
{allLocked && (
<div className="rounded-xl border border-gray-300 bg-gray-100 p-6 text-gray-800">
<span className="text-xl font-bold">Shipping paused</span>
<p className="mt-1 text-lg">{SHIPPING_PAUSE_MESSAGE}</p>
<span className="text-xl font-bold">Shipping locked</span>
<p className="mt-1 text-lg">{SHIPPING_LOCK_MESSAGE}</p>
</div>
)}

Expand Down
24 changes: 24 additions & 0 deletions app/frontend/utils/shippingLock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Shipping locked for good when the game ended on July 6th, 2026. Projects
// rejected after the lock may still be re-shipped, so their authors can
// address the rejection. Must match SHIPPING_LOCKED_AT in projects_controller.rb.
const SHIPPING_LOCKED_AT = new Date("2026-07-06T00:00:00Z");

export const SHIPPING_LOCK_MESSAGE =
"Shipping is locked now that the game has ended. Projects rejected after shipping locked can still be re-shipped.";

export function isShippingLocked(): boolean {
return new Date() >= SHIPPING_LOCKED_AT;
}

export function isShippingLockedForProject(project: {
aasm_state: string;
rejected_at: string | null;
}): boolean {
if (!isShippingLocked()) return false;
if (project.aasm_state === "rejected" && project.rejected_at !== null) {
if (new Date(project.rejected_at) >= SHIPPING_LOCKED_AT) {
return false;
}
}
return true;
}
31 changes: 0 additions & 31 deletions app/frontend/utils/shippingPause.ts

This file was deleted.

Loading