Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bumpy-ducks-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/service-utils": patch
---

expose project.services.iaw.smsEnabledCountryISOs from api response
53 changes: 53 additions & 0 deletions apps/dashboard/src/@/api/sms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";

export type SMSCountryTiers = {
tier1: string[];
tier2: string[];
tier3: string[];
tier4: string[];
tier5: string[];
};

export async function getSMSCountryTiers() {
if (!THIRDWEB_API_SECRET) {
throw new Error("API_SERVER_SECRET is not set");
}
const res = await fetch(`${API_SERVER_URL}/v1/sms/list-country-tiers`, {
headers: {
"Content-Type": "application/json",
"x-service-api-key": THIRDWEB_API_SECRET,
},
next: {
revalidate: 15 * 60, //15 minutes
},
});

if (!res.ok) {
console.error(
"Failed to fetch sms country tiers",
res.status,
res.statusText,
);
res.body?.cancel();
return {
tier1: [],
tier2: [],
tier3: [],
tier4: [],
tier5: [],
};
}

try {
return (await res.json()).data as SMSCountryTiers;
} catch (e) {
console.error("Failed to parse sms country tiers", e);
return {
tier1: [],
tier2: [],
tier3: [],
tier4: [],
tier5: [],
};
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getProject } from "@/api/projects";
import { getSMSCountryTiers } from "@/api/sms";
import { getTeamBySlug } from "@/api/team";
import { redirect } from "next/navigation";
import { InAppWalletSettingsPage } from "../../../../../../../components/embedded-wallets/Configure";
Expand All @@ -9,9 +10,10 @@ export default async function Page(props: {
}) {
const { team_slug, project_slug } = await props.params;

const [team, project] = await Promise.all([
const [team, project, smsCountryTiers] = await Promise.all([
getTeamBySlug(team_slug),
getProject(team_slug, project_slug),
getSMSCountryTiers(),
]);

if (!team) {
Expand All @@ -29,6 +31,7 @@ export default async function Page(props: {
trackingCategory="in-app-wallet-project-settings"
teamSlug={team_slug}
validTeamPlan={getValidTeamPlan(team)}
smsCountryTiers={smsCountryTiers}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ function Variants(props: {
isUpdating={false}
trackingCategory="foo"
updateApiKey={() => {}}
smsCountryTiers={{
// scaffold some countries to play around with the UI
tier1: ["US", "CA"],
tier2: ["GB", "AU", "NZ"],
tier3: ["FR", "DE", "ES", "IT"],
tier4: ["JP", "KR", "MX", "RU"],
tier5: ["BR", "AR", "CO", "CL", "PE", "VE", "SA"],
}}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import type { Project } from "@/api/projects";
import type { SMSCountryTiers } from "@/api/sms";
import { DynamicHeight } from "@/components/ui/DynamicHeight";
import { Spinner } from "@/components/ui/Spinner/Spinner";
import { UnderlineLink } from "@/components/ui/UnderlineLink";
Expand Down Expand Up @@ -36,13 +37,15 @@ import { type UseFormReturn, useFieldArray, useForm } from "react-hook-form";
import { toast } from "sonner";
import { toArrFromList } from "utils/string";
import type { Team } from "../../../@/api/team";
import CountrySelector from "./sms-country-select/country-selector";

type InAppWalletSettingsPageProps = {
trackingCategory: string;
project: Project;
teamId: string;
teamSlug: string;
validTeamPlan: Team["billingPlan"];
smsCountryTiers: SMSCountryTiers;
};

const TRACKING_CATEGORY = "embedded-wallet";
Expand Down Expand Up @@ -108,6 +111,7 @@ export function InAppWalletSettingsPage(props: InAppWalletSettingsPageProps) {
canEditAdvancedFeatures={props.validTeamPlan !== "free"}
updateApiKey={handleUpdateProject}
isUpdating={updateProject.isPending}
smsCountryTiers={props.smsCountryTiers}
/>
);
}
Expand All @@ -120,6 +124,7 @@ const InAppWalletSettingsPageUI: React.FC<
trackingData: UpdateAPIKeyTrackingData,
) => void;
isUpdating: boolean;
smsCountryTiers: SMSCountryTiers;
}
> = (props) => {
const embeddedWalletService = props.project.services.find(
Expand Down Expand Up @@ -185,6 +190,11 @@ export const InAppWalletSettingsUI: React.FC<
}
: undefined),
redirectUrls: (config.redirectUrls || []).join("\n"),
smsEnabledCountryISOs: config.smsEnabledCountryISOs
? config.smsEnabledCountryISOs
: canEditAdvancedFeatures
? ["US", "CA"]
: [],
},
});

Expand Down Expand Up @@ -228,6 +238,7 @@ export const InAppWalletSettingsUI: React.FC<
applicationImageUrl: branding?.applicationImageUrl,
applicationName: branding?.applicationName || props.project.name,
redirectUrls: toArrFromList(redirectUrls || "", true),
smsEnabledCountryISOs: values.smsEnabledCountryISOs,
};
});

Expand Down Expand Up @@ -256,6 +267,8 @@ export const InAppWalletSettingsUI: React.FC<
canEditAdvancedFeatures={canEditAdvancedFeatures}
/>

<NativeAppsFieldset form={form} />

{/* Authentication */}
<Fieldset legend="Authentication">
<JSONWebTokenFields
Expand All @@ -269,9 +282,15 @@ export const InAppWalletSettingsUI: React.FC<
form={form}
canEditAdvancedFeatures={canEditAdvancedFeatures}
/>
</Fieldset>

<NativeAppsFieldset form={form} />
<div className="h-5" />

<SMSCountryFields
form={form}
canEditAdvancedFeatures={canEditAdvancedFeatures}
smsCountryTiers={props.smsCountryTiers}
/>
</Fieldset>

<div className="flex justify-end">
<Button type="submit" variant="primary" className="gap-2">
Expand Down Expand Up @@ -364,6 +383,61 @@ function BrandingFieldset(props: {
);
}

function SMSCountryFields(props: {
form: UseFormReturn<ApiKeyEmbeddedWalletsValidationSchema>;
canEditAdvancedFeatures: boolean;
smsCountryTiers: SMSCountryTiers;
}) {
return (
<div>
<SwitchContainer
switchId="sms-switch"
title="SMS"
description="Optionally allow users in selected countries to login via SMS OTP."
>
<GatedSwitch
id="sms-switch"
trackingLabel="sms"
checked={
!!props.form.watch("smsEnabledCountryISOs").length &&
props.canEditAdvancedFeatures
}
upgradeRequired={!props.canEditAdvancedFeatures}
onCheckedChange={(checked) =>
props.form.setValue(
"smsEnabledCountryISOs",
checked
? // by default, enable US and CA only
["US", "CA"]
: [],
)
}
/>
</SwitchContainer>

<AdvancedConfigurationContainer
className="grid grid-cols-1"
show={
props.canEditAdvancedFeatures &&
!!props.form.watch("smsEnabledCountryISOs").length
}
>
<FormField
control={props.form.control}
name="smsEnabledCountryISOs"
render={({ field }) => (
<CountrySelector
countryTiers={props.smsCountryTiers}
selected={field.value}
onChange={field.onChange}
/>
)}
/>
</AdvancedConfigurationContainer>
</div>
);
}

function JSONWebTokenFields(props: {
form: UseFormReturn<ApiKeyEmbeddedWalletsValidationSchema>;
canEditAdvancedFeatures: boolean;
Expand Down
Loading