Skip to content

Commit f45014d

Browse files
committed
update styles of cards
1 parent 2493c3c commit f45014d

File tree

6 files changed

+424
-208
lines changed

6 files changed

+424
-208
lines changed

.astro/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/// <reference types="astro/client" />
2+
/// <reference path="content.d.ts" />

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ build
1515
npm-debug.log
1616
package-lock.json
1717
yarn-error.log
18+
19+
# astro build
20+
dist

README.md

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,62 @@
1-
This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
1+
# First Contributions
22

3-
3+
A website to help people make their first open source contribution.
44

5-
Below you will find some information on how to perform common tasks.<br>
5+
## Features
66

7-
You can find the most recent version of this guide [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md).
7+
- **Project Discovery**: Browse open source projects suitable for beginners
8+
- **Issue Integration**: View "good first issue" and "help wanted" issues directly from GitHub
9+
- **Bento Layout**: Modern, responsive card layout with varying heights
10+
- **Real-time Data**: Fetches live issue data from GitHub repositories
811

12+
## Setup
913

10-
## How to run this project
14+
### Prerequisites
15+
16+
- Node.js 18+
17+
- npm or pnpm
18+
19+
### Installation
20+
21+
```sh
22+
npm install
23+
# or
24+
pnpm install
25+
```
26+
27+
### GitHub API Setup (Optional)
28+
29+
To enable live issue fetching, you'll need a GitHub Personal Access Token:
30+
31+
1. Go to [GitHub Settings > Personal Access Tokens](https://github.com/settings/tokens)
32+
2. Generate a new token (no special permissions needed)
33+
3. Create a `.env` file in the project root:
34+
35+
```env
36+
GITHUB_TOKEN=your_token_here
37+
```
38+
39+
Without a token, the site will work but won't show live GitHub issues due to rate limiting.
40+
41+
### Development
1142

1243
```sh
13-
yarn
14-
yarn start
44+
npm run dev
45+
# or
46+
pnpm dev
1547
```
48+
49+
### Build
50+
51+
```sh
52+
npm run build
53+
# or
54+
pnpm build
55+
```
56+
57+
## Tech Stack
58+
59+
- **Astro**: Static site generator
60+
- **TypeScript**: Type safety
61+
- **GitHub API**: Live issue data
62+
- **CSS**: Modern styling with glassmorphism effects

src/components/IssueList.astro

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
export interface Props {
3+
projectLink: string;
4+
projectName: string;
5+
}
6+
7+
const { projectLink, projectName } = Astro.props;
8+
9+
// Extract owner/repo from GitHub URL
10+
function extractRepoInfo(url: string) {
11+
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
12+
if (match) {
13+
return {
14+
owner: match[1],
15+
repo: match[2].replace(/\/$/, '') // Remove trailing slash
16+
};
17+
}
18+
return null;
19+
}
20+
21+
// Fetch issues from GitHub API on the server
22+
async function fetchIssues(owner: string, repo: string) {
23+
try {
24+
// Use GitHub token if available (for higher rate limits)
25+
const githubToken = import.meta.env.GITHUB_TOKEN;
26+
const headers: Record<string, string> = {
27+
'Accept': 'application/vnd.github+json',
28+
'User-Agent': 'FirstContributions'
29+
};
30+
31+
if (githubToken) {
32+
headers['Authorization'] = `Bearer ${githubToken}`;
33+
}
34+
35+
// Try a single request with both labels first
36+
const labels = 'good first issue,help wanted';
37+
const encodedLabels = encodeURIComponent(labels);
38+
const url = `https://api.github.com/repos/${owner}/${repo}/issues?labels=${encodedLabels}&state=open&per_page=10&sort=updated`;
39+
40+
const response = await fetch(url, { headers });
41+
42+
if (response.status === 403) {
43+
return [];
44+
}
45+
46+
if (!response.ok) {
47+
if (response.status === 404) {
48+
return [];
49+
}
50+
throw new Error(`GitHub API responded with status: ${response.status}`);
51+
}
52+
53+
const issues = await response.json();
54+
55+
if (issues.length === 0) {
56+
return [];
57+
}
58+
59+
// Process and prioritize issues
60+
const processedIssues = issues.map((issue: any) => {
61+
const hasGoodFirstIssue = issue.labels.some((label: any) =>
62+
label.name.toLowerCase().includes('good first issue') ||
63+
label.name.toLowerCase().includes('good-first-issue')
64+
);
65+
66+
return {
67+
...issue,
68+
priority: hasGoodFirstIssue ? 'good first issue' : 'help wanted'
69+
};
70+
});
71+
72+
// Sort: good first issue first, then help wanted
73+
processedIssues.sort((a: any, b: any) => {
74+
if (a.priority === 'good first issue' && b.priority !== 'good first issue') return -1;
75+
if (a.priority !== 'good first issue' && b.priority === 'good first issue') return 1;
76+
return 0;
77+
});
78+
79+
return processedIssues.slice(0, 3); // Show only 3 issues
80+
} catch (error) {
81+
return [];
82+
}
83+
}
84+
85+
// Fetch issues on the server
86+
const repoInfo = extractRepoInfo(projectLink);
87+
let issues: any[] = [];
88+
89+
if (repoInfo) {
90+
issues = await fetchIssues(repoInfo.owner, repoInfo.repo);
91+
}
92+
---
93+
94+
<div class="Card-Issues">
95+
<div class="Issues-Header">
96+
<h4 class="Issues-Title">Open Issues</h4>
97+
<span class="Issues-Count">{issues.length}</span>
98+
</div>
99+
100+
{issues.length > 0 ? (
101+
<div class="Issues-List">
102+
{issues.map((issue) => (
103+
<a href={issue.html_url} target="_blank" class="Issue-Card">
104+
<div class="Issue-Content">
105+
<div class="Issue-Title">{issue.title}</div>
106+
<div class="Issue-Meta">
107+
<span class="Issue-Number">#{issue.number}</span>
108+
<span class="Issue-Date">
109+
{new Date(issue.updated_at).toLocaleDateString()}
110+
</span>
111+
</div>
112+
</div>
113+
<div class="Issue-Labels">
114+
<span class="Issue-Label {issue.priority === 'good first issue' ? 'good-first-issue' : 'help-wanted'}">
115+
{issue.priority === 'good first issue' ? 'Good First Issue' : 'Help Wanted'}
116+
</span>
117+
</div>
118+
</a>
119+
))}
120+
</div>
121+
) : (
122+
<div class="no-issues">
123+
<div class="no-issues-icon">🔍</div>
124+
<div class="no-issues-text">No issues found</div>
125+
<div class="no-issues-subtext">Try checking back later</div>
126+
</div>
127+
)}
128+
</div>
129+
130+
<style>
131+
.Card-Issues {
132+
margin-top: 1rem;
133+
padding-top: 1rem;
134+
border-top: 1px solid rgba(255, 255, 255, 0.1);
135+
}
136+
137+
.Issues-Header {
138+
display: flex;
139+
justify-content: space-between;
140+
align-items: center;
141+
margin-bottom: 1rem;
142+
}
143+
144+
.Issues-Title {
145+
font-size: 0.9rem;
146+
font-weight: 600;
147+
color: rgba(255, 255, 255, 0.9);
148+
margin: 0;
149+
}
150+
151+
.Issues-Count {
152+
background: rgba(102, 126, 234, 0.2);
153+
color: #a5b4fc;
154+
padding: 0.25rem 0.5rem;
155+
border-radius: 12px;
156+
font-size: 0.75rem;
157+
font-weight: 500;
158+
border: 1px solid rgba(102, 126, 234, 0.3);
159+
}
160+
161+
.Issues-List {
162+
display: flex;
163+
flex-direction: column;
164+
gap: 0.75rem;
165+
}
166+
167+
.Issue-Card {
168+
display: flex;
169+
justify-content: space-between;
170+
align-items: flex-start;
171+
padding: 0.875rem;
172+
background: rgba(255, 255, 255, 0.05);
173+
border: 1px solid rgba(255, 255, 255, 0.1);
174+
border-radius: 12px;
175+
text-decoration: none;
176+
color: inherit;
177+
transition: all 0.2s ease;
178+
gap: 0.75rem;
179+
}
180+
181+
.Issue-Card:hover {
182+
background: rgba(255, 255, 255, 0.08);
183+
border-color: rgba(102, 126, 234, 0.3);
184+
transform: translateY(-1px);
185+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
186+
}
187+
188+
.Issue-Content {
189+
flex: 1;
190+
min-width: 0;
191+
}
192+
193+
.Issue-Title {
194+
font-size: 0.85rem;
195+
color: rgba(255, 255, 255, 0.95);
196+
margin: 0 0 0.5rem 0;
197+
line-height: 1.4;
198+
display: -webkit-box;
199+
-webkit-line-clamp: 2;
200+
-webkit-box-orient: vertical;
201+
overflow: hidden;
202+
font-weight: 500;
203+
}
204+
205+
.Issue-Meta {
206+
display: flex;
207+
gap: 0.75rem;
208+
align-items: center;
209+
font-size: 0.75rem;
210+
color: rgba(255, 255, 255, 0.6);
211+
}
212+
213+
.Issue-Number {
214+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
215+
background: rgba(255, 255, 255, 0.1);
216+
padding: 0.125rem 0.375rem;
217+
border-radius: 6px;
218+
font-size: 0.7rem;
219+
}
220+
221+
.Issue-Date {
222+
font-size: 0.7rem;
223+
}
224+
225+
.Issue-Labels {
226+
flex-shrink: 0;
227+
}
228+
229+
.Issue-Label {
230+
font-size: 0.7rem;
231+
padding: 0.25rem 0.5rem;
232+
border-radius: 8px;
233+
font-weight: 600;
234+
text-transform: uppercase;
235+
letter-spacing: 0.025em;
236+
white-space: nowrap;
237+
}
238+
239+
.Issue-Label.good-first-issue {
240+
background: linear-gradient(135deg, rgba(34, 197, 94, 0.2) 0%, rgba(34, 197, 94, 0.1) 100%);
241+
color: #4ade80;
242+
border: 1px solid rgba(34, 197, 94, 0.3);
243+
box-shadow: 0 2px 4px rgba(34, 197, 94, 0.1);
244+
}
245+
246+
.Issue-Label.help-wanted {
247+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(59, 130, 246, 0.1) 100%);
248+
color: #60a5fa;
249+
border: 1px solid rgba(59, 130, 246, 0.3);
250+
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
251+
}
252+
253+
.no-issues {
254+
text-align: center;
255+
padding: 2rem 1rem;
256+
color: rgba(255, 255, 255, 0.6);
257+
}
258+
259+
.no-issues-icon {
260+
font-size: 2rem;
261+
margin-bottom: 0.75rem;
262+
opacity: 0.7;
263+
}
264+
265+
.no-issues-text {
266+
font-size: 0.9rem;
267+
font-weight: 500;
268+
margin-bottom: 0.25rem;
269+
color: rgba(255, 255, 255, 0.8);
270+
}
271+
272+
.no-issues-subtext {
273+
font-size: 0.75rem;
274+
opacity: 0.7;
275+
}
276+
277+
278+
/* Responsive design */
279+
@media (max-width: 768px) {
280+
.Issue-Card {
281+
flex-direction: column;
282+
align-items: stretch;
283+
gap: 0.5rem;
284+
}
285+
286+
.Issue-Labels {
287+
align-self: flex-start;
288+
}
289+
290+
.Issue-Meta {
291+
flex-wrap: wrap;
292+
gap: 0.5rem;
293+
}
294+
}
295+
</style>

0 commit comments

Comments
 (0)