Skip to content

Commit c881aa9

Browse files
committed
feat: implement grantee report submission system - Add report submission form and validation, integrate with GitHub API, add email confirmation, implement error handling
1 parent ea266b5 commit c881aa9

11 files changed

Lines changed: 1820 additions & 6 deletions

File tree

components/ErrorBoundary.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from 'react'
2+
3+
interface Props {
4+
children: React.ReactNode
5+
}
6+
7+
interface State {
8+
hasError: boolean
9+
error?: Error
10+
}
11+
12+
class ErrorBoundary extends React.Component<Props, State> {
13+
constructor(props: Props) {
14+
super(props)
15+
this.state = { hasError: false }
16+
}
17+
18+
static getDerivedStateFromError(error: Error): State {
19+
return { hasError: true, error }
20+
}
21+
22+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
23+
console.error('Error caught by boundary:', error, errorInfo)
24+
}
25+
26+
render() {
27+
if (this.state.hasError) {
28+
return (
29+
<div className="min-h-screen bg-white px-4 py-16 dark:bg-gray-900 sm:px-6 sm:py-24 md:grid md:place-items-center lg:px-8">
30+
<div className="mx-auto max-w-max">
31+
<main className="sm:flex">
32+
<p className="text-4xl font-bold tracking-tight text-orange-500 sm:text-5xl">
33+
Oops!
34+
</p>
35+
<div className="sm:ml-6">
36+
<div className="dark:border-gray-700 sm:border-l sm:border-gray-200 sm:pl-6">
37+
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-5xl">
38+
Something went wrong
39+
</h1>
40+
<p className="mt-1 text-base text-gray-500 dark:text-gray-400">
41+
{this.state.error?.message ||
42+
'An unexpected error occurred'}
43+
</p>
44+
<div className="mt-6 flex space-x-3 sm:border-l sm:border-transparent sm:pl-6">
45+
<button
46+
onClick={() => window.location.reload()}
47+
className="inline-flex items-center rounded-md border border-transparent bg-orange-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-orange-600 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2"
48+
>
49+
Try again
50+
</button>
51+
<button
52+
onClick={() => (window.location.href = '/')}
53+
className="inline-flex items-center rounded-md border border-transparent bg-gray-100 px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700"
54+
>
55+
Go back home
56+
</button>
57+
</div>
58+
</div>
59+
</div>
60+
</main>
61+
</div>
62+
</div>
63+
)
64+
}
65+
66+
return this.props.children
67+
}
68+
}
69+
70+
export default ErrorBoundary

components/FormButton.tsx

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,63 @@
1-
function FormButton({ variant, children, ...rest }) {
1+
interface FormButtonProps
2+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
3+
variant?: 'enabled' | 'disabled' | 'primary'
4+
loading?: boolean
5+
children?: React.ReactNode
6+
}
7+
8+
function FormButton({
9+
variant = 'primary',
10+
loading = false,
11+
children,
12+
className = '',
13+
...rest
14+
}: FormButtonProps) {
215
const defaultVariant =
3-
'bg-orange-500 hover:bg-orange-700 text-xl text-white font-bold py-2 px-4 rounded'
16+
'inline-flex justify-center items-center bg-orange-500 hover:bg-orange-600 text-white font-bold py-2 px-4 rounded-md transition-colors duration-200 ease-in-out border-0'
417
const buttonVariants = {
518
enabled: defaultVariant,
6-
disabled: `${defaultVariant} opacity-50 cursor-not-allowed`,
19+
disabled: `${defaultVariant} cursor-not-allowed opacity-50`,
20+
primary: defaultVariant,
721
}
822

923
return (
10-
<button className={`${buttonVariants[variant]} ...`} {...rest}>
11-
{children}
24+
<button
25+
className={`${buttonVariants[variant]} ${
26+
loading ? 'cursor-not-allowed opacity-50' : ''
27+
} ${className}`}
28+
disabled={loading}
29+
type="submit"
30+
{...rest}
31+
>
32+
{loading ? (
33+
<span className="flex w-full items-center justify-center">
34+
<svg
35+
className="-ml-1 mr-3 h-5 w-5 animate-spin text-white"
36+
xmlns="http://www.w3.org/2000/svg"
37+
fill="none"
38+
viewBox="0 0 24 24"
39+
>
40+
<circle
41+
className="opacity-25"
42+
cx="12"
43+
cy="12"
44+
r="10"
45+
stroke="currentColor"
46+
strokeWidth="4"
47+
/>
48+
<path
49+
className="opacity-75"
50+
fill="currentColor"
51+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
52+
/>
53+
</svg>
54+
Processing...
55+
</span>
56+
) : (
57+
<span className="flex w-full items-center justify-center">
58+
{children || 'Submit'}
59+
</span>
60+
)}
1261
</button>
1362
)
1463
}

0 commit comments

Comments
 (0)