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
24 changes: 11 additions & 13 deletions apps/web/src/features/curation-desk/curation-queue-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,26 @@ interface Props extends RowActions {
scalePct: number;
}

const NEW_ACCOUNT_DAYS = 30;

function accountAgeDays(authorCreated: string | null | undefined, now: number): number | null {
const ms = parseChainDate(authorCreated);
if (ms == null) return null;
return Math.floor((now - ms) / 86_400_000);
}

function formatAge(days: number): string {
if (days >= 365) return i18next.t("curation-desk.row.age-years", { count: Math.floor(days / 365) });
if (days >= 30) return i18next.t("curation-desk.row.age-months", { count: Math.floor(days / 30) });
return i18next.t("curation-desk.row.age-days", { count: days });
}

/**
* Account age of the author, amber under 30 days. Its own memo child on the
* shared clock, so the day counter never re-renders the row around it.
* Warns only while the author's account is under 30 days old. The plain age of
* an established account is not shown here: the author hover card already
* carries reputation and the joined date, so the byline would just repeat it.
* Its own memo child on the shared clock, so the day counter never re-renders
* the row around it.
*/
export const AuthorAgeChip = memo(function AuthorAgeChip({ authorCreated }: { authorCreated: string | null | undefined }) {
export const NewAccountChip = memo(function NewAccountChip({ authorCreated }: { authorCreated: string | null | undefined }) {
const now = useCurationTicker();
const days = accountAgeDays(authorCreated, now);
if (days == null) return null;
return <span className={clsx(days < 30 && "text-warning-ink dark:text-warning-default")}>{formatAge(days)}</span>;
if (days == null || days >= NEW_ACCOUNT_DAYS) return null;
return <Chip tone="amber">{i18next.t("curation-desk.row.new-account")}</Chip>;
});

function appLabel(app: string | null): string {
Expand Down Expand Up @@ -242,8 +241,7 @@ export const CurationQueueRow = memo(function CurationQueueRow(props: Props) {
<ProfilePopover entry={entryStub} />
</span>
</span>
{row.rep != null && <span>{i18next.t("curation-desk.row.rep", { rep: row.rep })}</span>}
<AuthorAgeChip authorCreated={row.author_created} />
<NewAccountChip authorCreated={row.author_created} />
{row.is_new_author && (
<Chip tone="green">{i18next.t("curation-desk.row.new-author", { n: row.author_post_count ?? 1 })}</Chip>
)}
Expand Down
4 changes: 1 addition & 3 deletions apps/web/src/features/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4482,9 +4482,7 @@
"untitled": "@{{author}}'s post",
"describe": "By @{{author}}, {{words}} words, curation window {{window}}",
"rep": "rep {{rep}}",
"age-days": "{{count}} d",
"age-months": "{{count}} mo",
"age-years": "{{count}} y",
"new-account": "New account",
"new-author": "New · post #{{n}}",
"app-unknown": "unknown app",
"no-community": "no community",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,22 @@ describe("author account age", () => {

it("warns in amber for an account younger than 30 days", () => {
renderRow(makeRow({ post_id: 5, author_created: new Date(Date.now() - 9 * 24 * HOUR).toISOString() }));
const chip = screen.getByText("curation-desk.row.age-days");
expect(chip.className).toContain("text-warning-ink");
expect(screen.getByText("curation-desk.row.new-account").className).toContain("text-warning-ink");
});

it("stays neutral for an older account and renders nothing without author_created", () => {
it("says nothing for an established account or a missing creation date", () => {
const { unmount } = renderRow(makeRow({ post_id: 6, author_created: new Date(Date.now() - 800 * 24 * HOUR).toISOString() }));
expect(screen.getByText("curation-desk.row.age-years").className).not.toContain("text-warning-ink");
expect(screen.queryByText("curation-desk.row.new-account")).toBeNull();
unmount();

renderRow(makeRow({ post_id: 7, author_created: null }));
expect(screen.queryByText("curation-desk.row.age-days")).toBeNull();
expect(screen.queryByText("curation-desk.row.age-years")).toBeNull();
expect(screen.queryByText("curation-desk.row.new-account")).toBeNull();
});

// The hover card on the author carries reputation and the joined date, so the
// byline no longer repeats them.
it("keeps reputation and plain account age out of the byline", () => {
renderRow(makeRow({ post_id: 8, rep: 73, author_created: new Date(Date.now() - 800 * 24 * HOUR).toISOString() }));
expect(screen.queryByText("curation-desk.row.rep")).toBeNull();
});
});
Loading