-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathindex.tsx
113 lines (96 loc) · 3.09 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
'use client'
import React, { forwardRef, useCallback, useEffect, useState } from 'react'
import Calendar, {
Skeleton,
type Props as ActivityCalendarProps,
type ThemeInput,
} from 'react-activity-calendar'
import { transformData } from './lib'
import type { Activity, ApiErrorResponse, ApiResponse, Year } from './types'
export interface Props extends Omit<ActivityCalendarProps, 'data'> {
username: string
errorMessage?: string
throwOnError?: boolean
transformData?: (data: Array<Activity>) => Array<Activity>
transformTotalCount?: boolean
year?: Year
}
async function fetchCalendarData(username: string, year: Year): Promise<ApiResponse> {
const apiUrl = 'https://github-contributions-api.jogruber.de/v4/'
const response = await fetch(`${apiUrl}${username}?y=${year}`)
const data = (await response.json()) as ApiResponse | ApiErrorResponse
if (!response.ok) {
throw Error(
`Fetching GitHub contribution data for "${username}" failed: ${(data as ApiErrorResponse).error}`,
)
}
return data as ApiResponse
}
const GitHubCalendar = forwardRef<HTMLElement, Props>(
(
{
username,
year = 'last',
labels,
transformData: transformFn,
transformTotalCount = true,
throwOnError = false,
errorMessage = `Error – Fetching GitHub contribution data for "${username}" failed.`,
...props
},
ref,
) => {
const [data, setData] = useState<ApiResponse | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const fetchData = useCallback(() => {
setLoading(true)
setError(null)
fetchCalendarData(username, year)
.then(setData)
.catch((err: unknown) => {
if (err instanceof Error) {
setError(err)
}
})
.finally(() => {
setLoading(false)
})
}, [username, year])
useEffect(fetchData, [fetchData])
// React error boundaries can't handle asynchronous code, so rethrow.
if (error) {
if (throwOnError) {
throw error
} else {
return <div>{errorMessage}</div>
}
}
if (loading || !data) {
return <Skeleton {...props} loading />
}
const theme = props.theme ?? gitHubTheme
const defaultLabels = {
totalCount: `{{count}} contributions in ${year === 'last' ? 'the last year' : '{{year}}'}`,
}
const totalCount = year === 'last' ? data.total['lastYear'] : data.total[year]
return (
<Calendar
data={transformData(data.contributions, transformFn)}
labels={Object.assign({}, defaultLabels, labels)}
ref={ref}
totalCount={transformFn && transformTotalCount ? undefined : totalCount}
{...props}
theme={theme}
loading={Boolean(props.loading) || loading}
maxLevel={4}
/>
)
},
)
GitHubCalendar.displayName = 'GitHubCalendar'
const gitHubTheme = {
light: ['#ebedf0', '#9be9a8', '#40c463', '#30a14e', '#216e39'],
dark: ['#161b22', '#0e4429', '#006d32', '#26a641', '#39d353'],
} satisfies ThemeInput
export default GitHubCalendar