Compare commits
3 Commits
fix/react-
...
fix/create
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f4f507b58 | ||
|
|
d839195ad2 | ||
|
|
9815b5f273 |
@@ -207,7 +207,7 @@ export function LabelForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
{!isEdit && (
|
{!isEdit && (
|
||||||
<Toggle
|
<Toggle
|
||||||
label={t`Create another`}
|
label={t`Create another`}
|
||||||
|
|||||||
@@ -114,42 +114,6 @@ export function NewWorkspaceForm() {
|
|||||||
weekStartDay: 1,
|
weekStartDay: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If in cloud and Pro toggle is enabled, create checkout session for pro
|
|
||||||
if (env("NEXT_PUBLIC_KAN_ENV") === "cloud" && isProToggleEnabled) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
"/api/stripe/create_checkout_session",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
slug: slug || undefined,
|
|
||||||
workspacePublicId: values.publicId,
|
|
||||||
cancelUrl: window.location.pathname + window.location.search,
|
|
||||||
successUrl: "/boards",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
const url = (data as { url: string }).url;
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
window.location.href = url;
|
|
||||||
return; // Don't close modal if redirecting to checkout
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error creating checkout session:", error);
|
|
||||||
showPopup({
|
|
||||||
header: t`Error upgrading to Pro`,
|
|
||||||
message: t`Workspace created successfully. You can upgrade later in settings.`,
|
|
||||||
icon: "warning",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -361,7 +325,7 @@ export function NewWorkspaceForm() {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"mt-6 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600",
|
"mt-6 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600",
|
||||||
!showProBenefits && "mt-12",
|
!showProBenefits && "mt-12",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ const workspaceSlugSchema = z
|
|||||||
interface CheckoutSessionRequest {
|
interface CheckoutSessionRequest {
|
||||||
successUrl: string;
|
successUrl: string;
|
||||||
cancelUrl: string;
|
cancelUrl: string;
|
||||||
|
plan: "team" | "pro";
|
||||||
billing?: string;
|
billing?: string;
|
||||||
workspacePublicId?: string;
|
workspacePublicId?: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
workspaceName?: string;
|
workspaceName?: string;
|
||||||
workspaceDescription?: string;
|
workspaceDescription?: string;
|
||||||
workspaceSlug?: string;
|
workspaceSlug?: string;
|
||||||
plan?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRateLimit(
|
export default withRateLimit(
|
||||||
@@ -38,7 +38,6 @@ export default withRateLimit(
|
|||||||
return res.status(405).json({ error: "Method not allowed" });
|
return res.status(405).json({ error: "Method not allowed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const { user, db } = await createNextApiContext(req);
|
const { user, db } = await createNextApiContext(req);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -49,6 +48,7 @@ export default withRateLimit(
|
|||||||
const {
|
const {
|
||||||
successUrl,
|
successUrl,
|
||||||
cancelUrl,
|
cancelUrl,
|
||||||
|
plan,
|
||||||
billing,
|
billing,
|
||||||
workspacePublicId,
|
workspacePublicId,
|
||||||
slug,
|
slug,
|
||||||
@@ -57,7 +57,7 @@ export default withRateLimit(
|
|||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
if (!successUrl || !cancelUrl) {
|
if (!successUrl || !cancelUrl || !plan) {
|
||||||
return res.status(400).json({ error: "Missing required fields" });
|
return res.status(400).json({ error: "Missing required fields" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export default withRateLimit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subscription = await subscriptionRepo.create(db, {
|
const subscription = await subscriptionRepo.create(db, {
|
||||||
plan: "pro",
|
plan: plan,
|
||||||
referenceId: workspacePublicId,
|
referenceId: workspacePublicId,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
stripeCustomerId: user.stripeCustomerId ?? "",
|
stripeCustomerId: user.stripeCustomerId ?? "",
|
||||||
@@ -115,9 +115,9 @@ export default withRateLimit(
|
|||||||
|
|
||||||
const isTeam = body.plan === "team";
|
const isTeam = body.plan === "team";
|
||||||
const annualPriceId = isTeam
|
const annualPriceId = isTeam
|
||||||
? (process.env.STRIPE_TEAM_PLAN_ANNUAL_PRICE_ID ??
|
? (process.env.STRIPE_TEAM_PLAN_YEARLY_PRICE_ID ??
|
||||||
process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID)
|
process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID)
|
||||||
: (process.env.STRIPE_PRO_PLAN_ANNUAL_PRICE_ID ??
|
: (process.env.STRIPE_PRO_PLAN_YEARLY_PRICE_ID ??
|
||||||
process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID);
|
process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID);
|
||||||
const monthlyPriceId = isTeam
|
const monthlyPriceId = isTeam
|
||||||
? process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID
|
? process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID
|
||||||
@@ -141,14 +141,11 @@ export default withRateLimit(
|
|||||||
...(workspaceName && { workspaceName }),
|
...(workspaceName && { workspaceName }),
|
||||||
...(workspaceDescription && { workspaceDescription }),
|
...(workspaceDescription && { workspaceDescription }),
|
||||||
...(subscriptionId && { subscriptionId: String(subscriptionId) }),
|
...(subscriptionId && { subscriptionId: String(subscriptionId) }),
|
||||||
plan: body.plan ?? "pro",
|
plan: plan,
|
||||||
isNewWorkspace: workspaceName ? "true" : "false",
|
isNewWorkspace: workspaceName ? "true" : "false",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({ url: session.url });
|
return res.status(200).json({ url: session.url });
|
||||||
} catch (error) {
|
|
||||||
return res.status(500).json({ error: "Error creating checkout session" });
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ export function NewCardForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-5 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<Toggle
|
<Toggle
|
||||||
label={t`Create another`}
|
label={t`Create another`}
|
||||||
isChecked={isCreateAnotherEnabled}
|
isChecked={isCreateAnotherEnabled}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export function NewListForm({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<Toggle
|
<Toggle
|
||||||
label={t`Create another`}
|
label={t`Create another`}
|
||||||
isChecked={isCreateAnotherEnabled}
|
isChecked={isCreateAnotherEnabled}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ const SelectSource = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -310,7 +310,7 @@ const ImportGithub: React.FC = () => {
|
|||||||
<form onSubmit={handleSubmitProjects(onSubmitProjects)}>
|
<form onSubmit={handleSubmitProjects(onSubmitProjects)}>
|
||||||
<div className="h-[105px] overflow-auto px-5">{renderContent()}</div>
|
<div className="h-[105px] overflow-auto px-5">{renderContent()}</div>
|
||||||
|
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<Toggle
|
<Toggle
|
||||||
label={t`Select all`}
|
label={t`Select all`}
|
||||||
isChecked={!!isSelectAllEnabled}
|
isChecked={!!isSelectAllEnabled}
|
||||||
@@ -459,7 +459,7 @@ const ImportTrello: React.FC = () => {
|
|||||||
<form onSubmit={handleSubmitBoards(onSubmitBoards)}>
|
<form onSubmit={handleSubmitBoards(onSubmitBoards)}>
|
||||||
<div className="h-[105px] overflow-auto px-5">{renderContent()}</div>
|
<div className="h-[105px] overflow-auto px-5">{renderContent()}</div>
|
||||||
|
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<Toggle
|
<Toggle
|
||||||
label={t`Select all`}
|
label={t`Select all`}
|
||||||
isChecked={!!isSelectAllEnabled}
|
isChecked={!!isSelectAllEnabled}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function NewBoardForm({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
showTemplates={showTemplates}
|
showTemplates={showTemplates}
|
||||||
customTemplates={formattedTemplates ?? []}
|
customTemplates={formattedTemplates ?? []}
|
||||||
/>
|
/>
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
{!isTemplate && (
|
{!isTemplate && (
|
||||||
<Toggle
|
<Toggle
|
||||||
label={t`Use template`}
|
label={t`Use template`}
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ export function InviteMemberForm({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
<div className="mt-12 flex items-center justify-end space-x-4 border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
<Toggle
|
<Toggle
|
||||||
label={
|
label={
|
||||||
isShareInviteLinkEnabled
|
isShareInviteLinkEnabled
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export function UpgradeToProConfirmation({
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
...(entityId && { slug: entityId }),
|
...(entityId && { slug: entityId }),
|
||||||
workspacePublicId: workspacePublicId,
|
workspacePublicId: workspacePublicId,
|
||||||
|
plan: "pro",
|
||||||
|
billing: "monthly",
|
||||||
cancelUrl: "/settings",
|
cancelUrl: "/settings",
|
||||||
successUrl: "/settings",
|
successUrl: "/settings",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ export function withApiLogging(
|
|||||||
const requestId = randomUUID();
|
const requestId = randomUUID();
|
||||||
const route = req.url?.split("?")[0] ?? "unknown";
|
const route = req.url?.split("?")[0] ?? "unknown";
|
||||||
const input = {
|
const input = {
|
||||||
...(req.query && Object.keys(req.query).length > 0 && { query: req.query }),
|
...(req.query &&
|
||||||
...(req.body && typeof req.body === "object" && Object.keys(req.body).length > 0 && { body: req.body }),
|
Object.keys(req.query).length > 0 && { query: req.query }),
|
||||||
|
...(req.body &&
|
||||||
|
typeof req.body === "object" &&
|
||||||
|
Object.keys(req.body).length > 0 && { body: req.body }),
|
||||||
};
|
};
|
||||||
|
|
||||||
let statusCode = 200;
|
let statusCode = 200;
|
||||||
@@ -41,7 +44,16 @@ export function withApiLogging(
|
|||||||
// unauthenticated or auth unavailable
|
// unauthenticated or auth unavailable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let handlerError: unknown;
|
||||||
|
try {
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
|
} catch (err) {
|
||||||
|
handlerError = err;
|
||||||
|
statusCode = 500;
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
const meta = {
|
const meta = {
|
||||||
@@ -53,6 +65,10 @@ export function withApiLogging(
|
|||||||
...(isCloud && email && { email }),
|
...(isCloud && email && { email }),
|
||||||
...(Object.keys(input).length > 0 && { input }),
|
...(Object.keys(input).length > 0 && { input }),
|
||||||
status: statusCode,
|
status: statusCode,
|
||||||
|
...(handlerError instanceof Error && {
|
||||||
|
error: handlerError.message,
|
||||||
|
stack: handlerError.stack,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (statusCode < 400) {
|
if (statusCode < 400) {
|
||||||
|
|||||||
Reference in New Issue
Block a user