Thanks to visit codestin.com
Credit goes to github.com

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
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@ export function PageClient() {
domain,
logo,
programType,
rewardful,
type,
amount,
rewardful,
tolt,
partnerstack,
} = data;

if (!name || !url || !domain || !logo) {
Expand All @@ -61,7 +62,7 @@ export function PageClient() {
return false;
}

if (programType === "import" && !rewardful && !tolt) {
if (programType === "import" && !rewardful && !tolt && !partnerstack) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ export function Form() {
case "partnerstack":
return (
<ImportPartnerStackForm
watch={watch}
setValue={setValue}
onSuccess={() => onSubmit(getValues())}
isPending={isPendingAction}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,39 @@

import { setPartnerStackTokenAction } from "@/lib/actions/partners/set-partnerstack-token";
import useWorkspace from "@/lib/swr/use-workspace";
import { ProgramData } from "@/lib/types";
import { Button, Input } from "@dub/ui";
import { useAction } from "next-safe-action/hooks";
import Link from "next/link";
import { useState } from "react";
import { UseFormSetValue, UseFormWatch } from "react-hook-form";
import { toast } from "sonner";

export const ImportPartnerStackForm = ({
watch,
setValue,
onSuccess,
isPending,
}: {
watch: UseFormWatch<ProgramData>;
setValue: UseFormSetValue<ProgramData>;
onSuccess: () => void;
isPending: boolean;
}) => {
const { id: workspaceId } = useWorkspace();
const [publicKey, setPublicKey] = useState("");
const [secretKey, setSecretKey] = useState("");

const partnerStack = watch("partnerstack");

const { executeAsync, isPending: isSettingPartnerStackToken } = useAction(
setPartnerStackTokenAction,
{
onSuccess: () => {
onSuccess: ({ data }) => {
setValue("partnerstack", {
publicKey: data?.publicKey,
maskedSecretKey: data?.maskedSecretKey,
});
onSuccess();
toast.success("PartnerStack credentials saved successfully!");
},
Expand Down Expand Up @@ -55,7 +67,7 @@ export const ImportPartnerStackForm = ({
type="password"
placeholder="Public key"
className="mt-2 max-w-full"
value={publicKey}
value={publicKey || partnerStack?.publicKey || ""}
onChange={(e) => setPublicKey(e.target.value)}
/>
<div className="mt-2 text-xs font-normal leading-[1.1] text-neutral-600">
Expand All @@ -79,7 +91,7 @@ export const ImportPartnerStackForm = ({
type="password"
placeholder="Secret key"
className="mt-2 max-w-full"
value={secretKey}
value={secretKey || partnerStack?.maskedSecretKey || ""}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The secret key input field will display a masked value (like "sk_***") when credentials are already saved, making it confusing for users who expect to see their actual input or a blank field.

View Details
📝 Patch Details
diff --git a/apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-partnerstack-form.tsx b/apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-partnerstack-form.tsx
index b38199581..7f534644c 100644
--- a/apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-partnerstack-form.tsx
+++ b/apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/import-partnerstack-form.tsx
@@ -67,7 +67,7 @@ export const ImportPartnerStackForm = ({
           type="password"
           placeholder="Public key"
           className="mt-2 max-w-full"
-          value={publicKey || partnerStack?.publicKey || ""}
+          value={publicKey || ""}
           onChange={(e) => setPublicKey(e.target.value)}
         />
         <div className="mt-2 text-xs font-normal leading-[1.1] text-neutral-600">
@@ -91,7 +91,7 @@ export const ImportPartnerStackForm = ({
           type="password"
           placeholder="Secret key"
           className="mt-2 max-w-full"
-          value={secretKey || partnerStack?.maskedSecretKey || ""}
+          value={secretKey || ""}
           onChange={(e) => setSecretKey(e.target.value)}
         />
       </div>

Analysis

The secret key input field on line 94 uses value={secretKey || partnerStack?.maskedSecretKey || ""}. This means that when PartnerStack credentials have been previously saved, the input will display the masked secret key (e.g., "sk_***") instead of being empty or showing the user's current input.

This creates a poor user experience because:

  1. Users will see asterisks in the input field, which looks like they're seeing the actual secret but is actually just a masked representation
  2. If users try to edit the field, they'll be typing over the masked value rather than entering a fresh secret
  3. The masked value is not useful for editing - users need to enter their actual secret key

The input should either be empty when showing previously saved credentials, or should not show the masked value at all. A better pattern would be:

  • value={secretKey || ""} to only show the current user input
  • Or add conditional logic to show a different UI state when credentials are already saved

onChange={(e) => setSecretKey(e.target.value)}
/>
</div>
Expand Down
5 changes: 5 additions & 0 deletions apps/web/lib/actions/partners/set-partnerstack-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ export const setPartnerStackTokenAction = authActionClient
publicKey,
secretKey,
});

return {
publicKey,
maskedSecretKey: secretKey.slice(0, 3) + "*".repeat(secretKey.length - 3),
};
});
6 changes: 6 additions & 0 deletions apps/web/lib/zod/schemas/program-onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export const programRewardSchema = z
id: z.string(),
affiliates: z.number(),
}).nullish(),
partnerstack: z
.object({
publicKey: z.string().nullish(),
maskedSecretKey: z.string().nullish(),
})
.nullish(),
})
.merge(
z.object({
Expand Down
Loading