mirror of
https://github.com/MAGICGrants/campaign-site.git
synced 2026-01-08 20:08:05 -05:00
* Apply OpenSats UI enhancements * Add register modal and some UI fixes * Add login modal * Add reset password button * Use @t3-oss/env-nextjs for env variables * Email verification without keycloak UI * Password reset without keycloak UI * Add "My donations" page with one-time stripe donations list * Display crypto donations in "My donations" page * Donation form fixes and improvements * Display recurring annual fiat donations * Include keycloak realm export file and remove hardcoded client values * fix: correctly handle btcpay webhooks and fix donationList query * feat: add membership modal and implement membership payment using btcpay * feat: add procedure for membership purchases with stripe and use db as single source of truth for donations * feat: use webhooks to update stripe donation/membership status * feat: memberships list page and fixes * feat: db schema changes and webhook fixes * feat: re-add "donate" and add "get annual membership" buttons to project page * feat: open register modal when clicking membership button while logged out * feat: replace "Get Membership" button with "My Memberships" button when user already has a membership for that project * feat: multiple funds support * deps: bump axios * feat: add different color schemes for each fund and some fixes * feat: add home page * feat: add missing titles and responsiveness improvements * chore: add prod workflow file and compose file * chore(deploy workflow): set environment name * chore(Dockerfile): add necessary lines for prisma * chore: make it skip env validation on build * fix: prevent donation amounts from being fetched from db during build * chore(nginx.conf): remove copy-paste junk * chore(docker compose): correctly set APP_URL env * feat: replace Sendgrid with SES * deps: audit fix * chore(deploy.yml): remove unecessary env * fix: correctly manage client and server env * fix(trpc.submitApplication): get recipient emails from server side env * fix(Dockerfile): define NEXT_PUBLIC_ env on build * chore(trpc): make it log any errors * chore(trpc): improve displaying of errors * chore(trpc): improve displaying of errors * fix: buggy link buttons * feat: use single btcpay store * feat: show form 8283 info in donation form and handle tax deductible donations * feat: have only one privacy and terms page for the entire site * feat: support many social links * feat: add funding required endpoint (wip) * feat: get rates using btcpay api and small refactor * deps: audit fix * fix: correctly handle payment methods on InvoiceSettled event * fix: make index on Donation.btcPayInvoiceId * feat(funding-required): improve asset parameter response * feat(funding-required): add project_status param * feat(funding-required): add fund param * feat(funding-required): implement caching * fix(funding-required): minor fixes * feat(funding-required): add remaining_amount_<currency> fields and fixes * chore: include all services in docker-compose.dev.yml and update .env.example * feat: move terms and privacy links to footer * fix: address font not always loading bug * feat: use fund logos as header image * feat: donation confirmation email * fix: use correct stripe client for each fund on webhooks * feat: add account settings page with change password form * feat: add email change form to settings page * fix: address wrong btcpay invoice url redirect * chore: email change request debug * fix(api): better handle user attributes * feat: ui improvements * feat: add btcpay invoice item description * chore(nginx): api rate limit * feat: remove typing component from fund landing pages * feat: implement refresh token rotation using keycloak * refactor: have gross and net amounts for donations * feat: invalidate user sessions on password/email change * fix: make "Create an account" button work on donate/membership modals * refactor: project props * fix(utils.md): correctly load md project attributes * chore(prisma): make composite unique constraint for fundSlug and projectSlug on ProjectAddresses * chore: mark example project as not funded * fix(utils.md): serialization error * chore(funding-required): btcpay invoice payment methods debug * fix(funding-required): get bitcoin address from correct payment method * fix(funding-required): correctly handle project_status ANY filter * fix(btcpay webhook handler): correctly handle payment methods on InvoicePaymentSettled * chore(docker-compose.yml): expose nginx port 80 * fix(funding-required): correctly concat project url * feat: ui improvements for smaller screens * fix(btcpay webhook handler): correctly get payment method amount on InvoiceSettled * fix(btcpay webhook handler): respond with 200 immediately if there is no metadata * chore(funding-required): debugging * chore(funding-required): debugging * chore(funding-required): debugging * fix(Dockerfile): define BUILD_MODE as arg instead of env to make it blank at runtime * fix: correctly pass current and goal values to project card progress * fix(funding-required): set high monitoring time for static address invoice * fix: correctly handle refresh token expiration on the ui * feat: ui improvements * chore: update README * chore: update README * Initial site text * fix: colors * chore: mention funds accordingly in texts * chore: update realm-export.json * chore: rename docker compose files * Update emails * Form updates * Remove unused pages and page improvements * feat: allow editing navbar links for each fund * Cleanup and Firo projects * chore(deploy.yml): change deploy branch to master * fix(auth): use fetch instead of axios when fetching refresh token due to edge runtime compatibility * fix: keep empty project folders * Fix code scanning alert no. 20: DOM text reinterpreted as HTML Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * chore: sanitize md file paths * Text and link updates --------- Co-authored-by: Artur N <arturnunespe@gmail.com> Co-authored-by: Justin Ehrenhofer <12520755+SamsungGalaxyPlayer@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
185 lines
7.1 KiB
TypeScript
185 lines
7.1 KiB
TypeScript
import * as React from 'react'
|
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
|
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons'
|
|
|
|
import { cn } from '../../utils/cn'
|
|
|
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
|
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
|
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
|
|
|
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
|
|
|
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
|
|
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
|
|
const DropdownMenuSubTrigger = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
inset?: boolean
|
|
}
|
|
>(({ className, inset, children, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.SubTrigger
|
|
ref={ref}
|
|
className={cn(
|
|
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-primary/10 data-[state=open]:bg-primary',
|
|
inset && 'pl-8',
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
|
</DropdownMenuPrimitive.SubTrigger>
|
|
))
|
|
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
|
|
|
|
const DropdownMenuSubContent = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
>(({ className, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.SubContent
|
|
ref={ref}
|
|
className={cn(
|
|
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-white p-1 text-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
|
|
|
|
const DropdownMenuContent = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.Portal>
|
|
<DropdownMenuPrimitive.Content
|
|
ref={ref}
|
|
sideOffset={sideOffset}
|
|
className={cn(
|
|
'z-50 min-w-[8rem] overflow-hidden rounded-md shadow-sm bg-white p-1 text-foreground',
|
|
'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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
</DropdownMenuPrimitive.Portal>
|
|
))
|
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
|
|
const DropdownMenuItem = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
inset?: boolean
|
|
}
|
|
>(({ className, inset, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.Item
|
|
ref={ref}
|
|
className={cn(
|
|
'relative flex select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-primary/10 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer',
|
|
inset && 'pl-8',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
|
|
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
>(({ className, children, checked, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.CheckboxItem
|
|
ref={ref}
|
|
className={cn(
|
|
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-primary/10 focus:text-primary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
className
|
|
)}
|
|
checked={checked}
|
|
{...props}
|
|
>
|
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
<DropdownMenuPrimitive.ItemIndicator>
|
|
<CheckIcon className="h-4 w-4" />
|
|
</DropdownMenuPrimitive.ItemIndicator>
|
|
</span>
|
|
{children}
|
|
</DropdownMenuPrimitive.CheckboxItem>
|
|
))
|
|
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
|
|
|
|
const DropdownMenuRadioItem = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
>(({ className, children, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.RadioItem
|
|
ref={ref}
|
|
className={cn(
|
|
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-primary/10 focus:text-primary-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
className
|
|
)}
|
|
{...props}
|
|
>
|
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
<DropdownMenuPrimitive.ItemIndicator>
|
|
<DotFilledIcon className="h-4 w-4 fill-current" />
|
|
</DropdownMenuPrimitive.ItemIndicator>
|
|
</span>
|
|
{children}
|
|
</DropdownMenuPrimitive.RadioItem>
|
|
))
|
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
|
|
|
const DropdownMenuLabel = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
inset?: boolean
|
|
}
|
|
>(({ className, inset, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.Label
|
|
ref={ref}
|
|
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
|
|
const DropdownMenuSeparator = React.forwardRef<
|
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
>(({ className, ...props }, ref) => (
|
|
<DropdownMenuPrimitive.Separator
|
|
ref={ref}
|
|
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
|
{...props}
|
|
/>
|
|
))
|
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
|
|
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
|
|
}
|
|
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
|
|
|
|
export {
|
|
DropdownMenu,
|
|
DropdownMenuTrigger,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuCheckboxItem,
|
|
DropdownMenuRadioItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuShortcut,
|
|
DropdownMenuGroup,
|
|
DropdownMenuPortal,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
DropdownMenuRadioGroup,
|
|
}
|