Skip to content

Commit 5b8ca1b

Browse files
joshenlimkevcodez
andauthored
Chore/fix redeem code UI (supabase#42615)
## 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>
1 parent ee484c8 commit 5b8ca1b

3 files changed

Lines changed: 40 additions & 31 deletions

File tree

apps/studio/components/interfaces/Organization/BillingSettings/CreditCodeRedemption.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { useLatest } from '@/hooks/misc/useLatest'
3838
const FORM_ID = 'credit-code-redemption'
3939

4040
const FormSchema = z.object({
41-
code: z.coerce.string(),
41+
code: z.string().min(1, 'Code is required'),
4242
})
4343

4444
type CreditCodeRedemptionForm = z.infer<typeof FormSchema>
@@ -78,6 +78,7 @@ export const CreditCodeRedemption = ({
7878
resolver: zodResolver(FormSchema),
7979
defaultValues: { code: '' },
8080
})
81+
const { isValid } = form.formState
8182

8283
const {
8384
mutate: redeemCode,
@@ -224,19 +225,24 @@ export const CreditCodeRedemption = ({
224225
</div>
225226
)}
226227

227-
<div className="mt-4 flex flex-col gap-y-4">
228-
<Separator />
229-
<div className="flex justify-center items-center gap-x-2">
230-
{org?.plan.id === 'free' && (
231-
<UpgradePlanButton plan="Pro" source="code-redeem" slug={org.slug}>
232-
Upgrade organization
233-
</UpgradePlanButton>
234-
)}
235-
<Button asChild type="default">
236-
<Link href={`/org/${org?.slug}`}>Go to organization</Link>
237-
</Button>
228+
{(!router.pathname.includes('/org/') || org?.plan.id === 'free') && (
229+
<div className="mt-4 flex flex-col gap-y-4">
230+
<Separator />
231+
<div className="flex justify-center items-center gap-x-2">
232+
{org?.plan.id === 'free' && (
233+
<UpgradePlanButton plan="Pro" source="code-redeem" slug={org.slug}>
234+
Upgrade organization
235+
</UpgradePlanButton>
236+
)}
237+
238+
{!router.pathname.includes('/org/') && (
239+
<Button asChild type="default">
240+
<Link href={`/org/${org?.slug}`}>Go to organization</Link>
241+
</Button>
242+
)}
243+
</div>
238244
</div>
239-
</div>
245+
)}
240246
</div>
241247
) : (
242248
<>
@@ -265,7 +271,12 @@ export const CreditCodeRedemption = ({
265271
control={form.control}
266272
name="code"
267273
render={({ field }) => (
268-
<FormItemLayout label="Code" className="gap-1" layout="horizontal">
274+
<FormItemLayout
275+
hideMessage
276+
label="Code"
277+
className="gap-1"
278+
layout="horizontal"
279+
>
269280
<Input_Shadcn_
270281
{...field}
271282
className="uppercase w-56 ml-auto"
@@ -312,7 +323,7 @@ export const CreditCodeRedemption = ({
312323
type="primary"
313324
className="pointer-events-auto"
314325
loading={redeemingCode}
315-
disabled={codeRedemptionDisabled}
326+
disabled={codeRedemptionDisabled || !isValid}
316327
htmlType="submit"
317328
tooltip={{
318329
content: {

apps/studio/components/interfaces/Organization/OrganizationCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const OrganizationCard = ({
2929
<ActionCard
3030
bgColor="bg border"
3131
className={cn(
32-
'flex items-center min-h-[70px] [&>div]:w-full [&>div]:items-center',
32+
'flex items-center min-h-[70px] [&>div]:w-full [&>div]:items-center max-h-min',
3333
className
3434
)}
3535
icon={<Boxes size={18} strokeWidth={1} className="text-foreground" />}

apps/studio/pages/redeem.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useFlag } from 'common'
1+
import { FeatureFlagContext, useFlag } from 'common'
22
import Link from 'next/link'
3-
import { useState } from 'react'
3+
import { useContext, useState } from 'react'
44
import { Button } from 'ui'
55
import { Admonition, ShimmeringLoader } from 'ui-patterns'
66

@@ -15,10 +15,14 @@ import {
1515
} from '@/components/layouts/Scaffold'
1616
import AlertError from '@/components/ui/AlertError'
1717
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
18+
import { useProfile } from '@/lib/profile'
1819
import type { NextPageWithLayout } from '@/types'
1920

2021
const RedeemCreditsContent = () => {
2122
const redeemCodeEnabled = useFlag('redeemCodeEnabled')
23+
const { isLoading: isLoadingProfile } = useProfile()
24+
const { hasLoaded } = useContext(FeatureFlagContext)
25+
2226
const [selectedOrg, setSelectedOrg] = useState<string | null>(null)
2327

2428
const {
@@ -28,18 +32,6 @@ const RedeemCreditsContent = () => {
2832
isError: isErrorOrganizations,
2933
} = useOrganizationsQuery()
3034

31-
if (isLoadingOrganizations) {
32-
return (
33-
<div className="grid md:grid-cols-2 pt-10 gap-8">
34-
<ShimmeringLoader className="w-full" />
35-
<div className="space-y-4">
36-
<ShimmeringLoader className="w-full h-14" />
37-
<ShimmeringLoader className="w-full h-14" />
38-
</div>
39-
</div>
40-
)
41-
}
42-
4335
if (isErrorOrganizations) {
4436
return (
4537
<AlertError
@@ -74,7 +66,13 @@ const RedeemCreditsContent = () => {
7466
</div>
7567

7668
<div className="flex flex-col gap-y-2">
77-
{redeemCodeEnabled ? (
69+
{/* [Joshen] Checking for profile as well as organizations query internally waits for profile to be loaded */}
70+
{isLoadingProfile || isLoadingOrganizations || !hasLoaded ? (
71+
<>
72+
<ShimmeringLoader className="w-full h-[70px]" />
73+
<ShimmeringLoader className="w-full h-[70px]" />
74+
</>
75+
) : redeemCodeEnabled ? (
7876
organizations?.map((org) => (
7977
<OrganizationCard
8078
key={org.id}

0 commit comments

Comments
 (0)