Skip to content

Commit 0967397

Browse files
committed
feat: first iteration of the new ledger page
1 parent 1ef9f1b commit 0967397

File tree

15 files changed

+441
-5
lines changed

15 files changed

+441
-5
lines changed

app/(account)/confirm-email/[token]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const MailConfirmationPage = () => {
2020
fetchData().then(r => {
2121
setResponse(r);
2222
});
23-
}, []);
23+
}, [token]);
2424

2525
if (!token) {
2626
return <main>

app/(account)/logout/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const LogoutPage = () => {
1111
authState.logout().then(() => {
1212
redirect("login");
1313
});
14-
}, []);
14+
}, [authState]);
1515
}
1616

1717
export default LogoutPage;

app/(home)/latestNews.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import classNames from "classnames";
33
import Container from "../common/uiLibrary/container";
44
import PageSectionTitle from "../common/uiLibrary/pageSectionTitle";
55
import LinkButton from "../common/uiLibrary/linkButton";
6+
import Image from "next/image";
67

78
interface PostPreviewCardProps {
89
post: BlogPost,
@@ -30,6 +31,7 @@ const PostPreviewImage = ({post, isMain = false, className}: PostPreviewCardProp
3031
return (
3132
<div className={classNames(outerContainerStyles, {className})}>
3233
<div className={innerContainerStyles}>
34+
{/* eslint-disable-next-line @next/next/no-img-element */}
3335
<img
3436
src={post.socials_image}
3537
alt={post.title}

app/common/defaultNavbar.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export default function DefaultNavbar() {
5757
<Link href='/changelog'>
5858
<p>Changelog</p>
5959
</Link>
60+
<Link href='/ledger'>
61+
<p>Ledger</p>
62+
</Link>
6063
<Navbar.Link href={playerWiki} target="_blank">
6164
<div className="flex flex-row gap-1">
6265
<p>Player&apos;s wiki</p>
@@ -73,5 +76,3 @@ export default function DefaultNavbar() {
7376
</Navbar>
7477
)
7578
}
76-
77-

app/ledger/page.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {LedgerApiProvider} from "../../context/ledger/LedgerApiProvider";
2+
import LedgerPresentation from "./presentation";
3+
import {LedgerTableProvider} from "../../context/ledger/LedgerDataTableProvider";
4+
5+
const LedgerPage = () => {
6+
return (
7+
<LedgerApiProvider>
8+
<LedgerTableProvider>
9+
<LedgerPresentation />
10+
</LedgerTableProvider>
11+
</LedgerApiProvider>
12+
)
13+
}
14+
15+
export default LedgerPage;

app/ledger/presentation.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use client';
2+
3+
import DataTable from "../../components/organisms/DataTable";
4+
import {useLedgerTableContext} from "../../context/ledger/LedgerDataTableProvider";
5+
import Container from "../common/uiLibrary/container";
6+
import PageHeading from "../common/uiLibrary/PageHeading";
7+
import {useLedgerApiProvider} from "../../context/ledger/LedgerApiProvider";
8+
import Panel from "../common/uiLibrary/panel";
9+
import {RiPatreonFill} from "react-icons/ri";
10+
import {FaPaypal} from "react-icons/fa";
11+
import {PATREON_URL, PAYPAL_DONATION_URL} from "../../utils/urlContants";
12+
13+
export default function LedgerPresentation() {
14+
const content = useLedgerTableContext();
15+
const {hasNextPage, hasPreviousPage, goToPreviousPage, goToNextPage, currentBalance} = useLedgerApiProvider();
16+
17+
return (
18+
<Container>
19+
<PageHeading>Funding Ledger</PageHeading>
20+
<div className="flex justify-between gap-4">
21+
<Panel className="rounded-lg w-50">
22+
<div className="text-lg font-semibold text-gray-200">Current Balance</div>
23+
<div className="text-3xl font-bold text-green-400">
24+
${currentBalance}
25+
</div>
26+
<p className="mt-2 text-sm text-gray-400">
27+
This is the amount currently available in Unitystation’s project fund.
28+
It updates manually after we receive a donation or withdraw from Patreon.
29+
</p>
30+
<p className="mt-2 text-sm text-gray-400">
31+
If your donation is not listed yet, it will appear soon once we update the ledger.
32+
</p>
33+
</Panel>
34+
<Panel className="rounded-lg">
35+
<div className="text-lg font-semibold text-gray-200 mb-2">
36+
Where does our funding come from?
37+
</div>
38+
39+
<p className="text-sm text-gray-400 mb-4">
40+
Unitystation is sustained entirely through community support; whether by backing us on Patreon or sending direct donations. Every contribution helps cover hosting, development, and infrastructure.
41+
</p>
42+
43+
<div className="flex gap-4 items-center mt-4">
44+
<a
45+
href={PATREON_URL}
46+
target="_blank"
47+
rel="noopener noreferrer"
48+
className="flex items-center gap-2 px-4 py-2 text-white bg-[#ff424d] rounded hover:bg-[#e63946] transition"
49+
>
50+
<RiPatreonFill size={20} />
51+
Support us on Patreon
52+
</a>
53+
54+
<a
55+
href={PAYPAL_DONATION_URL}
56+
className="flex items-center gap-2 px-4 py-2 text-white bg-[#00457C] rounded hover:bg-[#003a6b] transition"
57+
>
58+
<FaPaypal size={20} />
59+
Donate via PayPal
60+
</a>
61+
</div>
62+
</Panel>
63+
</div>
64+
65+
<DataTable columns={content.columns} data={content.data} />
66+
67+
{/*TODO: make this shit a generic component and stylise it*/}
68+
<div className="flex justify-between p-5">
69+
<div className="flex-1">
70+
{hasPreviousPage && (
71+
<button className="hover:!text-blue-700" onClick={goToPreviousPage}>Previous</button>
72+
)}
73+
</div>
74+
75+
<div className="flex-1 text-right">
76+
{hasNextPage && (
77+
<button className="hover:!text-blue-700" onClick={goToNextPage}>Next</button>
78+
)}
79+
</div>
80+
</div>
81+
</Container>
82+
);
83+
}

components/atoms/TableCell.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {ReactNode} from "react";
2+
3+
const TableCell: React.FC<{ children: ReactNode }> = ({ children }) => (
4+
<td className="p-2 border-b border-slate-700">{children}</td>
5+
);
6+
7+
export default TableCell;

components/atoms/TableHeaderCell.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {ReactNode} from "react";
2+
import classNames from "classnames";
3+
4+
export type SortDirection = 'asc' | 'desc';
5+
6+
const TableHeaderCell: React.FC<{
7+
children: ReactNode;
8+
sortable: boolean;
9+
active: boolean;
10+
dir: SortDirection;
11+
onClick?: () => void;
12+
}> = ({ children, sortable, active, dir, onClick }) => {
13+
14+
const classes = classNames(
15+
"p-2 bg-gray-800 text-left select-none",
16+
{
17+
"cursor-pointer": sortable,
18+
}
19+
)
20+
21+
return (
22+
<th
23+
className={classes}
24+
onClick={sortable ? onClick : undefined}
25+
>
26+
{children}
27+
{sortable && (
28+
<span className="ml-1 text-xs">{active ? (dir === 'asc' ? '▲' : '▼') : '⇅'}</span>
29+
)}
30+
</th>
31+
)
32+
}
33+
34+
export default TableHeaderCell;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {Column} from "./TableRow";
2+
import TableHeaderCell, {SortDirection} from "../atoms/TableHeaderCell";
3+
4+
const TableHeaderRow = <T,>({
5+
columns,
6+
sortBy,
7+
sortDir,
8+
setSort,
9+
}: {
10+
columns: Column<T>[];
11+
sortBy: number | null;
12+
sortDir: SortDirection;
13+
setSort: (col: number) => void;
14+
}) => (
15+
<tr>
16+
{columns.map((col, i) => (
17+
<TableHeaderCell
18+
key={i}
19+
sortable={!!col.sortFn}
20+
active={sortBy === i}
21+
dir={sortDir}
22+
onClick={() => col.sortFn && setSort(i)}
23+
>
24+
{col.header}
25+
</TableHeaderCell>
26+
))}
27+
</tr>
28+
);
29+
30+
export default TableHeaderRow;

components/molecules/TableRow.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {ReactNode} from "react";
2+
import TableCell from "../atoms/TableCell";
3+
4+
export interface Column<T> {
5+
header: string;
6+
cell: (row: T) => ReactNode;
7+
sortFn?: (a: T, b: T) => number;
8+
}
9+
10+
const TableRow = <T,>({ columns, row }: { columns: Column<T>[]; row: T }) => (
11+
<tr>
12+
{columns.map((col, i) => (
13+
<TableCell key={i}>{col.cell(row)}</TableCell>
14+
))}
15+
</tr>
16+
);
17+
18+
export default TableRow;

0 commit comments

Comments
 (0)