Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ yarn-error.log*
test.rest
test-webchat

# Cursor
.cursor/skills/
CLAUDE.md

.pnpm-store/
.plans/
8 changes: 7 additions & 1 deletion apps/builder/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@
"upgradeToPro": "Upgrade to Pro",
"uploadFile": "Upload File",
"view": "View",
"viewAnalytics": "View Analytics",
"duplicate": "Duplicate",
"getDraftLink": "Get Draft Link",
"getPublishedLink": "Get Published Link",
"analytics": "Analytics",
"deleteAnalytics": "Delete Analytics",
"flowVersions": "Flow Versions",
"revertToPublished": "Revert to Published",
"search": "Search",
Expand Down Expand Up @@ -198,7 +200,11 @@
"blockedConversationsHelp": "Conversations that your account blocked.",
"blockedContacts": "Blocked contacts",
"blockedContactsHelp": "Contacts don't want to receive messages from your account",
"newContactsByCountry": "New contacts by country"
"newContactsByCountry": "New contacts by country",
"date": "Date",
"total": "Total",
"sessionsThroughTheRef": "Sessions through the Ref \"{ref}\" per day",
"sessionsThroughTheMagicLink": "Sessions through the Magic Link \"{ref}\" per day"
},
"autoAssignConversation": {
"rule": {
Expand Down
8 changes: 7 additions & 1 deletion apps/builder/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@
"upgradeToPro": "Nâng cấp lên Pro",
"uploadFile": "Tải lên tệp",
"view": "Xem",
"viewAnalytics": "Xem phân tích",
"duplicate": "Nhân bản",
"getDraftLink": "Lấy liên kết bản nháp",
"getPublishedLink": "Lấy liên kết đã xuất bản",
"analytics": "Phân tích",
"deleteAnalytics": "Xóa phân tích",
"flowVersions": "Phiên bản luồng",
"revertToPublished": "Khôi phục về phiên bản đã xuất bản",
"search": "Tìm kiếm",
Expand Down Expand Up @@ -198,7 +200,11 @@
"blockedConversationsHelp": "Cuộc trò chuyện mà tài khoản của bạn đã chặn.",
"blockedContacts": "Liên hệ bị chặn",
"blockedContactsHelp": "Khách hàng không muốn nhận tin nhắn từ tài khoản của bạn",
"newContactsByCountry": "Liên hệ mới theo quốc gia"
"newContactsByCountry": "Liên hệ mới theo quốc gia",
"date": "Ngày",
"total": "Tổng",
"sessionsThroughTheRef": "Phiên qua Ref '{ref}' mỗi ngày",
"sessionsThroughTheMagicLink": "Phiên qua Magic Link '{ref}' mỗi ngày"
},
"autoAssignConversation": {
"rule": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client"

import { MagicLinkAnalytics } from "@chatbotx.io/analytics-nextjs/components/magic-link-analytics"

export function MagicLinkAnalyticsClient({
workspaceId,
linkId,
linkName,
}: {
workspaceId: string
linkId: string
linkName: string
}) {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
return (
<MagicLinkAnalytics
defaultSearchParams={{ workspaceId, linkId, timezone, linkName }}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { db } from "@chatbotx.io/database/client"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { notFound, redirect } from "next/navigation"
import { z } from "zod"
import { getCurrentUserAndTargetWorkspace } from "@/lib/auth/utils"
import { MagicLinkAnalyticsClient } from "./magic-link-analytics-client"

const paramsSchema = z.object({
workspaceId: zodBigintAsString(),
magicLinkId: zodBigintAsString(),
})

type Props = {
params: Promise<{ workspaceId: string; magicLinkId: string }>
}

export default async function MagicLinkAnalyticsPage({ params }: Props) {
const { data } = await paramsSchema.safeParse(await params)
if (!data) {
return notFound()
}

const result = await getCurrentUserAndTargetWorkspace(data.workspaceId)
if (!result) {
return redirect("/")
}

const magicLink = await db.query.magicLinkModel.findFirst({
where: {
id: data.magicLinkId,
workspaceId: data.workspaceId,
},
})
if (!magicLink) {
return notFound()
}

return (
<div className="container mx-auto flex flex-col gap-6 py-6">
<MagicLinkAnalyticsClient
linkId={data.magicLinkId}
linkName={magicLink.name}
workspaceId={data.workspaceId}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { db } from "@chatbotx.io/database/client"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { notFound, redirect } from "next/navigation"
import { z } from "zod"
import { getCurrentUserAndTargetWorkspace } from "@/lib/auth/utils"
import { ReflinkAnalyticsClient } from "./reflink-analytics-client"

const paramsSchema = z.object({
workspaceId: zodBigintAsString(),
reflinkId: zodBigintAsString(),
})

type Props = {
params: Promise<{ workspaceId: string; reflinkId: string }>
}

export default async function ReflinkAnalyticsPage({ params }: Props) {
const { data } = await paramsSchema.safeParse(await params)
if (!data) {
return notFound()
}

const result = await getCurrentUserAndTargetWorkspace(data.workspaceId)
if (!result) {
return redirect("/")
}

const reflink = await db.query.reflinkModel.findFirst({
where: {
id: data.reflinkId,
workspaceId: data.workspaceId,
},
})
if (!reflink) {
return notFound()
}

return (
<div className="container mx-auto flex flex-col gap-6 py-6">
<ReflinkAnalyticsClient
linkId={data.reflinkId}
linkName={reflink.name}
workspaceId={data.workspaceId}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client"

import { ReflinkAnalytics } from "@chatbotx.io/analytics-nextjs/components/reflink-analytics"

export function ReflinkAnalyticsClient({
workspaceId,
linkId,
linkName,
}: {
workspaceId: string
linkId: string
linkName: string
}) {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
return (
<ReflinkAnalytics
defaultSearchParams={{ workspaceId, linkId, timezone, linkName }}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export const duplicateFlow = async (ctx: {
flowId: newFlowId,
workspaceId: flow.workspaceId,
})
await tx.insert(flowAnalyticsSessionModel).values({
flowId: newFlowId,
workspaceId: flow.workspaceId,
createdAt: new Date(),
updatedAt: new Date(),
})

await tx.insert(flowVersionModel).values({
...draftVersion,
Expand Down
14 changes: 13 additions & 1 deletion apps/builder/src/features/magic-links/magic-links-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { useDataTable } from "@chatbotx.io/ui/hooks/use-data-table"
import type { ColumnDef, Row } from "@tanstack/react-table"
import {
ChartLineIcon,
LinkIcon,
MoreHorizontalIcon,
PencilIcon,
Expand Down Expand Up @@ -186,6 +187,17 @@ export const MagicLinksTable = ({
{t("actions.qrCode")}
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => {
router.push(
`/space/${workspaceId}/magic-links/${row.original.id}/analytics`,
)
}}
>
<ChartLineIcon />
{t("actions.viewAnalytics")}
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => setRowAction({ row, variant: "update" })}
>
Expand All @@ -207,7 +219,7 @@ export const MagicLinksTable = ({
enableHiding: false,
},
],
[copy, t],
[copy, t, router.push, workspaceId],
)

const { table } = useDataTable({
Expand Down
25 changes: 25 additions & 0 deletions apps/builder/src/features/reflinks/api/authenticated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { workspaceAuthorizedMidddleware } from "@/middlewares/auth"
import { authorizedAPI } from "@/orpc"
import { findReflink } from "../queries"
import { getReflinkRequest } from "../schemas/query"
import { reflinkResponse } from "../schemas/resource"

export const refLinkAuthenticatedAPI = {
getRefLinksAuthenticatedAPI: authorizedAPI
.route({
method: "GET",
path: "/workspaces/{workspaceId}/ref-links/{id}",
summary: "Get a specific ref link",
tags: ["Ref Links"],
})
.input(getReflinkRequest)
.use(workspaceAuthorizedMidddleware, (input) => input.workspaceId)
.output(reflinkResponse)
.handler(
async ({ input }) =>
await findReflink({
workspaceId: input.workspaceId,
id: input.id,
}),
),
}
7 changes: 7 additions & 0 deletions apps/builder/src/features/reflinks/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { refLinkAuthenticatedAPI } from "./authenticated"
import refLinksWorkspaceTokenAPIs from "./workspace-token"

export const refLinksAPI = {
...refLinkAuthenticatedAPI,
...refLinksWorkspaceTokenAPIs,
}
30 changes: 30 additions & 0 deletions apps/builder/src/features/reflinks/api/workspace-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { notFoundException } from "@chatbotx.io/business/errors"
import { zodBigintAsString } from "@chatbotx.io/utils"
import { z } from "zod"
import { workspaceTokenAuthAPI } from "@/orpc"
import { findReflink } from "../queries"
import { reflinkResource } from "../schemas/resource"

export const refLinksWorkspaceTokenAPIs = {
getRefLinkWorkspaceTokenAPI: workspaceTokenAuthAPI
.route({
method: "GET",
path: "/v1/ref-links/{id}",
summary: "Get a specific ref link",
tags: ["Ref Links"],
})
.input(z.object({ id: zodBigintAsString() }))
.output(reflinkResource)
.handler(async ({ context, input }) => {
const reflink = await findReflink({
workspaceId: context.workspace.id,
id: input.id,
})
if (!reflink) {
throw notFoundException("Ref link not found")
}
return reflink
}),
}

export default refLinksWorkspaceTokenAPIs
8 changes: 4 additions & 4 deletions apps/builder/src/features/reflinks/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "@chatbotx.io/database/utils"
import { assertCurrentUserCanAccessChatbot } from "@/lib/auth/utils"
import type {
GetReflinkRequest,
ListReflinksRequest,
ListReflinksResponse,
} from "../schemas/query"
Expand Down Expand Up @@ -43,10 +44,9 @@ export async function listReflinks(
return { data, pageCount }
}

export async function findReflink(where: {
workspaceId: string
id: string
}): Promise<ReflinkResource | undefined> {
export async function findReflink(
where: GetReflinkRequest,
): Promise<ReflinkResource | undefined> {
return await db.query.reflinkModel.findFirst({
where: { ...where, type: "refLink" },
})
Expand Down
14 changes: 13 additions & 1 deletion apps/builder/src/features/reflinks/reflinks-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useDataTable } from "@chatbotx.io/ui/hooks/use-data-table"
import type { DataTableRowAction } from "@chatbotx.io/ui/types/data-table"
import type { ColumnDef } from "@tanstack/react-table"
import {
ChartLineIcon,
LinkIcon,
MoreHorizontalIcon,
PencilIcon,
Expand Down Expand Up @@ -156,6 +157,17 @@ export function ReflinksTable({ workspaceId, promises }: ReflinksTableProps) {
{t("actions.copyUrl")}
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => {
router.push(
`/space/${workspaceId}/reflinks/${row.original.id}/analytics`,
)
}}
>
<ChartLineIcon />
{t("actions.viewAnalytics")}
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => setRowAction({ row, variant: "update" })}
>
Expand All @@ -177,7 +189,7 @@ export function ReflinksTable({ workspaceId, promises }: ReflinksTableProps) {
enableHiding: false,
},
],
[t],
[t, router.push, workspaceId],
)

const { table } = useDataTable({
Expand Down
6 changes: 6 additions & 0 deletions apps/builder/src/features/reflinks/schemas/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ export const listReflinksResponse = z.object({
pageCount: z.number(),
})
export type ListReflinksResponse = z.infer<typeof listReflinksResponse>

export const getReflinkRequest = z.object({
workspaceId: z.string(),
id: z.string(),
})
export type GetReflinkRequest = z.infer<typeof getReflinkRequest>
3 changes: 3 additions & 0 deletions apps/builder/src/features/reflinks/schemas/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ export const reflinkResource = createSelectSchema(reflinkModel, {
type: reflinkTypes,
})
export type ReflinkResource = z.infer<typeof reflinkResource>

export const reflinkResponse = reflinkResource.optional()
export type ReflinkResponse = z.infer<typeof reflinkResponse>
Loading
Loading