mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
## Context Main fix was to address the size of the `OrganizationCard` on the redeem code page - was taking up the height of the flex div when the number of cards is <= 3 (Main fix is `max-h-min` in `OrganizationCard`) ### Before <img width="1270" height="411" alt="image" src="https://github.com/user-attachments/assets/73228b62-f9db-43c8-813b-cb04c9f6cb2b" /> ### After <img width="1255" height="420" alt="image" src="https://github.com/user-attachments/assets/a1d82753-daba-4c3d-a64d-3bb913ded161" /> ## Other changes - Small improvement to perceived loading speed by only rendering the cards as loaders, text content on the left should render immediately on page load - Loading state to consider if the feature flag has been loaded as well to prevent flicker of UI - Hide the "Go to org" button when redeemed on the org page already <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved loading/gating so the redemption UI waits for feature flags and profile data before showing content. * Disabled submit when the redemption form is invalid to prevent empty submissions; submit button respects form validity, permissions, and loading state. * **UI** * Conditionally shows upgrade and "Go to organization" options based on organization plan and current path. * Adjusted card layout to better constrain content height. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Kevin Grüneberg <k.grueneberg1994@gmail.com>
115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
import { FeatureFlagContext, useFlag } from 'common'
|
|
import Link from 'next/link'
|
|
import { useContext, useState } from 'react'
|
|
import { Button } from 'ui'
|
|
import { Admonition, ShimmeringLoader } from 'ui-patterns'
|
|
|
|
import { CreditCodeRedemption } from '@/components/interfaces/Organization/BillingSettings/CreditCodeRedemption'
|
|
import { OrganizationCard } from '@/components/interfaces/Organization/OrganizationCard'
|
|
import RedeemCreditsLayout from '@/components/layouts/RedeemCreditsLayout'
|
|
import {
|
|
ScaffoldContainer,
|
|
ScaffoldDivider,
|
|
ScaffoldHeader,
|
|
ScaffoldTitle,
|
|
} from '@/components/layouts/Scaffold'
|
|
import AlertError from '@/components/ui/AlertError'
|
|
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
|
import { useProfile } from '@/lib/profile'
|
|
import type { NextPageWithLayout } from '@/types'
|
|
|
|
const RedeemCreditsContent = () => {
|
|
const redeemCodeEnabled = useFlag('redeemCodeEnabled')
|
|
const { isLoading: isLoadingProfile } = useProfile()
|
|
const { hasLoaded } = useContext(FeatureFlagContext)
|
|
|
|
const [selectedOrg, setSelectedOrg] = useState<string | null>(null)
|
|
|
|
const {
|
|
data: organizations,
|
|
error: errorOrganizations,
|
|
isLoading: isLoadingOrganizations,
|
|
isError: isErrorOrganizations,
|
|
} = useOrganizationsQuery()
|
|
|
|
if (isErrorOrganizations) {
|
|
return (
|
|
<AlertError
|
|
className="mt-4"
|
|
error={errorOrganizations}
|
|
subject="Failed to retrieve organizations"
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="grid md:grid-cols-2 pt-10 gap-8">
|
|
<div className="flex flex-col gap-y-8">
|
|
<div className="flex flex-col gap-y-1">
|
|
<p>To redeem your credits, select one of your organizations.</p>
|
|
<p className="text-sm text-foreground-light text-balance">
|
|
The credits will be applied to that organization only and cannot be transferred or
|
|
shared between organizations.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p>Want to start fresh?</p>
|
|
<p className="mt-1 text-sm text-foreground-light text-balance">
|
|
Create a new organization first. You will have to revisit this link after creating the
|
|
organization to redeem the code.
|
|
</p>
|
|
<Button asChild className="mt-4 w-min" size="tiny" type="primary">
|
|
<Link href="/new">Create organization</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-y-2">
|
|
{/* [Joshen] Checking for profile as well as organizations query internally waits for profile to be loaded */}
|
|
{isLoadingProfile || isLoadingOrganizations || !hasLoaded ? (
|
|
<>
|
|
<ShimmeringLoader className="w-full h-[70px]" />
|
|
<ShimmeringLoader className="w-full h-[70px]" />
|
|
</>
|
|
) : redeemCodeEnabled ? (
|
|
organizations?.map((org) => (
|
|
<OrganizationCard
|
|
key={org.id}
|
|
isLink={false}
|
|
organization={org}
|
|
onClick={() => setSelectedOrg(org.slug)}
|
|
/>
|
|
))
|
|
) : (
|
|
<Admonition type="note" title="Code redemption coming soon" />
|
|
)}
|
|
</div>
|
|
|
|
{selectedOrg && (
|
|
<CreditCodeRedemption
|
|
modalVisible
|
|
slug={selectedOrg}
|
|
onClose={() => setSelectedOrg(null)}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const RedeemCreditsPage: NextPageWithLayout = () => {
|
|
return (
|
|
<ScaffoldContainer>
|
|
<ScaffoldHeader>
|
|
<ScaffoldTitle>Code Redemption</ScaffoldTitle>
|
|
</ScaffoldHeader>
|
|
<ScaffoldDivider />
|
|
<RedeemCreditsContent />
|
|
</ScaffoldContainer>
|
|
)
|
|
}
|
|
|
|
RedeemCreditsPage.getLayout = (page) => <RedeemCreditsLayout>{page}</RedeemCreditsLayout>
|
|
|
|
export default RedeemCreditsPage
|