feat: make billing better (#203)
This commit is contained in:
@@ -26,6 +26,8 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@unsend/ui/src/form";
|
||||
import { useUpgradeModalStore } from "~/store/upgradeModalStore";
|
||||
import { LimitReason } from "~/lib/constants/plans";
|
||||
|
||||
const contactBookSchema = z.object({
|
||||
name: z.string({ required_error: "Name is required" }).min(1, {
|
||||
@@ -38,6 +40,11 @@ export default function AddContactBook() {
|
||||
const createContactBookMutation =
|
||||
api.contacts.createContactBook.useMutation();
|
||||
|
||||
const limitsQuery = api.limits.get.useQuery({
|
||||
type: LimitReason.CONTACT_BOOK,
|
||||
});
|
||||
const { openModal } = useUpgradeModalStore((s) => s.action);
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const contactBookForm = useForm<z.infer<typeof contactBookSchema>>({
|
||||
@@ -48,6 +55,11 @@ export default function AddContactBook() {
|
||||
});
|
||||
|
||||
function handleSave(values: z.infer<typeof contactBookSchema>) {
|
||||
if (limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
createContactBookMutation.mutate(
|
||||
{
|
||||
name: values.name,
|
||||
@@ -59,14 +71,23 @@ export default function AddContactBook() {
|
||||
setOpen(false);
|
||||
toast.success("Contact book created successfully");
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onOpenChange(_open: boolean) {
|
||||
if (_open && limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(_open);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
||||
onOpenChange={(_open) => (_open !== open ? onOpenChange(_open) : null)}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
@@ -108,7 +129,9 @@ export default function AddContactBook() {
|
||||
<Button
|
||||
className=" w-[100px]"
|
||||
type="submit"
|
||||
disabled={createContactBookMutation.isPending}
|
||||
disabled={
|
||||
createContactBookMutation.isPending || limitsQuery.isLoading
|
||||
}
|
||||
>
|
||||
{createContactBookMutation.isPending
|
||||
? "Creating..."
|
||||
|
@@ -4,6 +4,7 @@ import { AppSidebar } from "~/components/AppSideBar";
|
||||
import { SidebarInset, SidebarTrigger } from "@unsend/ui/src/sidebar";
|
||||
import { SidebarProvider } from "@unsend/ui/src/sidebar";
|
||||
import { useIsMobile } from "@unsend/ui/src/hooks/use-mobile";
|
||||
import { UpgradeModal } from "~/components/payments/UpgradeModal";
|
||||
|
||||
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
const isMobile = useIsMobile();
|
||||
@@ -21,6 +22,7 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
</main>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
<UpgradeModal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -35,6 +35,8 @@ import {
|
||||
SelectValue,
|
||||
} from "@unsend/ui/src/select";
|
||||
import { toast } from "@unsend/ui/src/toaster";
|
||||
import { useUpgradeModalStore } from "~/store/upgradeModalStore";
|
||||
import { LimitReason } from "~/lib/constants/plans";
|
||||
|
||||
const domainSchema = z.object({
|
||||
region: z.string({ required_error: "Region is required" }).min(1, {
|
||||
@@ -49,6 +51,9 @@ export default function AddDomain() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const regionQuery = api.domain.getAvailableRegions.useQuery();
|
||||
const limitsQuery = api.limits.get.useQuery({ type: LimitReason.DOMAIN });
|
||||
|
||||
const { openModal } = useUpgradeModalStore((s) => s.action);
|
||||
|
||||
const addDomainMutation = api.domain.createDomain.useMutation();
|
||||
|
||||
@@ -73,6 +78,11 @@ export default function AddDomain() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
addDomainMutation.mutate(
|
||||
{
|
||||
name: values.domain,
|
||||
@@ -91,10 +101,19 @@ export default function AddDomain() {
|
||||
);
|
||||
}
|
||||
|
||||
function onOpenChange(_open: boolean) {
|
||||
if (_open && limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(_open);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(_open) => (_open !== open ? setOpen(_open) : null)}
|
||||
onOpenChange={(_open) => (_open !== open ? onOpenChange(_open) : null)}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
@@ -171,7 +190,9 @@ export default function AddDomain() {
|
||||
<Button
|
||||
className=" w-[100px]"
|
||||
type="submit"
|
||||
disabled={addDomainMutation.isPending}
|
||||
disabled={
|
||||
addDomainMutation.isPending || limitsQuery.isLoading
|
||||
}
|
||||
>
|
||||
{addDomainMutation.isPending ? "Adding..." : "Add"}
|
||||
</Button>
|
||||
|
@@ -34,6 +34,8 @@ import {
|
||||
} from "@unsend/ui/src/form";
|
||||
import { useTeam } from "~/providers/team-context";
|
||||
import { isCloud, isSelfHosted } from "~/utils/common";
|
||||
import { useUpgradeModalStore } from "~/store/upgradeModalStore";
|
||||
import { LimitReason } from "~/lib/constants/plans";
|
||||
|
||||
const inviteTeamMemberSchema = z.object({
|
||||
email: z
|
||||
@@ -50,6 +52,11 @@ export default function InviteTeamMember() {
|
||||
const { currentIsAdmin } = useTeam();
|
||||
const { data: domains } = api.domain.domains.useQuery();
|
||||
|
||||
const limitsQuery = api.limits.get.useQuery({
|
||||
type: LimitReason.TEAM_MEMBER,
|
||||
});
|
||||
const { openModal } = useUpgradeModalStore((s) => s.action);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
@@ -65,6 +72,11 @@ export default function InviteTeamMember() {
|
||||
const createInvite = api.team.createTeamInvite.useMutation();
|
||||
|
||||
function onSubmit(values: FormData) {
|
||||
if (limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
createInvite.mutate(
|
||||
{
|
||||
email: values.email,
|
||||
@@ -82,11 +94,16 @@ export default function InviteTeamMember() {
|
||||
console.error(error);
|
||||
toast.error(error.message || "Failed to send invitation");
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function onCopyLink() {
|
||||
if (limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
createInvite.mutate(
|
||||
{
|
||||
email: form.getValues("email"),
|
||||
@@ -97,7 +114,7 @@ export default function InviteTeamMember() {
|
||||
onSuccess: (invite) => {
|
||||
void utils.team.getTeamInvites.invalidate();
|
||||
navigator.clipboard.writeText(
|
||||
`${location.origin}/join-team?inviteId=${invite.id}`
|
||||
`${location.origin}/join-team?inviteId=${invite.id}`,
|
||||
);
|
||||
form.reset();
|
||||
setOpen(false);
|
||||
@@ -107,16 +124,28 @@ export default function InviteTeamMember() {
|
||||
console.error(error);
|
||||
toast.error(error.message || "Failed to copy invitation link");
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onOpenChange(_open: boolean) {
|
||||
if (_open && limitsQuery.data?.isLimitReached) {
|
||||
openModal(limitsQuery.data.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(_open);
|
||||
}
|
||||
|
||||
if (!currentIsAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(_open) => (_open !== open ? onOpenChange(_open) : null)}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">
|
||||
<PlusIcon className="mr-2 h-4 w-4" />
|
||||
@@ -201,7 +230,7 @@ export default function InviteTeamMember() {
|
||||
</Button>
|
||||
{isSelfHosted() ? (
|
||||
<Button
|
||||
disabled={createInvite.isPending}
|
||||
disabled={createInvite.isPending || limitsQuery.isLoading}
|
||||
isLoading={createInvite.isPending}
|
||||
className="w-[150px]"
|
||||
onClick={form.handleSubmit(onCopyLink)}
|
||||
@@ -212,7 +241,7 @@ export default function InviteTeamMember() {
|
||||
{isCloud() || domains?.length ? (
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={createInvite.isPending}
|
||||
disabled={createInvite.isPending || limitsQuery.isLoading}
|
||||
isLoading={createInvite.isPending}
|
||||
className="w-[150px]"
|
||||
>
|
||||
|
Reference in New Issue
Block a user