Skip to content
Merged

Dev #2170

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
7 changes: 7 additions & 0 deletions apps/address-enrichment/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @anticapture/address-enrichment

## 1.1.3

### Patch Changes

- Updated dependencies [[`5c3da3b`](https://github.com/blockful/anticapture/commit/5c3da3b399569ac8046403c46c5264d0802b73c4)]:
- @anticapture/observability@1.1.0

## 1.1.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion apps/address-enrichment/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anticapture/address-enrichment",
"version": "1.1.2",
"version": "1.1.3",
"private": true,
"scripts": {
"dev": "tsx watch src/index.ts",
Expand Down
7 changes: 7 additions & 0 deletions apps/api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @anticapture/api

## 1.8.4

### Patch Changes

- Updated dependencies [[`5c3da3b`](https://github.com/blockful/anticapture/commit/5c3da3b399569ac8046403c46c5264d0802b73c4)]:
- @anticapture/observability@1.1.0

## 1.8.3

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anticapture/api",
"version": "1.8.3",
"version": "1.8.4",
"private": true,
"main": "dist/index.js",
"type": "module",
Expand Down
7 changes: 7 additions & 0 deletions apps/authful/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @anticapture/authful

## 0.6.1

### Patch Changes

- Updated dependencies [[`5c3da3b`](https://github.com/blockful/anticapture/commit/5c3da3b399569ac8046403c46c5264d0802b73c4)]:
- @anticapture/observability@1.1.0

## 0.6.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion apps/authful/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anticapture/authful",
"version": "0.6.0",
"version": "0.6.1",
"private": true,
"type": "module",
"main": "dist/index.js",
Expand Down
12 changes: 12 additions & 0 deletions apps/dashboard/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @anticapture/dashboard

## 2.17.0

### Minor Changes

- [#2168](https://github.com/blockful/anticapture/pull/2168) [`ac8e8a0`](https://github.com/blockful/anticapture/commit/ac8e8a00d5ba97c4afd1e8ab1ebd22ffef43fef8) Thanks [@alextnetto](https://github.com/alextnetto)! - Add the Security Council card to the Shutter DAO overview: the 5-of-8 multisig that owns the Snapshot X space and vetoes proposals via `cancel` before execution. Support councils with no fixed term ("No expiration") in the card.

### Patch Changes

- [#2169](https://github.com/blockful/anticapture/pull/2169) [`68414b8`](https://github.com/blockful/anticapture/commit/68414b873120e6288afc27036b020c0f36100885) Thanks [@brunod-e](https://github.com/brunod-e)! - Fix the whitelabel delegates table clipping the Delegate button and scrolling horizontally, and stop the hidden row actions from squeezing the address name

- [#2167](https://github.com/blockful/anticapture/pull/2167) [`f705ff8`](https://github.com/blockful/anticapture/commit/f705ff8d5066370b793c332b3c244cebf361f9a9) Thanks [@alextnetto](https://github.com/alextnetto)! - Service providers page: respect each program's report deadline (`reportDueDays` in `ensdao/spp`), so SPP3 reports are due 30 days after each quarter ends, per its Program Terms

## 2.16.0

### Minor Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export const CountdownDaoInfo = ({
const targetTimestamp = securityCouncil?.expiration.timestamp;
const countdown = useCountdown(targetTimestamp);

// timestamp === 0 marks a council with no fixed term: show a static label
// instead of a countdown.
const noExpiration =
!!securityCouncil && !securityCouncil.expiration.timestamp;

const formattedCountdown = useMemo(() => {
if (!countdown || countdown.isLoading) return null;
return {
Expand All @@ -25,6 +30,14 @@ export const CountdownDaoInfo = ({
};
}, [countdown]);

if (noExpiration) {
return (
<p className="text-primary text-sm font-normal leading-5">
No expiration
</p>
);
}

if (!formattedCountdown) return null;

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export const SecurityCouncilCard = ({
const { securityCouncil } = daoOverview;

const progress = useMemo(() => {
if (!securityCouncil) return 0;
// timestamp === 0 marks a council with no fixed term: no progress to show.
if (!securityCouncil || !securityCouncil.expiration.timestamp) return 0;
Comment thread
brunod-e marked this conversation as resolved.
Resolved
const start = new Date(securityCouncil.expiration.startDate).getTime();
const end = new Date(securityCouncil.expiration.date).getTime();
const now = Date.now();
Expand All @@ -29,7 +30,8 @@ export const SecurityCouncilCard = ({
}, [securityCouncil]);

const warning = useMemo(() => {
if (!securityCouncil) return 0;
// No fixed term (timestamp === 0) means no danger zone to render.
if (!securityCouncil || !securityCouncil.expiration.timestamp) return 0;

const start = new Date(securityCouncil.expiration.startDate).getTime();
const end = new Date(securityCouncil.expiration.date).getTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,9 @@ export const Delegates = ({
/>
</div>
{!isMobile && (
<div className="flex items-center opacity-0 transition-opacity [tr:hover_&]:opacity-100">
// Only in the flow while hovered, so the invisible actions do
// not reserve ~130px next to the name on every row.
<div className="hidden items-center [tr:hover_&]:flex">
<CopyAndPasteButton
textToCopy={address as `0x${string}`}
customTooltipText={{
Expand Down Expand Up @@ -471,7 +473,7 @@ export const Delegates = ({
</Button>
),
meta: {
columnClassName: isWhitelabel ? "w-[13%]" : "w-[16%]",
columnClassName: isWhitelabel ? "w-[12%]" : "w-[16%]",
},
},
{
Expand Down Expand Up @@ -528,7 +530,7 @@ export const Delegates = ({
</h4>
),
meta: {
columnClassName: isWhitelabel ? "w-[10%]" : "w-[11%]",
columnClassName: isWhitelabel ? "w-[9%]" : "w-[11%]",
},
},
{
Expand Down Expand Up @@ -579,7 +581,7 @@ export const Delegates = ({
</div>
),
meta: {
columnClassName: isWhitelabel ? "w-[13%]" : "w-[16%]",
columnClassName: isWhitelabel ? "w-[12%]" : "w-[16%]",
},
},
{
Expand Down Expand Up @@ -622,7 +624,7 @@ export const Delegates = ({
</Button>
),
meta: {
columnClassName: isWhitelabel ? "w-[11%]" : "w-[12%]",
columnClassName: isWhitelabel ? "w-[10%]" : "w-[12%]",
},
},
...(isWhitelabel
Expand Down Expand Up @@ -652,7 +654,7 @@ export const Delegates = ({
},
header: () => null,
meta: {
columnClassName: "w-24 px-2",
columnClassName: "w-40 px-2",
},
} satisfies ColumnDef<DelegateTableData>,
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ export const ServiceProvidersTable = ({
const isYear2 = year2ColIndex !== undefined;
const isCurrentQuarter =
col.year === currentYear && col.quarter === currentQuarter;
const dueDateLabel = getDueDateLabel(col.quarter);
const dueDateLabel = getDueDateLabel(
col.year,
col.quarter,
program.reportDueDays,
);

return {
id: key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const buildProviders = (
githubSlug: entry.slug,
streamDuration: programEntry.streamDuration,
years: Object.fromEntries(
Object.entries(result.data)
Object.entries(result.data[programKey] ?? {})
.filter(([, slugData]) => slugData[entry.slug])
.map(([year, slugData]) => [year, slugData[entry.slug]]),
),
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/features/service-providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export type ProgramConfig = {
year2Quarters?: string[];
budget: number;
startDate: string;
/** Days after each quarter ends that its report is due, per the program's terms. */
reportDueDays?: number;
discussionUrl: string;
budgetProposal: ProgramProposal;
selectionProposal: ProgramProposal;
Expand Down Expand Up @@ -78,6 +80,7 @@ export type ProgramDefinition = {
name: string;
year1Quarters: ParsedQuarter[];
year2Quarters: ParsedQuarter[];
reportDueDays: number;
discussionUrl: string;
budgetProposal: ProgramProposal;
selectionProposal: ProgramProposal;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
computeQuarterStatus,
getDueDate,
getDueDateLabel,
} from "@/features/service-providers/utils/computeQuarterStatus";

describe("getDueDate", () => {
test("defaults to the last second of the quarter", () => {
expect(getDueDate(2026, "Q3").toISOString()).toBe(
"2026-09-30T23:59:59.000Z",
);
});

test("adds the program's report window after the quarter ends", () => {
expect(getDueDate(2026, "Q3", 30).toISOString()).toBe(
"2026-10-30T23:59:59.000Z",
);
});

test("rolls a Q4 report window into the next year", () => {
expect(getDueDate(2026, "Q4", 30).toISOString()).toBe(
"2027-01-30T23:59:59.000Z",
);
});
});

describe("getDueDateLabel", () => {
test("labels the quarter end when there is no report window", () => {
expect(getDueDateLabel(2026, "Q3")).toBe("Due by Sep 30");
});

test("labels the actual deadline when there is a report window", () => {
expect(getDueDateLabel(2026, "Q3", 30)).toBe("Due by Oct 30");
expect(getDueDateLabel(2026, "Q4", 30)).toBe("Due by Jan 30");
});
});

describe("computeQuarterStatus", () => {
const oct5 = new Date("2026-10-05T12:00:00Z");

test("is overdue the day after the quarter ends without a report window", () => {
expect(computeQuarterStatus(2026, "Q3", oct5)).toBe("overdue");
});

test("is still due soon inside the program's report window", () => {
expect(computeQuarterStatus(2026, "Q3", oct5, 30)).toBe("due_soon");
});

test("is overdue once the report window has passed", () => {
expect(
computeQuarterStatus(2026, "Q3", new Date("2026-10-31T00:00:00Z"), 30),
).toBe("overdue");
});

test("is upcoming more than 30 days before the deadline", () => {
expect(
computeQuarterStatus(2026, "Q3", new Date("2026-09-28T00:00:00Z"), 30),
).toBe("upcoming");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,37 @@ const QUARTER_END_DATES: Record<QuarterKey, { month: number; day: number }> = {
Q4: { month: 12, day: 31 },
};

export const getDueDate = (year: number, quarter: QuarterKey): Date => {
// reportDueDays: days after the quarter ends that the program gives providers
// to publish the report (e.g. SPP3 Program Terms §6.3: 30 days).
export const getDueDate = (
year: number,
quarter: QuarterKey,
reportDueDays = 0,
): Date => {
const { month, day } = QUARTER_END_DATES[quarter];
return new Date(Date.UTC(year, month - 1, day, 23, 59, 59));
return new Date(Date.UTC(year, month - 1, day + reportDueDays, 23, 59, 59));
};

export const getDueDateLabel = (quarter: QuarterKey): string => {
const { month, day } = QUARTER_END_DATES[quarter];
const monthName = new Date(2000, month - 1).toLocaleString("en-US", {
export const getDueDateLabel = (
year: number,
quarter: QuarterKey,
reportDueDays = 0,
): string => {
const dueDate = getDueDate(year, quarter, reportDueDays);
const monthName = dueDate.toLocaleString("en-US", {
month: "short",
timeZone: "UTC",
});
return `Due by ${monthName} ${day}`;
return `Due by ${monthName} ${dueDate.getUTCDate()}`;
};

export const computeQuarterStatus = (
year: number,
quarter: QuarterKey,
now: Date,
reportDueDays = 0,
): ReportStatus => {
const deadline = getDueDate(year, quarter);
const deadline = getDueDate(year, quarter, reportDueDays);

if (now > deadline) return "overdue";

Expand Down
Loading
Loading