Merge branch 'master' into perks

This commit is contained in:
Artur
2024-10-18 16:25:48 -03:00
112 changed files with 2270 additions and 2205 deletions

View File

@@ -5,9 +5,11 @@ NEXT_PUBLIC_STRAPI_URL="http://172.17.0.1:1337"
STRAPI_API_URL="http://172.17.0.1:1337/api"
STRAPI_API_TOKEN=""
NEXTAUTH_SECRET=""
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_URL_INTERNAL="http://localhost:3000"
NEXTAUTH_SECRET=""
USER_SETTINGS_JWT_SECRET=""
SMTP_HOST="sandbox.smtp.mailtrap.io"
SMTP_PORT="2525"
@@ -25,6 +27,7 @@ STRIPE_GENERAL_SECRET_KEY=""
STRIPE_GENERAL_WEBHOOK_SECRET=""
BTCPAY_URL="http://btcpayserver:49392"
BTCPAY_EXTERNAL_URL="http://localhost:49392"
BTCPAY_API_KEY=""
BTCPAY_STORE_ID=""
BTCPAY_WEBHOOK_SECRET=""

View File

@@ -3,7 +3,7 @@ name: Deploy app to donate.magicgrants.org
on:
push:
branches:
- v2
- master
jobs:
deploy:
@@ -20,7 +20,7 @@ jobs:
ssh -o StrictHostKeyChecking=no ${{ secrets.VPS_USER }}@${{ secrets.VPS_IP }} << 'EOF'
export HISTFILE=/dev/null
cd campaign-site
git checkout v2
git checkout master
echo "Pulling changes..."
git pull
echo "Building and starting..."
@@ -29,6 +29,7 @@ jobs:
POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} \
DATABASE_URL=${{ secrets.DATABASE_URL }} \
NEXTAUTH_SECRET=${{ secrets.NEXTAUTH_SECRET }} \
USER_SETTINGS_JWT_SECRET=${{ secrets.USER_SETTINGS_JWT_SECRET }} \
SMTP_USER=${{ secrets.SMTP_USER }} \
SMTP_PASS=${{ secrets.SMTP_PASS }} \
STRIPE_MONERO_SECRET_KEY=${{ secrets.STRIPE_MONERO_SECRET_KEY }} \
@@ -45,5 +46,5 @@ jobs:
BTCPAY_API_KEY=${{ secrets.BTCPAY_API_KEY }} \
BTCPAY_STORE_ID=${{ secrets.BTCPAY_STORE_ID }} \
BTCPAY_WEBHOOK_SECRET=${{ secrets.BTCPAY_WEBHOOK_SECRET }} \
docker compose up -d --build
docker compose -f docker-compose.prod.yml up -d --build
EOF

View File

@@ -32,9 +32,9 @@ ENV NEXT_TELEMETRY_DISABLED 1
ENV BUILD_MODE 1
ENV PRISMA_BINARY_TARGETS='["native", "rhel-openssl-1.0.x"]'
ENV NEXT_PUBLIC_MONERO_APPLICATION_RECIPIENT='monerofund@magicgrants.org'
ENV NEXT_PUBLIC_FIRO_APPLICATION_RECIPIENT='monerofund@magicgrants.org'
ENV NEXT_PUBLIC_PRIVACY_GUIDES_APPLICATION_RECIPIENT='monerofund@magicgrants.org'
ENV NEXT_PUBLIC_GENERAL_APPLICATION_RECIPIENT='monerofund@magicgrants.org'
ENV NEXT_PUBLIC_FIRO_APPLICATION_RECIPIENT='firofund@magicgrants.org'
ENV NEXT_PUBLIC_PRIVACY_GUIDES_APPLICATION_RECIPIENT='privacyguidesfund@magicgrants.org'
ENV NEXT_PUBLIC_GENERAL_APPLICATION_RECIPIENT='info@magicgrants.org'
RUN npx prisma generate
RUN \
@@ -48,6 +48,8 @@ RUN \
FROM base AS runner
WORKDIR /app
ARG BUILD_MODE=1
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1

128
README.md
View File

