Skip to content

Commit 53daf07

Browse files
committed
Merge branch 'feat/t-replace' of github.com:boundlessfi/boundless into feat/t-replace
2 parents 348b8d8 + 63922cb commit 53daf07

29 files changed

Lines changed: 39205 additions & 24264 deletions
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Close Linked Issues
2+
3+
# Auto-close issues referenced with Closes/Fixes/Resolves #N when a PR merges.
4+
#
5+
# GitHub's built-in auto-close only fires for PRs merged into the default branch
6+
# (main). This repo's feature PRs merge into integration branches such as
7+
# feat/t-replace, so linked issues are otherwise left open. This workflow closes
8+
# them on any base branch.
9+
#
10+
# pull_request_target runs in the base-repo context, so it has an issues:write
11+
# token even for PRs opened from forks (the normal pull_request event would get
12+
# a read-only token). It never checks out or runs PR head code, so it is safe.
13+
14+
on:
15+
pull_request_target:
16+
types: [closed]
17+
18+
permissions:
19+
issues: write
20+
contents: read
21+
22+
jobs:
23+
close-linked-issues:
24+
name: Close linked issues
25+
runs-on: ubuntu-latest
26+
if: github.event.pull_request.merged == true
27+
steps:
28+
- name: Close issues referenced in the PR body
29+
uses: actions/github-script@v7
30+
with:
31+
script: |
32+
const pr = context.payload.pull_request;
33+
const body = pr.body || '';
34+
35+
// Match "<keyword> #123" / "<keyword>: #123", keyword case-insensitive.
36+
const keyword = '(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)';
37+
const re = new RegExp(`${keyword}\\s*:?\\s+#(\\d+)`, 'gi');
38+
39+
const numbers = new Set();
40+
for (const m of body.matchAll(re)) {
41+
numbers.add(Number(m[1]));
42+
}
43+
44+
if (numbers.size === 0) {
45+
core.info('No "Closes/Fixes/Resolves #N" references found in the PR body.');
46+
return;
47+
}
48+
49+
for (const issue_number of numbers) {
50+
try {
51+
const { data: issue } = await github.rest.issues.get({
52+
owner: context.repo.owner,
53+
repo: context.repo.repo,
54+
issue_number,
55+
});
56+
57+
// Skip PRs (the API treats PRs as issues) and already-closed issues.
58+
if (issue.pull_request) {
59+
core.info(`#${issue_number} is a pull request, skipping.`);
60+
continue;
61+
}
62+
if (issue.state === 'closed') {
63+
core.info(`#${issue_number} is already closed, skipping.`);
64+
continue;
65+
}
66+
67+
await github.rest.issues.createComment({
68+
owner: context.repo.owner,
69+
repo: context.repo.repo,
70+
issue_number,
71+
body: `Closed by #${pr.number} (merged into \`${pr.base.ref}\`).`,
72+
});
73+
await github.rest.issues.update({
74+
owner: context.repo.owner,
75+
repo: context.repo.repo,
76+
issue_number,
77+
state: 'closed',
78+
state_reason: 'completed',
79+
});
80+
core.info(`Closed #${issue_number}.`);
81+
} catch (err) {
82+
core.warning(`Could not close #${issue_number}: ${err.message}`);
83+
}
84+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Metadata } from 'next';
2+
3+
import { generatePageMetadata } from '@/lib/metadata';
4+
import BountyDetail from '@/components/bounties/detail/BountyDetail';
5+
6+
export const metadata: Metadata = generatePageMetadata('bounties');
7+
8+
interface BountyDetailPageProps {
9+
params: Promise<{ id: string }>;
10+
}
11+
12+
export default async function BountyDetailPageRoute({
13+
params,
14+
}: BountyDetailPageProps) {
15+
const { id } = await params;
16+
17+
return (
18+
<div className='relative mx-auto min-h-screen max-w-[1440px] px-5 py-8 md:px-[50px] lg:px-[100px]'>
19+
<BountyDetail id={id} />
20+
</div>
21+
);
22+
}

app/(landing)/bounties/page.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { redirect } from 'next/navigation';
21
import { Metadata } from 'next';
2+
33
import { generatePageMetadata } from '@/lib/metadata';
4+
import BountiesPage from '@/components/bounties/marketplace/BountiesPage';
45

5-
export const metadata: Metadata = generatePageMetadata('grants');
6+
export const metadata: Metadata = generatePageMetadata('bounties');
67

7-
const GrantPage = () => {
8-
redirect('/coming-soon');
8+
export default function BountiesPageRoute() {
99
return (
10-
<div className='mx-auto mt-10 max-w-[1440px] px-5 py-5 text-center text-4xl font-bold text-white md:px-[50px] lg:px-[100px]'>
11-
Bounties Page
10+
<div className='relative mx-auto min-h-screen max-w-[1440px] px-5 py-8 md:px-[50px] lg:px-[100px]'>
11+
<BountiesPage />
1212
</div>
1313
);
14-
};
15-
16-
export default GrantPage;
14+
}

app/me/bounties/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import MyBountiesPage from '@/components/me/bounties/MyBountiesPage';
2+
3+
export default function MyBountiesRoutePage() {
4+
return <MyBountiesPage />;
5+
}

app/me/earnings/page.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ const ActivityItem: React.FC<ActivityItemProps> = ({ activity }) => (
104104
<span className='capitalize'>{activity.source}</span>
105105
<span></span>
106106
<span>{new Date(activity.occurredAt).toLocaleDateString()}</span>
107+
{/* Reward-receipt tx (txHash arrives with backend #335; cast until codegen). */}
108+
{(activity as { txHash?: string }).txHash && (
109+
<>
110+
<span></span>
111+
<a
112+
href={`https://stellar.expert/explorer/testnet/tx/${(activity as { txHash?: string }).txHash}`}
113+
target='_blank'
114+
rel='noreferrer'
115+
className='text-primary hover:underline'
116+
>
117+
View tx
118+
</a>
119+
</>
120+
)}
107121
</div>
108122
</div>
109123
<div className='text-right'>

components/app-sidebar.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
IconUserCircle,
1010
IconUsers,
1111
IconRocket,
12+
IconTarget,
1213
} from '@tabler/icons-react';
1314

1415
import { NavMain } from '@/components/nav-main';
@@ -67,6 +68,13 @@ const getNavigationData = (counts?: {
6768
: undefined,
6869
},
6970
],
71+
bounties: [
72+
{
73+
title: 'My Bounties',
74+
url: '/me/bounties',
75+
icon: IconTarget,
76+
},
77+
],
7078
account: [
7179
{
7280
title: 'Profile',
@@ -151,6 +159,7 @@ export function AppSidebar({
151159
<NavMain items={navigationData.main} />
152160
<NavMain items={navigationData.crowdfunding} label='Crowdfunding' />
153161
<NavMain items={navigationData.hackathons} label='Hackathons' />
162+
<NavMain items={navigationData.bounties} label='Bounties' />
154163
<NavMain items={navigationData.account} label='Account' />
155164
</SidebarContent>
156165
{/* Footer with User */}

0 commit comments

Comments
 (0)