Merge pull request #186 from himanshu-ntml/main
Fix contact update response
This commit is contained in:
@@ -248,12 +248,17 @@ export class Contacts {
|
|||||||
|
|
||||||
const { id, email, subscribed, data } = ContactSchemas.manage.parse(req.body);
|
const { id, email, subscribed, data } = ContactSchemas.manage.parse(req.body);
|
||||||
|
|
||||||
const contact = id ? await ContactService.id(id) : await ContactService.email(project.id, email as string);
|
let contact = id ? await ContactService.id(id) : await ContactService.email(project.id, email as string);
|
||||||
|
|
||||||
if (!contact || contact.projectId !== project.id) {
|
if (!contact || contact.projectId !== project.id) {
|
||||||
throw new NotFound("contact");
|
throw new NotFound("contact");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateData: Record<string, unknown> = {
|
||||||
|
email,
|
||||||
|
subscribed: subscribed ?? contact.subscribed,
|
||||||
|
};
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const givenUserData = Object.entries(data);
|
const givenUserData = Object.entries(data);
|
||||||
const dataToUpdate = JSON.parse(contact.data ?? "{}");
|
const dataToUpdate = JSON.parse(contact.data ?? "{}");
|
||||||
@@ -266,18 +271,12 @@ export class Contacts {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.contact.update({
|
updateData.data = JSON.stringify(dataToUpdate);
|
||||||
where: { id: contact.id },
|
|
||||||
data: { data: JSON.stringify(dataToUpdate) },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.contact.update({
|
contact = await prisma.contact.update({
|
||||||
where: { id: contact.id },
|
where: { id: contact.id },
|
||||||
data: {
|
data: updateData,
|
||||||
email,
|
|
||||||
subscribed: subscribed ?? contact.subscribed,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await redis.del(Keys.Project.contacts(project.id));
|
await redis.del(Keys.Project.contacts(project.id));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import React, { useState } from "react";
|
|||||||
import { type FieldError, useFieldArray, useForm } from "react-hook-form";
|
import { type FieldError, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Card, Empty, FullscreenLoader, Modal, Skeleton, Table, Toggle } from "../../components";
|
import { Card, Empty, FullscreenLoader, Modal, Skeleton, Table, Toggle, Dropdown } from "../../components";
|
||||||
import { Dashboard } from "../../layouts";
|
import { Dashboard } from "../../layouts";
|
||||||
import { searchContacts, useContacts } from "../../lib/hooks/contacts";
|
import { searchContacts, useContacts } from "../../lib/hooks/contacts";
|
||||||
import { useActiveProject } from "../../lib/hooks/projects";
|
import { useActiveProject } from "../../lib/hooks/projects";
|
||||||
@@ -31,8 +31,9 @@ interface ContactValues {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [query, setQuery] = useState<string>();
|
const [query, setQuery] = useState<string>();
|
||||||
|
const [statusFilter, setStatusFilter] = useState<string>("all");
|
||||||
|
|
||||||
const project = useActiveProject();
|
const project = useActiveProject();
|
||||||
const { data: user } = useUser();
|
const { data: user } = useUser();
|
||||||
@@ -125,47 +126,56 @@ export default function Index() {
|
|||||||
return <Skeleton type={"table"} />;
|
return <Skeleton type={"table"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search && query !== undefined) {
|
if (search && query !== undefined) {
|
||||||
if (search.contacts.length > 0) {
|
const filtered = search.contacts
|
||||||
return (
|
.filter((c) =>
|
||||||
<>
|
statusFilter === "all"
|
||||||
<Table
|
? true
|
||||||
values={search.contacts
|
: statusFilter === "subscribed"
|
||||||
.sort((a, b) => {
|
? c.subscribed
|
||||||
const aTrigger = a.triggers.length > 0 ? a.triggers.sort()[0].createdAt : a.createdAt;
|
: !c.subscribed,
|
||||||
|
);
|
||||||
|
|
||||||
const bTrigger = b.triggers.length > 0 ? b.triggers.sort()[0].createdAt : b.createdAt;
|
if (filtered.length > 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table
|
||||||
|
values={filtered
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aTrigger = a.triggers.length > 0 ? a.triggers.sort()[0].createdAt : a.createdAt;
|
||||||
|
|
||||||
return bTrigger > aTrigger ? 1 : -1;
|
const bTrigger = b.triggers.length > 0 ? b.triggers.sort()[0].createdAt : b.createdAt;
|
||||||
})
|
|
||||||
.map((u) => {
|
return bTrigger > aTrigger ? 1 : -1;
|
||||||
return {
|
})
|
||||||
Email: u.email,
|
.map((u) => {
|
||||||
"Last Activity": dayjs()
|
return {
|
||||||
.to(
|
Email: u.email,
|
||||||
[...u.triggers, ...u.emails].length > 0
|
"Last Activity": dayjs()
|
||||||
? [...u.triggers, ...u.emails].sort((a, b) => {
|
.to(
|
||||||
return a.createdAt > b.createdAt ? -1 : 1;
|
[...u.triggers, ...u.emails].length > 0
|
||||||
})[0].createdAt
|
? [...u.triggers, ...u.emails].sort((a, b) => {
|
||||||
: u.createdAt,
|
return a.createdAt > b.createdAt ? -1 : 1;
|
||||||
)
|
})[0].createdAt
|
||||||
.toString(),
|
: u.createdAt,
|
||||||
Subscribed: u.subscribed,
|
)
|
||||||
Edit: (
|
.toString(),
|
||||||
<Link href={`/contacts/${u.id}`} className={"transition hover:text-neutral-800"}>
|
Subscribed: u.subscribed,
|
||||||
<Edit2 size={18} />
|
Edit: (
|
||||||
</Link>
|
<Link href={`/contacts/${u.id}`} className={"transition hover:text-neutral-800"}>
|
||||||
),
|
<Edit2 size={18} />
|
||||||
};
|
</Link>
|
||||||
})}
|
),
|
||||||
/>
|
};
|
||||||
</>
|
})}
|
||||||
);
|
/>
|
||||||
}
|
</>
|
||||||
return (
|
);
|
||||||
<>
|
}
|
||||||
<Empty
|
return (
|
||||||
icon={
|
<>
|
||||||
|
<Empty
|
||||||
|
icon={
|
||||||
<svg width="24" height="24" fill="none" viewBox="0 0 24 24">
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -183,24 +193,32 @@ export default function Index() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contacts) {
|
if (contacts) {
|
||||||
if (contacts.contacts.length > 0) {
|
const filtered = contacts.contacts.filter((c) =>
|
||||||
return (
|
statusFilter === "all"
|
||||||
<>
|
? true
|
||||||
<Table
|
: statusFilter === "subscribed"
|
||||||
values={contacts.contacts
|
? c.subscribed
|
||||||
.sort((a, b) => {
|
: !c.subscribed,
|
||||||
const aTrigger = a.triggers.length > 0 ? a.triggers.sort()[0].createdAt : a.createdAt;
|
);
|
||||||
|
|
||||||
const bTrigger = b.triggers.length > 0 ? b.triggers.sort()[0].createdAt : b.createdAt;
|
if (filtered.length > 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table
|
||||||
|
values={filtered
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aTrigger = a.triggers.length > 0 ? a.triggers.sort()[0].createdAt : a.createdAt;
|
||||||
|
|
||||||
|
const bTrigger = b.triggers.length > 0 ? b.triggers.sort()[0].createdAt : b.createdAt;
|
||||||
|
|
||||||
return bTrigger > aTrigger ? 1 : -1;
|
return bTrigger > aTrigger ? 1 : -1;
|
||||||
})
|
})
|
||||||
.map((u) => {
|
.map((u) => {
|
||||||
return {
|
return {
|
||||||
Email: u.email,
|
Email: u.email,
|
||||||
"Last Activity": dayjs()
|
"Last Activity": dayjs()
|
||||||
.to(
|
.to(
|
||||||
u.triggers.length > 0
|
u.triggers.length > 0
|
||||||
? u.triggers.sort((a, b) => {
|
? u.triggers.sort((a, b) => {
|
||||||
return a.createdAt > b.createdAt ? -1 : 1;
|
return a.createdAt > b.createdAt ? -1 : 1;
|
||||||
@@ -215,16 +233,16 @@ export default function Index() {
|
|||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<nav className="flex items-center justify-between py-3" aria-label="Pagination">
|
<nav className="flex items-center justify-between py-3" aria-label="Pagination">
|
||||||
<div className="hidden sm:block">
|
<div className="hidden sm:block">
|
||||||
<p className="text-sm text-neutral-700">
|
<p className="text-sm text-neutral-700">
|
||||||
Showing <span className="font-medium">{(page - 1) * 20}</span> to{" "}
|
Showing <span className="font-medium">{(page - 1) * 20}</span> to{" "}
|
||||||
<span className="font-medium">{page * 20}</span> of <span className="font-medium">{contacts.count}</span>{" "}
|
<span className="font-medium">{page * 20}</span> of <span className="font-medium">{filtered.length}</span>{" "}
|
||||||
contacts
|
contacts
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 justify-between gap-1 sm:justify-end">
|
<div className="flex flex-1 justify-between gap-1 sm:justify-end">
|
||||||
{page > 1 && (
|
{page > 1 && (
|
||||||
<button
|
<button
|
||||||
@@ -441,18 +459,28 @@ export default function Index() {
|
|||||||
title={"Contacts"}
|
title={"Contacts"}
|
||||||
description={"View and manage your contacts"}
|
description={"View and manage your contacts"}
|
||||||
actions={
|
actions={
|
||||||
<div className={"grid w-full gap-3 md:w-fit md:grid-cols-2"}>
|
<div className={"grid w-full gap-3 md:w-fit md:grid-cols-3"}>
|
||||||
<input
|
<input
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
autoComplete={"off"}
|
autoComplete={"off"}
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={"Search email or metadata"}
|
placeholder={"Search email or metadata"}
|
||||||
className={
|
className={
|
||||||
"rounded border-neutral-300 transition ease-in-out focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
|
"rounded border-neutral-300 transition ease-in-out focus:border-neutral-800 focus:ring-neutral-800 sm:text-sm"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<motion.button
|
<Dropdown
|
||||||
|
onChange={(v) => setStatusFilter(v)}
|
||||||
|
values={[
|
||||||
|
{ name: "All", value: "all" },
|
||||||
|
{ name: "Subscribed", value: "subscribed" },
|
||||||
|
{ name: "Unsubscribed", value: "unsubscribed" },
|
||||||
|
]}
|
||||||
|
selectedValue={statusFilter}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.button
|
||||||
onClick={() => setContactModal(true)}
|
onClick={() => setContactModal(true)}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user