Skip to content

Commit 18c73c4

Browse files
authored
Merge branch 'main' into ph/engineWallets
2 parents 4832eeb + 9adf6e6 commit 18c73c4

File tree

13 files changed

+225
-54
lines changed

13 files changed

+225
-54
lines changed

apps/dashboard/src/@3rdweb-sdk/react/hooks/useLoggedInUser.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ export function useLoggedInUser(): {
5656
retry: false,
5757
queryFn: async () => {
5858
const searchParams = new URLSearchParams();
59-
if (pathname) {
60-
searchParams.set("pathname", pathname);
61-
}
59+
6260
if (connectedAddress) {
6361
searchParams.set("address", connectedAddress);
6462
}
@@ -76,10 +74,18 @@ export function useLoggedInUser(): {
7674
// legit use-case for now
7775
// eslint-disable-next-line no-restricted-syntax
7876
useEffect(() => {
79-
if (query.data?.redirectTo) {
80-
router.replace(query.data.redirectTo);
77+
if (query.data?.isLoggedIn === false) {
78+
// not using useSearchParams hook here to avoid adding Suspense boundaries everywhere this hook is being used
79+
const currentHref = new URL(window.location.href);
80+
const currentPathname = currentHref.pathname;
81+
const currentSearchParams = currentHref.searchParams.toString();
82+
router.replace(
83+
buildLoginPath(
84+
`${currentPathname}${currentSearchParams ? `?${currentSearchParams}` : ""}`,
85+
),
86+
);
8187
}
82-
}, [query.data?.redirectTo, router]);
88+
}, [query.data?.isLoggedIn, router]);
8389

8490
// if we are "disconnected" we are not logged in
8591
if (connectionStatus === "disconnected") {
@@ -135,3 +141,7 @@ export function useLoggedInUser(): {
135141
: null,
136142
};
137143
}
144+
145+
function buildLoginPath(pathname: string | undefined): string {
146+
return `/login${pathname ? `?next=${encodeURIComponent(pathname)}` : ""}`;
147+
}

apps/dashboard/src/app/api/auth/ensure-login/route.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,13 @@ import { cookies } from "next/headers";
44
import { type NextRequest, NextResponse } from "next/server";
55
import { getAddress } from "thirdweb/utils";
66

7-
export type EnsureLoginPayload = {
8-
pathname: string;
9-
address?: string;
10-
};
11-
127
export type EnsureLoginResponse = {
138
isLoggedIn: boolean;
149
jwt?: string;
15-
redirectTo?: string;
1610
};
1711

1812
export const GET = async (req: NextRequest) => {
1913
const address = req.nextUrl.searchParams.get("address");
20-
const pathname = req.nextUrl.searchParams.get("pathname");
2114

2215
const cookieStore = cookies();
2316
// if we are "disconnected" we are not logged in, clear the cookie and redirect to login
@@ -34,7 +27,6 @@ export const GET = async (req: NextRequest) => {
3427
cookieStore.delete(COOKIE_ACTIVE_ACCOUNT);
3528
return NextResponse.json({
3629
isLoggedIn: false,
37-
redirectTo: buildLoginPath(pathname),
3830
});
3931
}
4032

@@ -49,7 +41,6 @@ export const GET = async (req: NextRequest) => {
4941
cookieStore.delete(COOKIE_ACTIVE_ACCOUNT);
5042
return NextResponse.json({
5143
isLoggedIn: false,
52-
redirectTo: buildLoginPath(pathname),
5344
});
5445
}
5546

@@ -65,7 +56,6 @@ export const GET = async (req: NextRequest) => {
6556
cookieStore.delete(authCookieName);
6657
return NextResponse.json({
6758
isLoggedIn: false,
68-
redirectTo: buildLoginPath(pathname),
6959
});
7060
}
7161

@@ -87,7 +77,3 @@ export const GET = async (req: NextRequest) => {
8777
// if everything is good simply return true
8878
return NextResponse.json({ isLoggedIn: true, jwt: token });
8979
};
90-
91-
function buildLoginPath(pathname?: string | null): string {
92-
return `/login${pathname ? `?next=${encodeURIComponent(pathname)}` : ""}`;
93-
}

apps/dashboard/src/components/settings/Account/Billing/ApplyCouponCard.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ function statusStub(status: number) {
5454
function Story() {
5555
return (
5656
<div className="container flex max-w-[1100px] flex-col gap-10 py-10">
57+
<BadgeContainer label="Prefill code - XYZ, Success - 200">
58+
<ApplyCouponCardUI
59+
submit={statusStub(200)}
60+
onCouponApplied={undefined}
61+
prefillPromoCode="XYZ"
62+
/>
63+
</BadgeContainer>
64+
5765
<BadgeContainer label="Success - 200">
5866
<ApplyCouponCardUI
5967
submit={statusStub(200)}

apps/dashboard/src/components/settings/Account/Billing/CouponCard.tsx

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { zodResolver } from "@hookform/resolvers/zod";
1616
import { useMutation, useQuery } from "@tanstack/react-query";
1717
import { format, fromUnixTime } from "date-fns";
1818
import { TagIcon } from "lucide-react";
19-
import { useState } from "react";
19+
import { useSearchParams } from "next/navigation";
20+
import { Suspense, useEffect, useRef, useState } from "react";
2021
import { useForm } from "react-hook-form";
2122
import { toast } from "sonner";
2223
import { z } from "zod";
@@ -37,9 +38,13 @@ function ApplyCouponCard(props: {
3738
teamId: string | undefined;
3839
onCouponApplied: (data: ActiveCouponResponse) => void;
3940
}) {
41+
const searchParams = useSearchParams();
42+
const couponCode = searchParams?.get("coupon");
4043
return (
4144
<ApplyCouponCardUI
4245
onCouponApplied={props.onCouponApplied}
46+
prefillPromoCode={couponCode || undefined}
47+
scrollIntoView={!!couponCode}
4348
submit={async (promoCode: string) => {
4449
const res = await fetch("/api/server-proxy/api/v1/coupons/redeem", {
4550
method: "POST",
@@ -79,14 +84,30 @@ export function ApplyCouponCardUI(props: {
7984
data: null | ActiveCouponResponse;
8085
}>;
8186
onCouponApplied: ((data: ActiveCouponResponse) => void) | undefined;
87+
prefillPromoCode?: string;
88+
scrollIntoView?: boolean;
8289
}) {
90+
const containerRef = useRef<HTMLFormElement | null>(null);
8391
const form = useForm<z.infer<typeof couponFormSchema>>({
8492
resolver: zodResolver(couponFormSchema),
8593
defaultValues: {
86-
promoCode: "",
94+
promoCode: props.prefillPromoCode,
8795
},
8896
});
8997

98+
const scrolled = useRef(false);
99+
// eslint-disable-next-line no-restricted-syntax
100+
useEffect(() => {
101+
if (props.scrollIntoView && !scrolled.current) {
102+
const el = containerRef.current;
103+
if (el) {
104+
el.scrollIntoView({ behavior: "smooth", block: "start" });
105+
el.querySelector("input")?.focus();
106+
scrolled.current = true;
107+
}
108+
}
109+
}, [props.scrollIntoView]);
110+
90111
const applyCoupon = useMutation({
91112
mutationFn: (promoCode: string) => props.submit(promoCode),
92113
});
@@ -133,7 +154,7 @@ export function ApplyCouponCardUI(props: {
133154

134155
return (
135156
<Form {...form}>
136-
<form onSubmit={form.handleSubmit(onSubmit)}>
157+
<form onSubmit={form.handleSubmit(onSubmit)} ref={containerRef}>
137158
<SettingsCard
138159
header={{
139160
title: "Apply Coupon",
@@ -272,11 +293,7 @@ export function CouponSection(props: { teamId: string | undefined }) {
272293
});
273294

274295
if (activeCoupon.isPending) {
275-
return (
276-
<div className="flex h-[300px] items-center justify-center rounded-lg border border-border bg-muted/50">
277-
<Spinner className="size-6" />
278-
</div>
279-
);
296+
return <LoadingCouponSection />;
280297
}
281298

282299
const couponData = optimisticCouponData
@@ -296,17 +313,27 @@ export function CouponSection(props: { teamId: string | undefined }) {
296313
}
297314

298315
return (
299-
<ApplyCouponCard
300-
teamId={props.teamId}
301-
onCouponApplied={(coupon) => {
302-
setOptimisticCouponData({
303-
type: "added",
304-
data: coupon,
305-
});
306-
activeCoupon.refetch().then(() => {
307-
setOptimisticCouponData(undefined);
308-
});
309-
}}
310-
/>
316+
<Suspense fallback={<LoadingCouponSection />}>
317+
<ApplyCouponCard
318+
teamId={props.teamId}
319+
onCouponApplied={(coupon) => {
320+
setOptimisticCouponData({
321+
type: "added",
322+
data: coupon,
323+
});
324+
activeCoupon.refetch().then(() => {
325+
setOptimisticCouponData(undefined);
326+
});
327+
}}
328+
/>
329+
</Suspense>
330+
);
331+
}
332+
333+
function LoadingCouponSection() {
334+
return (
335+
<div className="flex h-[300px] items-center justify-center rounded-lg border border-border bg-muted/50">
336+
<Spinner className="size-6" />
337+
</div>
311338
);
312339
}

apps/dashboard/src/middleware.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ export async function middleware(request: NextRequest) {
3232
// check if the user is logged in (has a valid auth cookie)
3333

3434
if (!authCookie) {
35+
const searchParamsString = request.nextUrl.searchParams.toString();
36+
3537
// if not logged in, rewrite to login page
3638
return redirect(
3739
request,
3840
"/login",
39-
`next=${encodeURIComponent(pathname)}`,
41+
`next=${encodeURIComponent(`${pathname}${searchParamsString ? `?${searchParamsString}` : ""}`)}`,
4042
false,
4143
);
4244
}

packages/thirdweb/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# thirdweb
22

3+
## 5.61.4
4+
5+
### Patch Changes
6+
7+
- [#4969](https://github.com/thirdweb-dev/js/pull/4969) [`3446b4c`](https://github.com/thirdweb-dev/js/commit/3446b4cabf0a8b877c344d810f97fd571753df2e) Thanks [@MananTank](https://github.com/MananTank)! - Remove extra text shown in Error Message in Pay UI
8+
9+
## 5.61.3
10+
11+
### Patch Changes
12+
13+
- [#4965](https://github.com/thirdweb-dev/js/pull/4965) [`24981a7`](https://github.com/thirdweb-dev/js/commit/24981a7f60c2a45976c748826339822d81154ce3) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Respect raw accountSalt passed as hex
14+
315
## 5.61.2
416

517
### Patch Changes

packages/thirdweb/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "thirdweb",
3-
"version": "5.61.2",
3+
"version": "5.61.4",
44
"repository": {
55
"type": "git",
66
"url": "git+https://github.com/thirdweb-dev/js.git#main"

packages/thirdweb/src/extensions/erc721/read/getNFT.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { describe, expect, it } from "vitest";
22

3+
import { ANVIL_CHAIN } from "~test/chains.js";
4+
import { TEST_CONTRACT_URI } from "~test/ipfs-uris.js";
5+
import { TEST_CLIENT } from "~test/test-clients.js";
36
import { DOODLES_CONTRACT } from "~test/test-contracts.js";
7+
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
8+
import { getContract } from "../../../contract/contract.js";
9+
import { deployERC721Contract } from "../../../extensions/prebuilts/deploy-erc721.js";
10+
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
11+
import { parseNFT } from "../../../utils/nft/parseNft.js";
12+
import { setTokenURI } from "../__generated__/INFTMetadata/write/setTokenURI.js";
13+
import { mintTo } from "../write/mintTo.js";
414
import { getNFT } from "./getNFT.js";
515

616
describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
@@ -89,4 +99,60 @@ describe.runIf(process.env.TW_SECRET_KEY)("erc721.getNFT", () => {
8999
}
90100
`);
91101
});
102+
103+
it("should return a default value if the URI of the token doesn't exist", async () => {
104+
/**
105+
* To create this test scenario, we first deploy an NFTCollection/Edition contract,
106+
* mint a token, then purposefully change that token's URI to an empty string, using setTokenURI
107+
*/
108+
const contract = getContract({
109+
address: await deployERC721Contract({
110+
chain: ANVIL_CHAIN,
111+
client: TEST_CLIENT,
112+
account: TEST_ACCOUNT_A,
113+
type: "TokenERC721",
114+
params: {
115+
name: "",
116+
contractURI: TEST_CONTRACT_URI,
117+
},
118+
}),
119+
chain: ANVIL_CHAIN,
120+
client: TEST_CLIENT,
121+
});
122+
123+
await sendAndConfirmTransaction({
124+
transaction: mintTo({
125+
contract,
126+
nft: { name: "token 0" },
127+
to: TEST_ACCOUNT_A.address,
128+
}),
129+
account: TEST_ACCOUNT_A,
130+
});
131+
132+
await sendAndConfirmTransaction({
133+
transaction: setTokenURI({
134+
contract,
135+
tokenId: 0n,
136+
// Need to have some spaces because NFTMetadata.sol does not allow to update an empty valud
137+
uri: " ",
138+
}),
139+
account: TEST_ACCOUNT_A,
140+
});
141+
142+
expect(await getNFT({ contract, tokenId: 0n })).toStrictEqual(
143+
parseNFT(
144+
{
145+
id: 0n,
146+
type: "ERC721",
147+
uri: "",
148+
},
149+
{
150+
tokenId: 0n,
151+
tokenUri: "",
152+
type: "ERC721",
153+
owner: null,
154+
},
155+
),
156+
);
157+
});
92158
});

packages/thirdweb/src/extensions/erc721/read/getNFT.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function getNFT(
4848
: null,
4949
]);
5050

51-
if (!uri) {
51+
if (!uri?.trim()) {
5252
return parseNFT(
5353
{
5454
id: options.tokenId,

0 commit comments

Comments
 (0)