@@ -1,19 +1,127 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# MAGIC Grants Campaign Site
PRs welcome!
## Development
Thanks for supporting MAGIC Monero Fund.
### Requirements
---
- Docker
- Docker Compose
- NodeJS >=20
# Upgrading Server
### Running containers
ssh into the machine, and cd into the correct folder, under Production. Then:
First, install the application's dependencies and then run the containers:
`sudo git pull`
```bash
$ npm i
```
`sudo docker-compose up --build`
```bash
$ docker-compse up -d
```
After building coompletes, quit out with Ctrl + C
The app will be available at `http://localhost:3000`.
`sudo docker start monerofund-frontend-page`
### Configuration
Create a `.env `file as a copy of `.env.example` and set the values for the empty variables.
### Setting up Keycloak
1. Open up http://localhost:8080 in your browser, then login using `admin` for both username and password;
2. Open the dropdown menu on the top left corner of the screen (where it says Keycloak) and click **Create realm**;
3. Upload the `realm-export.json` file from this repo, name the realm `magic` and click **Create**;
4. Once the realm is created, go to **Clients** -> **Credentials**, and under **Client Secret**, click **Regenerate**. Copy the secret and add it to the `KEYCLOAK_CLIENT_SECRET` environment variable in your `.env` file.
### Setting up BTCPayServer
1. Open up http://localhost:49392 in your browser and create an account;
2. Once logged in, open the **XMR Wallet** page and upload a view-only wallet file, you can get one from [Feather Wallet](https://featherwallet.org/);
3. In the **Webhooks** tab, create a new webhook by setting the **Payload URL** to `http://campaign-site:3000/api/btcpay/webhook`, copy the secret and add it to the `BTCPAY_WEBHOOK_SECRET` environment variable in your `.env` file, and finally click **Add webhook**.
4. Create a new API key at **Account** -> **Manage Account** -> **API Keys**, you'll need the following permissions: **View invoices**, **Create an invoice** and **View your stores**. Then copy the API key and add it to the `BTCPAY_API_KEY` environment variable in your `.env` file.
### Setting up Stripe
1. Open up the [Stripe Dashboard](https://dashboard.stripe.com) in your browser;
2. Create a new account in test mode;
3. Go to **Developers** -> **API keys**, and get a secret key, add it to the `STRIPE_MONERO_SECRET_KEY` environment variable in your `.env` file;
4. Go to **Developers** -> **Webhooks**, and add a new webhook endpoint. Add `<Your app public address>/api/stripe/monero-webhook` to the URL field replacing `<Your app public address>` with your app's public address, then add the secret to the `STRIPE_MONERO_WEBHOOK_SECRET` environment variable in your `.env` file. To expose the app to the internet so Stripe can reach the webhook endpoint, you can use tunneling services like Visual Studio Code's built-in [port-forwarding feature](https://code.visualstudio.com/docs/editor/port-forwarding) or [Ngrok](https://ngrok.io).
This makes you able to test Stripe donations in the Monero fund, which should be enough for development. You can add random values to the other Stripe enviroment variables to bypass validation.
You are now all set up to start developing!
## Funding required endpoint
Endpoint: `GET /api/funding-required`
Request query parameters
| Parameter | Required | Default | Accepted values | Description |
| - | - | - | - | - |
| `fund` | No | - | `monero` `firo` `privacyguides` `general` | Filters projects by fund. |
| `asset` | No | - | `BTC` `XMR` `USD` | Only return project amounts and address for a specific asset. Specifying this parameter changes the response JSON schema as shown below. |
| `project_status` | No | `NOT_FUNDED` | `NOT_FUNDED` `FUNDED` `ANY` | Filters projects by status. |
Response body (`asset` parameter **not** specified)
```ts
[
{
title: string
fund: 'monero' | 'firo' | 'privacyguides' | 'general'
date: string // YYYY-MM-DD
author: string
url: string
is_funded: boolean
raised_amount_percent: number
contributions: number
target_amount_btc: number
target_amount_xmr: number
target_amount_usd: number
remaining_amount_btc: number
remaining_amount_xmr: number
remaining_amount_usd: number
address_btc: string | null
address_xmr: string | null
}
]
```
Response body (`asset` parameter specified)
```ts
[
{
title: string
fund: 'monero' | 'firo' | 'privacyguides' | 'general'
date: string // YYYY-MM-DD
author: string
url: string
is_funded: boolean
raised_amount_percent: number
contributions: number
asset: 'BTC' | 'XMR' | 'USD'
target_amount: number
remaining_amount: number
address: string | null
}
]
```
# Contributing
Pull requests welcome!
Thanks for supporting MAGIC Grants.
# License
[MIT](LICENSE)

17
auth.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
import { Session } from 'next-auth'
import { DefaultJWT, JWT } from 'next-auth/jwt'
declare module 'next-auth' {
interface Session extends Session {
error?: 'RefreshAccessTokenError'
}
}
declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
accessToken: string
accessTokenExpiresAt: number
refreshToken: string
error?: 'RefreshAccessTokenError'
}
}

View File

@@ -24,9 +24,11 @@ import CustomLink from './CustomLink'
type Props = {
project: ProjectItem | undefined
close: () => void
openRegisterModal: () => void
}
const DonationFormModal: React.FC<Props> = ({ project }) => {
const DonationFormModal: React.FC<Props> = ({ project, openRegisterModal, close }) => {
const fundSlug = useFundSlug()
const session = useSession()
const isAuthed = session.status === 'authenticated'
@@ -147,7 +149,7 @@ const DonationFormModal: React.FC<Props> = ({ project }) => {
</div>
<Form {...form}>
<form className="flex flex-col gap-6">
<form className="flex flex-col gap-4">
{!isAuthed && (
<>
<FormField
@@ -347,7 +349,12 @@ const DonationFormModal: React.FC<Props> = ({ project }) => {
<div className="flex flex-col items-center">
<p>Want to support more projects from now on?</p>
<Button type="button" size="lg" variant="link">
<Button
type="button"
size="lg"
variant="link"
onClick={() => (openRegisterModal(), close())}
>
Create an account
</Button>
</div>

View File

@@ -4,17 +4,27 @@ import CustomLink from './CustomLink'
function Footer() {
return (
<footer>
<div className="mb-4 mt-16 flex flex-col items-center">
<div className="space-x-4 text-center text-xs text-gray-500">
MAGIC Grants is a 501(c)(3) non-profit organization. All gifts and donations are
tax-deductible to the full extent of the law.
</div>
<div className="mb-2 flex space-x-2 text-xs text-gray-500"></div>
<div className="space-x-4 text-center text-xs text-gray-500">
© {new Date().getFullYear()} MAGIC Grants. This website builds upon technology by Open
Sats.
</div>
<footer className="pb-4 mt-16 flex flex-col items-center space-y-2">
<div className="flex flex-row space-x-2 justify-center">
<CustomLink href="/terms" className="text-xs hover:underline">
Terms of Use
</CustomLink>
<span className="text-xs text-muted-foreground">|</span>
<CustomLink href="/privacy" className="text-xs hover:underline">
Privacy Policy
</CustomLink>
</div>
<div className="space-x-4 text-center text-xs text-gray-500">
MAGIC Grants is a 501(c)(3) non-profit organization. All gifts and donations are
tax-deductible to the full extent of the law.
</div>
<div className="space-x-4 text-center text-xs text-gray-500">
© {new Date().getFullYear()} MAGIC Grants. This website builds upon technology by Open
Sats.
</div>
</footer>
)

View File

@@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
import Link from './CustomLink'
import MobileNav from './MobileNav'
import { fundHeaderNavLinks, homeHeaderNavLinks } from '../data/headerNavLinks'
import { fundHeaderNavLinks } from '../data/headerNavLinks'
import MagicLogo from './MagicLogo'
import { Dialog, DialogContent, DialogTrigger } from './ui/dialog'
import { Button } from './ui/button'
@@ -23,6 +23,9 @@ import {
import { useFundSlug } from '../utils/use-fund-slug'
import CustomLink from './CustomLink'
import { funds } from '../utils/funds'
import MoneroLogo from './MoneroLogo'
import FiroLogo from './FiroLogo'
import PrivacyGuidesLogo from './PrivacyGuidesLogo'
const Header = () => {
const [registerIsOpen, setRegisterIsOpen] = useState(false)
@@ -48,33 +51,21 @@ const Header = () => {
aria-label="Home"
className="flex items-center mr-3 gap-4"
>
<MagicLogo className="w-12 h-12" />
{
<span className="text-foreground text-lg font-bold">
{fund ? fund.title : 'MAGIC Grants'}
</span>
}
{!fundSlug && <MagicLogo className="w-12 h-12" />}
{fundSlug === 'monero' && <MoneroLogo className="w-12 h-12" />}
{fundSlug === 'firo' && <FiroLogo className="w-12 h-12" />}
{fundSlug === 'privacyguides' && <PrivacyGuidesLogo className="w-12 h-12" />}
{fundSlug === 'general' && <MagicLogo className="w-12 h-12" />}
<span className="text-foreground text-lg font-bold hidden sm:block">
{fund ? fund.title : 'MAGIC Grants'}
</span>
</Link>
</div>
<div className="flex gap-2 items-center text-base leading-5">
{!fund &&
homeHeaderNavLinks.map((link) => (
<CustomLink
key={link.title}
href={`/${link.href}`}
className={
link.isButton
? 'rounded border border-primary bg-transparent px-4 py-2 font-semibold text-primary hover:border-transparent hover:bg-primary hover:text-white'
: 'hidden p-1 font-medium text-gray-900 sm:p-4 md:inline-block'
}
>
{link.title}
</CustomLink>
))}
{!!fund &&
fundHeaderNavLinks.map((link) => (
fundHeaderNavLinks[fund.slug].map((link) => (
<CustomLink
key={link.title}
href={`/${fundSlug}/${link.href}`}
@@ -92,7 +83,12 @@ const Header = () => {
<>
<Dialog open={loginIsOpen} onOpenChange={setLoginIsOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="w-24">
<Button variant="outline" className="w-18 block sm:hidden" size="sm">
Login
</Button>
</DialogTrigger>
<DialogTrigger asChild>
<Button variant="outline" className="w-24 hidden sm:block">
Login
</Button>
</DialogTrigger>
@@ -107,7 +103,12 @@ const Header = () => {
<Dialog open={registerIsOpen} onOpenChange={setRegisterIsOpen}>
<DialogTrigger asChild>
<Button className="w-24">Register</Button>
<Button className="w-18 block sm:hidden" size="sm">
Register
</Button>
</DialogTrigger>
<DialogTrigger asChild>
<Button className="w-24 hidden sm:block">Register</Button>
</DialogTrigger>
<DialogContent>
<RegisterFormModal
@@ -143,6 +144,9 @@ const Header = () => {
<DropdownMenuItem>Point History</DropdownMenuItem>
</CustomLink>
<DropdownMenuItem>Settings</DropdownMenuItem>
<CustomLink href={`/${fundSlug}/account/settings`} className="text-foreground">
<DropdownMenuItem>Settings</DropdownMenuItem>
</CustomLink>
<DropdownMenuItem onClick={() => signOut({ callbackUrl: `/${fundSlug}` })}>
Logout
</DropdownMenuItem>
@@ -150,7 +154,7 @@ const Header = () => {
</DropdownMenu>
)}
<MobileNav />
{!!fundSlug && <MobileNav />}
</div>
<Dialog open={passwordResetIsOpen} onOpenChange={setPasswordResetIsOpen}>

View File

@@ -1,23 +1,48 @@
import { ReactNode } from 'react'
import { ReactNode, useEffect } from 'react'
import { signOut, useSession } from 'next-auth/react'
import { Inter } from 'next/font/google'
import SectionContainer from './SectionContainer'
import Footer from './Footer'
import Header from './Header'
import { useFundSlug } from '../utils/use-fund-slug'
interface Props {
children: ReactNode
}
const inter = Inter({ subsets: ['latin'] })
const LayoutWrapper = ({ children }: Props) => {
const fundSlug = useFundSlug()
const { data: session } = useSession()
useEffect(() => {
if (session?.error === 'RefreshAccessTokenError') {
if (fundSlug) {
signOut({ callbackUrl: `/${fundSlug}/?loginEmail=${session?.user.email}` })
} else {
signOut({ callbackUrl: '/' })
}
}
}, [session])
return (
<SectionContainer>
<div className="flex h-screen flex-col justify-between">
<Header />
<main className="grow">{children}</main>
<Footer />
</div>
</SectionContainer>
<>
<style jsx global>{`
body {
font-family: ${inter.style.fontFamily};
}
`}</style>
<SectionContainer>
<div className="flex h-screen flex-col justify-between">
<Header />
<main className="grow">{children}</main>
<Footer />
</div>
</SectionContainer>
</>
)
}

View File

@@ -132,7 +132,11 @@ function LoginFormModal({ close, openPasswordResetModal, openRegisterModal }: Pr
Register
</Button>
<Button className="grow basis-0" type="submit" disabled={form.formState.isSubmitting}>
<Button
className="grow basis-0"
type="submit"
disabled={!form.formState.isValid || form.formState.isSubmitting}
>
{form.formState.isSubmitting && <Spinner />} Login
</Button>
</div>

View File

@@ -21,9 +21,11 @@ import { useFundSlug } from '../utils/use-fund-slug'
type Props = {
project: ProjectItem | undefined
close: () => void
openRegisterModal: () => void
}
const MembershipFormModal: React.FC<Props> = ({ project }) => {
const MembershipFormModal: React.FC<Props> = ({ project, close, openRegisterModal }) => {
const fundSlug = useFundSlug()
const session = useSession()
const isAuthed = session.status === 'authenticated'
@@ -135,7 +137,7 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
</div>
<Form {...form}>
<form className="flex flex-col gap-6">
<form className="flex flex-col gap-4">
{!isAuthed && (
<>
<FormField
@@ -170,7 +172,7 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
<div className="flex flex-col space-y-3">
<FormLabel>Amount</FormLabel>
<span className="flex flex-row font-medium text-gray-700">
<span className="flex flex-row">
<DollarSign className="text-primary" />
100.00
</span>
@@ -192,13 +194,13 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
<FormControl>
<RadioGroupItem value="no" />
</FormControl>
<FormLabel className="font-normal text-gray-700">No</FormLabel>
<FormLabel className="font-normal">No</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="yes" />
</FormControl>
<FormLabel className="font-normal text-gray-700">Yes</FormLabel>
<FormLabel className="font-normal">Yes</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
@@ -225,7 +227,7 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
<FormControl>
<RadioGroupItem value="no" />
</FormControl>
<FormLabel className="font-normal text-gray-700">No</FormLabel>
<FormLabel className="font-normal">No</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
@@ -322,7 +324,12 @@ const MembershipFormModal: React.FC<Props> = ({ project }) => {
<div className="flex flex-col items-center ">
<p>Want to support more projects from now on?</p>
<Button type="button" size="lg" variant="link">
<Button
type="button"
size="lg"
variant="link"
onClick={() => (openRegisterModal(), close())}
>
Create an account
</Button>
</div>

View File

@@ -1,7 +1,7 @@
import { useState } from 'react'
import CustomLink from './CustomLink'
import { fundHeaderNavLinks, homeHeaderNavLinks } from '../data/headerNavLinks'
import { fundHeaderNavLinks } from '../data/headerNavLinks'
import { useFundSlug } from '../utils/use-fund-slug'
import { funds } from '../utils/funds'
@@ -69,21 +69,8 @@ const MobileNav = () => {
</button>
</div>
<nav className="fixed mt-8 h-full">
{!fund &&
homeHeaderNavLinks.map((link) => (
<div key={link.title} className="px-12 py-4">
<CustomLink
href={`/${link.href}`}
className="text-2xl font-bold tracking-tight text-gray-900"
onClick={onToggleNav}
>
{link.title}
</CustomLink>
</div>
))}
{!!fund &&
fundHeaderNavLinks.map((link) => (
fundHeaderNavLinks[fund.slug].map((link) => (
<div key={link.title} className="px-12 py-4">
<CustomLink
href={`/${fundSlug}/${link.href}`}

View File

@@ -21,21 +21,20 @@ type Props = { close: () => void }
function PasswordResetFormModal({ close }: Props) {
const { toast } = useToast()
const form = useForm<PasswordResetFormInputs>({
resolver: zodResolver(schema),
})
const form = useForm<PasswordResetFormInputs>({ resolver: zodResolver(schema) })
const requestPasswordResetMutation = trpc.auth.requestPasswordReset.useMutation()
async function onSubmit(data: PasswordResetFormInputs) {
await requestPasswordResetMutation.mutateAsync(data)
try {
await requestPasswordResetMutation.mutateAsync(data)
toast({
title: 'A password reset link has been sent to your email.',
})
close()
form.reset({ email: '' })
toast({ title: 'A password reset link has been sent to your email.' })
close()
form.reset({ email: '' })
} catch (error) {
toast({ title: 'Sorry, something went wrong.', variant: 'destructive' })
}
}
return (
@@ -61,7 +60,7 @@ function PasswordResetFormModal({ close }: Props) {
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
<Button type="submit" disabled={!form.formState.isValid || form.formState.isSubmitting}>
{form.formState.isSubmitting && <Spinner />} Reset Password
</Button>
</form>

View File

@@ -66,7 +66,14 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project, customImageStyles })
</span>
</div>
<Progress current={20000} goal={10000} />
<Progress
current={
project.totalDonationsBTCInFiat +
project.totalDonationsXMRInFiat +
project.totalDonationsFiat
}
goal={project.goal}
/>
</figcaption>
</figure>
</Link>

View File

@@ -17,8 +17,8 @@ const ProjectList: React.FC<ProjectListProps> = ({
projects,
}) => {
return (
<section className="bg-light items-left flex flex-col">
<ul className="mx-auto grid max-w-5xl grid-cols-1 sm:mx-0 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<section className="flex flex-col">
<ul className="mx-auto grid max-w-5xl grid-cols-1 sm:mx-0 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{projects &&
projects.slice(0, 6).map((p, i) => (
<li key={i} className="">

View File

@@ -77,7 +77,7 @@ function RegisterFormModal({ close, openLoginModal }: Props) {
<>
<DialogHeader>
<DialogTitle>Register</DialogTitle>
<DialogDescription>Start supporting Monero projects today!</DialogDescription>
<DialogDescription>Start supporting projects today!</DialogDescription>
</DialogHeader>
<Form {...form}>
@@ -148,7 +148,11 @@ function RegisterFormModal({ close, openLoginModal }: Props) {
I already have an account
</Button>
<Button type="submit" disabled={form.formState.isSubmitting} className="grow basis-0">
<Button
type="submit"
disabled={!form.formState.isValid || form.formState.isSubmitting}
className="grow basis-0"
>
{form.formState.isSubmitting && <Spinner />} Register
</Button>
</div>

View File

@@ -33,7 +33,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full sm:max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
'max-h-dvh fixed left-[50%] top-[50%] z-50 grid w-full overflow-auto sm:max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}

View File

@@ -3,3 +3,5 @@ export const MIN_AMOUNT = 1
export const MAX_AMOUNT = 5000
export const AMOUNT_STEP = 5
export const MEMBERSHIP_PRICE = 100
export const POINTS_PER_USD = 1
export const POINTS_REDEEM_PRICE_USD = 0.1

View File

@@ -1,12 +1,18 @@
export const fundHeaderNavLinks = [
{ title: 'Apply', href: 'apply', isButton: false },
{ title: 'FAQs', href: 'faq', isButton: false },
{ title: 'About', href: 'about', isButton: false },
{ title: 'Privacy', href: 'privacy', isButton: false },
{ title: 'Terms', href: 'terms', isButton: false },
]
import { FundSlug } from '@prisma/client'
export const homeHeaderNavLinks = [
{ title: 'Privacy', href: 'privacy', isButton: false },
{ title: 'Terms', href: 'terms', isButton: false },
]
export const fundHeaderNavLinks: Record<
FundSlug,
{ title: string; href: string; isButton: boolean }[]
> = {
monero: [
{ title: 'Apply', href: 'apply', isButton: false },
{ title: 'FAQs', href: 'faq', isButton: false },
{ title: 'About', href: 'about', isButton: false },
],
firo: [{ title: 'About', href: 'about', isButton: false }],
privacyguides: [{ title: 'About', href: 'about', isButton: false }],
general: [
{ title: 'FAQs', href: 'faq', isButton: false },
{ title: 'About', href: 'about', isButton: false },
],
}

89
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,89 @@
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run
environment:
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN}
REAL_IP_HEADER: Cf-Connecting-Ip
depends_on:
- nginx
nginx:
image: nginx:1
container_name: nginx
restart: unless-stopped
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
app:
build:
context: .
dockerfile: Dockerfile
container_name: app
restart: unless-stopped
environment:
APP_URL: https://donate.magicgrants.org
DATABASE_URL: ${DATABASE_URL}
NEXTAUTH_URL: https://donate.magicgrants.org
NEXTAUTH_URL_INTERNAL: http://localhost:3000
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
USER_SETTINGS_JWT_SECRET: ${USER_SETTINGS_JWT_SECRET}
SMTP_HOST: email-smtp.us-east-2.amazonaws.com
SMTP_PORT: 587
SMTP_USER: ${SMTP_USER}
SMTP_PASS: ${SMTP_PASS}
SES_VERIFIED_SENDER: info@magicgrants.org
STRIPE_MONERO_SECRET_KEY: ${STRIPE_MONERO_SECRET_KEY}
STRIPE_MONERO_WEBHOOK_SECRET: ${STRIPE_MONERO_WEBHOOK_SECRET}
STRIPE_FIRO_SECRET_KEY: ${STRIPE_FIRO_SECRET_KEY}
STRIPE_FIRO_WEBHOOK_SECRET: ${STRIPE_FIRO_WEBHOOK_SECRET}
STRIPE_PRIVACY_GUIDES_SECRET_KEY: ${STRIPE_PRIVACY_GUIDES_SECRET_KEY}
STRIPE_PRIVACY_GUIDES_WEBHOOK_SECRET: ${STRIPE_PRIVACY_GUIDES_WEBHOOK_SECRET}
STRIPE_GENERAL_SECRET_KEY: ${STRIPE_GENERAL_SECRET_KEY}
STRIPE_GENERAL_WEBHOOK_SECRET: ${STRIPE_GENERAL_WEBHOOK_SECRET}
KEYCLOAK_URL: ${KEYCLOAK_URL}
KEYCLOAK_CLIENT_ID: app
KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET}
KEYCLOAK_REALM_NAME: magic
BTCPAY_URL: ${BTCPAY_URL}
BTCPAY_EXTERNAL_URL: https://btcpay.magicgrants.org
BTCPAY_API_KEY: ${BTCPAY_API_KEY}
BTCPAY_STORE_ID: ${BTCPAY_STORE_ID}
BTCPAY_WEBHOOK_SECRET: ${BTCPAY_WEBHOOK_SECRET}
MONERO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
FIRO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
PRIVACY_GUIDES_APPLICATION_RECIPIENT: monerofund@magicgrants.org
GENERAL_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_MONERO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_FIRO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_PRIVACY_GUIDES_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_GENERAL_APPLICATION_RECIPIENT: monerofund@magicgrants.org
depends_on:
- postgres
postgres:
image: postgres:16-alpine
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: magic
POSTGRES_DB: magic
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- 'postgres_data:/var/lib/postgresql/data'
volumes:
postgres_data:

View File

@@ -1,86 +1,129 @@
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
btcpayserver:
restart: unless-stopped
command: tunnel --no-autoupdate run
container_name: generated_btcpayserver_1
image: ${BTCPAY_IMAGE:-btcpayserver/btcpayserver:1.13.3-altcoins}
expose:
- '49392'
environment:
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN}
REAL_IP_HEADER: Cf-Connecting-Ip
depends_on:
- nginx
nginx:
image: nginx:1
container_name: nginx
restart: unless-stopped
BTCPAY_POSTGRES: User ID=postgres;Host=btcpay-postgres;Port=5432;Application Name=btcpayserver;Database=btcpayserver${NBITCOIN_NETWORK:-mainnet}
BTCPAY_EXPLORERPOSTGRES: User ID=postgres;Host=btcpay-postgres;Port=5432;Application Name=btcpayserver;MaxPoolSize=80;Database=nbxplorer${NBITCOIN_NETWORK:-mainnet}
BTCPAY_NETWORK: ${NBITCOIN_NETWORK:-mainnet}
BTCPAY_BIND: 0.0.0.0:49392
BTCPAY_ROOTPATH: ${BTCPAY_ROOTPATH:-/}
BTCPAY_SSHCONNECTION: 'root@host.docker.internal'
BTCPAY_SSHTRUSTEDFINGERPRINTS: ${BTCPAY_SSHTRUSTEDFINGERPRINTS}
BTCPAY_SSHKEYFILE: ${BTCPAY_SSHKEYFILE}
BTCPAY_SSHAUTHORIZEDKEYS: ${BTCPAY_SSHAUTHORIZEDKEYS}
BTCPAY_DEBUGLOG: btcpay.log
BTCPAY_UPDATEURL: https://api.github.com/repos/btcpayserver/btcpayserver/releases/latest
BTCPAY_DOCKERDEPLOYMENT: 'true'
BTCPAY_CHAINS: 'xmr'
BTCPAY_XMR_DAEMON_URI: http://xmr-node.cakewallet.com:18081
BTCPAY_XMR_WALLET_DAEMON_URI: http://monerod_wallet:18082
BTCPAY_XMR_WALLET_DAEMON_WALLETDIR: /root/xmr_wallet
labels:
traefik.enable: 'true'
traefik.http.routers.btcpayserver.rule: Host(`${BTCPAY_HOST}`)
extra_hosts:
- 'host.docker.internal:host-gateway'
links:
- btcpay-postgres
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
app:
build:
context: .
dockerfile: Dockerfile
container_name: app
- 'btcpay_datadir:/datadir'
- 'nbxplorer_datadir:/root/.nbxplorer'
- 'btcpay_pluginsdir:/root/.btcpayserver/Plugins'
- 'xmr_wallet:/root/xmr_wallet'
- 'tor_servicesdir:/var/lib/tor/hidden_services'
- 'tor_torrcdir:/usr/local/etc/tor/'
ports:
- '49392:49392'
monerod_wallet:
restart: unless-stopped
container_name: btcpayserver_monero_wallet
image: btcpayserver/monero:0.18.3.3
entrypoint: monero-wallet-rpc --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=xmr-node.cakewallet.com:18081 --wallet-file=/wallet/wallet --password-file /wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -X GET http://btcpayserver:49392/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
expose:
- '18082'
ports:
- 18082:18082
volumes:
- 'xmr_wallet:/wallet'
nbxplorer:
restart: unless-stopped
container_name: generated_nbxplorer_1
image: nicolasdorier/nbxplorer:2.5.2
expose:
- '32838'
environment:
APP_URL: https://donate.magicgrants.org
DATABASE_URL: ${DATABASE_URL}
NBXPLORER_NETWORK: ${NBITCOIN_NETWORK:-mainnet}
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_TRIMEVENTS: 10000
NBXPLORER_SIGNALFILESDIR: /datadir
NBXPLORER_POSTGRES: User ID=postgres;Host=btcpay-postgres;Port=5432;Application Name=nbxplorer;MaxPoolSize=20;Database=nbxplorer${NBITCOIN_NETWORK:-mainnet}
NBXPLORER_AUTOMIGRATE: 1
NBXPLORER_NOMIGRATEEVTS: 1
NBXPLORER_DELETEAFTERMIGRATION: 1
links:
- btcpay-postgres
volumes:
- 'nbxplorer_datadir:/datadir'
NEXTAUTH_URL: https://donate.magicgrants.org
NEXTAUTH_URL_INTERNAL: http://localhost:3000
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
SMTP_HOST: email-smtp.us-east-2.amazonaws.com
SMTP_PORT: 587
SMTP_USER: ${SMTP_USER}
SMTP_PASS: ${SMTP_PASS}
SES_VERIFIED_SENDER: info@magicgrants.org
STRIPE_MONERO_SECRET_KEY: ${STRIPE_MONERO_SECRET_KEY}
STRIPE_MONERO_WEBHOOK_SECRET: ${STRIPE_MONERO_WEBHOOK_SECRET}
STRIPE_FIRO_SECRET_KEY: ${STRIPE_FIRO_SECRET_KEY}
STRIPE_FIRO_WEBHOOK_SECRET: ${STRIPE_FIRO_WEBHOOK_SECRET}
STRIPE_PRIVACY_GUIDES_SECRET_KEY: ${STRIPE_PRIVACY_GUIDES_SECRET_KEY}
STRIPE_PRIVACY_GUIDES_WEBHOOK_SECRET: ${STRIPE_PRIVACY_GUIDES_WEBHOOK_SECRET}
STRIPE_GENERAL_SECRET_KEY: ${STRIPE_GENERAL_SECRET_KEY}
STRIPE_GENERAL_WEBHOOK_SECRET: ${STRIPE_GENERAL_WEBHOOK_SECRET}
KEYCLOAK_URL: ${KEYCLOAK_URL}
KEYCLOAK_CLIENT_ID: app
KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET}
KEYCLOAK_REALM_NAME: magic
BTCPAY_URL: ${BTCPAY_URL}
BTCPAY_API_KEY: ${BTCPAY_API_KEY}
BTCPAY_STORE_ID: ${BTCPAY_STORE_ID}
BTCPAY_WEBHOOK_SECRET: ${BTCPAY_WEBHOOK_SECRET}
MONERO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
FIRO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
PRIVACY_GUIDES_APPLICATION_RECIPIENT: monerofund@magicgrants.org
GENERAL_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_MONERO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_FIRO_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_PRIVACY_GUIDES_APPLICATION_RECIPIENT: monerofund@magicgrants.org
NEXT_PUBLIC_GENERAL_APPLICATION_RECIPIENT: monerofund@magicgrants.org
depends_on:
- postgres
postgres:
image: postgres:16-alpine
container_name: postgres
btcpay-postgres:
restart: unless-stopped
container_name: generated_postgres_1
shm_size: 256mb
image: btcpayserver/postgres:13.13
command: ['-c', 'random_page_cost=1.0', '-c', 'shared_preload_libraries=pg_stat_statements']
environment:
POSTGRES_HOST_AUTH_METHOD: trust
volumes:
- 'btcpay_postgres_datadir:/var/lib/postgresql/data'
magic-postgres:
image: postgres:16-alpine
container_name: magic-postgres
restart: unless-stopped
ports:
- 5432:5432
environment:
POSTGRES_USER: magic
POSTGRES_DB: magic
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PASSWORD: magic
volumes:
- 'postgres_data:/var/lib/postgresql/data'
- 'magic_postgres_data:/var/lib/postgresql/data'
keycloak:
image: quay.io/keycloak/keycloak:25.0.6
container_name: magic-keycloak
restart: unless-stopped
command: start-dev
ports:
- 8080:8080
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
campaign-site:
image: node:20-alpine
container_name: magic-campaign-site
restart: unless-stopped
working_dir: /app
command: npm run dev
ports:
- 3000:3000
volumes:
- '.:/app'
volumes:
postgres_data:
btcpay_datadir:
btcpay_pluginsdir:
xmr_wallet:
xmr_data:
tor_datadir:
tor_torrcdir:
tor_servicesdir:
nbxplorer_datadir:
btcpay_postgres_datadir:
magic_postgres_data:

View File

@@ -1,24 +1,9 @@
# About the MAGIC Monero Fund
# About the MAGIC Firo Fund
MAGIC (Multidisciplinary Academic Grants in Cryptocurrencies) Grants is a 501(c)(3) U.S. nonprofit that focuses on building strong cryptocurrency communities and networks. [The MAGIC Monero Fund](https://magicgrants.org/funds/monero) is a subentity of MAGIC Grants guided by a five-member committee elected by [Monero community members](https://magicgrants.org/funds/monero/monero_fund_voters/).
The MAGIC Firo Fund was established in December 2022. It is currently administered by the MAGIC Board. It does not have a committee.
### MAGIC Monero Fund Vision and Goals:
The MAGIC Firo Fund is used to further the Firo and associated ecosystem with research projects, educational projects, and more.
* Improve the Monero ecosystem.
* Support Monero development and research.
* Create Monero educational resources.
* Fund Monero security audits.
* Run essential services that support the Monero ecosystem.
### Committee Members
* [Rucknium](https://github.com/Rucknium) is a Monero Research Lab researcher and economist who has focused on statistical obfuscation on Monero and Bitcoin Cash.
* [kayabaNerve](https://twitter.com/kayabaNerve) is a Monero-ecosystem developer and security researcher who was the first to implement Monero atomic swaps with ASMR.
* [monerobull](https://twitter.com/monerobull) is a distributor of marketing materials for Monero Outreach and moderates various communication channels of the Monero community.
* [artlimber](https://github.com/artlimber) is a Monero user and community supporter who believes privacy is a human right.
* [kowalabearhugs](https://twitter.com/kowalabearhugs) is a Monero community member, photographer, artist, seasonal farmer, and offseason wanderer.
The hope is that the Firo community grows large enough to sustain its own committee for this Fund.
For more information, please email [firofund@magicgrants.org](mailto:firofund@magicgrants.org).

View File

@@ -1,80 +0,0 @@
# MAGIC Monero Fund Request for Research Proposals
## Mission
The MAGIC Monero Fund is offering a grant program to support actionable Monero research, especially research relating to privacy, security, user experience, and efficiency. Proposals can be related to the Monero protocol directly, or they can be related to other areas of the Monero ecosystem.
## Target Research Areas
We are interested in Monero's [open research questions](https://github.com/monero-project/research-lab/issues/94), along with the following:
- Security proofs around Monero's existing constructions
- Arithmetic-circuit-based membership proofs, specifically ones without a trusted setup based on battle-tested cryptographic assumptions
While the above topics are especially encouraged, the Fund is open to funding other topics as well.
The proposal's chance to be approved will be increased if it also helps support one or more of the additional mission areas of the MAGIC Monero Fund: Development, Security (including audits); Critical Infrastructure; Community Building and Community Infrastructure; Interoperability; Feature Enhancements; Efficiency Improvements; Educational Materials.
## Funding Amount
The MAGIC Monero Fund anticipates funding requests of the equivalent of 5,000 to 20,000 USD at this time.
## Deadline
The MAGIC Monero Fund accepts applications on a rolling basis. Applications will be notified of the committee's vote on approval within three weeks of submission except in extraordinary circumstances.
## Proposal Format
Applicants must submit their applications on the web form [here](/apply). Alternatively, applicants can submit a PDF file by email to MoneroFund@magicgrants.org . Applicants are free to use their legal name or a pseudonym at this step, although note the "Eligibility" section below.
The research plan should be 3-5 pages (not counting citations, any images, and biographical sketch) and include the following:
* The research question you seek to answer, and how the Monero Project benefits from the research.
* A literature review of existing work and how your proposal improves upon it.
* Any preliminary work you have already completed.
* Description of the plan to carry out the research.
* A plan to work with Monero's developers and/or researchers to integrate the results of the research into Monero's protocol or ecosystem.
* A budget, likely consisting mostly of labor costs for the researcher(s). This should include the desired payment schedule, which should be no more frequent than monthly. Any additional budgetary item such as equipment should be included as well. Specify the desired form of payment, which can be cryptocurrency or fiat currency.
* OPTIONAL: A biographical sketch such as a resume or CV. Alternatively, a list of contributions to open source projects.
## Computing Resources Available
Computing and data storage resources can be made available to applicants depending on research requirements, resource availability, and vetting by a third party. These resources include a research computing server with a 64-thread Threadripper CPU, 256 GB RAM, 2TB SSD, and 12TB RAID1 HDD.
## Proposal Evaluation Criteria
Submitted proposals will be evaluated by the committee based on the following criteria:
* Impact on Monero's privacy, scaling, decentralization, and/or user experience
* Feasibility of research plan
* Prior work and qualifications of applicant(s)
* Clear plan to integrate research findings into Monero's protocol and/or ecosystem
## Eligibility
All competent researchers are eligible to apply, regardless of educational attainment or occupation. However, as a nonprofit organization registered under U.S. tax laws, MAGIC Grants is required to comply with certain laws when disbursing funds to grant recipients.
Grant recipients must complete a Due Diligence checklist, which are the last two pages of [this document](https://magicgrants.org/funds/MAGIC%20Fund%20Grant%20Disbursement%20Process%20and%20Requirements.pdf). This includes the collection of your ID and tax information. We will conduct sanctions checks.
## Vulnerability Research
If your proposal seek to uncover weaknesses in the privacy and/or security features of Monero as it exists today, then the Committee will require that you share any significant weaknesses with the Committee (and any critical vulnerabilities with [Monero's official Vulnerability Response Process](https://github.com/monero-project/meta/blob/master/VULNERABILITY_RESPONSE_PROCESS.md)) 90 days before publishing the research so that action can be taken to mitigate the vulnerabilities.
## How to Submit an Application
Applicants must submit their applications on the web form [here](/apply). Alternatively, applicants can submit a PDF file by email to MoneroFund@magicgrants.org
## Contact
Contact MoneroFund@magicgrants.org for further questions.

View File

@@ -1,75 +0,0 @@
# FAQ
## What is the mission statement of the Monero Fund?
- We are a committee of a 501(c)(3) public charity that funds Monero-related open source software, research and associated educational initiatives.
## How are funds distributed?
- Donations to the **general fund** are disbursed by our committee as needs arise. For projects listed on our site, we review and approve submissions carefully to ensure donated funds are positively impacting the Monero ecosystem. To hold researchers and developers accountable, payment schedules are aligned with approved project milestones.
## How much of my donation goes towards individuals and projects supported by the Monero Fund?
- We set up campaigns to closely match the amount needed to pay these individuals and projects. There are some credit card processing fees and other related fees that we try to estimate. Typically, we add a buffer of 3% to the project campaign to account for these.
- If there are any proceeds are retained by the MAGIC Monero Fund, and will be used on related projects.
- If a campaign fails to raise the full amount of funds, then the committee will work with the intended recipient to revise the scope of the project, or the raised funds will be used on related projects.
## Can an individual or project be funded by both the Monero Community Crowdfunding System (CCS) and the Monero Fund?
- Yes. There is no expectation of exclusivity, but a proposer cannot raise funds elsewhere for the exact same work that a MAGIC Monero Fund proposal is intended to cover. This allows a proposer to maintain multiple, simultaneous funding options, so long as the scope of work included in the proposal is unique.
## Do you distribute grants in Monero, Bitcoin or fiat?
- We distribute grants in the currency of the researchers choosing. We prefer to pay in XMR but will gladly distribute funds in BTC, USD, or other crypto or fiat currencies that are highly liquid.
## How do you collect Monero donations?
- We use BTCPay Server to receive all cryptocurrency donations. For fiat donations, we leverage Stripe Payments.
## Can you provide the view key to verify the Fund's incoming Monero donations?
- Yes. You can create a view-only Monero wallet with this information:
- Main address:
```
4458gM5iAPuJBeVf5u9DwS4Hj82qNPzAcgD7wF9VrRCRb4M635UtmZNPPDonqXkBsb2RmKjUSRLXtJNZCjZgnNFKKW3SxBz
```
- Secret view key:
```
2595ab540844231f66c2266f12b174461f81c09b39010dc42697a6a58a7d680c
```
- Restore height: 2724728
## How can I communicate with you?
- You can reach the committee via [Discord](https://discord.gg/yEuhhdtbHN), Matrix or [email](mailto:MoneroFund@magicgrants.org).
- Applications are reviewed as submitted. Research approval depends on several factors, including funding availability. Projects that align with our mission statement are encouraged to apply. Committee members evaluate each application based on technical merit, potential impact, and the team&#39;s ability to execute.
- All projects should be free and open source and related to Monero or an ancillary technology (like Tor, etc.) which helps Monero use. Your project must align with the mission of our charity to be eligible for donations.
- To reach MAGIC Grants, please [email us](mailto:info@magicgrants.org).
## How can I submit a project for fundraising?
- Any project that is free and open source and is aligned with the Monero Fund mission are free to [submit an application](/apply) for their project to be listed on the [Monero Fund](/projects) project page.
- All applications which fulfill the criteria will be evaluated by the Monero Fund Committee. Upon approval, projects will be listed on the MoneroFund project portal. Donors will be able to contribute funds directly to the these pre-approved, mission-aligned projects.
- If you are helping Monero and Free and Open Source Software (FOSS), please [submit an application](/apply) to be listed on our site.
## Are my donations tax deductible?
- Yes. MAGIC Monero Fund is committee under Multidisciplinary Academic Grants in Cryptocurrencies (MAGIC Grants), a 501(c)(3) non-profit organization. All gifts and donations are tax-deductible in the USA.
- If you would like to receive a tax deduction, we may need to collect supplementary information such as your name and email for required record keeping. If you donate over $500 with cryptocurrencies, you will need to fill out [Form 8283](https://www.irs.gov/pub/irs-pdf/f8283.pdf) and email that form to [info@maigcgrants.org](mailto:info@magicgrants.org) for us to sign Part V.
The Monero Fund relies on donors like you to help promote and support a sustainable ecosystem of contributors to the Monero ecosystem. We greatly appreciate your contributions.
# Can I join the committee?
- MAGIC Monero Fund committee elections happen annually. Learn more on the [MAGIC Grants website](https://magicgrants.org/funds/monero).
# Can I donate with bank transfer / wire?
- Yes! [Email us](mailto:MoneroFund@magicgrants.org), and we will send you the bank details and manually add your donation. We can accept domestic and international wire transfers. Wires are appreciated for large donations, so that ~3% more of your donation can go to the recipient instead of credit card fees.

View File

@@ -0,0 +1,49 @@
---
fund: firo
title: 'Aram Jivanyan to Research Elliptic Curves'
summary: "Aram Jivanyan, a researcher with years of research and implementation experience with Firo and Lelantus Spark, is working with MAGIC Grants to conduct six months of additional curve research to improve the privacy of Lelantus Spark."
nym: 'Aram Jivanyan'
coverImage: '/img/project/firo-curve-trees.png'
website: 'https://magicgrants.org/2024/05/15/Aram-Jivanyan-Curve-Tree-Research.html'
personalWebsite: 'https://x.com/aramjivanyan'
socialLinks:
- 'https://x.com/aramjivanyan'
type: 'Other Free and Open Source Project'
date: '2023-05-24'
staticXMRaddress: ''
goal: 40000
isFunded: true
numDonationsXMR: 0
totalDonationsXMRInFiat: 0
totalDonationsXMR: 0
numDonationsBTC: 0
totalDonationsBTCInFiat: 0
totalDonationsBTC: 0
numDonationsFiat: 40000
totalDonationsFiat: 1
---
### Result: [Research paper](https://magicgrants.org/2024/05/15/Aram-Jivanyan-Curve-Tree-Research.html)
Aram Jivanyan, a researcher with years of research and implementation experience with Firo and Lelantus Spark, is working with MAGIC Grants to conduct six months of additional curve research to improve the privacy of Lelantus Spark.
This research grant was made possible because of the [generous donation](https://magicgrants.org/2022/12/22/200000-Donation-from-Arcadia-for-Firo) to the [MAGIC Firo Fund](https://magicgrants.org/funds/firo) by [Arcadia](https://www.arcadiamgroup.com/).
Before working with MAGIC Grants, Aram did preliminary work to assess the feasibility of this research direction. Following this initial analysis, he is confident that this research project is likely to bring greater anonymity sets to the final Lelantus Spark implementation.
The aim of the further 6-month research is the following:
1. Research the most efficient path forward for implementing paired elliptic curve (relating to secP256k1, secQ256k1, and/or other applicable curves). One direction will be implementation of the curve on C++ based on the existing curve implementation. Another possible direction can be bindings of Rust implementation into our C++ library.
2. Nail down how the membership proofs with curve trees will be integrated into the full lelantus spark design and what should be modified to make them interoperable with other spark components.
3. Design the bulletproof-based circuit for set membership checks and nail the implementation details for coding.
4. Write a new paper which will summarizes all research, findings and design for enabling Scaling Lelantus Spark Anonymity Set with Curve Trees.
The research aims to solve the greatest challenge of Spark which is enabling anonymity sets in the millions. Other privacy cryptocurrency protocols such as Monero's Seraphis could potentially benefit of this research as well.
The funding will also cover the Aram's continuing research on the [Aura voting protocol](https://eprint.iacr.org/2022/543) to improve the paper, work on the implementation design and architecture and improve the preprint to get it submitted to crypto conferences.
Stay updated with the MAGIC Grants and Firo social media accounts for updates on this research project.

View File

@@ -0,0 +1,31 @@
---
fund: firo
title: 'Lelantus Spark Flutter Library'
summary: "This library will make it easier to build Flutter applications on Firo Lelantus Spark."
nym: 'Cypher Stack'
coverImage: ''
website: 'https://magicgrants.org/2024/05/16/Firo-Lelantus-Spark-Flutter-Library-Stack-Wallet.html'
personalWebsite: 'https://cypherstack.com'
socialLinks:
- 'https://twitter.com/cypher_stack'
type: 'Other Free and Open Source Project'
date: '2023-05-24'
staticXMRaddress: ''
goal: 13000
isFunded: true
numDonationsXMR: 0
totalDonationsXMRInFiat: 0
totalDonationsXMR: 0
numDonationsBTC: 0
totalDonationsBTCInFiat: 0
totalDonationsBTC: 0
numDonationsFiat: 13000
totalDonationsFiat: 1
---
### Result: [Flutter plugin library](https://github.com/cypherstack/flutter_libsparkmobile)
With Firo's Lelantus Spark [mainnet activation on January 18, 2024](https://firo.org/2024/01/18/spark-is-live.html), the MAGIC Firo Fund tasked Stack Wallet last December and January to build a Flutter plugin for Firo's [sparkmobile](https://github.com/firoorg/sparkmobile) library. [This Flutter plugin](https://github.com/cypherstack/flutter_libsparkmobile) makes it easy for Flutter wallet apps to integrate Firo Lelantus Spark, and because it is MIT-licensed, anyone can use the plugin.
Lelantus Spark is an important evolution in Firo's network. Spark transactions within Spark addresses hide the sender, receiver, and amount transferred.

View File

@@ -1,24 +1,19 @@
# About the MAGIC Monero Fund
# About MAGIC Grants
MAGIC (Multidisciplinary Academic Grants in Cryptocurrencies) Grants is a 501(c)(3) U.S. nonprofit that focuses on building strong cryptocurrency communities and networks. [The MAGIC Monero Fund](https://magicgrants.org/funds/monero) is a subentity of MAGIC Grants guided by a five-member committee elected by [Monero community members](https://magicgrants.org/funds/monero/monero_fund_voters/).
MAGIC Grants is a public charity that provides undergraduate scholarships for students interested in cryptocurrencies and privacy, supports public cryptocurrency infrastructure, and supports privacy.
### MAGIC Monero Fund Vision and Goals:
## Scholarships
* Improve the Monero ecosystem.
* Support Monero development and research.
* Create Monero educational resources.
* Fund Monero security audits.
* Run essential services that support the Monero ecosystem.
MAGIC Grants offers undergraduate scholarships to students who are interested in cryptocurrencies.
### Committee Members
## Infrastructure Grants
* [Rucknium](https://github.com/Rucknium) is a Monero Research Lab researcher and economist who has focused on statistical obfuscation on Monero and Bitcoin Cash.
MAGIC Grants offers infrastructure grants to cryptocurrency ecosystems. These grants can help with security audits, research and development, or community-building.
* [kayabaNerve](https://twitter.com/kayabaNerve) is a Monero-ecosystem developer and security researcher who was the first to implement Monero atomic swaps with ASMR.
## Education Grants
* [monerobull](https://twitter.com/monerobull) is a distributor of marketing materials for Monero Outreach and moderates various communication channels of the Monero community.
MAGIC Grants supports leading educational resources for cryptocurrencies and privacy.
* [artlimber](https://github.com/artlimber) is a Monero user and community supporter who believes privacy is a human right.
* [kowalabearhugs](https://twitter.com/kowalabearhugs) is a Monero community member, photographer, artist, seasonal farmer, and offseason wanderer.
## MAGIC Funds
Semi-autonomous committees raise funds and issue qualifying grants to empower their communities.

View File

@@ -1,75 +1,26 @@
# FAQ
## What is the mission statement of the Monero Fund?
## What type of charity is MAGIC Grants?
- We are a committee of a 501(c)(3) public charity that funds Monero-related open source software, research and associated educational initiatives.
MAGIC Grants is a 501(c)(3) public charity registered in Colorado.
## What are MAGIC Funds?
MAGIC Funds are semi-autonomous dedicated funds that allow for their committees to fund important projects in their respective ecosystems. For more information, check out the [dedicated MAGIC Funds page](https://magicgrants.org/funds).
## How are funds distributed?
## How can I apply for a scholarship?
- Donations to the **general fund** are disbursed by our committee as needs arise. For projects listed on our site, we review and approve submissions carefully to ensure donated funds are positively impacting the Monero ecosystem. To hold researchers and developers accountable, payment schedules are aligned with approved project milestones.
Apply for a scholarship [here](https://magicgrants.org/scholarships/scholarship-application).
## How can I apply for a grant?
## How much of my donation goes towards individuals and projects supported by the Monero Fund?
Reach out the the MAGIC Fund that you would like to apply for a grant from. If your project does not fit within the scope of an existing MAGIC Fund, please contact [info@magicgrants.org](mailto:info@magicgrants.org).
- We set up campaigns to closely match the amount needed to pay these individuals and projects. There are some credit card processing fees and other related fees that we try to estimate. Typically, we add a buffer of 3% to the project campaign to account for these.
- If there are any proceeds are retained by the MAGIC Monero Fund, and will be used on related projects.
- If a campaign fails to raise the full amount of funds, then the committee will work with the intended recipient to revise the scope of the project, or the raised funds will be used on related projects.
## How can I contribute to MAGIC Grants?
## Can an individual or project be funded by both the Monero Community Crowdfunding System (CCS) and the Monero Fund?
Your donation is much appreciated! You can contribute a MAGIC Fund or MAGIC Grants at [donate.maigcgrants.org](https://donatemagicgrants.org).
- Yes. There is no expectation of exclusivity, but a proposer cannot raise funds elsewhere for the exact same work that a MAGIC Monero Fund proposal is intended to cover. This allows a proposer to maintain multiple, simultaneous funding options, so long as the scope of work included in the proposal is unique.
## Do you distribute grants in Monero, Bitcoin or fiat?
- We distribute grants in the currency of the researchers choosing. We prefer to pay in XMR but will gladly distribute funds in BTC, USD, or other crypto or fiat currencies that are highly liquid.
## How do you collect Monero donations?
- We use BTCPay Server to receive all cryptocurrency donations. For fiat donations, we leverage Stripe Payments.
## Can you provide the view key to verify the Fund's incoming Monero donations?
- Yes. You can create a view-only Monero wallet with this information:
- Main address:
```
4458gM5iAPuJBeVf5u9DwS4Hj82qNPzAcgD7wF9VrRCRb4M635UtmZNPPDonqXkBsb2RmKjUSRLXtJNZCjZgnNFKKW3SxBz
```
- Secret view key:
```
2595ab540844231f66c2266f12b174461f81c09b39010dc42697a6a58a7d680c
```
- Restore height: 2724728
## How can I communicate with you?
- You can reach the committee via [Discord](https://discord.gg/yEuhhdtbHN), Matrix or [email](mailto:MoneroFund@magicgrants.org).
- Applications are reviewed as submitted. Research approval depends on several factors, including funding availability. Projects that align with our mission statement are encouraged to apply. Committee members evaluate each application based on technical merit, potential impact, and the team&#39;s ability to execute.
- All projects should be free and open source and related to Monero or an ancillary technology (like Tor, etc.) which helps Monero use. Your project must align with the mission of our charity to be eligible for donations.
- To reach MAGIC Grants, please [email us](mailto:info@magicgrants.org).
## How can I submit a project for fundraising?
- Any project that is free and open source and is aligned with the Monero Fund mission are free to [submit an application](/apply) for their project to be listed on the [Monero Fund](/projects) project page.
- All applications which fulfill the criteria will be evaluated by the Monero Fund Committee. Upon approval, projects will be listed on the MoneroFund project portal. Donors will be able to contribute funds directly to the these pre-approved, mission-aligned projects.
- If you are helping Monero and Free and Open Source Software (FOSS), please [submit an application](/apply) to be listed on our site.
## Are my donations tax deductible?
- Yes. MAGIC Monero Fund is committee under Multidisciplinary Academic Grants in Cryptocurrencies (MAGIC Grants), a 501(c)(3) non-profit organization. All gifts and donations are tax-deductible in the USA.
- If you would like to receive a tax deduction, we may need to collect supplementary information such as your name and email for required record keeping. If you donate over $500 with cryptocurrencies, you will need to fill out [Form 8283](https://www.irs.gov/pub/irs-pdf/f8283.pdf) and email that form to [info@maigcgrants.org](mailto:info@magicgrants.org) for us to sign Part V.
The Monero Fund relies on donors like you to help promote and support a sustainable ecosystem of contributors to the Monero ecosystem. We greatly appreciate your contributions.
# Can I join the committee?
- MAGIC Monero Fund committee elections happen annually. Learn more on the [MAGIC Grants website](https://magicgrants.org/funds/monero).
# Can I donate with bank transfer / wire?
- Yes! [Email us](mailto:MoneroFund@magicgrants.org), and we will send you the bank details and manually add your donation. We can accept domestic and international wire transfers. Wires are appreciated for large donations, so that ~3% more of your donation can go to the recipient instead of credit card fees.
## How can I propose a new MAGIC Fund?
Please review the process to propose a new MAGIC Fund [here](https://magicgrants.org/funds/fund_initial_steps).

View File

View File

@@ -1,6 +1,6 @@
# About the MAGIC Monero Fund
MAGIC (Multidisciplinary Academic Grants in Cryptocurrencies) Grants is a 501(c)(3) U.S. nonprofit that focuses on building strong cryptocurrency communities and networks. [The MAGIC Monero Fund](https://magicgrants.org/funds/monero) is a subentity of MAGIC Grants guided by a five-member committee elected by [Monero community members](https://magicgrants.org/funds/monero/monero_fund_voters/).
MAGIC Grants is a 501(c)(3) U.S. nonprofit that focuses on building strong cryptocurrency communities and networks. [The MAGIC Monero Fund](https://magicgrants.org/funds/monero) is a subentity of MAGIC Grants guided by a five-member committee elected by [Monero community members](https://magicgrants.org/funds/monero/monero_fund_voters).
### MAGIC Monero Fund Vision and Goals:
@@ -21,4 +21,3 @@ MAGIC (Multidisciplinary Academic Grants in Cryptocurrencies) Grants is a 501(c)
* [artlimber](https://github.com/artlimber) is a Monero user and community supporter who believes privacy is a human right.
* [kowalabearhugs](https://twitter.com/kowalabearhugs) is a Monero community member, photographer, artist, seasonal farmer, and offseason wanderer.

View File

@@ -25,7 +25,7 @@ The MAGIC Monero Fund accepts applications on a rolling basis. Applications will
## Proposal Format
Applicants must submit their applications on the web form [here](/apply). Alternatively, applicants can submit a PDF file by email to MoneroFund@magicgrants.org . Applicants are free to use their legal name or a pseudonym at this step, although note the "Eligibility" section below.
Applicants must submit their applications on the web form [here](/monero/apply). Alternatively, applicants can submit a PDF file by email to MoneroFund@magicgrants.org . Applicants are free to use their legal name or a pseudonym at this step, although note the "Eligibility" section below.
The research plan should be 3-5 pages (not counting citations, any images, and biographical sketch) and include the following:
@@ -73,7 +73,7 @@ If your proposal seek to uncover weaknesses in the privacy and/or security featu
## How to Submit an Application
Applicants must submit their applications on the web form [here](/apply). Alternatively, applicants can submit a PDF file by email to MoneroFund@magicgrants.org
Applicants must submit their applications on the web form [here](/monero/apply). Alternatively, applicants can submit a PDF file by email to MoneroFund@magicgrants.org
## Contact

View File

@@ -28,7 +28,6 @@
- We use BTCPay Server to receive all cryptocurrency donations. For fiat donations, we leverage Stripe Payments.
## Can you provide the view key to verify the Fund's incoming Monero donations?
- Yes. You can create a view-only Monero wallet with this information:
@@ -60,7 +59,7 @@
## Are my donations tax deductible?
- Yes. MAGIC Monero Fund is committee under Multidisciplinary Academic Grants in Cryptocurrencies (MAGIC Grants), a 501(c)(3) non-profit organization. All gifts and donations are tax-deductible in the USA.
- Yes, your donation may be tax deductible. MAGIC Monero Fund is committee under MAGIC Grants, a 501(c)(3) non-profit organization. All gifts and donations are tax-deductible in the USA.
- If you would like to receive a tax deduction, we may need to collect supplementary information such as your name and email for required record keeping. If you donate over $500 with cryptocurrencies, you will need to fill out [Form 8283](https://www.irs.gov/pub/irs-pdf/f8283.pdf) and email that form to [info@maigcgrants.org](mailto:info@magicgrants.org) for us to sign Part V.
The Monero Fund relies on donors like you to help promote and support a sustainable ecosystem of contributors to the Monero ecosystem. We greatly appreciate your contributions.
@@ -72,4 +71,3 @@ The Monero Fund relies on donors like you to help promote and support a sustaina
# Can I donate with bank transfer / wire?
- Yes! [Email us](mailto:MoneroFund@magicgrants.org), and we will send you the bank details and manually add your donation. We can accept domestic and international wire transfers. Wires are appreciated for large donations, so that ~3% more of your donation can go to the recipient instead of credit card fees.

View File

@@ -14,20 +14,22 @@ type: 'Other Free and Open Source Project'
staticXMRaddress: '8454MvGDuZPFP1WKSMcgbRJqV1sXHz7Z3KyURMpkoLXR3CJUZiebjymjQGc6YvTWqhFZEtJwELbcgFHZ9qGPwPsF7fWLWPT'
goal: 28800
isFunded: true
numdonationsxmr: 43
totaldonationsinfiatxmr: 28800
totaldonationsxmr: 226.1
numdonationsbtc: 0
totaldonationsinfiatbtc: 0
totaldonationsbtc: 0
fiatnumdonations: 0
fiattotaldonationsinfiat: 0
fiattotaldonations: 0
numDonationsXMR: 43
totalDonationsXMRInFiat: 28800
totalDonationsXMR: 226.1
numDonationsBTC: 0
totalDonationsBTCInFiat: 0
totalDonationsBTC: 0
numDonationsFiat: 0
totalDonationsFiat: 0
---
### Funded goal: 28,800 USD
### Start: February 2024
### End: July 2024
### Result: Development Milestones [1](https://github.com/MAGICGrants/Monero-Fund/issues/27), [2](https://github.com/MAGICGrants/Monero-Fund/issues/29), and [3](https://github.com/MAGICGrants/Monero-Fund/issues/31)
vtnerd (Lee Clagett) is the author of [Monero-LWS](https://github.com/vtnerd/monero-lws), and has been a [contributor to the Monero codebase since 2016](https://github.com/monero-project/monero/pulls?page=7&q=is%3Apr+author%3Avtnerd+created%3A%3E2016-10-01). He is a veteran of four CCS proposals; [[1]](https://ccs.getmonero.org/proposals/vtnerd-tor-tx-broadcasting.html), [[2]](https://ccs.getmonero.org/proposals/vtnerd-2020-q4.html), [[3]](https://ccs.getmonero.org/proposals/vtnerd-2021-q1.html), [[4]](https://ccs.getmonero.org/proposals/vtnerd-2023-q3.html)

View File

@@ -13,15 +13,15 @@ date: '2023-06-08'
staticXMRaddress: '87LZA8XLDvhVKLi974MaxUANcvkWdL6n986R7WNgKLXY16y31t69Z8228EWcg8THQq3tuAWfQ7Np35Tt3AhPrjzcNbm8Jr5'
goal: 29260
isFunded: true
numdonationsxmr: 27
totaldonationsinfiatxmr: 29260
totaldonationsxmr: 220
numdonationsbtc: 0
totaldonationsinfiatbtc: 0
totaldonationsbtc: 0
fiatnumdonations: 0
fiattotaldonationsinfiat: 0
fiattotaldonations: 0
numDonationsXMR: 27
totalDonationsXMRInFiat: 29260
totalDonationsXMR: 220
numDonationsBTC: 0
totalDonationsBTCInFiat: 0
totalDonationsBTC: 0
numDonationsFiat: 0
totalDonationsFiat: 0
---
### Funded Goal: 220 XMR (86 XMR contributed from MAGIC Monero Fund general fund)

View File

@@ -13,15 +13,15 @@ socialLinks:
type: 'Other Free and Open Source Project'
goal: 24000
isFunded: true
numdonationsxmr: 66
totaldonationsinfiatxmr: 18861
totaldonationsxmr: 125.74
numdonationsbtc: 0
totaldonationsinfiatbtc: 0
totaldonationsbtc: 0
fiatnumdonations: 8
fiattotaldonationsinfiat: 5139
fiattotaldonations: 5139
numDonationsXMR: 66
totalDonationsXMRInFiat: 18861
totalDonationsXMR: 125.74
numDonationsBTC: 0
totalDonationsBTCInFiat: 0
totalDonationsBTC: 0
numDonationsFiat: 8
totalDonationsFiat: 5139
---
### Funded Goal: 24,000 USD (5,000 USD contributed from the MAGIC Monero Fund general fund)

View File

@@ -13,15 +13,15 @@ socialLinks:
type: 'Other Free and Open Source Project'
goal: 12000
isFunded: true
numdonationsxmr: 1
totaldonationsinfiatxmr: 12000
totaldonationsxmr: 80
numdonationsbtc: 0
totaldonationsinfiatbtc: 0
totaldonationsbtc: 0
fiatnumdonations: 0
fiattotaldonationsinfiat: 0
fiattotaldonations: 0
numDonationsXMR: 1
totalDonationsXMRInFiat: 12000
totalDonationsXMR: 80
numDonationsBTC: 0
totalDonationsBTCInFiat: 0
totalDonationsBTC: 0
numDonationsFiat: 0
totalDonationsFiat: 0
---
### Funded Goal: 12,000 USD (12,000 USD contributed from MAGIC Monero Fund general fund)

View File

@@ -1,75 +1,51 @@
# Privacy Policy
# MAGIC Grants Privacy Policy
**Last Updated: November 5, 2021**
**Last Updated: October 14, 2024**
Open Sats Initiative&#39;s core purpose is to foster an ecosystem that supports the collaborative and public development of free and open-source software projects (each, a &quot;Project&quot;). This privacy policy (&quot;Privacy Policy&quot;) describes our policies and procedures about the collection, use, disclosure and sharing, or other processing of your personal information when you use our websites (e.g., opensats.org) or participate in or use our project sites (collectively, the &quot;Sites&quot;), as well as when you interact with or participate in our educations and training programs and events. (collectively, the &quot;Services&quot;). This Privacy Policy applies to activities by Open Sats Initiative and its affiliates, subsidiaries and related entities (collectively &quot;Open Sats&quot; &quot;we&quot; or &quot;us&quot;), including activities that we perform for other entities through third party agreements.
MAGIC Grants's is a Colorado public charity.
For purposes of the General Data Protection Regulation (&quot;GDPR&quot;), Open Sats is the controller of your personal information. Where processing of personal information is undertaken by our affiliates, subsidiaries and related entities, they are a joint controller with Open Sats Initiative for your personal information.
This privacy policy (**Privacy Policy**) describes our policies and procedures about the collection, use, disclosure and sharing, or other processing of your personal information when you use our websites (e.g., magcigrants.org) or participate in or use our project sites (collectively, the **Sites**), as well as when you interact with or participate in our educations and training programs and events (collectively, the **Services**). This Privacy Policy applies to activities by MAGIC Grants and its affiliates, subsidiaries and related entities (collectively **MAGIC Grants**; **we**, or **us**), including activities that we perform for other entities through third party agreements.
Capitalized terms that are not defined in this Privacy Policy have the meaning given them in our Terms and Conditions (the &quot;Terms&quot;). In this Privacy Policy, &quot;personal information&quot; includes references to &quot;personal data&quot; as defined under applicable laws. Your use of our Sites and Services, and any dispute over privacy, is subject to this Policy and the relevant Terms, including the applicable limitations on damages and the resolution of disputes. The Terms are incorporated by reference into this Policy.
For purposes of the General Data Protection Regulation (**GDPR**), MAGIC Grants is the controller of your personal information. Where processing of personal information is undertaken by our affiliates, subsidiaries and related entities, they are a joint controller with MAGIC Grants for your personal information.
## Personal Information That Open Sats Collects
Capitalized terms that are not defined in this Privacy Policy have the meaning given them in our Terms and Conditions (the **Terms**). In this Privacy Policy, **personal information** includes references to **personal data** as defined under applicable laws. Your use of our Sites and Services, and any dispute over privacy, is subject to this Policy and the relevant Terms, including the applicable limitations on damages and the resolution of disputes. The Terms are incorporated by reference into this Policy.
We collect personal information directly from individuals, from third parties, and automatically through the Sites and Services. You do not have to provide us your personal information. However, if you choose not to disclose certain information, we will not be able to provide you with access to certain services or features, including registering on the Sites, registration for training, events, or other programs, or participation in certain aspects of our open-source projects.
## Summary
_Registration Information._ We collect personal information when you register for an account or register to participate in an Open Sats event or Program:
MAGIC Grants aims to collect the least information required. You may provide a name and email address to create an account with our Sites and Services. We do not share these with third parties except as required by law. We retain records of donor and grant information as required by law.
_Account and Profile Information._ Users may sign up for, request, or order our Services and may register to receive materials on our Sites. Users may also create a login, which is a single sign-on account which is used for common access to many of our Services. Personal information collected on the Sites includes community forum content, profiles, photographs, names, unique identifiers, information about your current and past employment affiliations, contact information (address, telephone number, email address, preferred pronoun, etc.), and transaction information (to the extent you share that information). In order to access certain personalized services on the Sites, you may be asked to also create and store a username and password for an account from Open Sats. In order to improve the functioning of the website and our subsequent communications to users we may also ask users to provide additional optional information regarding your interests, demographics, experience and detailed contact preferences.
You may subscribe to our email service, which you can opt out of at any time.
_Donations._ To register for and participate in the Open Sats Projects and related Services, users must have an active login ID and account (see above for information collected related to account registration). Open Sats may collect the following information related to financial contributions and :Depending on the Community Bridge Services in which users participate, we may also collect additional information relating to their use of those Services, including:
This Privacy Policy does not cover your interactions with third parties, such as you joining a Discord server or Matrix channel to communicate with MAGIC Grants community members. The third party's privacy policy will apply.
- _Donations_: We collect information about financial contributions made, as well as and funds received through the Sites. Open Sats generally only records the result of the transaction and any references to the transaction record provided by the third-party site. For example, when users make financial contributions to projects, we collect and process the donation amount, allocation to certain projects, and identifiers used to associate that donation with the donor and project in the project&#39;s open and transparent public ledger (unless otherwise agreed to by the donor and Open Sats. We also collect disbursement amount and category, recipient name and email, and identifiers related to disbursements of project funds for projects. Further, we use third-party services including Stripe to facilitate funding and disbursements. If applicable, the third-party site may collect payment information directly to facilitate a transaction.
If you have questions about this Privacy Policy, please contact info@magicgrants.org.
_Events Registration._ When you register for an Open Sats event (training, conference, or other event) to participate as an attendee, a speaker or a sponsor, we collect personal information that includes name, company, contact information, and other information. We may also collect other optional personal information such as likes, interests, preferred pronoun, dietary restriction, size preferences for conference attire gifts and other background information. In addition, if you provide it, we may collect (1) personal information about disabilities, medical conditions and allergies in order to provide appropriate accommodations for attendees, and (2) personal information about your citizenship, date of birth, and passport details if you request assistance from us with obtaining a visa letter to travel to one of our events.
## Personal Information That MAGIC Grants Collects
For in-person events requiring attendees to be vaccinated against COVID-19, in order to provide a safer environment for attendees and staff, we may collect information to verify your identity and COVID-19 vaccination status. We may collect this information via direct verification of identity and vaccination status documents by Open Sats staff or third-party contractors, and/or through the use of third-party vaccination status apps and service providers.
We collect personal information directly from individuals, from third parties, and automatically through the Sites and Services. You do not have to provide us your personal information. However, if you choose not to disclose certain information, that may limit your eligibility for certain products and services.
_Training and Certification Exam Registration_. When you participate in one of our training or certification programs, we collect registration-related personal information that includes name, company, certifications, contact information, and other information depending on the circumstances.
_Registration Information._ We collect personal information when you register for an account, or when you provide it when filling out a form.
_Registration for Projects._ You can register to receive access to various information provided by Open Sats and its free and open-source Projects relating to the open-source ecosystem, open source project development, collaboration and best practices. This includes providing us with personal information such as your email address and name to receive newsletters, mailing list postings and social media postings, to view webinars, and to access other resources made available by Open Sats and its Projects.
_Account and Profile Information._ Users may sign up for, request, or order our Services and may register to receive materials on our Sites. Users may also create a login, which is a single sign-on account which is used for common access to many of our Services. Personal information collected on the Sites may include community forum content, profiles, photographs, names, unique identifiers, information about your current and past employment affiliations, contact information (address, telephone number, email address, preferred pronoun, etc.), and transaction information (to the extent you share that information). In order to access certain personalized services on the Sites, you may be asked to also create and store a username and password for an account from MAGIC Grants. In order to improve the functioning of the website and our subsequent communications to users we may also ask users to provide additional optional information regarding your interests, demographics, experience and detailed contact preferences.
**Your Contributions to Open Source Projects**.
_Donations._ To register for and participate in select MAGIC Grants Projects and related Services, users may need an login ID and account (see above for information collected related to account registration). MAGIC Grants may collect the following information related to financial contributions, and we may also collect additional information relating to their use of those Services. Your donation records are kept by MAGIC Grants so that we can meet our charitable requirements.
_Project Integrity and Credit for Attribution_. When you contribute source code, documentation or other content to one of our Projects (whether on your own behalf or through a third party entity), we collect and store the information and content that you contribute. This includes the contents of those contributions, as well as information required to confirm the provenance of intellectual property contained in those contributions, and personal information that you make publicly available in the record of the contribution pursuant to sign-offs under the a Certificate of Origin as Follows:
_Training and Certification Exam Registration_. If you participate in any training and/or certification program, we may collect information about you such as your name, employer, address, and other information that you provide.
_Developer&#39;s Certificate of Origin:_
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I have the right to submit it under the open-source license indicated in the file; or
(b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open-source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or
(c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.
(d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. [end]
Some Projects require additional agreements or information pursuant to their intellectual property policies; in such cases we collect and store information related to your acceptance of those agreements. We may also collect information relating to your participation in technical, governance, or other Project-related meetings.
_Other Project-related Content._ The content you provide in relation to Projects also includes materials that you make publicly available in connection with Project development, collaboration and communication, such as on mailing lists, blogs, Project pages and issue trackers, and related services.
_Your Content_. We collect and store the information and content that you post to the Sites, including your questions, answers, comments, forum postings, and responses to surveys.
_Communications_. When you communicate with us (via email, phone, through the Sites or otherwise), we may maintain a record of your communication.
_Payment Information._ To participate in or purchase Services (including registering for events, training and certification exams), users may be asked to be directed to a third-party site, such as Stripe, to pay for their purchases. If applicable, the third-party site may collect payment information directly to facilitate a transaction. Open Sats generally only records the result of the transaction and any references to the transaction record provided by the third-party site.
_Automatically Collected Information._ In addition, Open Sats may automatically collect the following information about users&#39; use of the Sites or Services through cookies, web beacons, and other technologies: your domain name; your browser type and operating system; web pages you view; when you open certain emails we send; links you click; your IP address; your country of location; the length of time you visit our Sites and or use our Services; and the referring URL, or the webpage that led you to our Sites. We may combine this information with other information that we have collected about you, including, where applicable, your user name, name, and other personal information.
_Membership_. If you create an account and become a member, we collect and retain information necessary for the maintenance of your membership.
**Basis for Use of Personal Information:**
**Purposes and Legitimate Interests**
Open Sats uses the personal information we collect for our legitimate non-proft and charitable business interests, which include the following purposes:
MAGIC Grants uses the personal information we collect for our legitimate non-proft and charitable business interests, which include the following purposes:
- _Providing our Sites and Services_. To provide the Services and our Sites, to communicate with you about your use of our Sites and Services, to respond to your inquiries, provide support and maintenance of the Sites and for other purposes to support users and the community.
- _Operating our Open-Source Projects_. To enable communication between and among open source developers in the community; to facilitate and document Project governance and technical decision-making; to maintain, and make publicly available on a perpetual basis, records regarding intellectual property provenance and license compliance for Project contributions; and for related activities to further Open Sats&#39;s core purpose of fostering an ecosystem that supports the collaborative and public development of free and open source software projects. See the &quot;Project Integrity and Credit for Attribution&quot; section above for more information.
- _Operating our Open-Source Projects_. To enable communication between and among open source developers in the community; to facilitate and document Project governance and technical decision-making; to maintain, and make publicly available on a perpetual basis, records regarding intellectual property provenance and license compliance for Project contributions; and for related activities to further MAGIC Grants's core purposes.
- _Maintain our Training and Certification Programs_. To maintain records about who has attended or registered to attend educational or training programs.
- _Event Administration_. To plan, organize, and facilitate access to events and related services and activities, and to carry out informative and safe events for participants, including attendees, speakers and sponsors. If you provide us information about disabilities, medical conditions and allergies, we will use this information in order to provide appropriate accommodations for attendees and to ensure their health and safety; we will not use this information for other purposes, unless required by law or as necessary to defend our legal rights. For in-person events requiring attendees to be vaccinated against COVID-19, we use information regarding your COVID-19 vaccination status to provide a safer environment for attendees and staff, in order to confirm vaccination status before permitting access to the event venue space.
- _Event Administration_. To plan, organize, and facilitate access to events and related services and activities, and to carry out informative and safe events for participants, including attendees, speakers and sponsors. If you provide us information about disabilities, medical conditions and allergies, we will use this information in order to provide appropriate accommodations for attendees and to ensure their health and safety; we will not use this information for other purposes, unless required by law or as necessary to defend our legal rights.
- _Personalization_. To tailor the content and information that we may send or display to you on our Sites and in our Services, to offer location customization and personalized help and instructions and to otherwise personalize your experiences.
- _Marketing and Promotions_. For marketing and promotional purposes, such as to send you news and newsletters, special offers, and promotions, or to otherwise contact you about Projects, Services, events, trainings or other information we think may interest you related to Open Sats, and, subject to applicable law, our affiliates, subsidiaries and managed services entities.
- _Advertising_. For targeting advertising to you on our Sites and third-party sites and measuring the effectiveness and reach of ads and services (through third-party ad networks and services).
- _Analytics_. To gather metrics to better understand how users access and use our Sites and Services and participate in our Projects; to evaluate and improve the Sites, including personalization, to develop new services; and to understand metrics regarding the community health of our Projects. If a user voluntary provides and explicitly consents to our processing of personal information regarding their demographics and socioeconomics, we process such personal information for the specific purposes for which you have consented, which may include for the purpose of compiling, analyzing and disclosing aggregate statistics regarding diversity of participation in open source projects and communities.
- _Marketing and Promotions_. For marketing and promotional purposes, such as to send you news and newsletters, special offers, and promotions, or to otherwise contact you about Projects, Services, events, trainings or other information we think may interest you related to MAGIC Grants, and, subject to applicable law, our affiliates, subsidiaries and managed services entities.
- _Compliance_. To comply with legal obligations and requests. For example, to comply with laws that compel us to disclose information to public authorities, courts, law enforcement or regulators, maintain records for a certain period, or maintain records demonstrating enforcement and sublicensing of our trademarks and those of our Projects.
- _Business and Legal Operations_. As part of our general charitable and non-profit business and legal operations (e.g., accounting, record keeping, and for other business administration purposes), and as necessary to establish, exercise and defend (actual and potential) legal claims.
- _Prevent Misuse_. Where we believe necessary to investigate, prevent or take action regarding illegal activities, suspected fraud, situations involving potential threats to the safety of any person or violations of the relevant Terms and Conditions or this Privacy Policy.
@@ -80,11 +56,11 @@ We disclose personal information as set forth below, and where individuals have
- _Publicly Available Information, including Your Contributions to Open-Source Projects._ User names, other user ids, email addresses and other attribution information related to the information and contributions that a user posts in conjunction with or subject to an Open Source license are publicly available in the relevant Project source code repositories. Your contributions to Open-Source Projects, and certain of your other Content such as comments and messages posted to public forums, are available to other participants and users of our Projects and of our Services, and may be viewed publicly. In some cases you may be able to provide Project or contribution-related information directly to third-party sites and services; these third parties are independent data controllers and their use of your personal information is subject to their own policies.
- _Service Providers._ We may share your information with third party service providers who use this information to perform services for us, such as payment processors, hosting providers, auditors, advisors, contractors and consultants.
- _Affiliates._ The information collected about you may be accessed by or shared with subsidiaries and affiliates of Open Sats, whose use and disclosure of your personal information is subject to this Privacy Policy, unless an affiliate has its own separate privacy policy.
- _Event Participants_. If you register for an event, we may ask for your consent to share your personal information with third party sponsors and other participants. We will not share your event information with third parties without your consent. For in-person events requiring attendees to be vaccinated against COVID-19, we may use third-party service providers to validate your identity and COVID-19 vaccination status.
- _Affiliates._ The information collected about you may be accessed by or shared with subsidiaries and affiliates of MAGIC Grants, whose use and disclosure of your personal information is subject to this Privacy Policy, unless an affiliate has its own separate privacy policy.
- _Event Participants_. If you register for an event, we may ask for your consent to share your personal information with third party sponsors and other participants. We will not share your event information with third parties without your consent.
- _Training and Program Sponsors_. If you participate in one of our education, certification, or training programs that a third party has sponsored or engaged us to provide to you and others (for example, your employers), we may receive attendee list information from them and may share information about your completion of the program, including confirmation of your participation and your certification exam results, as applicable; these third parties are independent data controllers and their use of your personal information is subject to their own policies. You may also elect to provide third parties (e.g., your employers or your prospective employers) with information that will enable them to look up your certification exam status; if you do so, we may share your certification exam status with such third parties.
- _Legally Required._ We may disclose your information if we are required to do so by law (including to law enforcement in the U.S. and other jurisdictions).
- _Protection of Rights._ We may disclose information where we believe it necessary to respond to claims asserted against us or, comply with legal process (e.g., subpoenas or warrants), enforce or administer our agreements and terms, for fraud prevention, risk assessment, investigation, and protect the rights, property or safety of Open Sats, its Users, participants in its events or Projects, or others.
- _Protection of Rights._ We may disclose information where we believe it necessary to respond to claims asserted against us or, comply with legal process (e.g., subpoenas or warrants), enforce or administer our agreements and terms, for fraud prevention, risk assessment, investigation, and protect the rights, property or safety of MAGIC Grants, its Users, participants in its events or Projects, or others.
- _Anonymized and Aggregated Information._ We may share aggregated information with third parties for research, marketing, analytics and other purposes, provided such information does not identify a particular individual.
**Cookies, Tracking, and Interest-Based Ads**
@@ -92,20 +68,17 @@ We disclose personal information as set forth below, and where individuals have
We and our third-party providers use cookies, pixel tags, JavaScript, log files, and other mechanisms to automatically collect and record information about your usage and browsing activities on our Site and across third party sites or online services. We may combine this information with other information we collect about users. Below, we provide a brief summary these activities.
- _Cookies._ These are small files with a unique identifier that are transferred to your browser through our websites. They allow us to remember users who are logged in, to understand how users navigate through and use the Sites, and to display personalized content and targeted ads (including on third party sites and applications).
- _Pixels, web beacons, clear GIFs_ These are tiny graphics with a unique identifier, similar in function to cookies, which we track browsing activities.
- _Analytics Tools._ We may use internal and third-party analytics tools. The third-party analytics companies we work with may combine the information collected with other information they have independently collected from other websites and/or other online products and services. Their collection and use of information is subject to their own privacy policies.
**Targeted Ads.** As discussed in our Cookie Policy, we may work with third-party advertisers to display more relevant ads on our website and on third party sites; these third parties may display ads to you based on your visit to our Sites and other third-party sites.
**Data Security**
We have implemented commercially reasonable precautions designed to protect the information we collect from loss, misuse, and unauthorized access, disclosure, alteration, and destruction. Please be aware that despite our best efforts, Open Sats provides no guarantee that any security measure will be completely and totally secure.
We have implemented commercially reasonable precautions designed to protect the information we collect from loss, misuse, and unauthorized access, disclosure, alteration, and destruction. Please be aware that despite our best efforts, MAGIC Grants provides no guarantee that any security measure will be completely and totally secure.
We are not responsible for any lost, stolen, or compromised passwords or for any activity on your account via unauthorized password activity. We ask you to promptly notify us if you become aware that any information provided by or submitted to our Sites or through our Services is lost, stolen, or used without permission at info@opensats.org.
We are not responsible for any lost, stolen, or compromised passwords or for any activity on your account via unauthorized password activity. We ask you to promptly notify us if you become aware that any information provided by or submitted to our Sites or through our Services is lost, stolen, or used without permission at info@magicgrants.org.
**Marketing Choices**
You may opt out of or withdraw your consent to receive direct marketing emails from us by using the unsubscribe or opt out mechanisms included in our marketing emails or by emailing info@opensats.org. You may also unsubscribe from mailing lists via the applicable mailing list&#39;s subscription website or, in some cases, by using the unsubscribe mechanisms included in such emails.
You may opt out of or withdraw your consent to receive direct marketing emails from us by using the unsubscribe or opt out mechanisms included in our marketing emails or by emailing info@magicgrants.org. You may also unsubscribe from mailing lists via the applicable mailing list's subscription website or by using the unsubscribe mechanisms included in such emails.
**Retention of Your Personal Information**
@@ -113,13 +86,13 @@ We generally keep personal information only for as long as required to fulfill t
**International Information:**
**Scope**. This section applies to individuals in the European Union &quot;EU&quot; (for these purposes, reference to the EU also includes the European Economic Area countries of Iceland, Liechtenstein and Norway and, to the extent applicable, Switzerland).
**Scope**. This section applies to individuals in the European Union **EU** (for these purposes, reference to the EU also includes the European Economic Area countries of Iceland, Liechtenstein and Norway and, to the extent applicable, Switzerland).
**Data Controller**. Open Sats is the data controller for the processing of Personal data related to donor and non-profit accounts and information on our Sites. You can find our contact information below.
**Data Controller**. MAGIC Grants is the data controller for the processing of Personal data related to donor and non-profit accounts and information on our Sites. You can find our contact information below.
Open Sats is the data processor with respect to processing personal information related to donations and any interaction with Projects. If you wish to exercise one of the below rights with respect to your contribution (whether financial or to a Project) please contact us.
MAGIC Grants is the data processor with respect to processing personal information related to donations and any interaction with Projects. If you wish to exercise one of the below rights with respect to your contribution (whether financial or to a Project) please contact us.
**Your Rights**. Pursuant to the GDPR, to the extent Open Sats is a data controller of your Personal data, you have the following rights in relation to your personal data, under certain circumstances:
**Your Rights**. Pursuant to the GDPR, to the extent MAGIC Grants is a data controller of your Personal data, you have the following rights in relation to your personal data, under certain circumstances:
**Right of access** : If you ask us, we will confirm whether we are processing your personal data and, if so, provide you with a copy of that personal data along with certain other details. If you require additional copies, we may need to charge a reasonable fee.
@@ -127,7 +100,7 @@ Open Sats is the data processor with respect to processing personal information
**Right to erasure** : You may ask us to delete or remove your personal data, such as where you withdraw your consent. If we shared your data with others, we will tell them about the erasure where possible. If you ask us, and where possible and lawful to do so, we will also tell you with whom we shared your Personal data with so you can contact them directly.
**Right to restrict processing** : You may ask us to restrict or &#39;block&#39; the processing of your personal data in certain circumstances, such as where you contest the accuracy of the data or object to us processing it (please read below for information on your right to object). We will tell you before we lift any restriction on processing. If we shared your Personal data with others, we will tell them about the restriction where possible. If you ask us, and where possible and lawful to do so, we will also tell you with whom we shared your Personal data so you can contact them directly.
**Right to restrict processing** : You may ask us to restrict or **block** the processing of your personal data in certain circumstances, such as where you contest the accuracy of the data or object to us processing it (please read below for information on your right to object). We will tell you before we lift any restriction on processing. If we shared your Personal data with others, we will tell them about the restriction where possible. If you ask us, and where possible and lawful to do so, we will also tell you with whom we shared your Personal data so you can contact them directly.
**Right to data portability** : You have the right to obtain your personal data from us that you consented to give us or that was provided to us as necessary in connection with our contract with you, and that is processed by automated means. We will give you your personal data in a structured, commonly used and machine-readable format. You may reuse it elsewhere.
@@ -139,33 +112,33 @@ If we are relying on a legitimate interest to process your personal data (unless
**Right to lodge a complaint with the data protection authority** : If you have a concern about our privacy practices, including the way we handled your personal data, you can report it to the data protection authority that is authorized to hear those concerns.
Please see the section below with our contact information on how to reach Open Sats to exercise your rights.
Please see the section below with our contact information on how to reach MAGIC Grants to exercise your rights.
**Legitimate Interest**. &quot;Legitimate interests&quot; means our interests in conducting our charitable business and developing a relationship with you. This Privacy Policy describes when we process your Personal data for our legitimate interests, what these interests are and your rights. We will not use your Personal data for activities where the impact on you overrides our interests, unless we have your consent, or those activities are otherwise required or permitted by law.
**International Transfers of Personal Data**. Because Open Sats is a non-profit organization that is not subject to the jurisdiction of the United States Federal Trade Commission, it is not eligible for certification under the EU-U.S. and Swiss-U.S. Privacy Shield frameworks (&quot;Frameworks&quot;) as set forth by the U.S. Department of Commerce regarding the processing of personal data transferred from the EU, United Kingdom, and Switzerland to the U.S. (for these purposes, reference to the EU also includes the European Economic Area countries of Iceland, Liechtenstein and Norway). However, Open Sats commits to process Personal data transferred from the EU to the United States in accordance with the principles of Notice, Choice, Accountability for Onward Transfer, Security, Data Integrity and Purpose Limitation, Access, and Recourse, Enforcement and Liability, as described below.
**International Transfers of Personal Data**. Because MAGIC Grants is a non-profit organization that is not subject to the jurisdiction of the United States Federal Trade Commission, it is not eligible for certification under the EU-U.S. and Swiss-U.S. Privacy Shield frameworks (**Frameworks**) as set forth by the U.S. Department of Commerce regarding the processing of personal data transferred from the EU, United Kingdom, and Switzerland to the U.S. (for these purposes, reference to the EU also includes the European Economic Area countries of Iceland, Liechtenstein and Norway). However, MAGIC Grants commits to process Personal data transferred from the EU to the United States in accordance with the principles of Notice, Choice, Accountability for Onward Transfer, Security, Data Integrity and Purpose Limitation, Access, and Recourse, Enforcement and Liability, as described below.
**Notice and Choice**. This Privacy Policy provides notice of the personal data collected and transferred to the United States and the choices that you have with respect to such personal data. It also provides information about the protections applicable to transferred data below.
**Accountability for Onward Transfers**. We may be accountable for the personal data transfer to third-party service providers. If we are and our third-party service providers process personal data in a manner inconsistent with these principles, we are responsible and liable for the harm caused, unless we prove that we are not responsible for the event giving rise to the damage.
**Security**. We maintain security measures to protect personal data as described in the &quot;Data Security&quot; section of this Privacy Policy.
**Security**. We maintain security measures to protect personal data as described in the **Data Security** section of this Privacy Policy.
**Data Integrity and Purpose Limitation**. We take reasonable steps to ensure that personal data is reliable for its intended use, and that it is accurate, complete and current for as long as we retain it. We will retain the data as long as necessary for the following purposes: delivering the Services, engaging in customer service, complying with legal obligations, auditing, performing security and fraud prevention, responding to legal and regulatory inquiries, and preserving or defending our legal rights or those of other users or third parties.
**Access**. EU users have certain rights to access, correct, amend, or delete personal data where it is inaccurate, or has been processed in violation of these principles. Please see the &quot;Your Rights&quot; section above for more information on the rights of users in the EU (and, to the extent applicable, users in Switzerland).
**Access**. EU users have certain rights to access, correct, amend, or delete personal data where it is inaccurate, or has been processed in violation of these principles. Please see the **Your Rights** section above for more information on the rights of users in the EU (and, to the extent applicable, users in Switzerland).
**Recourse, Enforcement, Liability**. Open Sats commits to resolve complaints about our processing of your personal data. European Union, United Kingdom, and Swiss users with inquiries or complaints regarding our processing of Personal data should first contact Open Sats as follows:
**Recourse, Enforcement, Liability**. MAGIC Grants commits to resolve complaints about our processing of your personal data. European Union, United Kingdom, and Swiss users with inquiries or complaints regarding our processing of Personal data should first contact MAGIC Grants as follows:
Email: info@opensats.org
Email: info@magicgrants.org
Attention: Information Security
We will respond to such inquiries or complaints within thirty (30) days.
**Children&#39;s Privacy**
**Children's Privacy**
Open Sats does not knowingly collect or solicit personal information from anyone under the age of sixteen (16), or knowingly allow such persons to register. If we become aware that we have collected personal information from a child under the relevant age without parental consent, we take steps to delete that information. Where we specifically indicate that we collect personal information from children under sixteen (16), we will obtain the parent or guardian&#39;s consent and provide adequate notice.
MAGIC Grants does not knowingly collect or solicit personal information from anyone under the age of sixteen (16), or knowingly allow such persons to register. If we become aware that we have collected personal information from a child under the relevant age without parental consent, we take steps to delete that information. Where we specifically indicate that we collect personal information from children under sixteen (16), we will obtain the parent or guardian's consent and provide adequate notice.
**Links to Third Party Sites and Services**
@@ -173,14 +146,12 @@ The Sites may contain links to third party sites or online services. Please refe
**California Privacy Rights**
Only to the extent that Open Sats meets the minimum thresholds as required under California law to be subject to the California Privacy Rights Act of 2020, residents of California will be able to request and obtain from us once a year, free of charge, a list of the third parties to whom we have disclosed their personal information (if any) for their direct marketing purposes in the prior calendar year, as well as the types of personal information disclosed to those parties. If you are a California resident and would like to request this information, please submit your request in an email to [info@opensats.org](mailto:info@opensats.org). We reserve the right to ask for verification of your California residence and deny this request if Open Sats believes it is not subject to this requirement.
Only to the extent that MAGIC Grants meets the minimum thresholds as required under California law to be subject to the California Privacy Rights Act of 2020, residents of California will be able to request and obtain from us once a year, free of charge, a list of the third parties to whom we have disclosed their personal information (if any) for their direct marketing purposes in the prior calendar year, as well as the types of personal information disclosed to those parties. If you are a California resident and would like to request this information, please submit your request in an email to [info@magicgrants.org](mailto:info@magicgrants.org). We reserve the right to ask for verification of your California residence and deny this request if MAGIC Grants believes it is not subject to this requirement.
**Contact Us**
If you have any questions about our practices or this Privacy Policy, please contact us at [info@opensats.org](mailto:info@opensats.org), or write to us at: Open Sats Initiative, Attn: Legal Department, 3605 Glenwood Ave., Suite 500, Raleigh, North Carolina, 27612, USA.
If you have any questions about our practices or this Privacy Policy, please contact us at [info@magicgrants.org](mailto:info@magicgrants.org).
**Changes to the Privacy Policy**
This Policy is current as of the effective date set forth above. If we change our privacy policies and procedures, we will post those changes on this page and/or continue to provide access to a copy of the prior version. If we make any changes to this Privacy Policy that materially change how we treat your personal information, we will endeavor to provide you with reasonable notice of such changes, such as via prominent notice on our Sites or to your email address of record, and where required by law, we will obtain your consent or give you the opportunity to opt out of such changes.
3564707v1.AWB.31306.G51883

View File

@@ -1,24 +1,7 @@
# About the MAGIC Monero Fund
# About the MAGIC Privacy Guides Fund
MAGIC (Multidisciplinary Academic Grants in Cryptocurrencies) Grants is a 501(c)(3) U.S. nonprofit that focuses on building strong cryptocurrency communities and networks. [The MAGIC Monero Fund](https://magicgrants.org/funds/monero) is a subentity of MAGIC Grants guided by a five-member committee elected by [Monero community members](https://magicgrants.org/funds/monero/monero_fund_voters/).
Privacy Guides is a socially motivated website that provides information for protecting your data security and privacy. We are a non-profit project with a mission to inform the public about the value of digital privacy, and about global government initiatives which aim to monitor your online activity. Our website is free of advertisements and not affiliated with any of the listed providers.
### MAGIC Monero Fund Vision and Goals:
* Improve the Monero ecosystem.
* Support Monero development and research.
* Create Monero educational resources.
* Fund Monero security audits.
* Run essential services that support the Monero ecosystem.
### Committee Members
* [Rucknium](https://github.com/Rucknium) is a Monero Research Lab researcher and economist who has focused on statistical obfuscation on Monero and Bitcoin Cash.
* [kayabaNerve](https://twitter.com/kayabaNerve) is a Monero-ecosystem developer and security researcher who was the first to implement Monero atomic swaps with ASMR.
* [monerobull](https://twitter.com/monerobull) is a distributor of marketing materials for Monero Outreach and moderates various communication channels of the Monero community.
* [artlimber](https://github.com/artlimber) is a Monero user and community supporter who believes privacy is a human right.
* [kowalabearhugs](https://twitter.com/kowalabearhugs) is a Monero community member, photographer, artist, seasonal farmer, and offseason wanderer.
Privacy Guides is fiscally hosted by MAGIC Grants, a 501(c)(3) public charity.
More information: [https://www.privacyguides.org/en/about/](https://www.privacyguides.org/en/about/)

View File

View File

@@ -1,97 +1,77 @@
# Open Sats Initiative terms of use
# MAGIC Grants Terms of Use
**Last updated: November 14, 2021**
**Last updated: October 14, 2024**
Welcome to the Opensats.org website and/or mobile application (individually and collectively, the &quot;Site&quot;). Your access and use of the Site and of features, software, products and services provided by Open Sats Initiative (&quot;Open Sats,&quot; &quot;we,&quot; &quot;us,&quot; or &quot;our&quot;) through the Site, (individually and collectively, the &quot;Service&quot;) is subject to the terms and conditions in this Terms of Use (&quot;Terms of Use&quot; or &quot;Terms&quot;).
Welcome to the donate.magicgrants.org website and/or mobile application (individually and collectively, the **Site**). Your access and use of the Site and of features, software, products and services provided by MAGIC Grants (**MAGIC Grants**, **we**, **us**, or **our**) through the Site, (individually and collectively, the **Service**) is subject to the terms and conditions in this Terms of Use (**Terms of Use** or **Terms**).
You must agree to the Terms in order to use the Site and/or the Service. If you use the Site and/or the Service, or click to accept or agree to the Terms if presented to you in a user interface for the Service, this is your acceptance of the Terms and your agreement to all of its terms and conditions. By accepting the Terms or using the Site and/or the Service, you represent and warrant that you are at least 18 years of age and have the legal capacity to enter a contract in the jurisdiction where you reside. If you do not accept the Terms, then you may not use the Site or the Service. If you are using the Site or the Service on behalf of another entity, you represent and warrant that you have full legal authority to bind such other entity to the Terms. If you do not have such authority, then you may not use the Site or the Service and you must discontinue all use of the Site and the Service immediately.
1. Our Services. The purpose of the Service is to promote the use of open source software to enhance blockchain technology and to promote the development of the Bitcoin infrastructure along with other free and open source software projects. As a non-profit organization ourselves, Open Sats Initiative does accept donations on the opensats.org website.
2. Privacy Policy. By clicking on the Terms of Use and Privacy Policy box or using the Site or the Service, you represent that you have read and consent to our Privacy Policy located at [/privacy](/privacy) in addition to the Terms. Open Sats may revise the Privacy Policy at any time, and the new versions will be available on the Site. If at any point you do not agree to any portion of the Privacy Policy, you must immediately stop using the Site and/or Service.
3. Terms of Use Updates. Open Sats may update the Terms at any time, and Open Sats will post the updated version of the Terms on the Site. You understand and agree that you will be deemed to have accepted the updated Terms if you use the Site or the Service after the updated Terms are posted on the Site. If at any point you do not agree to any portion of the Terms then in effect, you must immediately stop using the Site and the Service.
4. Provision of the Services. You understand that all information, data, text, software, graphics or other materials (&quot;Content&quot;), whether publicly posted or privately transmitted, are the sole responsibility of the person from whom such Content originated. This means that you, and not Open Sats, is/are entirely responsible for all Content that you upload, post, email, transmit or otherwise make available via Services. Open Sats does not control the Content posted and, as such, does not guarantee the accuracy, integrity or quality of such Content. Under no circumstances will Open Sats be liable in any way for any Content, including, but not limited to, any errors or omissions in any Content, or any loss or damage of any kind incurred as a result of the use of any Content posted, emailed, transmitted or otherwise made available via the Open Sats Service.
1. Our Services. The purpose of the Service is to promote the charitable mission of MAGIC Grants and its fundraising efforts.
2. Privacy Policy. By clicking on the Terms of Use and Privacy Policy box or using the Site or the Service, you represent that you have read and consent to our Privacy Policy located at [/privacy](/privacy) in addition to the Terms. MAGIC Grants may revise the Privacy Policy at any time, and the new versions will be available on the Site. If at any point you do not agree to any portion of the Privacy Policy, you must immediately stop using the Site and/or Service.
3. Terms of Use Updates. MAGIC Grants may update the Terms at any time, and MAGIC Grants will post the updated version of the Terms on the Site. You understand and agree that you will be deemed to have accepted the updated Terms if you use the Site or the Service after the updated Terms are posted on the Site. If at any point you do not agree to any portion of the Terms then in effect, you must immediately stop using the Site and the Service.
4. Provision of the Services. You understand that all information, data, text, software, graphics or other materials (**Content**), whether publicly posted or privately transmitted, are the sole responsibility of the person from whom such Content originated. This means that you, and not MAGIC Grants, is/are entirely responsible for all Content that you upload, post, email, transmit or otherwise make available via Services. MAGIC Grants does not control the Content posted and, as such, does not guarantee the accuracy, integrity or quality of such Content. Under no circumstances will MAGIC Grants be liable in any way for any Content, including, but not limited to, any errors or omissions in any Content, or any loss or damage of any kind incurred as a result of the use of any Content posted, emailed, transmitted or otherwise made available via the MAGIC Grants Service.
You also agree that you will not collect or store personal data about other users in connection with any prohibited conduct and activities. You acknowledge that Open Sats may or may not pre-screen Content, but that Open Sats and its designees shall have the right (but not the obligation) in their sole discretion to pre-screen, refuse, move, or remove any Content that is available via the Services and which violates the Terms of Service. You agree that you must evaluate, and bear all risks associated with, the use of any Content, including any reliance on the accuracy, completeness, or usefulness of such Content. You acknowledge, consent and agree that Open Sats may access, preserve and disclose your account information and Content if required to do so by law or in a good faith belief that such access preservation or disclosure is reasonably necessary to:
You also agree that you will not collect or store personal data about other users in connection with any prohibited conduct and activities. You acknowledge that MAGIC Grants may or may not pre-screen Content, but that MAGIC Grants and its designees shall have the right (but not the obligation) in their sole discretion to pre-screen, refuse, move, or remove any Content that is available via the Services and which violates the Terms of Service. You agree that you must evaluate, and bear all risks associated with, the use of any Content, including any reliance on the accuracy, completeness, or usefulness of such Content. You acknowledge, consent and agree that MAGIC Grants may access, preserve and disclose your account information and Content if required to do so by law or in a good faith belief that such access preservation or disclosure is reasonably necessary to:
- comply with legal process;
- enforce the Terms of Use and the Privacy Policy;
- respond to claims that any Content violates the rights of third parties;
- respond to your requests for customer service; or
- protect the rights, property or personal safety of Open Sats its users and the public.
- protect the rights, property or personal safety of MAGIC Grants its users and the public.
1. Provision of the Service Not an Exchange. Open Sats is not a bitcoin exchange or wallet service and does not receive, hold or exchange digital currencies on behalf of any other person or entity. Open Sats does not withhold funds for tax purposes or otherwise.
2. Tax Advice. Open Sats makes no representation as to whether all or any portion of your donations to us are tax deductible or eligible for tax credits. Open Sats will have no liability for any claim by any federal, state, local or any other tax authority with respect to the characterization on any applicable tax return of any donation by you. You should consult your tax advisor as to the amount of your donation that is tax deductible or eligible for tax recognition, if any.
3. Access Restriction. Open Sats reserves the right to deny access or service to any person or entity at Open Sats&#39; sole and absolute discretion. You acknowledge and agree that Open Sats may stop providing the Site and/or the Service or restrict your use of the Site and/or the Service at any time, without notifying you in advance, for any reason or no reason, including, without limitation, for any violation of the Terms and/or if Open Sats suspects that you have used any aspect of the Service to conduct any fraudulent or illegal activity. If Open Sats disables your access to your account, you may be prevented from accessing the Service, your account details and/or any materials contained in your account.
4. Accounts and Security.
5. Account. Open Sats may require registration for an account to access the Site and the Services. There will be a registration process for the creation of accounts to complete the registration process. You may be required to provide information about yourself or your company as part of the registration process or your continued use of the Service. You agree that any registration information that you submit to Open Sats will be correct, accurate, and up to date and that you will not impersonate any person or entity, or falsely state or otherwise misrepresent your affiliation with a person or entity.
6. Account Security. Maintaining account security is very important. You are solely responsible for maintaining the confidentiality of your account password. You agree to notify Open Sats immediately if you become aware of any unauthorized use of your password or of your account.
7. Account Sharing or Transfers. Accounts are registered to individuals or entities and may not be sold, traded, gifted or otherwise transferred at any time under any circumstances. An account may not be shared. Passwords may not be disclosed to anyone else.
8. Cancellation by You. You have the right to cancel your account at any time. You may cancel your account by emailing us at info@opensats.org to submit a request to our support team.
9. Termination by Open Sats. Open Sats may at any time terminate your account for any reason or no reason including, without limitation, if:
* Provision of the Service Not an Exchange. MAGIC Grants is not a bitcoin exchange or wallet service and does not receive, hold or exchange digital currencies on behalf of any other person or entity. MAGIC Grants does not withhold funds for tax purposes or otherwise.
* Tax Advice. MAGIC Grants makes no representation as to whether all or any portion of your donations to us are tax deductible or eligible for tax credits. MAGIC Grants will have no liability for any claim by any federal, state, local or any other tax authority with respect to the characterization on any applicable tax return of any donation by you. You should consult your tax advisor as to the amount of your donation that is tax deductible or eligible for tax recognition, if any.
* Access Restriction. MAGIC Grants reserves the right to deny access or service to any person or entity at MAGIC Grants's sole and absolute discretion. You acknowledge and agree that MAGIC Grants may stop providing the Site and/or the Service or restrict your use of the Site and/or the Service at any time, without notifying you in advance, for any reason or no reason, including, without limitation, for any violation of the Terms and/or if MAGIC Grants suspects that you have used any aspect of the Service to conduct any fraudulent or illegal activity. If MAGIC Grants disables your access to your account, you may be prevented from accessing the Service, your account details and/or any materials contained in your account.
- Open Sats determines that you are (a) in breach of or otherwise acting inconsistently with the Terms or written agreements (b) engaging in fraudulent or illegal activities or other conduct that may result in liability to Open Sats;
- Open Sats determines it is required by law to terminate your account; or
- Open Sats decides to stop providing the Service or critical portions of the Service in the country where you reside, access the Site or use the Service or Open Sats determines that it is no longer in its charitable business interests to continue providing the Service or portions of the Service.
## Accounts and Security
1. Content Licensing. Open Sats mission includes educating the public on free and open-source software through content such as articles, images, photographs, comments, software code, audio and video clips, and other materials (collectively, &quot;Content&quot;). Content is authored by Open Sats, contributors to Open Sats projects, and other sources.
* Account. MAGIC Grants may require registration for an account to access the Site and the Services. There will be a registration process for the creation of accounts to complete the registration process. You may be required to provide information about yourself or your company as part of the registration process or your continued use of the Service. You agree that any registration information that you submit to MAGIC Grants will be correct, accurate, and up to date and that you will not impersonate any person or entity, or falsely state or otherwise misrepresent your affiliation with a person or entity.
* *Account Security. Maintaining account security is very important. You are solely responsible for maintaining the confidentiality of your account password. You agree to notify MAGIC Grants immediately if you become aware of any unauthorized use of your password or of your account.
* Account Sharing or Transfers. Accounts are registered to individuals or entities and may not be sold, traded, gifted or otherwise transferred at any time under any circumstances. An account may not be shared. Passwords may not be disclosed to anyone else.
* Cancellation by You. You have the right to cancel your account at any time. You may cancel your account by emailing us at info@magicgrants.org to submit a request to our support team.
* Termination by MAGIC Grants. MAGIC Grants may at any time terminate your account for any reason or no reason including, without limitation, if:
Content authored by Open Sats is generally made available for public sharing and reuse through open licenses such as Creative Commons (for expressive material) or the Open Sats Public License. In most cases we ask Open Sats contributors to release Content under open licenses.
* MAGIC Grants determines that you are (a) in breach of or otherwise acting inconsistently with the Terms or written agreements (b) engaging in fraudulent or illegal activities or other conduct that may result in liability to MAGIC Grants;
* MAGIC Grants determines it is required by law to terminate your account; or
* MAGIC Grants decides to stop providing the Service or critical portions of the Service in the country where you reside, access the Site or use the Service or MAGIC Grants determines that it is no longer in its charitable business interests to continue providing the Service or portions of the Service.
Some Content in our Site may be acquired from sources that prohibit further use of their Content without advance permission. Where possible, the Content will display a notice with the applicable license. You agree to abide by such notices. However it may occur that:
* Effect of Account Termination or Cancellation. If you voluntarily terminate your account or allow your account to lapse, you will be able to reactivate that account at any time through the account interface on the Site. Accounts terminated by MAGIC Grants for any type of abuse including, without limitation, a violation of the Terms, may not be reactivated for any reason. If you are blocked by us from accessing the Site or Service (including by blocking your IP address), you agree not to implement any measures to circumvent such blocking (e.g., by masking your IP address or using a proxy IP address).
1. Some Content expressly indicates that the author does not intend for an open license to apply. You should contact the author or author&#39;s agent for permission to use such Content. Questions on Open Sats authored content can be sent to: info@opensats.org.
2. Some Content contains trademarks, trade dress, logos and brand assets of Open Sats and other parties (&quot;Trademarks&quot;). Except for a few limited circumstances, Trademarks cannot be used without advance written permission of the owner of the Trademark.
3. Software used by our Websites is licensed under the Open Sats Public License or similarly permissive open source licenses.
4. Content Submissions **.** You may contribute Content when interacting with our Site and Services, including but not limited to commenting on an article, blogging, contributing code, or contributing graphics or written material (each a &quot;Submission&quot;). Unless your Submission is made under a separate agreement with Open Sats, in which case that agreement will govern, then
## Restrictions and Conditions of Use
- For Submissions to Open Sats&#39; open source Projects:
* Use of the Site. MAGIC Grants permits users to view and use the Site solely for personal or nonprofit use. You agree not to license, create derivative works from, transfer, sell or re-sell any information, content, materials, data or services obtained from the Site or through the Service unless specifically allowed by an Open Source license or express permission.
* Accessing the Service. You agree not to access, or attempt to access, the Service by any means other than through the user interface provided through the Site or any mobile application provided by MAGIC Grants. You specifically agree not to access, or attempt to access, the Service through any automated means (including, without limitation, through the use of scripts, bots, spiders or web crawlers) and you agree to comply with the instructions contained in any robots.txt file present on the Site or the Service.
* No Violation of Laws. You agree that you will not, in connection with your use of the Site or the Service, violate any applicable law, ordinance, rule, regulation or treaty. Without limiting the foregoing, you agree that you will not make available through the Site and/or the Service any material or information that infringes any copyright, trademark, patent, trade secret, or other right of any party (including rights of privacy or publicity).
* Use Restrictions. You may not connect to or use the Site or the Service in any way that is not expressly permitted by the Terms.
* You may not: (a) remove any proprietary notices from the Service; (b) cause, permit or authorize the modification, creation of derivative works, translation, reverse engineering, decompiling, disassembling or hacking of the Site or the Service; (c) sell, assign, rent, lease, act as a service bureau, or grant rights in the Service, including, without limitation, through sublicense, to any other person or entity without the prior written consent of MAGIC Grants; or (d) make any false, misleading or deceptive statement or representation regarding MAGIC Grants and/or the Site or the Service.
* Without limiting the foregoing, you agree that you will not: (i) institute, assist, or become involved in any type of attack including, without limitation, denial of service attacks, upon the Site and/or the Service (or any servers, systems, or networks connected to the Site or the Service) or otherwise attempt to obstruct, disrupt, or interfere with the operation of the Site and/or the Service or any other person's or entity's use of the Site and/or the Service (or any servers, systems or networks connected to the Site or the Service); (ii) attempt to gain unauthorized access to the Site, the Service, accounts registered to other users, or any servers, systems or networks connected to the Site and/or the Service; (iii) use the Site or the Service for any commercial purpose or the benefit of any third party, or charge any person or entity, or receive any compensation for, the use of the Site or the Service, unless you are specifically authorized to do so in a separate written agreement with MAGIC Grants; (iv) use the Site or the Service to (y) develop, generate, transmit or store information that is defamatory, harmful, abusive, obscene or hateful; or (z) perform any unsolicited commercial communication; or (v) engage in any activity that (A) constitutes harassment or a violation of privacy or threatens other people or groups of people; (B) is harmful to children in any manner; (C) constitutes phishing, pharming, or impersonates any other person or entity, or steals or assumes any person's identity (whether a real identity or online nickname or alias); (D) violates any applicable law, ordinance, rule, regulation or treaty or (E) in MAGIC Grants's sole judgment, is objectionable or that restricts or inhibits any other person from using or enjoying our Site or Service, or which may expose us or our users to any harm or liability of any type.
* No Data Mining or Harmful Code. You agree that you will not (a) obtain or attempt to obtain any information from the Site or Services including, without limitation, any personal information of other account holders or other software data; (b) intercept, examine or otherwise observe any proprietary communications protocol used by the Site or the Service, whether through the use of a network analyzer, packet sniffer, or other device; or (c) use any type of bot, spider, virus, clock, timer, counter, worm, software lock, drop dead device, Trojan horse routing, trap door, time bomb or any other codes, instructions or third party software that is designed to provide a means of surreptitious or unauthorized access to, or distort, delete, damage or disassemble, the Site or the Service.
* Violation of the Terms. You acknowledge and agree that you are solely responsible, and MAGIC Grants has no responsibility or liability to you or any other person or entity, for any breach by you of the Terms or for the consequences of any such breach.
1. You agree to license your Submission under the terms of the corresponding license of the particular open source project to which you are contributing. For more information on the specific license, please see the applicable source.
## Links
- For all other Submissions, you agree to the following in connection with each:
* Links from the Site. The Site may contain links to websites operated by other parties. MAGIC Grants provides these links to other websites as a convenience and use of these websites is at your own risk. The linked websites are not under the control of MAGIC Grants and MAGIC Grants is not responsible for the content available on the other websites. Such links do not imply MAGIC Grants's endorsement of information or material on any other website and MAGIC Grants disclaims all liability with regard to your access to and use of such linked websites.
* Links to the Site. Unless otherwise set forth in a written agreement between you and MAGIC Grants, you must adhere to MAGIC Grants's linking policy as follows: (i) the appearance, position, and other aspects of the link may not be such as to damage or dilute the goodwill associated with MAGIC Grants's and/or its licensors's names and trademarks; (ii) the appearance, position and other attributes of the link may not create the false appearance that your organization or entity is sponsored by, affiliated with, or associated with MAGIC Grants; and (iii) when selected by a user, the link must display the Site on full-screen and not within a **frame** on the linking Site. MAGIC Grants reserves the right to revoke its consent to the link at any time and in its sole discretion.
1. You represent and warrant that your Submission will comply with these Terms and any additional terms that may govern your Submission.
2. You hereby grant us a nonexclusive, royalty-free, worldwide, sublicensable (to those we work with) license to use your Submission in connection with the Content, Site, and Services and online and offline promotion of Open Sats&#39; mission, products and services.
3. You acknowledge that your Submissions may be accessible by other registered users of the applicable service or the public.
4. If your Submission contains expressive material or software code, you agree to license it in a manner that is compatible with the particular website you are making a Submission to.
5. You represent and warrant that you have the rights necessary to grant the rights granted herein, and further, that the uses contemplated under these Terms will not infringe the proprietary or intellectual property rights of any third party.
6. You understand and agree that Open Sats reserves the right, at its discretion, to review, modify, or remove any Submission that it deems is objectionable or in violation of these Terms.
## Intellectual Property
1. Effect of Account Termination or Cancellation. If you voluntarily terminate your account or allow your account to lapse, you will be able to reactivate that account at any time through the account interface on the Site. Accounts terminated by Open Sats for any type of abuse including, without limitation, a violation of the Terms, may not be reactivated for any reason. If you are blocked by us from accessing the Site or Service (including by blocking your IP address), you agree not to implement any measures to circumvent such blocking (e.g., by masking your IP address or using a proxy IP address).
2. Restrictions and Conditions of Use.
3. Use of the Site. Open Sats permits users to view and use the Site solely for personal or nonprofit use. You agree not to license, create derivative works from, transfer, sell or re-sell any information, content, materials, data or services obtained from the Site or through the Service unless specifically allowed by an Open Source license or express permission.
4. Accessing the Service. You agree not to access, or attempt to access, the Service by any means other than through the user interface provided through the Site or any mobile application provided by Open Sats. You specifically agree not to access, or attempt to access, the Service through any automated means (including, without limitation, through the use of scripts, bots, spiders or web crawlers) and you agree to comply with the instructions contained in any robots.txt file present on the Site or the Service.
5. No Violation of Laws. You agree that you will not, in connection with your use of the Site or the Service, violate any applicable law, ordinance, rule, regulation or treaty. Without limiting the foregoing, you agree that you will not make available through the Site and/or the Service any material or information that infringes any copyright, trademark, patent, trade secret, or other right of any party (including rights of privacy or publicity).
6. Use Restrictions. You may not connect to or use the Site or the Service in any way that is not expressly permitted by the Terms.
7. You may not: (a) remove any proprietary notices from the Service; (b) cause, permit or authorize the modification, creation of derivative works, translation, reverse engineering, decompiling, disassembling or hacking of the Site or the Service; (c) sell, assign, rent, lease, act as a service bureau, or grant rights in the Service, including, without limitation, through sublicense, to any other person or entity without the prior written consent of Open Sats; or (d) make any false, misleading or deceptive statement or representation regarding Open Sats and/or the Site or the Service.
8. Without limiting the foregoing, you agree that you will not: (i) institute, assist, or become involved in any type of attack including, without limitation, denial of service attacks, upon the Site and/or the Service (or any servers, systems, or networks connected to the Site or the Service) or otherwise attempt to obstruct, disrupt, or interfere with the operation of the Site and/or the Service or any other person&#39;s or entity&#39;s use of the Site and/or the Service (or any servers, systems or networks connected to the Site or the Service); (ii) attempt to gain unauthorized access to the Site, the Service, accounts registered to other users, or any servers, systems or networks connected to the Site and/or the Service; (iii) use the Site or the Service for any commercial purpose or the benefit of any third party, or charge any person or entity, or receive any compensation for, the use of the Site or the Service, unless you are specifically authorized to do so in a separate written agreement with Open Sats; (iv) use the Site or the Service to (y) develop, generate, transmit or store information that is defamatory, harmful, abusive, obscene or hateful; or (z) perform any unsolicited commercial communication; or (v) engage in any activity that (A) constitutes harassment or a violation of privacy or threatens other people or groups of people; (B) is harmful to children in any manner; (C) constitutes phishing, pharming, or impersonates any other person or entity, or steals or assumes any person&#39;s identity (whether a real identity or online nickname or alias); (D) violates any applicable law, ordinance, rule, regulation or treaty or (E) in Open Sats&#39; sole judgment, is objectionable or that restricts or inhibits any other person from using or enjoying our Site or Service, or which may expose us or our users to any harm or liability of any type.
9. No Data Mining or Harmful Code. You agree that you will not (a) obtain or attempt to obtain any information from the Site or Services including, without limitation, any personal information of other account holders or other software data; (b) intercept, examine or otherwise observe any proprietary communications protocol used by the Site or the Service, whether through the use of a network analyzer, packet sniffer, or other device; or (c) use any type of bot, spider, virus, clock, timer, counter, worm, software lock, drop dead device, Trojan horse routing, trap door, time bomb or any other codes, instructions or third party software that is designed to provide a means of surreptitious or unauthorized access to, or distort, delete, damage or disassemble, the Site or the Service.
10. Violation of the Terms. You acknowledge and agree that you are solely responsible, and Open Sats has no responsibility or liability to you or any other person or entity, for any breach by you of the Terms or for the consequences of any such breach.
11. Links.
12. Links from the Site. The Site may contain links to websites operated by other parties. Open Sats provides these links to other websites as a convenience and use of these websites is at your own risk. The linked websites are not under the control of Open Sats and Open Sats is not responsible for the content available on the other websites. Such links do not imply Open Sats&#39; endorsement of information or material on any other website and Open Sats disclaims all liability with regard to your access to and use of such linked websites.
13. Links to the Site. Unless otherwise set forth in a written agreement between you and Open Sats, you must adhere to Open Sats&#39; linking policy as follows: (i) the appearance, position, and other aspects of the link may not be such as to damage or dilute the goodwill associated with Open Sats&#39; and/or its licensors&#39; names and trademarks; (ii) the appearance, position and other attributes of the link may not create the false appearance that your organization or entity is sponsored by, affiliated with, or associated with Open Sats; and (iii) when selected by a user, the link must display the Site on full-screen and not within a &quot;frame&quot; on the linking Site. Open Sats reserves the right to revoke its consent to the link at any time and in its sole discretion.
14. Intellectual Property.
15. Trademarks. The Open Sats marks are trademarks of Open Sats. Unless permitted in a separate written agreement with Open Sats, you do not have the right to use any of Open Sats&#39; trademarks, service marks, or logos; and your unauthorized use of any of these may be a violation of federal and state trademark laws.
16. Ownership. You acknowledge and agree that Open Sats, or its licensors, owns all right, title and interest in and to the Site and the Service, including all intellectual property, industrial property and proprietary rights recognized anywhere in the world at any time and that the Site and the Service are protected by U.S. and international copyright laws. Further, you acknowledge that the Service may contain information that Open Sats has designated as confidential and you agree not to disclose such information without Open Sats&#39; prior written consent.
* Trademarks. The MAGIC Grants marks are trademarks of MAGIC Grants. Unless permitted in a separate written agreement with MAGIC Grants, you do not have the right to use any of MAGIC Grants's trademarks, service marks, or logos; and your unauthorized use of any of these may be a violation of federal and state trademark laws.
* Ownership. You acknowledge and agree that MAGIC Grants, or its licensors, owns all right, title and interest in and to the Site and the Service, including all intellectual property, industrial property and proprietary rights recognized anywhere in the world at any time and that the Site and the Service are protected by U.S. and international copyright laws. Further, you acknowledge that the Service may contain information that MAGIC Grants has designated as confidential and you agree not to disclose such information without MAGIC Grants's prior written consent.
17. Location. The Site and the Service are operated by Open Sats in the United States. If you choose to access the Site and/or the Service from a location outside of the United States, you do so on your own initiative and you are responsible for compliance with applicable local laws.
## Miscellaneous
18. Children. The Site and the Service are not directed toward children under 13 years of age nor does Open Sats knowingly collect information from children under 13 or allow them to create an account or access account features. If you are under 13, please do not submit any personal information about yourself to Open Sats.
19. Term/Termination. These Terms will continue to apply until ended by either you or Open Sats. You can choose to end them at any time for any reason by discontinuing your use of our Site and Services and, if applicable, deleting your account. We may suspend or terminate your access to our Site and Services at any time for any reason, including, but not limited to, if we reasonably believe: (i) you have violated these Terms, our Privacy Policy, or other relevant policy; (ii) you create risk or possible legal exposure for us; or (iii) our provision of the Site and Services to you is no longer commercially viable. In all such cases, these Terms shall terminate, except that the following sections shall continue to apply: Indemnification, Disclaimer; Limitation of Liability, Modification, and Miscellaneous.
20. Indemnification **.** You agree to defend, indemnify and hold harmless Open Sats, its contractors, contributors, licensors, and partners; and the respective directors, officers, employees and agents of the foregoing (&quot;Indemnified Parties&quot;) from and against any and all third party claims and expenses, including attorneys&#39; fees, arising out of or related to your use of our Site and Services (including, but not limited to, from your Submissions or from your violation of any these Terms).
21. Disclaimer; Limitation of Liability. THE SITE AND SERVICES ARE PROVIDED &quot;AS IS&quot; WITH ALL FAULTS. TO THE EXTENT PERMITTED BY LAW, OPEN SATS AND THE INDEMNIFIED PARTIES HEREBY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES THAT THE CONTENT, SITE, AND SERVICES (AS APPLICABLE) ARE FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, AND NON-INFRINGING. YOU BEAR THE ENTIRE RISK AS TO USING THE SITE AND SERVICES FOR YOUR PURPOSES AND AS TO THE QUALITY AND PERFORMANCE OF THE SITE AND SERVICES, INCLUDING WITHOUT LIMITATION THE RISK THAT YOUR HARDWARE, SOFTWARE, OR CONTENT IS DELETED OR CORRUPTED, THAT SOMEONE ELSE GAINS UNAUTHORIZED ACCESS TO YOUR INFORMATION, OR THAT ANOTHER USER MISUSES OR MISAPPROPRIATES YOUR SUBMISSION. THIS LIMITATION WILL APPLY NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF IMPLIED WARRANTIES, SO THIS DISCLAIMER MAY NOT APPLY TO YOU. EXCEPT AS REQUIRED BY LAW, OPEN SATS AND THE INDEMNIFIED PARTIES WILL NOT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES ARISING OUT OF OR IN ANY WAY RELATING TO THESE TERMS OR THE USE OF OR INABILITY TO USE THE CONTENT, SITE, AND SERVICES, INCLUDING WITHOUT LIMITATION DIRECT AND INDIRECT DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, LOST PROFITS, LOSS OF DATA, AND COMPUTER FAILURE OR MALFUNCTION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF THE THEORY (CONTRACT, TORT, OR OTHERWISE) UPON WHICH SUCH CLAIM IS BASED. THE COLLECTIVE LIABILITY OF OPEN SATS AND THE INDEMNIFIED PARTIES UNDER THIS AGREEMENT WILL NOT EXCEED \$500 (FIVE HUNDRED DOLLARS). SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL, CONSEQUENTIAL, OR SPECIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
22. Modifications to these Terms **.** We may update these Terms from time to time to address a new feature of the Site and Services or to clarify a provision. The updated Terms will be posted online. If the changes are substantive, we will announce the update through our usual channels for such announcements such as blog posts, banners, emails, or forums. Your continued use of our Site and Services after the effective date of such changes constitutes your acceptance of such changes. To make your review more convenient, we will post an effective date at the top of this page.
23. Miscellaneous. These Terms constitute the entire agreement between you and Open Sats concerning our Site and Services and supersede any prior versions of these Terms. The Site and Services and these Terms are governed by the laws of the state of North Carolina, U.S.A., excluding its conflict of law provisions. All claims and disputes arising out of the Site and Services or these Terms shall be brought exclusively in the courts of Wake County, North Carolina, and you consent to personal jurisdiction in those courts. If any portion of these Terms is held to be invalid or unenforceable, the remaining portions will remain in full force and effect. In the event of a conflict between a translated version of these Terms and the English language version, the English language version shall control. In the event of a conflict between these Terms and relevant additional terms, the additional terms shall control.
24. Contact Open Sats.
* Location. The Site and the Service are operated by MAGIC Grants in the United States. If you choose to access the Site and/or the Service from a location outside of the United States, you do so on your own initiative and you are responsible for compliance with applicable local laws.
* Children. The Site and the Service are not directed toward children under 13 years of age nor does MAGIC Grants knowingly collect information from children under 13 or allow them to create an account or access account features. If you are under 13, please do not submit any personal information about yourself to MAGIC Grants.
* Term/Termination. These Terms will continue to apply until ended by either you or MAGIC Grants. You can choose to end them at any time for any reason by discontinuing your use of our Site and Services and, if applicable, deleting your account. We may suspend or terminate your access to our Site and Services at any time for any reason, including, but not limited to, if we reasonably believe: (i) you have violated these Terms, our Privacy Policy, or other relevant policy; (ii) you create risk or possible legal exposure for us; or (iii) our provision of the Site and Services to you is no longer commercially viable. In all such cases, these Terms shall terminate, except that the following sections shall continue to apply: Indemnification, Disclaimer; Limitation of Liability, Modification, and Miscellaneous.
* Indemnification. You agree to defend, indemnify and hold harmless MAGIC Grants, its contractors, contributors, licensors, and partners; and the respective directors, officers, employees and agents of the foregoing (**Indemnified Parties**) from and against any and all third party claims and expenses, including attorneys' fees, arising out of or related to your use of our Site and Services (including, but not limited to, from your Submissions or from your violation of any these Terms).
21. Disclaimer; Limitation of Liability. THE SITE AND SERVICES ARE PROVIDED "AS IS" WITH ALL FAULTS. TO THE EXTENT PERMITTED BY LAW, MAGIC Grants AND THE INDEMNIFIED PARTIES HEREBY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES THAT THE CONTENT, SITE, AND SERVICES (AS APPLICABLE) ARE FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, AND NON-INFRINGING. YOU BEAR THE ENTIRE RISK AS TO USING THE SITE AND SERVICES FOR YOUR PURPOSES AND AS TO THE QUALITY AND PERFORMANCE OF THE SITE AND SERVICES, INCLUDING WITHOUT LIMITATION THE RISK THAT YOUR HARDWARE, SOFTWARE, OR CONTENT IS DELETED OR CORRUPTED, THAT SOMEONE ELSE GAINS UNAUTHORIZED ACCESS TO YOUR INFORMATION, OR THAT ANOTHER USER MISUSES OR MISAPPROPRIATES YOUR SUBMISSION. THIS LIMITATION WILL APPLY NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF IMPLIED WARRANTIES, SO THIS DISCLAIMER MAY NOT APPLY TO YOU. EXCEPT AS REQUIRED BY LAW, MAGIC Grants AND THE INDEMNIFIED PARTIES WILL NOT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES ARISING OUT OF OR IN ANY WAY RELATING TO THESE TERMS OR THE USE OF OR INABILITY TO USE THE CONTENT, SITE, AND SERVICES, INCLUDING WITHOUT LIMITATION DIRECT AND INDIRECT DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, LOST PROFITS, LOSS OF DATA, AND COMPUTER FAILURE OR MALFUNCTION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF THE THEORY (CONTRACT, TORT, OR OTHERWISE) UPON WHICH SUCH CLAIM IS BASED. THE COLLECTIVE LIABILITY OF MAGIC Grants AND THE INDEMNIFIED PARTIES UNDER THIS AGREEMENT WILL NOT EXCEED \$500 (FIVE HUNDRED DOLLARS). SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL, CONSEQUENTIAL, OR SPECIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
22. Modifications to these Terms. We may update these Terms from time to time to address a new feature of the Site and Services or to clarify a provision. The updated Terms will be posted online. If the changes are substantive, we will announce the update through our usual channels for such announcements such as blog posts, banners, emails, or forums. Your continued use of our Site and Services after the effective date of such changes constitutes your acceptance of such changes. To make your review more convenient, we will post an effective date at the top of this page.
23. Miscellaneous. These Terms constitute the entire agreement between you and MAGIC Grants concerning our Site and Services and supersede any prior versions of these Terms. The Site and Services and these Terms are governed by the laws of the state of Colorado, U.S.A., excluding its conflict of law provisions. All claims and disputes arising out of the Site and Services or these Terms shall be brought exclusively in the courts of Denver County, Colorado, and you consent to personal jurisdiction in those courts. If any portion of these Terms is held to be invalid or unenforceable, the remaining portions will remain in full force and effect. In the event of a conflict between a translated version of these Terms and the English language version, the English language version shall control. In the event of a conflict between these Terms and relevant additional terms, the additional terms shall control.
> Open Sats
## Contact MAGIC Grants.
> MAGIC Grants
>
> Attn: Open Sats Legal Notices
>
> P.O. Box 20389
>
> Raleigh, NC 27612
>
> info@opensats.org
>
> 3566450v1.AWB.31306.G51883
> Attn: MAGIC Grants Legal Notices
>
> info@magicgrants.org

View File

@@ -11,6 +11,7 @@ export const env = createEnv({
BUILD_MODE: z.boolean(),
APP_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(32),
USER_SETTINGS_JWT_SECRET: z.string().min(32),
STRAPI_API_URL: z.string().url(),
STRAPI_API_TOKEN: z.string().length(256),
@@ -36,6 +37,7 @@ export const env = createEnv({
KEYCLOAK_REALM_NAME: z.string().min(1),
BTCPAY_URL: z.string().url(),
BTCPAY_EXTERNAL_URL: z.string().url(),
BTCPAY_API_KEY: z.string().min(1),
BTCPAY_STORE_ID: z.string().min(1),
BTCPAY_WEBHOOK_SECRET: z.string().min(1),
@@ -67,6 +69,7 @@ export const env = createEnv({
BUILD_MODE: !!process.env.BUILD_MODE,
APP_URL: process.env.APP_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
USER_SETTINGS_JWT_SECRET: process.env.USER_SETTINGS_JWT_SECRET,
NEXT_PUBLIC_STRAPI_URL: process.env.NEXT_PUBLIC_STRAPI_URL,
STRAPI_API_URL: process.env.STRAPI_API_URL,
@@ -88,6 +91,7 @@ export const env = createEnv({
STRIPE_GENERAL_WEBHOOK_SECRET: process.env.STRIPE_GENERAL_WEBHOOK_SECRET,
KEYCLOAK_URL: process.env.KEYCLOAK_URL,
BTCPAY_EXTERNAL_URL: process.env.BTCPAY_EXTERNAL_URL,
KEYCLOAK_CLIENT_ID: process.env.KEYCLOAK_CLIENT_ID,
KEYCLOAK_CLIENT_SECRET: process.env.KEYCLOAK_CLIENT_SECRET,
KEYCLOAK_REALM_NAME: process.env.KEYCLOAK_REALM_NAME,

View File

@@ -1,9 +1,27 @@
import { withAuth } from 'next-auth/middleware'
import { refreshToken } from './server/utils/auth'
export default withAuth({
pages: {
signIn: '/',
},
callbacks: {
async authorized({ token }) {
if (!token) return false
if (Date.now() < token.accessTokenExpiresAt && !token.error) {
return true
}
const newToken = await refreshToken(token)
if (Date.now() < newToken.accessTokenExpiresAt && !newToken.error) {
return true
}
return false
},
},
})
export const config = { matcher: ['/:path*/account/:path*'] }
export const config = { matcher: ['/:path/account/:path*'] }

View File

@@ -3,6 +3,8 @@ events {
}
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=2r/s;
server {
listen 80;
server_name donate.magicgrants.org;
@@ -15,5 +17,16 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
location /api {
proxy_pass http://app:3000/api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
limit_req zone=api burst=5 nodelay;
}
}
}

345
package-lock.json generated
View File

@@ -59,9 +59,10 @@
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"react-modal": "^3.16.1",
"react-social-icons": "^6.18.0",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"remark-html": "^16.0.1",
"router": "^1.3.8",
"sanitize-filename": "^1.6.3",
"sharp": "^0.33.4",
"stripe": "^15.9.0",
@@ -80,7 +81,6 @@
"@tailwindcss/line-clamp": "^0.4.4",
"@tailwindcss/typography": "^0.5.13",
"@types/base-64": "^1.0.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "20.14.0",
"@types/nodemailer": "^6.4.15",
"@types/react": "18.3.3",
@@ -4323,6 +4323,13 @@
"@types/unist": "*"
}
},
"node_modules/@types/memory-cache": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/@types/memory-cache/-/memory-cache-0.2.6.tgz",
"integrity": "sha512-G+9tuwWqss2hvX+T/RNY9CY8qSvuCx8uwnl+tXQWwXtBelXUGcn4j6zknDR+2EfdrgBsMZik0jCKNlDEHIBONQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -8447,18 +8454,8 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.390.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.390.0.tgz",
"integrity": "sha512-APqbfEcVuHnZbiy3E97gYWLeBdkE4e6NbY6AuVETZDZVn/bQCHYUoHyxcUHyvRopfPOHhFUEvDyyQzHwM+S9/w==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/mdast-util-from-markdown": {
@@ -8485,6 +8482,107 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-gfm": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
"integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
"license": "MIT",
"dependencies": {
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-gfm-autolink-literal": "^2.0.0",
"mdast-util-gfm-footnote": "^2.0.0",
"mdast-util-gfm-strikethrough": "^2.0.0",
"mdast-util-gfm-table": "^2.0.0",
"mdast-util-gfm-task-list-item": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-gfm-autolink-literal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
"integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"ccount": "^2.0.0",
"devlop": "^1.0.0",
"mdast-util-find-and-replace": "^3.0.0",
"micromark-util-character": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-gfm-footnote": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
"integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"devlop": "^1.1.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0",
"micromark-util-normalize-identifier": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-gfm-strikethrough": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
"integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-gfm-table": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
"integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"markdown-table": "^3.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-gfm-task-list-item": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
"integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"devlop": "^1.0.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-phrasing": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
@@ -8737,6 +8835,127 @@
"micromark-util-types": "^2.0.0"
}
},
"node_modules/micromark-extension-gfm": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
"integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
"license": "MIT",
"dependencies": {
"micromark-extension-gfm-autolink-literal": "^2.0.0",
"micromark-extension-gfm-footnote": "^2.0.0",
"micromark-extension-gfm-strikethrough": "^2.0.0",
"micromark-extension-gfm-table": "^2.0.0",
"micromark-extension-gfm-tagfilter": "^2.0.0",
"micromark-extension-gfm-task-list-item": "^2.0.0",
"micromark-util-combine-extensions": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm-autolink-literal": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
"integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
"license": "MIT",
"dependencies": {
"micromark-util-character": "^2.0.0",
"micromark-util-sanitize-uri": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm-footnote": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
"integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
"license": "MIT",
"dependencies": {
"devlop": "^1.0.0",
"micromark-core-commonmark": "^2.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-normalize-identifier": "^2.0.0",
"micromark-util-sanitize-uri": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm-strikethrough": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
"integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
"license": "MIT",
"dependencies": {
"devlop": "^1.0.0",
"micromark-util-chunked": "^2.0.0",
"micromark-util-classify-character": "^2.0.0",
"micromark-util-resolve-all": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm-table": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
"integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
"license": "MIT",
"dependencies": {
"devlop": "^1.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm-tagfilter": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
"integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
"license": "MIT",
"dependencies": {
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-extension-gfm-task-list-item": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
"integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
"license": "MIT",
"dependencies": {
"devlop": "^1.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/micromark-factory-destination": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
@@ -9276,44 +9495,6 @@
}
}
},
"node_modules/next-auth": {
"version": "4.24.7",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz",
"integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@panva/hkdf": "^1.0.2",
"cookie": "^0.5.0",
"jose": "^4.15.5",
"oauth": "^0.9.15",
"openid-client": "^5.4.0",
"preact": "^10.6.3",
"preact-render-to-string": "^5.1.19",
"uuid": "^8.3.2"
},
"peerDependencies": {
"next": "^12.2.5 || ^13 || ^14",
"nodemailer": "^6.6.5",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
},
"peerDependenciesMeta": {
"nodemailer": {
"optional": true
}
}
},
"node_modules/next-themes": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz",
"integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8 || ^17 || ^18",
"react-dom": "^16.8 || ^17 || ^18"
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -10475,29 +10656,10 @@
"unicode-match-property-value-ecmascript": "^2.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regjsparser": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz",
"integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"jsesc": "~0.5.0"
"node": ">=8"
},
"bin": {
"regjsparser": "bin/parser"
}
},
"node_modules/regjsparser/node_modules/jsesc": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
"dev": true,
"bin": {
"jsesc": "bin/jsesc"
"funding": {
"url": "https://github.com/sponsors/mysticatea"
}
},
"node_modules/remark": {
@@ -10516,6 +10678,24 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-gfm": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
"integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-gfm": "^3.0.0",
"micromark-extension-gfm": "^3.0.0",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-html": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/remark-html/-/remark-html-16.0.1.tgz",
@@ -10549,6 +10729,23 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-rehype": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz",
"integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
"mdast-util-to-hast": "^13.0.0",
"unified": "^11.0.0",
"vfile": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-stringify": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",

View File

@@ -62,8 +62,12 @@
"react-hook-form": "^7.51.5",
"react-modal": "^3.16.1",
"react-social-icons": "^6.18.0",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
"remark-gfm": "^4.0.0",
"remark-html": "^16.0.1",
"remark-rehype": "^11.1.1",
"sanitize-filename": "^1.6.3",
"sharp": "^0.33.4",
"stripe": "^15.9.0",
@@ -83,6 +87,7 @@
"@tailwindcss/typography": "^0.5.13",
"@types/base-64": "^1.0.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/memory-cache": "^0.2.6",
"@types/node": "20.14.0",
"@types/nodemailer": "^6.4.15",
"@types/react": "18.3.3",

View File

@@ -1,8 +1,9 @@
import xss from 'xss'
import { FundSlug } from '@prisma/client'
import sanitize from 'sanitize-filename'
import xss from 'xss'
import markdownToHtml from '../../utils/markdownToHtml'
import { getSingleFile } from '../../utils/md'
import { fileExists, getSingleFile } from '../../utils/md'
import { fundSlugs } from '../../utils/funds'
export default function About({ content }: { content: string }) {
@@ -15,7 +16,7 @@ export default function About({ content }: { content: string }) {
}
export async function getStaticProps({ params }: { params: { fund: FundSlug } }) {
const md = getSingleFile(`docs/${params.fund}/about_us.md`)
const md = getSingleFile(`docs/${sanitize(params.fund)}/about_us.md`)
const content = await markdownToHtml(md || '')
@@ -28,7 +29,9 @@ export async function getStaticProps({ params }: { params: { fund: FundSlug } })
export function getStaticPaths() {
return {
paths: fundSlugs.map((fund) => `/${fund}/about`),
paths: fundSlugs
.filter((fundSlug) => fileExists(`docs/${sanitize(fundSlug)}/about_us.md`))
.map((fundSlug) => `/${fundSlug}/about`),
fallback: true,
}
}

View File

@@ -33,26 +33,28 @@ function MyDonations() {
<div className="w-full max-w-5xl mx-auto flex flex-col">
<h1 className="text-3xl font-bold mb-4">My Donations</h1>
<Table className="">
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead>Method</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Date</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{donationListQuery.data?.map((donation) => (
<TableRow key={donation.createdAt.toISOString()}>
<TableCell>{donation.projectName}</TableCell>
<TableCell>{donation.btcPayInvoiceId ? 'Crypto' : 'Fiat'}</TableCell>
<TableCell>${donation.grossFiatAmount}</TableCell>
<TableCell>{dayjs(donation.createdAt).format('lll')}</TableCell>
<div className="w-full flex overflow-x-auto grow">
<Table className="min-w-[700px] grow">
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead>Method</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Date</TableHead>
</TableRow>
))}
</TableBody>
</Table>
</TableHeader>
<TableBody>
{donationListQuery.data?.map((donation) => (
<TableRow key={donation.createdAt.toISOString()}>
<TableCell>{donation.projectName}</TableCell>
<TableCell>{donation.btcPayInvoiceId ? 'Crypto' : 'Fiat'}</TableCell>
<TableCell>${donation.grossFiatAmount}</TableCell>
<TableCell>{dayjs(donation.createdAt).format('lll')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</>
)

View File

@@ -44,28 +44,30 @@ function MyMemberships() {
)}
</div>
<Table className="">
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead>Method</TableHead>
<TableHead>Recurring</TableHead>
<TableHead>Date</TableHead>
<TableHead>Period End</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{membershipListQuery.data?.memberships.map((membership) => (
<TableRow key={membership.createdAt.toISOString()}>
<TableCell>{membership.projectName}</TableCell>
<TableCell>{membership.btcPayInvoiceId ? 'Crypto' : 'Fiat'}</TableCell>
<TableCell>{membership.stripeSubscriptionId ? 'Yes' : 'No'}</TableCell>
<TableCell>{dayjs(membership.createdAt).format('lll')}</TableCell>
<TableCell>{dayjs(membership.membershipExpiresAt).format('lll')}</TableCell>
<div className="w-full flex overflow-x-auto grow">
<Table className="min-w-[700px] grow">
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead>Method</TableHead>
<TableHead>Recurring</TableHead>
<TableHead>Date</TableHead>
<TableHead>Period End</TableHead>
</TableRow>
))}
</TableBody>
</Table>
</TableHeader>
<TableBody>
{membershipListQuery.data?.memberships.map((membership) => (
<TableRow key={membership.createdAt.toISOString()}>
<TableCell>{membership.projectName}</TableCell>
<TableCell>{membership.btcPayInvoiceId ? 'Crypto' : 'Fiat'}</TableCell>
<TableCell>{membership.stripeSubscriptionId ? 'Yes' : 'No'}</TableCell>
<TableCell>{dayjs(membership.createdAt).format('lll')}</TableCell>
<TableCell>{dayjs(membership.membershipExpiresAt).format('lll')}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</>
)

View File

@@ -0,0 +1,220 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import Head from 'next/head'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '../../../components/ui/form'
import { Input } from '../../../components/ui/input'
import { useForm } from 'react-hook-form'
import { Button } from '../../../components/ui/button'
import Spinner from '../../../components/Spinner'
import { toast } from '../../../components/ui/use-toast'
import { trpc } from '../../../utils/trpc'
import { useFundSlug } from '../../../utils/use-fund-slug'
import { signOut, useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
const changePasswordFormSchema = z
.object({
currentPassword: z.string().min(8),
newPassword: z.string().min(8),
confirmNewPassword: z.string().min(8),
})
.refine((data) => data.newPassword === data.confirmNewPassword, {
message: 'Passwords do not match.',
path: ['confirmNewPassword'],
})
const changeEmailFormSchema = z.object({ newEmail: z.string().email() })
type ChangePasswordFormInputs = z.infer<typeof changePasswordFormSchema>
type ChangeEmailFormInputs = z.infer<typeof changeEmailFormSchema>
function Settings() {
const fundSlug = useFundSlug()
const session = useSession()
const changePasswordMutation = trpc.account.changePassword.useMutation()
const requestEmailChangeMutation = trpc.account.requestEmailChange.useMutation()
const changePasswordForm = useForm<ChangePasswordFormInputs>({
resolver: zodResolver(changePasswordFormSchema),
defaultValues: {
currentPassword: '',
newPassword: '',
confirmNewPassword: '',
},
mode: 'all',
})
const changeEmailForm = useForm<ChangeEmailFormInputs>({
resolver: zodResolver(changeEmailFormSchema),
defaultValues: { newEmail: '' },
mode: 'all',
})
async function onChangePasswordSubmit(data: ChangePasswordFormInputs) {
try {
await changePasswordMutation.mutateAsync({
currentPassword: data.currentPassword,
newPassword: data.newPassword,
})
changePasswordForm.reset()
toast({ title: 'Password successfully changed! Please log in again.' })
await signOut({ callbackUrl: `/${fundSlug}/?loginEmail=${session.data?.user.email}` })
} catch (error) {
const errorMessage = (error as any).message
if (errorMessage === 'INVALID_PASSWORD') {
return changePasswordForm.setError(
'currentPassword',
{ message: 'Invalid password.' },
{ shouldFocus: true }
)
}
return toast({
title: 'Sorry, something went wrong.',
variant: 'destructive',
})
}
}
async function onChangeEmailSubmit(data: ChangeEmailFormInputs) {
if (!fundSlug) return
try {
await requestEmailChangeMutation.mutateAsync({ fundSlug, newEmail: data.newEmail })
changeEmailForm.reset()
toast({ title: 'A verification link has been sent to your email.' })
} catch (error) {
const errorMessage = (error as any).message
if (errorMessage === 'EMAIL_TAKEN') {
return changeEmailForm.setError(
'newEmail',
{ message: 'Email is already taken.' },
{ shouldFocus: true }
)
}
return toast({
title: 'Sorry, something went wrong.',
variant: 'destructive',
})
}
}
return (
<>
<Head>
<title>MAGIC Grants - Settings</title>
</Head>
<div className="w-full max-w-lg mx-auto flex flex-col space-y-12">
<div className="w-full flex flex-col space-y-6">
<h1 className="font-bold">Change Password</h1>
<Form {...changePasswordForm}>
<form
onSubmit={changePasswordForm.handleSubmit(onChangePasswordSubmit)}
className="flex flex-col space-y-4"
>
<FormField
control={changePasswordForm.control}
name="currentPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Current password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={changePasswordForm.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel>New password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={changePasswordForm.control}
name="confirmNewPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm new password</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
disabled={
changePasswordForm.formState.isSubmitting || !changePasswordForm.formState.isValid
}
>
{changePasswordForm.formState.isSubmitting && <Spinner />} Change Password
</Button>
</form>
</Form>
</div>
<div className="w-full flex flex-col space-y-6">
<h1 className="font-bold">Change Email</h1>
<Form {...changeEmailForm}>
<form
onSubmit={changeEmailForm.handleSubmit(onChangeEmailSubmit)}
className="flex flex-col space-y-4"
>
<FormField
control={changeEmailForm.control}
name="newEmail"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder={session.data?.user.email} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
disabled={
changeEmailForm.formState.isSubmitting || !changeEmailForm.formState.isValid
}
>
{changeEmailForm.formState.isSubmitting && <Spinner />} Change Email
</Button>
</form>
</Form>
</div>
</div>
</>
)
}
export default Settings

View File

@@ -1,6 +1,8 @@
import { FundSlug } from '@prisma/client'
import sanitize from 'sanitize-filename'
import markdownToHtml from '../../utils/markdownToHtml'
import { getSingleFile } from '../../utils/md'
import { fileExists, getSingleFile } from '../../utils/md'
import BigDumbMarkdown from '../../components/BigDumbMarkdown'
import { fundSlugs } from '../../utils/funds'
@@ -9,7 +11,7 @@ export default function About({ content }: { content: string }) {
}
export async function getStaticProps({ params }: { params: { fund: FundSlug } }) {
const md = getSingleFile(`docs/${params.fund}/apply_research.md`)
const md = getSingleFile(`docs/${sanitize(params.fund)}/apply_research.md`)
const content = await markdownToHtml(md || '')
@@ -22,7 +24,9 @@ export async function getStaticProps({ params }: { params: { fund: FundSlug } })
export function getStaticPaths() {
return {
paths: fundSlugs.map((fund) => `/${fund}/apply_research`),
paths: fundSlugs
.filter((fundSlug) => fileExists(`docs/${sanitize(fundSlug)}/apply_research.md`))
.map((fundSlug) => `/${fundSlug}/apply_research`),
fallback: true,
}
}

View File

@@ -1,8 +1,9 @@
import xss from 'xss'
import { FundSlug } from '@prisma/client'
import sanitize from 'sanitize-filename'
import xss from 'xss'
import markdownToHtml from '../../utils/markdownToHtml'
import { getSingleFile } from '../../utils/md'
import { fileExists, getSingleFile } from '../../utils/md'
import { fundSlugs } from '../../utils/funds'
export default function Faq({ content }: { content: string }) {
@@ -15,7 +16,7 @@ export default function Faq({ content }: { content: string }) {
}
export async function getStaticProps({ params }: { params: { fund: FundSlug } }) {
const md = getSingleFile(`docs/${params.fund}/faq.md`)
const md = getSingleFile(`docs/${sanitize(params.fund)}/faq.md`)
const content = await markdownToHtml(md || '')
@@ -28,7 +29,9 @@ export async function getStaticProps({ params }: { params: { fund: FundSlug } })
export function getStaticPaths() {
return {
paths: fundSlugs.map((fund) => `/${fund}/faq`),
paths: fundSlugs
.filter((fundSlug) => fileExists(`docs/${sanitize(fundSlug)}/faq.md`))
.map((fundSlug) => `/${fundSlug}/faq`),
fallback: true,
}
}

View File

@@ -84,81 +84,86 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
<div className="divide-y divide-gray-200">
<PageHeading project={project}>
<div className="flex flex-col items-center space-x-2 pt-8 xl:block">
<div className="w-full mt-8 flex flex-col md:flex-row items-center md:space-x-8 xl:space-x-0 space-y-10 md:space-y-0 xl:block">
<Image
src={coverImage}
alt="avatar"
width={300}
height={300}
className="h-72 w-72 mx-auto object-contain xl:hidden"
width={700}
height={700}
className="w-full max-w-[700px] mx-auto object-contain xl:hidden"
/>
<div className="space-y-4">
{!project.isFunded && (
<div className="flex flex-col space-y-2">
<Button onClick={() => setDonateModalOpen(true)}>Donate</Button>
<div className="w-full max-w-96 space-y-8 p-6 bg-white rounded-xl">
<div className="w-full">
{!project.isFunded && (
<div className="flex flex-col space-y-2">
<Button onClick={() => setDonateModalOpen(true)}>Donate</Button>
{!userHasMembershipQuery.data && (
<Button
onClick={() =>
session.status === 'authenticated'
? setMemberModalOpen(true)
: setRegisterIsOpen(true)
}
variant="outline"
>
Get Annual Membership
</Button>
)}
{!userHasMembershipQuery.data && (
<Button
onClick={() =>
session.status === 'authenticated'
? setMemberModalOpen(true)
: setRegisterIsOpen(true)
}
variant="outline"
>
Get Annual Membership
</Button>
)}
{!!userHasMembershipQuery.data && (
<Button variant="outline">
<CustomLink href={`${fundSlug}/account/my-memberships`}>
My Memberships
</CustomLink>
</Button>
)}
</div>
)}
{!!userHasMembershipQuery.data && (
<Button variant="outline">
<CustomLink href={`${fundSlug}/account/my-memberships`}>
My Memberships
</CustomLink>
</Button>
)}
</div>
)}
</div>
<h1 className="mb-4 font-bold">Raised</h1>
<div className="w-full">
<h1 className="mb-4 font-bold">Raised</h1>
<Progress
current={
donationStats.xmr.fiatAmount +
donationStats.btc.fiatAmount +
donationStats.usd.fiatAmount
}
goal={goal}
/>
<Progress
current={
donationStats.xmr.fiatAmount +
donationStats.btc.fiatAmount +
donationStats.usd.fiatAmount
}
goal={goal}
/>
<ul className="font-semibold space-y-1">
<li className="flex items-center space-x-1">
<span className="text-green-500 text-xl">{`${formatUsd(donationStats.xmr.fiatAmount + donationStats.btc.fiatAmount + donationStats.usd.fiatAmount)}`}</span>{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count + donationStats.btc.count + donationStats.usd.count}{' '}
donations total
</span>
</li>
<li>
{donationStats.xmr.amount} XMR{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count} donations
</span>
</li>
<li>
{formatBtc(donationStats.btc.amount)}{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.btc.count} donations
</span>
</li>
<li>
{`${formatUsd(donationStats.usd.amount)}`} Fiat{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.usd.count} donations
</span>
</li>
</ul>
<ul className="font-semibold space-y-1">
<li className="flex items-center space-x-1">
<span className="text-green-500 text-xl">{`${formatUsd(donationStats.xmr.fiatAmount + donationStats.btc.fiatAmount + donationStats.usd.fiatAmount)}`}</span>{' '}
<span className="font-normal text-sm text-gray">
in{' '}
{donationStats.xmr.count + donationStats.btc.count + donationStats.usd.count}{' '}
donations total
</span>
</li>
<li>
{donationStats.xmr.amount} XMR{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.xmr.count} donations
</span>
</li>
<li>
{formatBtc(donationStats.btc.amount)}{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.btc.count} donations
</span>
</li>
<li>
{`${formatUsd(donationStats.usd.amount)}`} Fiat{' '}
<span className="font-normal text-sm text-gray">
in {donationStats.usd.count} donations
</span>
</li>
</ul>
</div>
</div>
</div>
@@ -171,13 +176,21 @@ const Project: NextPage<SingleProjectPageProps> = ({ project, donationStats }) =
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={project} />
<DonationFormModal
project={project}
close={() => setDonateModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={project} />
<MembershipFormModal
project={project}
close={() => setMemberModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>
@@ -226,19 +239,19 @@ export async function getServerSideProps({ params, resolvedUrl }: GetServerSideP
const donationStats = {
xmr: {
count: project.isFunded ? project.numdonationsxmr : 0,
amount: project.isFunded ? project.totaldonationsxmr : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatxmr : 0,
count: project.isFunded ? project.numDonationsXMR : 0,
amount: project.isFunded ? project.totalDonationsXMR : 0,
fiatAmount: project.isFunded ? project.totalDonationsXMRInFiat : 0,
},
btc: {
count: project.isFunded ? project.numdonationsbtc : 0,
amount: project.isFunded ? project.totaldonationsbtc : 0,
fiatAmount: project.isFunded ? project.totaldonationsinfiatbtc : 0,
count: project.isFunded ? project.numDonationsBTC : 0,
amount: project.isFunded ? project.totalDonationsBTC : 0,
fiatAmount: project.isFunded ? project.totalDonationsBTCInFiat : 0,
},
usd: {
count: project.isFunded ? project.fiatnumdonations : 0,
amount: project.isFunded ? project.fiattotaldonations : 0,
fiatAmount: project.isFunded ? project.fiattotaldonationsinfiat : 0,
count: project.isFunded ? project.numDonationsFiat : 0,
amount: project.isFunded ? project.totalDonationsFiat : 0,
fiatAmount: project.isFunded ? project.totalDonationsFiat : 0,
},
}

View File

@@ -55,7 +55,7 @@ function ResetPassword() {
})
toast({ title: 'Password successfully reset. You may now log in.' })
router.push(`/${fundSlug}/?loginEmail=${data.email}`)
router.push(`/${fundSlug}/?loginEmail=${encodeURIComponent(data.email)}`)
} catch (error) {
const errorMessage = (error as any).message

View File

@@ -4,6 +4,7 @@ import { useRouter } from 'next/router'
import { trpc } from '../../../utils/trpc'
import { useToast } from '../../../components/ui/use-toast'
import { useFundSlug } from '../../../utils/use-fund-slug'
import { signOut } from 'next-auth/react'
function VerifyEmail() {
const router = useRouter()
@@ -18,12 +19,9 @@ function VerifyEmail() {
if (!token) return
try {
const result = await verifyEmailMutation.mutateAsync({
token: token as string,
})
router.push(`/${fundSlug}/?loginEmail=${result.email}`)
const result = await verifyEmailMutation.mutateAsync({ token: token as string })
toast({ title: 'Email verified! You may now log in.' })
await signOut({ callbackUrl: `/${fundSlug}/?loginEmail=${result.email}` })
} catch (error) {
toast({ title: 'Invalid verification link.', variant: 'destructive' })
router.push(`/${fundSlug}`)

View File

@@ -1,48 +1,37 @@
import type { AppProps } from 'next/app'
import { ThemeProvider } from 'next-themes'
import { SessionProvider } from 'next-auth/react'
import { Inter } from 'next/font/google'
import Head from 'next/head'
import Layout from '../components/Layout'
import { Toaster } from '../components/ui/toaster'
import { trpc } from '../utils/trpc'
import { useFundSlug } from '../utils/use-fund-slug'
import '../styles/globals.css'
import { funds } from '../utils/funds'
const inter = Inter({ subsets: ['latin'], display: 'swap', adjustFontFallback: false })
import '../styles/globals.css'
function MyApp({ Component, pageProps }: AppProps) {
const fundSlug = useFundSlug()
return (
<>
<style jsx global>{`
html {
font-family: ${inter.style.fontFamily};
}
`}</style>
<SessionProvider session={pageProps.session}>
<ThemeProvider
attribute="class"
forcedTheme={fundSlug || 'general'}
themes={['monero', 'general', 'firo', 'priacyguides']}
enableSystem={false}
>
<Head>
<meta content="width=device-width, initial-scale=1" name="viewport" />
<title>{fundSlug ? funds[fundSlug].title : 'MAGIC Grants Campaigns'}</title>
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
<Toaster />
</ThemeProvider>
</SessionProvider>
</>
<SessionProvider session={pageProps.session}>
<ThemeProvider
attribute="class"
forcedTheme={fundSlug || 'general'}
themes={['monero', 'general', 'firo', 'priacyguides']}
enableSystem={false}
>
<Head>
<meta content="width=device-width, initial-scale=1" name="viewport" />
<title>{fundSlug ? funds[fundSlug].title : 'MAGIC Grants Campaigns'}</title>
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
<Toaster />
</ThemeProvider>
</SessionProvider>
)
}

View File

@@ -2,15 +2,45 @@ import NextAuth, { AuthOptions } from 'next-auth'
import { jwtDecode } from 'jwt-decode'
import CredentialsProvider from 'next-auth/providers/credentials'
import axios from 'axios'
import { env } from '../../../env.mjs'
type KeycloakJwtPayload = {
sub: string
email: string
}
import { env } from '../../../env.mjs'
import { KeycloakJwtPayload } from '../../../server/types'
import { refreshToken } from '../../../server/utils/auth'
export const authOptions: AuthOptions = {
session: { strategy: 'jwt' },
callbacks: {
jwt: async ({ token, user, account }) => {
// On sign in
if (user && account) {
const keycloakToken = (user as any).keycloakToken
return {
sub: user.id,
email: user.email,
accessToken: keycloakToken.access_token,
accessTokenExpiresAt: Date.now() + (keycloakToken.expires_in as number) * 1000,
refreshToken: keycloakToken.refresh_token,
}
}
// Return previous token if the access token has not expired yet
if (Date.now() < token.accessTokenExpiresAt) {
return token
}
// Refresh access token
return refreshToken(token)
},
session: ({ session, token }) => {
return {
user: {
sub: token.sub,
email: token.email,
},
error: token.error,
expires: session.expires,
}
},
},
providers: [
CredentialsProvider({
name: 'Credentials',
@@ -20,26 +50,26 @@ export const authOptions: AuthOptions = {
},
authorize: async (credentials) => {
try {
const { data } = await axios.post(
const { data: keycloakToken } = await axios.post(
`${env.KEYCLOAK_URL}/realms/${env.KEYCLOAK_REALM_NAME}/protocol/openid-connect/token`,
new URLSearchParams({
grant_type: 'password',
client_id: env.KEYCLOAK_CLIENT_ID,
client_secret: env.KEYCLOAK_CLIENT_SECRET,
grant_type: 'password',
username: credentials?.email || '',
password: credentials?.password || '',
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
const keycloakJwtPayload: KeycloakJwtPayload = jwtDecode(data.access_token)
const keycloakTokenPayload: KeycloakJwtPayload = jwtDecode(keycloakToken.access_token)
return {
id: keycloakJwtPayload.sub,
email: keycloakJwtPayload.email,
id: keycloakTokenPayload.sub,
email: keycloakTokenPayload.email,
keycloakToken,
}
} catch (error) {
console.log(error)
const errorMessage = (error as any).response.data.error
if (errorMessage === 'invalid_grant') {
throw new Error('INVALID_CREDENTIALS')

View File

@@ -11,6 +11,7 @@ import {
import { btcpayApi as _btcpayApi, btcpayApi, prisma } from '../../../server/services'
import { env } from '../../../env.mjs'
import { getUserPointBalance } from '../../../server/utils/perks'
import { sendDonationConfirmationEmail } from '../../../server/utils/mailing'
export const config = {
api: {
@@ -27,7 +28,8 @@ type BtcpayBody = Record<string, any> & {
timestamp: number
storeId: string
invoiceId: string
metadata: DonationMetadata
metadata?: DonationMetadata
paymentMethod: string
}
async function handleBtcpayWebhook(req: NextApiRequest, res: NextApiResponse) {
@@ -58,13 +60,20 @@ async function handleBtcpayWebhook(req: NextApiRequest, res: NextApiResponse) {
return
}
if (!body.metadata) {
return res.status(200).json({ success: true })
}
if (body.type === 'InvoicePaymentSettled') {
// Handle payments to funding required API invoices ONLY
if (body.metadata.staticGeneratedForApi === 'false') {
return res.status(200).json({ success: true })
}
const cryptoCode = body.paymentMethod === 'BTC-OnChain' ? 'BTC' : 'XMR'
// Handle payment methods like "BTC-LightningNetwork" if added in the future
const cryptoCode = body.paymentMethod.includes('-')
? body.paymentMethod.split('-')[0]
: body.paymentMethod
const { data: rates } = await btcpayApi.get<BtcPayGetRatesRes>(
`/rates?currencyPair=${cryptoCode}_USD`
@@ -103,6 +112,7 @@ async function handleBtcpayWebhook(req: NextApiRequest, res: NextApiResponse) {
// Create one donation and one point history for each invoice payment method
await Promise.all(
paymentMethods.map(async (paymentMethod) => {
if (!body.metadata) return
const shouldGivePointsBack = body.metadata.givePointsBack === 'true'
const cryptoRate = Number(paymentMethod.rate)
const grossCryptoAmount = Number(paymentMethod.amount)
@@ -150,6 +160,20 @@ async function handleBtcpayWebhook(req: NextApiRequest, res: NextApiResponse) {
},
})
}
if (body.metadata.donorEmail && body.metadata.donorName) {
sendDonationConfirmationEmail({
to: body.metadata.donorEmail,
donorName: body.metadata.donorName,
fundSlug: body.metadata.fundSlug,
projectName: body.metadata.projectName,
isMembership: body.metadata.isMembership === 'true',
isSubscription: false,
pointsReceived: pointsAdded,
btcpayAsset: paymentMethod.cryptoCode as 'BTC' | 'XMR',
btcpayCryptoAmount: grossCryptoAmount,
})
}
})
)
}

View File

@@ -2,7 +2,6 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { FundSlug } from '@prisma/client'
import { z } from 'zod'
import dayjs from 'dayjs'
import path from 'path'
import { getProjects } from '../../utils/md'
import { env } from '../../env.mjs'
@@ -14,7 +13,7 @@ import {
BtcPayGetRatesRes,
DonationMetadata,
} from '../../server/types'
import { funds, fundSlugs } from '../../utils/funds'
import { fundSlugs } from '../../utils/funds'
const ASSETS = ['BTC', 'XMR', 'USD'] as const
@@ -85,9 +84,11 @@ async function handle(
}
const projects = (await getProjects(query.fund)).filter((project) =>
query.project_status === 'ANY' || query.project_status === 'FUNDED'
query.project_status === 'FUNDED'
? project.isFunded
: !project.isFunded
: query.project_status === 'ANY'
? true
: !project.isFunded
)
const rates: Record<string, number | undefined> = {}
@@ -110,8 +111,8 @@ async function handle(
let moneroAddress: string | null = null
if (!project.isFunded) {
const existingAddresses = await prisma.projectAddresses.findFirst({
where: { projectSlug: project.slug, fundSlug: project.fund },
const existingAddresses = await prisma.projectAddresses.findUnique({
where: { projectSlug_fundSlug: { projectSlug: project.slug, fundSlug: project.fund } },
})
// Create invoice if there's no existing address
@@ -131,16 +132,17 @@ async function handle(
}
const { data: invoice } = await btcpayApi.post<BtcPayCreateInvoiceRes>('/invoices', {
checkout: { monitoringMinutes: 9999999 },
currency: CURRENCY,
metadata,
})
const paymentMethodsResponse = await btcpayApi.get<BtcPayGetPaymentMethodsRes>(
const { data: paymentMethods } = await btcpayApi.get<BtcPayGetPaymentMethodsRes>(
`/invoices/${invoice.id}/payment-methods`
)
paymentMethodsResponse.data.forEach((paymentMethod: any) => {
if (paymentMethod.paymentMethod === 'BTC-OnChain') {
paymentMethods.forEach((paymentMethod) => {
if (paymentMethod.paymentMethod === 'BTC') {
bitcoinAddress = paymentMethod.destination
}
@@ -181,9 +183,9 @@ async function handle(
const targetAmountUsd = project.goal
const allDonationsSumUsd =
project.totaldonationsinfiatbtc +
project.totaldonationsinfiatxmr +
project.fiattotaldonationsinfiat
project.totalDonationsBTCInFiat +
project.totalDonationsXMRInFiat +
project.totalDonationsFiat
const remainingAmountBtc = (project.goal - allDonationsSumUsd) / (rates.BTC || 0)
const remainingAmountXmr = (project.goal - allDonationsSumUsd) / (rates.XMR || 0)
@@ -194,7 +196,7 @@ async function handle(
fund: project.fund,
date: project.date,
author: project.nym,
url: path.join(env.APP_URL, project.fund, project.slug),
url: `${env.APP_URL}/${project.fund}/${project.slug}`,
is_funded: !!project.isFunded,
target_amount_btc: Number(targetAmountBtc.toFixed(8)),
target_amount_xmr: Number(targetAmountXmr.toFixed(12)),
@@ -204,13 +206,14 @@ async function handle(
remaining_amount_usd: Number((remainingAmountUsd > 0 ? remainingAmountUsd : 0).toFixed(2)),
address_btc: bitcoinAddress,
address_xmr: moneroAddress,
raised_amount_percent:
((project.totaldonationsinfiatxmr +
project.totaldonationsinfiatbtc +
project.fiattotaldonationsinfiat) /
raised_amount_percent: Math.floor(
((project.totalDonationsBTCInFiat +
project.totalDonationsXMRInFiat +
project.totalDonationsFiat) /
project.goal) *
100,
contributions: project.numdonationsbtc + project.numdonationsxmr + project.fiatnumdonations,
100
),
contributions: project.numDonationsBTC + project.numDonationsXMR + project.numDonationsFiat,
}
})
)

View File

@@ -7,4 +7,4 @@ export const config = {
},
}
export default getStripeWebhookHandler(env.STRIPE_FIRO_WEBHOOK_SECRET)
export default getStripeWebhookHandler('firo', env.STRIPE_FIRO_WEBHOOK_SECRET)

View File

@@ -7,4 +7,4 @@ export const config = {
},
}
export default getStripeWebhookHandler(env.STRIPE_GENERAL_WEBHOOK_SECRET)
export default getStripeWebhookHandler('general', env.STRIPE_GENERAL_WEBHOOK_SECRET)

View File

@@ -7,4 +7,4 @@ export const config = {
},
}
export default getStripeWebhookHandler(env.STRIPE_MONERO_WEBHOOK_SECRET)
export default getStripeWebhookHandler('monero', env.STRIPE_MONERO_WEBHOOK_SECRET)

View File

@@ -7,4 +7,4 @@ export const config = {
},
}
export default getStripeWebhookHandler(env.STRIPE_PRIVACY_GUIDES_WEBHOOK_SECRET)
export default getStripeWebhookHandler('privacyguides', env.STRIPE_PRIVACY_GUIDES_WEBHOOK_SECRET)

View File

@@ -1,295 +0,0 @@
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import Link from 'next/link'
import { Button } from '../../components/ui/button'
import { trpc } from '../../utils/trpc'
import { useFundSlug } from '../../utils/use-fund-slug'
import { useToast } from '../../components/ui/use-toast'
import { fundSlugToRecipientEmail } from '../../utils/funds'
export default function Apply() {
const fundSlug = useFundSlug()
const router = useRouter()
const { toast } = useToast()
const applyMutation = trpc.application.submitApplication.useMutation()
const {
register,
handleSubmit,
formState: { errors },
} = useForm()
async function onSubmit(data: Record<string, string>) {
if (!fundSlug) return
await applyMutation.mutateAsync({ fundSlug, formData: data })
toast({ title: 'Success', description: 'Application successfully submitted!' })
router.push(`/${fundSlug}/`)
}
if (!fundSlug) return <></>
return (
<div className="mx-auto flex-1 flex flex-col items-center justify-center gap-4 py-8 prose">
<form onSubmit={handleSubmit(onSubmit)} className="max-w-5xl flex flex-col gap-4 p-4">
<div>
<h1>Application for Monero Fund Project Listing or General Fund Grant</h1>
<p>Thanks for your interest in the Monero Fund!</p>
<p>
We&#39;re incredibly grateful to contributors like you working to support Monero,
Bitcoin and other free and open source projects.
</p>
<p>
The MAGIC Monero Fund is offering a grant program and fundraising platform to support
Monero research and development, especially relating to privacy, security, user
experience, and efficiency. Proposals can be related to the Monero protocol directly, or
they can be related to other areas of the Monero ecosystem. For research projects,
please refer to special instructions
<Link href="/apply_research"> here</Link>.
</p>
<h2>Proposal Evaluation Criteria</h2>
<div>
Submitted proposals will be evaluated by the committee based on the following criteria:
<ul>
<li>
<b>Impact:</b> The proposal should have a clear impact on the Monero Project.
</li>
<li>
<b>Originality:</b> The proposal should be original and not a rehash of existing
work.
</li>
<li>
<b>Feasibility:</b> The proposal should be feasible to complete within the proposed
time frame.
</li>
<li>
<b>Quality:</b> The proposal should be well-written and well-organized.
</li>
<li>
<b>Relevance:</b> The proposal should be relevant to the Monero Project.
</li>
</ul>
</div>
<h2 id="Eligibility">Eligibility</h2>
<p>
All qualified researchers are eligible to apply, regardless of educational attainment or
occupation. However, as a nonprofit organization registered under U.S. tax laws, MAGIC
Grants is required to comply with certain laws when disbursing funds to grant
recipients. Grant recipients must complete a Due Diligence checklist, which are the last
two pages of{' '}
<a href="https://magicgrants.org/funds/MAGIC%20Fund%20Grant%20Disbursement%20Process%20and%20Requirements.pdf">
this document
</a>
. This includes the collection of your ID and tax information. We will conduct sanctions
checks.
</p>
<h2>How to Submit a Proposal</h2>
<p>
To submit a proposal, please complete the form below or create an issue on{' '}
<a href="https://github.com/MAGICGrants/Monero-Fund/issues/new?assignees=&labels=&template=grant-application.md&title=[Grant+Title]">
Github
</a>
. Applicants are free to use their legal name or a pseudonym at this step, although note
the{' '}
<a href="#Eligibility">
<b>Eligibility</b>
</a>{' '}
section. You can choose to apply for a direct grant from the MAGIC Monero Fund&#39;s
General Fund and/or request that your project be listed on MoneroFund.org to raise funds
from Monero community members.
</p>
<p>
Please note this form is not considered confidential and is effectively equivalent to a
public GitHub issue. In order to reach out privately, please send an email to
MoneroFund@magicgrants.org.
</p>
</div>
<label className="checkbox">
<input type="checkbox" {...register('general_fund')} />
Apply to receive a grant from the Magic Monero Fund.
</label>
<label className="checkbox">
<input type="checkbox" {...register('explore_page')} />
Apply for project to be listed on the Monero Fund Donation Page.
</label>
<div className="w-full flex flex-col">
<label htmlFor="project_name">Project Name *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="project_name"
type="text"
{...register('project_name', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="your_name">Your Name or Pseudonym *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="your_name"
type="text"
{...register('your_name', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="email">Email *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="email"
type="text"
{...register('email', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="github">Project GitHub (if applicable)</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="github"
type="text"
{...register('github')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="personal_github">Personal GitHub (if applicable)</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="personal_github"
type="text"
{...register('personal_github')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="other_contact">Other Contact Details (if applicable)</label>
<small>
Please list any other relevant contact details you are comfortable sharing in case we
need to reach out with questions. These could include github username, twitter username,
LinkedIn, Reddit handle, other social media handles, emails, phone numbers, usernames,
etc.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="other_contact"
{...register('other_contact')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="short_description">Short Project Description *</label>
<small>
This will be listed on the explore projects page of the Monero Fund website. 2-3
sentences.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="short_description"
{...register('short_description', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="long_description">Long Project Description</label>
<small>
This will be listed on your personal project page of the Monero Fund website. It can be
longer and go into detail about your project.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="long_description"
{...register('long_description')}
/>
</div>
<label className="checkbox">
<input type="checkbox" {...register('free_open_source')} />
Is the project free and open source?
</label>
<label className="checkbox">
<input type="checkbox" {...register('are_you_lead')} />
Are you the Project Lead / Lead Contributor
</label>
<div className="w-full flex flex-col">
<label htmlFor="other_lead">
If someone else, please list the project&#39;s Lead Contributor or Maintainer
</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="other_lead"
type="text"
{...register('other_lead')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="potential_impact">Potential Impact *</label>
<small>Why is this project important to the Monero community?</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="potential_impact"
{...register('potential_impact', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="timelines">Project Timelines and Potential Milestones *</label>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="timelines"
{...register('timelines', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="proposed_budget">
If you&#39;re applying for a grant from the general fund, please submit a proposed
budget for the requested amount and how it will be used.
</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="proposed_budget"
type="text"
{...register('proposed_budget')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="bios">Applicant Bios (Optional)</label>
<small>List relevant accomplishments.</small>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="bios"
type="text"
{...register('bios')}
/>
</div>
<small>
The MAGIC Monero Fund may require each recipient to sign a Grant Agreement before any
funds are disbursed. This agreement will set milestones and funds will only be released
upon completion of milestones. In order to comply with US regulations, recipients will
need to identify themselves to MAGIC, in accordance with US law.
</small>
<Button disabled={applyMutation.isPending}>Apply</Button>
<p>
After submitting your application, please allow our team up to three weeks to review your
application. Email us at{' '}
<a href={`mailto:${fundSlugToRecipientEmail[fundSlug]}`}>
{fundSlugToRecipientEmail[fundSlug]}
</a>{' '}
if you have any questions.
</p>
</form>
</div>
)
}

View File

@@ -2,14 +2,11 @@ import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import { useSession } from 'next-auth/react'
import { useTheme } from 'next-themes'
import { getProjects } from '../../utils/md'
import { ProjectItem } from '../../utils/types'
import Typing from '../../components/Typing'
import CustomLink from '../../components/CustomLink'
import { Button } from '../../components/ui/button'
import { Dialog, DialogContent, DialogTrigger } from '../../components/ui/dialog'
import { Dialog, DialogContent } from '../../components/ui/dialog'
import DonationFormModal from '../../components/DonationFormModal'
import MembershipFormModal from '../../components/MembershipFormModal'
import ProjectList from '../../components/ProjectList'
@@ -51,11 +48,11 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<div className="divide-y divide-gray-200">
<div className="pt-4 md:pb-8">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
Support Firo
</h1>
<p className="max-w-3xl text-xl leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
Help us support security audits, essential infrastructure, and research for the Firo
ecosystem.
</p>
<div className="flex flex-col md:flex-row my-4 gap-2">
@@ -64,7 +61,7 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
Donate to Firo Fund
</Button>
{!userHasMembershipQuery.data && (
@@ -90,18 +87,8 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
)}
</div>
<div className="flex flex-row flex-wrap">
<p className="text-md leading-7 text-gray-500">
Want to receive funding for your work?
<CustomLink href={`/${fund.slug}/apply`}>
{' '}
Apply for a Monero development or research grant!
</CustomLink>
</p>
</div>
<p className="text-sm leading-7 text-gray-400">
We are a 501(c)(3) public charity. All donations are tax deductible.
We are a 501(c)(3) public charity. Your donation may qualify for a tax deduction.
</p>
</div>
</div>
@@ -125,13 +112,21 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={fund} />
<DonationFormModal
project={fund}
close={() => setDonateModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={fund} />
<MembershipFormModal
project={fund}
close={() => setMemberModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>

View File

@@ -1,17 +0,0 @@
import Link from 'next/link'
export default function Submitted() {
return (
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2>Thank you for your application!</h2>
<p>
If you have any questions or need a donation receipt, please reach out to{' '}
<a href="mailto:info@magicgrants.org">info@magicgrants.org</a>
</p>
.
<p>
<Link href="/">Return Home</Link>
</p>
</div>
)
}

View File

@@ -9,7 +9,7 @@ export default function ThankYou() {
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2 className="font-bold">Thank you for your donation!</h2>
<p>
If you have any questions, please reach out to{' '}
If you have any questions or need a donation receipt, please reach out to{' '}
<CustomLink href="mailto:info@magicgrants.org">info@magicgrants.org</CustomLink>
</p>

View File

@@ -1,295 +0,0 @@
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import Link from 'next/link'
import { Button } from '../../components/ui/button'
import { trpc } from '../../utils/trpc'
import { useFundSlug } from '../../utils/use-fund-slug'
import { useToast } from '../../components/ui/use-toast'
import { fundSlugToRecipientEmail } from '../../utils/funds'
export default function Apply() {
const fundSlug = useFundSlug()
const router = useRouter()
const { toast } = useToast()
const applyMutation = trpc.application.submitApplication.useMutation()
const {
register,
handleSubmit,
formState: { errors },
} = useForm()
async function onSubmit(data: Record<string, string>) {
if (!fundSlug) return
await applyMutation.mutateAsync({ fundSlug, formData: data })
toast({ title: 'Success', description: 'Application successfully submitted!' })
router.push(`/${fundSlug}/`)
}
if (!fundSlug) return <></>
return (
<div className="mx-auto flex-1 flex flex-col items-center justify-center gap-4 py-8 prose">
<form onSubmit={handleSubmit(onSubmit)} className="max-w-5xl flex flex-col gap-4 p-4">
<div>
<h1>Application for Monero Fund Project Listing or General Fund Grant</h1>
<p>Thanks for your interest in the Monero Fund!</p>
<p>
We&#39;re incredibly grateful to contributors like you working to support Monero,
Bitcoin and other free and open source projects.
</p>
<p>
The MAGIC Monero Fund is offering a grant program and fundraising platform to support
Monero research and development, especially relating to privacy, security, user
experience, and efficiency. Proposals can be related to the Monero protocol directly, or
they can be related to other areas of the Monero ecosystem. For research projects,
please refer to special instructions
<Link href="/apply_research"> here</Link>.
</p>
<h2>Proposal Evaluation Criteria</h2>
<div>
Submitted proposals will be evaluated by the committee based on the following criteria:
<ul>
<li>
<b>Impact:</b> The proposal should have a clear impact on the Monero Project.
</li>
<li>
<b>Originality:</b> The proposal should be original and not a rehash of existing
work.
</li>
<li>
<b>Feasibility:</b> The proposal should be feasible to complete within the proposed
time frame.
</li>
<li>
<b>Quality:</b> The proposal should be well-written and well-organized.
</li>
<li>
<b>Relevance:</b> The proposal should be relevant to the Monero Project.
</li>
</ul>
</div>
<h2 id="Eligibility">Eligibility</h2>
<p>
All qualified researchers are eligible to apply, regardless of educational attainment or
occupation. However, as a nonprofit organization registered under U.S. tax laws, MAGIC
Grants is required to comply with certain laws when disbursing funds to grant
recipients. Grant recipients must complete a Due Diligence checklist, which are the last
two pages of{' '}
<a href="https://magicgrants.org/funds/MAGIC%20Fund%20Grant%20Disbursement%20Process%20and%20Requirements.pdf">
this document
</a>
. This includes the collection of your ID and tax information. We will conduct sanctions
checks.
</p>
<h2>How to Submit a Proposal</h2>
<p>
To submit a proposal, please complete the form below or create an issue on{' '}
<a href="https://github.com/MAGICGrants/Monero-Fund/issues/new?assignees=&labels=&template=grant-application.md&title=[Grant+Title]">
Github
</a>
. Applicants are free to use their legal name or a pseudonym at this step, although note
the{' '}
<a href="#Eligibility">
<b>Eligibility</b>
</a>{' '}
section. You can choose to apply for a direct grant from the MAGIC Monero Fund&#39;s
General Fund and/or request that your project be listed on MoneroFund.org to raise funds
from Monero community members.
</p>
<p>
Please note this form is not considered confidential and is effectively equivalent to a
public GitHub issue. In order to reach out privately, please send an email to
MoneroFund@magicgrants.org.
</p>
</div>
<label className="checkbox">
<input type="checkbox" {...register('general_fund')} />
Apply to receive a grant from the Magic Monero Fund.
</label>
<label className="checkbox">
<input type="checkbox" {...register('explore_page')} />
Apply for project to be listed on the Monero Fund Donation Page.
</label>
<div className="w-full flex flex-col">
<label htmlFor="project_name">Project Name *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="project_name"
type="text"
{...register('project_name', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="your_name">Your Name or Pseudonym *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="your_name"
type="text"
{...register('your_name', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="email">Email *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="email"
type="text"
{...register('email', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="github">Project GitHub (if applicable)</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="github"
type="text"
{...register('github')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="personal_github">Personal GitHub (if applicable)</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="personal_github"
type="text"
{...register('personal_github')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="other_contact">Other Contact Details (if applicable)</label>
<small>
Please list any other relevant contact details you are comfortable sharing in case we
need to reach out with questions. These could include github username, twitter username,
LinkedIn, Reddit handle, other social media handles, emails, phone numbers, usernames,
etc.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="other_contact"
{...register('other_contact')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="short_description">Short Project Description *</label>
<small>
This will be listed on the explore projects page of the Monero Fund website. 2-3
sentences.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="short_description"
{...register('short_description', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="long_description">Long Project Description</label>
<small>
This will be listed on your personal project page of the Monero Fund website. It can be
longer and go into detail about your project.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="long_description"
{...register('long_description')}
/>
</div>
<label className="checkbox">
<input type="checkbox" {...register('free_open_source')} />
Is the project free and open source?
</label>
<label className="checkbox">
<input type="checkbox" {...register('are_you_lead')} />
Are you the Project Lead / Lead Contributor
</label>
<div className="w-full flex flex-col">
<label htmlFor="other_lead">
If someone else, please list the project&#39;s Lead Contributor or Maintainer
</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="other_lead"
type="text"
{...register('other_lead')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="potential_impact">Potential Impact *</label>
<small>Why is this project important to the Monero community?</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="potential_impact"
{...register('potential_impact', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="timelines">Project Timelines and Potential Milestones *</label>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="timelines"
{...register('timelines', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="proposed_budget">
If you&#39;re applying for a grant from the general fund, please submit a proposed
budget for the requested amount and how it will be used.
</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="proposed_budget"
type="text"
{...register('proposed_budget')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="bios">Applicant Bios (Optional)</label>
<small>List relevant accomplishments.</small>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="bios"
type="text"
{...register('bios')}
/>
</div>
<small>
The MAGIC Monero Fund may require each recipient to sign a Grant Agreement before any
funds are disbursed. This agreement will set milestones and funds will only be released
upon completion of milestones. In order to comply with US regulations, recipients will
need to identify themselves to MAGIC, in accordance with US law.
</small>
<Button disabled={applyMutation.isPending}>Apply</Button>
<p>
After submitting your application, please allow our team up to three weeks to review your
application. Email us at{' '}
<a href={`mailto:${fundSlugToRecipientEmail[fundSlug]}`}>
{fundSlugToRecipientEmail[fundSlug]}
</a>{' '}
if you have any questions.
</p>
</form>
</div>
)
}

View File

@@ -2,11 +2,8 @@ import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import { useSession } from 'next-auth/react'
import { useTheme } from 'next-themes'
import { getProjects } from '../../utils/md'
import { ProjectItem } from '../../utils/types'
import Typing from '../../components/Typing'
import CustomLink from '../../components/CustomLink'
import { Button } from '../../components/ui/button'
import { Dialog, DialogContent, DialogTrigger } from '../../components/ui/dialog'
@@ -51,11 +48,12 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<div className="divide-y divide-gray-200">
<div className="pt-4 md:pb-8">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
Support MAGIC Grants
</h1>
<p className="max-w-3xl text-xl leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
MAGIC Grants is a public charity that provides undergraduate scholarships for students
interested in cryptocurrencies and privacy, supports public cryptocurrency
infrastructure, and supports privacy.
</p>
<div className="flex flex-col md:flex-row my-4 gap-2">
@@ -64,7 +62,7 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
Donate to MAGIC Grants
</Button>
{!userHasMembershipQuery.data && (
@@ -90,18 +88,8 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
)}
</div>
<div className="flex flex-row flex-wrap">
<p className="text-md leading-7 text-gray-500">
Want to receive funding for your work?
<CustomLink href={`/${fund.slug}/apply`}>
{' '}
Apply for a Monero development or research grant!
</CustomLink>
</p>
</div>
<p className="text-sm leading-7 text-gray-400">
We are a 501(c)(3) public charity. All donations are tax deductible.
We are a 501(c)(3) public charity. Your donation may qualify for a tax deduction.
</p>
</div>
</div>
@@ -125,13 +113,21 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={fund} />
<DonationFormModal
project={fund}
close={() => setDonateModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={fund} />
<MembershipFormModal
project={fund}
close={() => setMemberModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>

View File

@@ -1,17 +0,0 @@
import Link from 'next/link'
export default function Submitted() {
return (
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2>Thank you for your application!</h2>
<p>
If you have any questions or need a donation receipt, please reach out to{' '}
<a href="mailto:info@magicgrants.org">info@magicgrants.org</a>
</p>
.
<p>
<Link href="/">Return Home</Link>
</p>
</div>
)
}

View File

@@ -9,7 +9,7 @@ export default function ThankYou() {
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2 className="font-bold">Thank you for your donation!</h2>
<p>
If you have any questions, please reach out to{' '}
If you have any questions or need a donation receipt, please reach out to{' '}
<CustomLink href="mailto:info@magicgrants.org">info@magicgrants.org</CustomLink>
</p>

View File

@@ -113,11 +113,13 @@ function Home({ projects }: { projects: ProjectItem[] }) {
</div>
</div>
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Campaigns
</h1>
<div className="w-full space-y-4">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Campaigns
</h1>
<ProjectList projects={projects} />
<ProjectList projects={projects} />
</div>
</div>
)
}

View File

@@ -45,8 +45,7 @@ export default function Apply() {
Monero research and development, especially relating to privacy, security, user
experience, and efficiency. Proposals can be related to the Monero protocol directly, or
they can be related to other areas of the Monero ecosystem. For research projects,
please refer to special instructions
<Link href="/apply_research"> here</Link>.
please refer to special instructions <Link href="/monero/apply_research">here</Link>.
</p>
<h2>Proposal Evaluation Criteria</h2>
<div>
@@ -88,7 +87,7 @@ export default function Apply() {
<p>
To submit a proposal, please complete the form below or create an issue on{' '}
<a href="https://github.com/MAGICGrants/Monero-Fund/issues/new?assignees=&labels=&template=grant-application.md&title=[Grant+Title]">
Github
GitHub
</a>
. Applicants are free to use their legal name or a pseudonym at this step, although note
the{' '}
@@ -108,12 +107,12 @@ export default function Apply() {
<label className="checkbox">
<input type="checkbox" {...register('general_fund')} />
Apply to receive a grant from the Magic Monero Fund.
Apply to receive a grant from the MAGIC Monero Fund.
</label>
<label className="checkbox">
<input type="checkbox" {...register('explore_page')} />
Apply for project to be listed on the Monero Fund Donation Page.
Apply for project to be listed on the Monero Fund donation page.
</label>
<div className="w-full flex flex-col">
@@ -170,7 +169,7 @@ export default function Apply() {
<label htmlFor="other_contact">Other Contact Details (if applicable)</label>
<small>
Please list any other relevant contact details you are comfortable sharing in case we
need to reach out with questions. These could include github username, twitter username,
need to reach out with questions. These could include GitHub username, Twitter username,
LinkedIn, Reddit handle, other social media handles, emails, phone numbers, usernames,
etc.
</small>
@@ -276,7 +275,7 @@ export default function Apply() {
The MAGIC Monero Fund may require each recipient to sign a Grant Agreement before any
funds are disbursed. This agreement will set milestones and funds will only be released
upon completion of milestones. In order to comply with US regulations, recipients will
need to identify themselves to MAGIC, in accordance with US law.
need to identify themselves to MAGIC Grants, in accordance with US law.
</small>
<Button disabled={applyMutation.isPending}>Apply</Button>

View File

@@ -2,14 +2,11 @@ import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import { useSession } from 'next-auth/react'
import { useTheme } from 'next-themes'
import { getProjects } from '../../utils/md'
import { ProjectItem } from '../../utils/types'
import Typing from '../../components/Typing'
import CustomLink from '../../components/CustomLink'
import { Button } from '../../components/ui/button'
import { Dialog, DialogContent, DialogTrigger } from '../../components/ui/dialog'
import { Dialog, DialogContent } from '../../components/ui/dialog'
import DonationFormModal from '../../components/DonationFormModal'
import MembershipFormModal from '../../components/MembershipFormModal'
import ProjectList from '../../components/ProjectList'
@@ -54,7 +51,7 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<div className="divide-y divide-gray-200">
<div className="pt-4 md:pb-8">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
Support Monero
</h1>
<p className="max-w-3xl text-lg leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
@@ -67,7 +64,7 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
Donate to Monero Fund
</Button>
{!userHasMembershipQuery.data && (
@@ -104,7 +101,7 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
</div>
<p className="text-sm leading-7 text-gray-400">
We are a 501(c)(3) public charity. All donations are tax deductible.
We are a 501(c)(3) public charity. Your donation may qualify for a tax deduction.
</p>
</div>
</div>
@@ -145,13 +142,21 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={fund} />
<DonationFormModal
project={fund}
close={() => setDonateModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={fund} />
<MembershipFormModal
project={fund}
close={() => setMemberModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>

View File

@@ -9,7 +9,7 @@ export default function ThankYou() {
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2 className="font-bold">Thank you for your donation!</h2>
<p>
If you have any questions, please reach out to{' '}
If you have any questions or need a donation receipt, please reach out to{' '}
<CustomLink href="mailto:info@magicgrants.org">info@magicgrants.org</CustomLink>
</p>

View File

@@ -4,7 +4,7 @@ import markdownToHtml from '../utils/markdownToHtml'
import { getSingleFile } from '../utils/md'
import BigDumbMarkdown from '../components/BigDumbMarkdown'
export default function Terms({ content }: { content: string }) {
export default function Privacy({ content }: { content: string }) {
return <BigDumbMarkdown content={content} />
}

View File

@@ -1,295 +0,0 @@
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import Link from 'next/link'
import { Button } from '../../components/ui/button'
import { trpc } from '../../utils/trpc'
import { useFundSlug } from '../../utils/use-fund-slug'
import { useToast } from '../../components/ui/use-toast'
import { fundSlugToRecipientEmail } from '../../utils/funds'
export default function Apply() {
const fundSlug = useFundSlug()
const router = useRouter()
const { toast } = useToast()
const applyMutation = trpc.application.submitApplication.useMutation()
const {
register,
handleSubmit,
formState: { errors },
} = useForm()
async function onSubmit(data: Record<string, string>) {
if (!fundSlug) return
await applyMutation.mutateAsync({ fundSlug, formData: data })
toast({ title: 'Success', description: 'Application successfully submitted!' })
router.push(`/${fundSlug}/`)
}
if (!fundSlug) return <></>
return (
<div className="mx-auto flex-1 flex flex-col items-center justify-center gap-4 py-8 prose">
<form onSubmit={handleSubmit(onSubmit)} className="max-w-5xl flex flex-col gap-4 p-4">
<div>
<h1>Application for Monero Fund Project Listing or General Fund Grant</h1>
<p>Thanks for your interest in the Monero Fund!</p>
<p>
We&#39;re incredibly grateful to contributors like you working to support Monero,
Bitcoin and other free and open source projects.
</p>
<p>
The MAGIC Monero Fund is offering a grant program and fundraising platform to support
Monero research and development, especially relating to privacy, security, user
experience, and efficiency. Proposals can be related to the Monero protocol directly, or
they can be related to other areas of the Monero ecosystem. For research projects,
please refer to special instructions
<Link href="/apply_research"> here</Link>.
</p>
<h2>Proposal Evaluation Criteria</h2>
<div>
Submitted proposals will be evaluated by the committee based on the following criteria:
<ul>
<li>
<b>Impact:</b> The proposal should have a clear impact on the Monero Project.
</li>
<li>
<b>Originality:</b> The proposal should be original and not a rehash of existing
work.
</li>
<li>
<b>Feasibility:</b> The proposal should be feasible to complete within the proposed
time frame.
</li>
<li>
<b>Quality:</b> The proposal should be well-written and well-organized.
</li>
<li>
<b>Relevance:</b> The proposal should be relevant to the Monero Project.
</li>
</ul>
</div>
<h2 id="Eligibility">Eligibility</h2>
<p>
All qualified researchers are eligible to apply, regardless of educational attainment or
occupation. However, as a nonprofit organization registered under U.S. tax laws, MAGIC
Grants is required to comply with certain laws when disbursing funds to grant
recipients. Grant recipients must complete a Due Diligence checklist, which are the last
two pages of{' '}
<a href="https://magicgrants.org/funds/MAGIC%20Fund%20Grant%20Disbursement%20Process%20and%20Requirements.pdf">
this document
</a>
. This includes the collection of your ID and tax information. We will conduct sanctions
checks.
</p>
<h2>How to Submit a Proposal</h2>
<p>
To submit a proposal, please complete the form below or create an issue on{' '}
<a href="https://github.com/MAGICGrants/Monero-Fund/issues/new?assignees=&labels=&template=grant-application.md&title=[Grant+Title]">
Github
</a>
. Applicants are free to use their legal name or a pseudonym at this step, although note
the{' '}
<a href="#Eligibility">
<b>Eligibility</b>
</a>{' '}
section. You can choose to apply for a direct grant from the MAGIC Monero Fund&#39;s
General Fund and/or request that your project be listed on MoneroFund.org to raise funds
from Monero community members.
</p>
<p>
Please note this form is not considered confidential and is effectively equivalent to a
public GitHub issue. In order to reach out privately, please send an email to
MoneroFund@magicgrants.org.
</p>
</div>
<label className="checkbox">
<input type="checkbox" {...register('general_fund')} />
Apply to receive a grant from the Magic Monero Fund.
</label>
<label className="checkbox">
<input type="checkbox" {...register('explore_page')} />
Apply for project to be listed on the Monero Fund Donation Page.
</label>
<div className="w-full flex flex-col">
<label htmlFor="project_name">Project Name *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="project_name"
type="text"
{...register('project_name', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="your_name">Your Name or Pseudonym *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="your_name"
type="text"
{...register('your_name', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="email">Email *</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="email"
type="text"
{...register('email', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="github">Project GitHub (if applicable)</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="github"
type="text"
{...register('github')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="personal_github">Personal GitHub (if applicable)</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="personal_github"
type="text"
{...register('personal_github')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="other_contact">Other Contact Details (if applicable)</label>
<small>
Please list any other relevant contact details you are comfortable sharing in case we
need to reach out with questions. These could include github username, twitter username,
LinkedIn, Reddit handle, other social media handles, emails, phone numbers, usernames,
etc.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="other_contact"
{...register('other_contact')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="short_description">Short Project Description *</label>
<small>
This will be listed on the explore projects page of the Monero Fund website. 2-3
sentences.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="short_description"
{...register('short_description', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="long_description">Long Project Description</label>
<small>
This will be listed on your personal project page of the Monero Fund website. It can be
longer and go into detail about your project.
</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="long_description"
{...register('long_description')}
/>
</div>
<label className="checkbox">
<input type="checkbox" {...register('free_open_source')} />
Is the project free and open source?
</label>
<label className="checkbox">
<input type="checkbox" {...register('are_you_lead')} />
Are you the Project Lead / Lead Contributor
</label>
<div className="w-full flex flex-col">
<label htmlFor="other_lead">
If someone else, please list the project&#39;s Lead Contributor or Maintainer
</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="other_lead"
type="text"
{...register('other_lead')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="potential_impact">Potential Impact *</label>
<small>Why is this project important to the Monero community?</small>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="potential_impact"
{...register('potential_impact', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="timelines">Project Timelines and Potential Milestones *</label>
<textarea
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="timelines"
{...register('timelines', { required: true })}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="proposed_budget">
If you&#39;re applying for a grant from the general fund, please submit a proposed
budget for the requested amount and how it will be used.
</label>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="proposed_budget"
type="text"
{...register('proposed_budget')}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="bios">Applicant Bios (Optional)</label>
<small>List relevant accomplishments.</small>
<input
className="appearance-none block w-full text-gray-700 border rounded py-2 px-3 mb-3 leading-tight focus:outline-none focus:ring-0"
id="bios"
type="text"
{...register('bios')}
/>
</div>
<small>
The MAGIC Monero Fund may require each recipient to sign a Grant Agreement before any
funds are disbursed. This agreement will set milestones and funds will only be released
upon completion of milestones. In order to comply with US regulations, recipients will
need to identify themselves to MAGIC, in accordance with US law.
</small>
<Button disabled={applyMutation.isPending}>Apply</Button>
<p>
After submitting your application, please allow our team up to three weeks to review your
application. Email us at{' '}
<a href={`mailto:${fundSlugToRecipientEmail[fundSlug]}`}>
{fundSlugToRecipientEmail[fundSlug]}
</a>{' '}
if you have any questions.
</p>
</form>
</div>
)
}

View File

@@ -2,14 +2,11 @@ import { useEffect, useState } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import { useSession } from 'next-auth/react'
import { useTheme } from 'next-themes'
import { getProjects } from '../../utils/md'
import { ProjectItem } from '../../utils/types'
import Typing from '../../components/Typing'
import CustomLink from '../../components/CustomLink'
import { Button } from '../../components/ui/button'
import { Dialog, DialogContent, DialogTrigger } from '../../components/ui/dialog'
import { Dialog, DialogContent } from '../../components/ui/dialog'
import DonationFormModal from '../../components/DonationFormModal'
import MembershipFormModal from '../../components/MembershipFormModal'
import ProjectList from '../../components/ProjectList'
@@ -51,11 +48,12 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<div className="divide-y divide-gray-200">
<div className="pt-4 md:pb-8">
<h1 className="py-4 text-3xl font-extrabold leading-9 tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
Support <Typing />
Support Privacy Guides
</h1>
<p className="max-w-3xl text-xl leading-7 text-gray-500">
Help us to provide sustainable funding for free and open-source contributors working on
freedom tech and projects that help Monero flourish.
Privacy Guides is a not-for-profit, volunteer-run project that hosts online communities
and publishes news and recommendations surrounding privacy and security tools, services,
and knowledge.
</p>
<div className="flex flex-col md:flex-row my-4 gap-2">
@@ -64,7 +62,7 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
onClick={() => setDonateModalOpen(true)}
size="lg"
>
Donate to Monero Comittee General Fund
Donate to Privacy Guides
</Button>
{!userHasMembershipQuery.data && (
@@ -90,18 +88,8 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
)}
</div>
<div className="flex flex-row flex-wrap">
<p className="text-md leading-7 text-gray-500">
Want to receive funding for your work?
<CustomLink href={`/${fund.slug}/apply`}>
{' '}
Apply for a Monero development or research grant!
</CustomLink>
</p>
</div>
<p className="text-sm leading-7 text-gray-400">
We are a 501(c)(3) public charity. All donations are tax deductible.
We are a 501(c)(3) public charity. Your donation may qualify for a tax deduction.
</p>
</div>
</div>
@@ -125,13 +113,21 @@ const Home: NextPage<{ projects: any }> = ({ projects }) => {
<Dialog open={donateModalOpen} onOpenChange={setDonateModalOpen}>
<DialogContent>
<DonationFormModal project={fund} />
<DonationFormModal
project={fund}
close={() => setDonateModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>
<Dialog open={memberModalOpen} onOpenChange={setMemberModalOpen}>
<DialogContent>
<MembershipFormModal project={fund} />
<MembershipFormModal
project={fund}
close={() => setMemberModalOpen(false)}
openRegisterModal={() => setRegisterIsOpen(true)}
/>
</DialogContent>
</Dialog>

View File

@@ -1,17 +0,0 @@
import Link from 'next/link'
export default function Submitted() {
return (
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2>Thank you for your application!</h2>
<p>
If you have any questions or need a donation receipt, please reach out to{' '}
<a href="mailto:info@magicgrants.org">info@magicgrants.org</a>
</p>
.
<p>
<Link href="/">Return Home</Link>
</p>
</div>
)
}

View File

@@ -9,7 +9,7 @@ export default function ThankYou() {
<div className="flex-1 flex flex-col items-center justify-center gap-4 py-8">
<h2 className="font-bold">Thank you for your donation!</h2>
<p>
If you have any questions, please reach out to{' '}
If you have any questions or need a donation receipt, please reach out to{' '}
<CustomLink href="mailto:info@magicgrants.org">info@magicgrants.org</CustomLink>
</p>

View File

@@ -0,0 +1,58 @@
-- CreateEnum
CREATE TYPE "FundSlug" AS ENUM ('monero', 'firo', 'privacyguides', 'general');
-- CreateTable
CREATE TABLE "Donation" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT,
"btcPayInvoiceId" TEXT,
"stripePaymentIntentId" TEXT,
"stripeInvoiceId" TEXT,
"stripeSubscriptionId" TEXT,
"projectSlug" TEXT NOT NULL,
"projectName" TEXT NOT NULL,
"fundSlug" "FundSlug" NOT NULL,
"cryptoCode" TEXT,
"grossCryptoAmount" DOUBLE PRECISION,
"netCryptoAmount" DOUBLE PRECISION,
"grossFiatAmount" DOUBLE PRECISION NOT NULL,
"netFiatAmount" DOUBLE PRECISION NOT NULL,
"pointsAdded" INTEGER NOT NULL DEFAULT 0,
"membershipExpiresAt" TIMESTAMP(3),
CONSTRAINT "Donation_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectAddresses" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"projectSlug" TEXT NOT NULL,
"fundSlug" "FundSlug" NOT NULL,
"btcPayInvoiceId" TEXT NOT NULL,
"bitcoinAddress" TEXT NOT NULL,
"moneroAddress" TEXT NOT NULL,
CONSTRAINT "ProjectAddresses_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Donation_stripeInvoiceId_key" ON "Donation"("stripeInvoiceId");
-- CreateIndex
CREATE INDEX "Donation_btcPayInvoiceId_idx" ON "Donation"("btcPayInvoiceId");
-- CreateIndex
CREATE INDEX "Donation_stripePaymentIntentId_idx" ON "Donation"("stripePaymentIntentId");
-- CreateIndex
CREATE INDEX "Donation_stripeSubscriptionId_idx" ON "Donation"("stripeSubscriptionId");
-- CreateIndex
CREATE INDEX "Donation_userId_idx" ON "Donation"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "ProjectAddresses_projectSlug_fundSlug_key" ON "ProjectAddresses"("projectSlug", "fundSlug");

View File

@@ -59,6 +59,8 @@ model ProjectAddresses {
btcPayInvoiceId String
bitcoinAddress String
moneroAddress String
@@unique([projectSlug, fundSlug])
}
model PointHistory {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

View File

@@ -561,7 +561,9 @@
"publicClient": true,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {},
"attributes": {
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
@@ -597,6 +599,7 @@
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1724090232",
"backchannel.logout.session.required": "true",
"post.logout.redirect.uris": "+",
"oauth2.device.authorization.grant.enabled": "false",
"backchannel.logout.revoke.offline.tokens": "false"
},
@@ -612,8 +615,9 @@
"consentRequired": false,
"config": {
"user.session.note": "client_id",
"id.token.claim": "true",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "client_id",
"jsonType.label": "String"
@@ -627,8 +631,9 @@
"consentRequired": false,
"config": {
"user.session.note": "clientHost",
"id.token.claim": "true",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientHost",
"jsonType.label": "String"
@@ -642,8 +647,9 @@
"consentRequired": false,
"config": {
"user.session.note": "clientAddress",
"id.token.claim": "true",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientAddress",
"jsonType.label": "String"
@@ -673,7 +679,9 @@
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {},
"attributes": {
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
@@ -700,7 +708,9 @@
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {},
"attributes": {
"post.logout.redirect.uris": "+"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": false,
"nodeReRegistrationTimeout": 0,
@@ -777,8 +787,9 @@
"consentRequired": false,
"config": {
"user.session.note": "AUTH_TIME",
"id.token.claim": "true",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "auth_time",
"jsonType.label": "long"
@@ -1392,14 +1403,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"oidc-address-mapper",
"saml-user-property-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper"
"saml-user-attribute-mapper"
]
}
},
@@ -1440,14 +1451,14 @@
"subComponents": {},
"config": {
"allowed-protocol-mapper-types": [
"saml-user-property-mapper",
"oidc-address-mapper",
"saml-role-list-mapper",
"oidc-usermodel-property-mapper",
"oidc-sha256-pairwise-sub-mapper",
"oidc-full-name-mapper",
"saml-user-attribute-mapper",
"oidc-usermodel-attribute-mapper"
"oidc-usermodel-property-mapper",
"oidc-address-mapper",
"oidc-full-name-mapper",
"saml-role-list-mapper",
"oidc-usermodel-attribute-mapper",
"saml-user-property-mapper"
]
}
},
@@ -1469,7 +1480,7 @@
"subComponents": {},
"config": {
"kc.user.profile.config": [
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"name\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"passwordResetTokenVersion\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripeMoneroCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripeFiroCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripePgCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripeGeneralCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"name\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"emailVerifyTokenVersion\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"passwordResetTokenVersion\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripeMoneroCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripeFiroCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripePgCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"stripeGeneralCustomerId\",\"displayName\":\"\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\"],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
]
}
}
@@ -2179,7 +2190,7 @@
"organizationsEnabled": "false",
"acr.loa.map": "{}"
},
"keycloakVersion": "25.0.4",
"keycloakVersion": "25.0.6",
"userManagedAccessAllowed": false,
"organizationsEnabled": false,
"clientProfiles": {

View File

@@ -3,12 +3,14 @@ import { applicationRouter } from './application'
import { authRouter } from './auth'
import { donationRouter } from './donation'
import { perkRouter } from './perk'
import { accountRouter } from './account'
export const appRouter = router({
auth: authRouter,
donation: donationRouter,
application: applicationRouter,
perk: perkRouter,
account: accountRouter,
})
export type AppRouter = typeof appRouter

113
server/routers/account.ts Normal file
View File

@@ -0,0 +1,113 @@
import { z } from 'zod'
import { jwtDecode } from 'jwt-decode'
import { TRPCError } from '@trpc/server'
import axios from 'axios'
import jwt from 'jsonwebtoken'
import { protectedProcedure, router } from '../trpc'
import { env } from '../../env.mjs'
import { KeycloakJwtPayload, UserSettingsJwtPayload } from '../types'
import { keycloak, transporter } from '../services'
import { authenticateKeycloakClient } from '../utils/keycloak'
import { fundSlugs } from '../../utils/funds'
export const accountRouter = router({
changePassword: protectedProcedure
.input(z.object({ currentPassword: z.string().min(1), newPassword: z.string().min(1) }))
.mutation(async ({ input, ctx }) => {
const userId = ctx.session.user.sub
const email = ctx.session.user.email
let accessToken = ''
try {
const { data: token } = await axios.post(
`${env.KEYCLOAK_URL}/realms/${env.KEYCLOAK_REALM_NAME}/protocol/openid-connect/token`,
new URLSearchParams({
grant_type: 'password',
client_id: env.KEYCLOAK_CLIENT_ID,
client_secret: env.KEYCLOAK_CLIENT_SECRET,
username: ctx.session.user.email,
password: input.currentPassword,
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
)
accessToken = token.access_token
} catch (error) {
const errorMessage = (error as any).response.data.error
if (errorMessage === 'invalid_grant') {
throw new TRPCError({ code: 'BAD_REQUEST', message: 'INVALID_PASSWORD' })
}
throw error
}
const keycloakJwtPayload: KeycloakJwtPayload = jwtDecode(accessToken)
if (keycloakJwtPayload.sub !== userId || keycloakJwtPayload.email !== email) {
throw new TRPCError({ code: 'FORBIDDEN' })
}
await authenticateKeycloakClient()
await keycloak.users.update(
{ id: userId },
{
email,
credentials: [{ type: 'password', value: input.newPassword, temporary: false }],
}
)
await keycloak.users.logout({ id: userId })
}),
requestEmailChange: protectedProcedure
.input(z.object({ fundSlug: z.enum(fundSlugs), newEmail: z.string().email() }))
.mutation(async ({ input, ctx }) => {
const userId = ctx.session.user.sub
const email = ctx.session.user.email
await authenticateKeycloakClient()
const usersAlreadyUsingEmail = await keycloak.users.find({ email: input.newEmail })
if (usersAlreadyUsingEmail.length) {
throw new TRPCError({ code: 'BAD_REQUEST', message: 'EMAIL_TAKEN' })
}
const user = await keycloak.users.findOne({ id: userId })
if (!user || !user.id || !user.email)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'USER_NOT_FOUND',
})
let emailVerifyTokenVersion = parseInt(user.attributes?.emailVerifyTokenVersion?.[0]) || null
if (!emailVerifyTokenVersion) {
await keycloak.users.update(
{ id: userId },
{ email: user.email, attributes: { emailVerifyTokenVersion: 1 } }
)
emailVerifyTokenVersion = 1
}
const payload: UserSettingsJwtPayload = {
action: 'email_verify',
userId: user.id,
email: input.newEmail,
tokenVersion: emailVerifyTokenVersion,
}
const token = jwt.sign(payload, env.USER_SETTINGS_JWT_SECRET, { expiresIn: '30m' })
// no await here as we don't want to block the response
transporter.sendMail({
from: env.SES_VERIFIED_SENDER,
to: input.newEmail,
subject: 'Verify your email',
html: `<a href="${env.APP_URL}/${input.fundSlug}/verify-email/${token}" target="_blank">Verify email</a>`,
})
}),
})

View File

@@ -2,24 +2,12 @@ import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import jwt from 'jsonwebtoken'
import { publicProcedure, router } from '../trpc'
import { protectedProcedure, publicProcedure, router } from '../trpc'
import { authenticateKeycloakClient } from '../utils/keycloak'
import { keycloak, transporter } from '../services'
import { env } from '../../env.mjs'
import { fundSlugs } from '../../utils/funds'
type EmailVerifyJwtPayload = {
action: 'email_verify'
userId: string
email: string
}
type PasswordResetJwtPayload = {
action: 'password-reset'
userId: string
email: string
tokenVersion: number
}
import { UserSettingsJwtPayload } from '../types'
export const authRouter = router({
register: publicProcedure
@@ -42,7 +30,11 @@ export const authRouter = router({
email: input.email,
credentials: [{ type: 'password', value: input.password, temporary: false }],
requiredActions: ['VERIFY_EMAIL'],
attributes: { name: input.name, passwordResetTokenVersion: 1 },
attributes: {
name: input.name,
passwordResetTokenVersion: 1,
emailVerifyTokenVersion: 1,
},
enabled: true,
})
} catch (error) {
@@ -53,15 +45,14 @@ export const authRouter = router({
throw error
}
const payload: EmailVerifyJwtPayload = {
const payload: UserSettingsJwtPayload = {
action: 'email_verify',
tokenVersion: 1,
userId: user.id,
email: input.email,
}
const emailVerifyToken = jwt.sign(payload, env.NEXTAUTH_SECRET, {
expiresIn: '1d',
})
const emailVerifyToken = jwt.sign(payload, env.USER_SETTINGS_JWT_SECRET, { expiresIn: '1d' })
// no await here as we don't want to block the response
transporter.sendMail({
@@ -75,10 +66,10 @@ export const authRouter = router({
verifyEmail: publicProcedure
.input(z.object({ token: z.string() }))
.mutation(async ({ input }) => {
let decoded: EmailVerifyJwtPayload
let decoded: UserSettingsJwtPayload
try {
decoded = jwt.verify(input.token, env.NEXTAUTH_SECRET) as EmailVerifyJwtPayload
decoded = jwt.verify(input.token, env.USER_SETTINGS_JWT_SECRET) as UserSettingsJwtPayload
} catch (error) {
throw new TRPCError({
code: 'FORBIDDEN',
@@ -95,11 +86,38 @@ export const authRouter = router({
await authenticateKeycloakClient()
const user = await keycloak.users.findOne({ id: decoded.userId })
if (!user || !user.id)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'USER_NOT_FOUND',
})
const emailVerifyTokenVersion =
parseInt(user.attributes?.emailVerifyTokenVersion?.[0]) || null
if (emailVerifyTokenVersion !== decoded.tokenVersion) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'INVALID_TOKEN_VERSION',
})
}
await keycloak.users.update(
{ id: decoded.userId },
{ emailVerified: true, requiredActions: [] }
{
email: decoded.email,
emailVerified: true,
requiredActions: [],
attributes: {
emailVerifyTokenVersion: (emailVerifyTokenVersion + 1).toString(),
},
}
)
await keycloak.users.logout({ id: decoded.userId })
return { email: decoded.email }
}),
@@ -110,31 +128,32 @@ export const authRouter = router({
const users = await keycloak.users.find({ email: input.email })
const user = users[0]
if (!user || !user.id || !user.attributes)
if (!user || !user.id)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'USER_NOT_FOUND',
})
const passwordResetTokenVersion =
parseInt(user.attributes.passwordResetTokenVersion?.[0]) || null
let passwordResetTokenVersion =
parseInt(user.attributes?.passwordResetTokenVersion?.[0]) || null
if (!passwordResetTokenVersion) {
console.error(`User ${user.id} has no passwordResetTokenVersion attribute`)
await keycloak.users.update(
{ id: user.id },
{ email: input.email, attributes: { passwordResetTokenVersion: 1 } }
)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
})
passwordResetTokenVersion = 1
}
const payload: PasswordResetJwtPayload = {
const payload: UserSettingsJwtPayload = {
action: 'password-reset',
userId: user.id,
email: input.email,
tokenVersion: passwordResetTokenVersion,
}
const passwordResetToken = jwt.sign(payload, env.NEXTAUTH_SECRET, {
const passwordResetToken = jwt.sign(payload, env.USER_SETTINGS_JWT_SECRET, {
expiresIn: '30m',
})
@@ -150,10 +169,10 @@ export const authRouter = router({
resetPassword: publicProcedure
.input(z.object({ token: z.string(), password: z.string().min(8) }))
.mutation(async ({ input }) => {
let decoded: PasswordResetJwtPayload
let decoded: UserSettingsJwtPayload
try {
decoded = jwt.verify(input.token, env.NEXTAUTH_SECRET) as PasswordResetJwtPayload
decoded = jwt.verify(input.token, env.USER_SETTINGS_JWT_SECRET) as UserSettingsJwtPayload
} catch (error) {
throw new TRPCError({
code: 'FORBIDDEN',
@@ -171,21 +190,22 @@ export const authRouter = router({
await authenticateKeycloakClient()
const user = await keycloak.users.findOne({ id: decoded.userId })
if (!user || !user.attributes)
if (!user || !user.id || !user.email)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'USER_NOT_FOUND',
})
const passwordResetTokenVersion =
parseInt(user.attributes.passwordResetTokenVersion?.[0]) || null
let passwordResetTokenVersion =
parseInt(user.attributes?.passwordResetTokenVersion?.[0]) || null
if (!passwordResetTokenVersion) {
console.error(`User ${user.id} has no passwordResetTokenVersion attribute`)
await keycloak.users.update(
{ id: user.id },
{ email: user.email, attributes: { passwordResetTokenVersion: 1 } }
)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
})
passwordResetTokenVersion = 1
}
if (decoded.tokenVersion !== passwordResetTokenVersion)
@@ -205,6 +225,8 @@ export const authRouter = router({
}
)
await keycloak.users.logout({ id: decoded.userId })
return { email: decoded.email }
}),
})

View File

@@ -9,7 +9,7 @@ import { env } from '../../env.mjs'
import { btcpayApi, keycloak, prisma, stripe as _stripe } from '../services'
import { authenticateKeycloakClient } from '../utils/keycloak'
import { BtcPayCreateInvoiceRes, DonationMetadata } from '../types'
import { fundSlugs } from '../../utils/funds'
import { funds, fundSlugs } from '../../utils/funds'
import { fundSlugToCustomerIdAttr } from '../utils/funds'
export const donationRouter = router({
@@ -131,6 +131,7 @@ export const donationRouter = router({
projectSlug: input.projectSlug,
projectName: input.projectName,
fundSlug: input.fundSlug,
itemDesc: `MAGIC ${funds[input.fundSlug].title}`,
isMembership: 'false',
isSubscription: 'false',
isTaxDeductible: input.taxDeductible ? 'true' : 'false',
@@ -145,7 +146,9 @@ export const donationRouter = router({
checkout: { redirectURL: `${env.APP_URL}/${input.fundSlug}/thankyou` },
})
return { url: invoice.checkoutLink }
const url = invoice.checkoutLink.replace(/^(https?:\/\/)([^\/]+)/, env.BTCPAY_EXTERNAL_URL)
return { url }
}),
payMembershipWithFiat: protectedProcedure
@@ -182,6 +185,7 @@ export const donationRouter = router({
const user = await keycloak.users.findOne({ id: userId })
const email = user?.email!
const name = user?.attributes?.name?.[0]!
let stripeCustomerId =
user?.attributes?.[fundSlugToCustomerIdAttr[input.fundSlug]]?.[0] || null
@@ -190,10 +194,7 @@ export const donationRouter = router({
stripeCustomerId = customer.id
await keycloak.users.update(
{ id: userId },
{ email: email, attributes: { stripeCustomerId } }
)
await keycloak.users.update({ id: userId }, { email, attributes: { stripeCustomerId } })
}
const metadata: DonationMetadata = {
@@ -302,6 +303,7 @@ export const donationRouter = router({
donorEmail: email,
projectSlug: input.projectSlug,
projectName: input.projectName,
itemDesc: `MAGIC ${funds[input.fundSlug].title}`,
fundSlug: input.fundSlug,
isMembership: 'true',
isSubscription: 'false',

Some files were not shown because too many files have changed in this diff Show More