diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 667a84b..ce28b41 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
settings:
autoInstallPeers: true
@@ -336,7 +336,8 @@ packages:
/@panva/hkdf@1.1.1:
resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==}
-
+ dev: false
+
/@radix-ui/number@1.0.1:
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
dependencies:
diff --git a/src/app/api/crm_example/route.ts b/src/app/api/crm_example/route.ts
index 635996b..20665c8 100644
--- a/src/app/api/crm_example/route.ts
+++ b/src/app/api/crm_example/route.ts
@@ -20,7 +20,13 @@ export async function GET(request: Request) {
const url = new URL(request.url);
const queryParams = new URLSearchParams(url.search);
- const phone: string = queryParams.get("phone") || "1234567890";
+ const phone: string | null = queryParams.get("phone");
+
+ if (!phone) {
+ return new Response("Invalid phone number", {
+ status: 400,
+ });
+ }
const clientInfo = await table.getClientByPhone(phone);
diff --git a/src/app/api/eventcallback/route.ts b/src/app/api/eventcallback/route.ts
new file mode 100644
index 0000000..f95e546
--- /dev/null
+++ b/src/app/api/eventcallback/route.ts
@@ -0,0 +1,14 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { twilioClient } from '@/lib/twilioClient';
+
+export async function POST(req: NextRequest) {
+
+
+ console.log("RAAAAA")
+ // console.log(req)
+
+ const params = req.nextUrl.searchParams;
+ console.log(Array.from(params.entries()));
+
+ return new NextResponse(null, { status: 204, headers: { 'Content-Type': 'application/json' } });
+}
\ No newline at end of file
diff --git a/src/app/api/reservations/route.ts b/src/app/api/reservations/route.ts
new file mode 100644
index 0000000..e3f2e1c
--- /dev/null
+++ b/src/app/api/reservations/route.ts
@@ -0,0 +1,57 @@
+import { NextRequest } from 'next/server';
+import { twilioClient } from '@/lib/twilioClient';
+
+const workspaceSid = process.env.TWILIO_WORKSPACE_SID || "";
+const accountSid = process.env.TWILIO_ACCOUNT_SID;
+const authToken = process.env.TWILIO_AUTH_TOKEN;
+
+const client = require('twilio')(accountSid, authToken);
+
+export async function GET(req: NextRequest) {
+ const workerSid = req.nextUrl.searchParams.get('workerSid');
+
+ if (!workspaceSid) {
+ throw new Error('TWILIO_WORKSPACE_SID is not set');
+ }
+
+ if (!workerSid) {
+ throw new Error('Worker sid is not provided');
+ }
+
+ try {
+ const reservations = await twilioClient.taskrouter.v1.workspaces(workspaceSid)
+ .workers(workerSid)
+ .reservations
+ .list();
+
+ const tasks = await Promise.all(reservations.map(async (reservation) => {
+ const task = await twilioClient.taskrouter.v1.workspaces(workspaceSid)
+ .tasks(reservation.taskSid)
+ .fetch();
+ return { task: task, reservation: reservation }
+ }));
+
+ return new Response(JSON.stringify(tasks), { status: 200 });
+ } catch (error) {
+ return new Response("something went wrong", { status: 500 });
+ }
+}
+
+export async function POST(req: NextRequest) {
+
+ try {
+ const task = req.nextUrl.searchParams.get('taskSid');
+ const status = req.nextUrl.searchParams.get('status');
+ const reservationSid = req.nextUrl.searchParams.get('reservationSid');
+
+ client.taskrouter.v1.workspaces(workspaceSid)
+ .tasks(task)
+ .reservations(reservationSid)
+ .update({ reservationStatus: status })
+
+ return new Response(`updated to ${status}`, { status: 200 });
+
+ } catch (error) {
+ return new Response("something went wrong", { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/tasks/route.ts b/src/app/api/tasks/route.ts
new file mode 100644
index 0000000..bab1e2f
--- /dev/null
+++ b/src/app/api/tasks/route.ts
@@ -0,0 +1,36 @@
+import { NextRequest } from 'next/server';
+import { twilioClient } from '@/lib/twilioClient';
+
+
+const accountSid = process.env.TWILIO_ACCOUNT_SID;
+const authToken = process.env.TWILIO_AUTH_TOKEN;
+const workspaceSid = process.env.TWILIO_WORKSPACE_SID || '';
+const client = require('twilio')(accountSid, authToken);
+
+export async function POST(req: NextRequest) {
+ try {
+ const worker = req.nextUrl.searchParams.get('client');
+ const reservation = req.nextUrl.searchParams.get('reservationSid');
+ const task = req.nextUrl.searchParams.get('taskSid');
+
+ console.log(" worker:", worker);
+ console.log(" reservation:", reservation);
+ console.log(" task:", task)
+
+ client.taskrouter.v1.workspaces(workspaceSid)
+ .tasks(task)
+ .reservations(reservation)
+ .update({
+ instruction: 'dequeue',
+ dequeueFrom: '+16134002002', // The phone number the call is connected from
+ // to: 'client:atsarapk@uwaterloo.ca' // The client to connect the call to
+ })
+ .then((reservation: { reservationStatus: any; }) => console.log(reservation.reservationStatus))
+ .catch((error: any) => console.error(error));
+
+ //{"contact_uri":"client:atsarapk@uwaterloo.ca"}
+ return new Response("dequeued", { status: 200 });
+ } catch (error) {
+ return new Response("something went wrong", { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/voice/route.ts b/src/app/api/voice/route.ts
index e4e7aa4..8462cda 100644
--- a/src/app/api/voice/route.ts
+++ b/src/app/api/voice/route.ts
@@ -3,6 +3,7 @@ import VoiceResponse from "twilio/lib/twiml/VoiceResponse";
export async function POST(req: NextRequest) {
const callerId = process.env.TWILIO_CALLER_ID;
+ const workflowSid = process.env.TWILIO_WORKFLOW_SID;
const resp = new VoiceResponse();
@@ -10,12 +11,17 @@ export async function POST(req: NextRequest) {
const queryString = await req.text();
const params = new URLSearchParams(queryString);
const bodyTo = params.get("To");
+ const bodyFrom = params.get("From") || undefined;
// If the request to the /voice endpoint is TO your Twilio Number,
// then it is an incoming call towards your Twilio.Device.
if (bodyTo == callerId) {
// Incoming call
- const dial = resp.dial();
+ resp.say("Please hold");
+ resp.enqueue({ workflowSid: workflowSid });
+ // const dial = resp.dial({ callerId: bodyFrom });
+ // dial.client('atsarapk@uwaterloo.ca');
+
} else if (bodyTo) {
// Outgoing call
const dial = resp.dial({ callerId });
diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx
index 73c62af..ed3f94a 100644
--- a/src/app/dashboard/layout.tsx
+++ b/src/app/dashboard/layout.tsx
@@ -17,11 +17,16 @@ export default function Layout({
if (status === 'loading') {
return Loading...;
} else if (session) {
- console.log(session)
- console.log("ASDASDAS")
const isProgramManager = true; // TODO
- const initials = "AA"; // TODO get initials from user name
-
+ let initials = "AA";
+ if (session.user?.name) {
+ const parts = session.user.name.split(' ');
+ if (parts.length > 1) {
+ initials = (parts[0][0] + parts[1][0]).toUpperCase();
+ } else if (parts.length > 0) {
+ initials = parts[0][0].toUpperCase();
+ }
+ }
return (
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 013e5ca..da358f5 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -1,7 +1,13 @@
+"use client"
+import IncomingCallModal from "@/components/(dashboard)/tasks/IncomingCallModal";
+import NotificationsCard from "@/components/appbar/NotificationsCard";
+
export default function Dashboard() {
return (
Dashboard
+ {/* {}} rejectCall={() => {}} /> */}
+ {/* */}
);
}
diff --git a/src/app/dashboard/tasks/page.tsx b/src/app/dashboard/tasks/page.tsx
index 0e539f3..ca6556b 100644
--- a/src/app/dashboard/tasks/page.tsx
+++ b/src/app/dashboard/tasks/page.tsx
@@ -1,7 +1,125 @@
+"use client"
+
+import { useState, useEffect } from 'react';
+import { useSession } from "next-auth/react";
+import { Button } from '@/components/ui/button';
+import useCalls from '@/lib/hooks/useCalls';
+
+function formatTime(seconds: number) {
+ const days = Math.floor(seconds / (24 * 60 * 60));
+ seconds -= days * 24 * 60 * 60;
+ const hrs = Math.floor(seconds / (60 * 60));
+ seconds -= hrs * 60 * 60;
+ const mnts = Math.floor(seconds / 60);
+ seconds -= mnts * 60;
+
+ if (days) return days + (days > 1 ? " days" : " day") + " ago";
+ if (hrs) return hrs + (hrs > 1 ? " hours" : " hour") + " ago";
+ if (mnts) return mnts + (mnts > 1 ? " minutes" : " minute") + " ago";
+ if (seconds) return seconds + (seconds > 1 ? " second" : " second") + " ago";
+
+}
+
export default function Tasks() {
+ const [tasks, setTasks] = useState([]);
+ const [activeTasks, setActiveTasks] = useState([]);
+ const { data: session } = useSession();
+
+ const fetchTasks = () => {
+ fetch('/api/reservations?workerSid=' + session?.employeeNumber)
+ .then(response => response.json())
+ .then(data => {
+ setTasks(data);
+ setActiveTasks(data.filter((task: any) => task.reservation.reservationStatus === 'accepted' || task.reservation.reservationStatus === 'pending'));
+ });
+
+ };
+
+ const updateReservation = (reservation: any, status: string) => {
+ try {
+ fetch(`/api/reservations?taskSid=${reservation.taskSid}&status=${status}&reservationSid=${reservation.sid}`, {
+ method: 'POST'
+ })
+ } catch (error) {
+ console.error("Error updating reservation", error)
+ }
+ fetchTasks()
+ }
+
+ const dequeueTask = (reservation: any) => {
+ try {
+ fetch(`/api/tasks?taskSid=${reservation.taskSid}&client=${session?.user?.email}&reservationSid=${reservation.sid}`, {
+ method: 'POST'
+ })
+ } catch (error) {
+ console.error("Error dequeing reservation", error)
+ }
+ fetchTasks()
+ }
+
+ useEffect(() => {
+ // Fetch tasks immediately and then every 5 seconds
+ fetchTasks();
+ const intervalId = setInterval(fetchTasks, 5000);
+
+ // Clear interval on component unmount
+ return () => clearInterval(intervalId);
+ }, []);
+
return (
- Tasks
+ Tasks
+ See all unresolved communications with clients here.
+ {activeTasks.length} outstanding task{activeTasks.length == 1 ? "" : "s"}
+
+
+
+ | Task |
+ Initiated |
+ Actions |
+
+
+
+ {activeTasks && Array.isArray(activeTasks) && activeTasks
+ .map((task: any) => (
+
+ |
+ {task.task.taskChannelUniqueName === 'voice' ? (
+ <>Call {JSON.parse(task.task.attributes).from || "unknown"}>
+ ) : task.task.taskChannelUniqueName === 'chat' ? (
+ <>Respond to message from {JSON.parse(task.task.attributes).from || "unknown"}>
+ ) : null}
+ |
+ {formatTime(task.task.age)} |
+
+ {task.task.taskChannelUniqueName === 'voice' ? (
+
+ ) : task.task.taskChannelUniqueName === 'chat' ? (
+
+ ) : null}
+
+ {/* */}
+ |
+
+ ))}
+
+
);
-}
+}
\ No newline at end of file
diff --git a/src/components/(dashboard)/tasks/IncomingCallModal.tsx b/src/components/(dashboard)/tasks/IncomingCallModal.tsx
new file mode 100644
index 0000000..2544280
--- /dev/null
+++ b/src/components/(dashboard)/tasks/IncomingCallModal.tsx
@@ -0,0 +1,46 @@
+import { Button } from '@/components/ui/button';
+import { formatPhoneNumber } from '@/lib/utils';
+import { CircleUser } from 'lucide-react';
+
+export default function IncomingCallModal({
+ number,
+ acceptCall,
+ rejectCall,
+}: {
+ number: string;
+ acceptCall: () => void;
+ rejectCall: () => void;
+}) {
+ return (
+
+
+
+
+
+
+ {formatPhoneNumber(number)}
+
+
+ Incoming Call
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/appbar/Dialpad.tsx b/src/components/appbar/Dialpad.tsx
index df556e0..abb15f8 100644
--- a/src/components/appbar/Dialpad.tsx
+++ b/src/components/appbar/Dialpad.tsx
@@ -4,6 +4,8 @@ import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
+import { ClientProfile } from "@/components/ui/client-profile";
+
import {
Phone,
Delete,
@@ -14,6 +16,7 @@ import {
Hash,
StickyNote,
} from "lucide-react";
+import { CRMEntry } from "@/lib/crm/types";
export default function DialPad({
number,
@@ -29,6 +32,27 @@ export default function DialPad({
endCall: () => void;
}) {
const onPress = (value: string) => setNumber(number.concat(value));
+ const [name, setName] = useState("");
+ const [notes, setNotes] = useState("");
+ const [clientData, setClientData] = useState
({});
+
+ const isFullNumber = number.length === 10;
+
+ // hook to fetch data associated with the number
+ useEffect(() => {
+ console.log("Number changing...")
+ if (isFullNumber) {
+ fetch(`/api/crm_example?phone=${number}`)
+ .then((res) => res.json())
+ .then((data) => {
+ console.log("Data fetched", data);
+ setClientData(data);
+ });
+ } else {
+ setClientData(null);
+ }
+ }, [number]);
+
return (
@@ -48,6 +72,9 @@ export default function DialPad({
}
/>
+ {clientData && (
+
+ )}
@@ -78,9 +105,9 @@ export default function DialPad({
-
+ {/*
-
+ */}
@@ -91,6 +118,9 @@ export default function DialPad({
+ {clientData && (
+
+ )}
endCall()} />
>
diff --git a/src/components/appbar/index.tsx b/src/components/appbar/index.tsx
index 85cb3fc..be0d319 100644
--- a/src/components/appbar/index.tsx
+++ b/src/components/appbar/index.tsx
@@ -37,7 +37,8 @@ import NotificationsCard from "./NotificationsCard";
import { signOut } from "next-auth/react";
import useCalls from "@/lib/hooks/useCalls";
-// import { useSession } from "next-auth/react";
+import { useSession } from "next-auth/react";
+import IncomingCallModal from "../(dashboard)/tasks/IncomingCallModal";
interface AppbarProps extends React.HTMLAttributes {
initials: string;
@@ -49,12 +50,21 @@ export default function Appbar({
initials,
isProgramManager,
}: AppbarProps) {
- // const { data: session, status } = useSession();
+ const { data: session, status } = useSession();
- const { inCall, number, makeCall, setNumber, endCall } = useCalls({
- email: "michelleshx462@gmail.com", // TODO replace with okta auth info
- workerSid: "WK3b277b4e6a1d67f2240477fa33f75ea4", // session?.employeeNumber,
- friendlyName: "michelleshx462", // session?.user.name ?? '',
+ const {
+ inCall,
+ number,
+ makeCall,
+ setNumber,
+ endCall,
+ incomingCall,
+ acceptCall,
+ rejectCall
+ } = useCalls({
+ email: session?.user?.email || '',
+ workerSid: session?.employeeNumber || '',
+ friendlyName: session?.user?.name || '',
});
return (
@@ -114,7 +124,7 @@ export default function Appbar({
My Account
-
+
Settings
@@ -123,7 +133,7 @@ export default function Appbar({
<>
-
+
Agents
@@ -143,6 +153,7 @@ export default function Appbar({
+ {incomingCall && ()}
);
}
diff --git a/src/components/ui/client-profile.tsx b/src/components/ui/client-profile.tsx
new file mode 100644
index 0000000..99725fc
--- /dev/null
+++ b/src/components/ui/client-profile.tsx
@@ -0,0 +1,39 @@
+import { CRMEntry } from "@/lib/crm/types";
+import { useEffect, useState } from "react";
+
+export function ClientProfile({ clientData }: { clientData: CRMEntry }) {
+ const [seeMore, setSeeMore] = useState(false);
+
+ const imgUrl = clientData.img_src
+
+
+ return (
+
+ View profile: {clientData.name}
+ {seeMore && (
+
+
+
+
+ | Notes: |
+ {clientData.notes} 🖊️ |
+
+
+ | Address: |
+ {clientData.Address} |
+
+
+
+

+
+ )}
+
+ {seeMore ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index 311ebf8..eed8171 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -17,14 +17,13 @@ export const authOptions: NextAuthOptions = {
callbacks: {
async jwt({ token, account }: any) {
if (account) {
-
token.accessToken = account.access_token;
token.idToken = account.id_token;
token.oktaId = account.providerAccountId;
token.groups = account.groups;
+ token.employeeNumber = account.employeeNumber;
}
-
// Decrypting JWT to check if expired
var tokenParsed = JSON.parse(
Buffer.from(token.idToken.split('.')[1], 'base64').toString()
@@ -34,8 +33,41 @@ export const authOptions: NextAuthOptions = {
throw Error('expired token');
}
- // Add fields to token that are useful
- token.employeeNumber = tokenParsed.employeeNumber;
+ // Request token from Okta API to get user's employeeNumber
+ if (!token.employeeNumber) {
+ const response = await fetch(`${process.env.OKTA_OAUTH2_ISSUER}/api/v1/users/${tokenParsed.sub}`, {
+ headers: {
+ 'Authorization': `SSWS ${process.env.OKTA_API_KEY}`
+ }
+ });
+ const userData = await response.json();
+ token.employeeNumber = userData.profile.employeeNumber;
+ }
+
+ // If employeeNumber is still not found, create a new Twilio worker and assign ID
+ if (!token.employeeNumber) {
+ const accountSid = process.env.TWILIO_ACCOUNT_SID;
+ const authToken = process.env.TWILIO_AUTH_TOKEN;
+ const client = require('twilio')(accountSid, authToken);
+
+ client.taskrouter.v1.workspaces(process.env.TWILIO_WORKSPACE_SID)
+ .workers
+ .create({friendlyName: tokenParsed.email})
+ .then(async (worker: { sid: any; }) => {
+ token.employeeNumber = worker.sid;
+ const profile = {'profile':{"employeeNumber": worker.sid}};
+
+ const response = await fetch(`${process.env.OKTA_OAUTH2_ISSUER}/api/v1/users/${tokenParsed.sub}`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `SSWS ${process.env.OKTA_API_KEY}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(profile)
+ });
+ })
+ }
+
token.groups = tokenParsed.groups;
return token;
@@ -47,6 +79,8 @@ export const authOptions: NextAuthOptions = {
session.userType = token.userType;
session.employeeNumber = token.employeeNumber;
session.groups = token.groups;
+ session.user.email = token.email
+ session.user.name = token.name
return session;
},
diff --git a/src/lib/hooks/useCalls.ts b/src/lib/hooks/useCalls.ts
index 6a285a3..2fb7775 100644
--- a/src/lib/hooks/useCalls.ts
+++ b/src/lib/hooks/useCalls.ts
@@ -119,7 +119,7 @@ export default function useCalls({
device.current.on("registered", function () {
console.log("Twilio.Device Ready to make and receive calls!");
});
-
+
device.current.on("error", function (error: { message: string }) {
console.log("Twilio.Device Error: " + error.message);
});
@@ -171,7 +171,7 @@ export default function useCalls({
checkEmail.current = email;
const initializeCalls = async () => {
await Promise.all([
- await initializeDevice(email).then((newDevice) => {
+ await initializeDevice(email, workerSid).then((newDevice) => {
device.current = newDevice;
initializeDeviceListeners();
}),
@@ -221,12 +221,13 @@ export default function useCalls({
const makeCall = async (number: string) => {
if (!device.current) return;
-
const params = {
// get the phone number to call from the DOM
To: number,
};
+ console.log("Making a call to", number);
+
const newCall = await device.current.connect({ params });
call.current = newCall;
@@ -333,9 +334,10 @@ export default function useCalls({
* this is the same value as the "client_url" in the worker's attribute
*
*/
-async function initializeDevice(client: string) {
+async function initializeDevice(client: string, workerSid: string) {
+ console.log("Initializing device", client)
const token = await fetch(
- `${process.env.NEXT_PUBLIC_URL}/api/token?client=${client}`
+ `http://localhost:3000/api/token?client=${client}`
);
const value = await token.json();
@@ -346,6 +348,7 @@ async function initializeDevice(client: string) {
});
await device.register();
+
return device;
}
@@ -360,32 +363,33 @@ async function initializeDevice(client: string) {
* @param friendlyName - the friendly name found in Okta and Twliio
*
*/
-const initializeWorker = async (
- workerSid: string | undefined,
- email: string,
- friendlyName: string
-) => {
- try {
- if (!workerSid) {
- throw `The user ${friendlyName} with email ${email} does not have an employeeNumber in Okta`;
- }
- const tokenResponse = await fetch(
- `${process.env.NEXT_PUBLIC_URL}/api/workerToken?email=${email}&workerSid=${workerSid}`
- );
-
- if (tokenResponse.status !== 200) {
- throw `Failed to generate valid token for ${friendlyName} with email ${email}`;
- }
-
- const token = await tokenResponse.json();
-
- const worker = new Worker(token);
- await timeout(1000); // For some reason, this is some much needed black magic
- return worker;
- } catch (e) {
- console.error(e);
- }
-};
+// const initializeWorker = async (
+// workerSid: string | undefined,
+// email: string,
+// friendlyName: string
+// ) => {
+// try {
+// if (!workerSid) {
+// throw `The user ${friendlyName} with email ${email} does not have an employeeNumber in Okta`;
+// }
+// const tokenResponse = await fetch(
+// `${process.env.NEXT_PUBLIC_URL}/api/workerToken?email=${email}&workerSid=${workerSid}`
+// );
+
+// if (tokenResponse.status !== 200) {
+// throw `Failed to generate valid token for ${friendlyName} with email ${email}`;
+// }
+
+// const token = await tokenResponse.json();
+
+// const worker = new Worker(token);
+// console.log("WORKER IS", worker)
+// await timeout(1000); // For some reason, this is some much needed black magic
+// return worker;
+// } catch (e) {
+// console.error(e);
+// }
+// };
function timeout(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts
new file mode 100644
index 0000000..cd54a93
--- /dev/null
+++ b/src/types/next-auth.d.ts
@@ -0,0 +1,14 @@
+import NextAuth, { DefaultSession } from "next-auth"
+
+declare module "next-auth" {
+
+// extend session interface
+ interface Session {
+ employeeNumber: string,
+ groups: string[],
+ user: {
+ email: string,
+ name: string
+ }
+ }
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index e59724b..ed67f38 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,6 @@
"@/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app/api/worker"],
"exclude": ["node_modules"]
}