initial commit

This commit is contained in:
shafin-r
2025-07-03 01:43:25 +06:00
commit 5dc53b896e
279 changed files with 28956 additions and 0 deletions

View File

@ -0,0 +1,36 @@
import NextAuth, { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { JWT } from "next-auth/jwt";
import { Session } from "next-auth";
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorization: {
params: {
scope:
"openid profile email https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly",
},
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
async session({ session, token }: { session: Session; token: JWT }) {
session.accessToken = token.accessToken as string;
return session;
},
async jwt({ token, account }: { token: JWT; account: any }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@ -0,0 +1,42 @@
import { NextResponse } from 'next/server';
import { google } from 'googleapis';
import { getServerSession } from 'next-auth';
import { authOptions } from '../auth/[...nextauth]/route';
async function listCSVFileNames(accessToken: string): Promise<string[]> {
const auth = new google.auth.OAuth2();
console.log(accessToken)
auth.setCredentials({ access_token: accessToken });
const drive = google.drive({ version: 'v3', auth });
try {
const res = await drive.files.list({
q: "mimeType='text/csv'",
fields: 'files(name)',
});
const fileNames = res.data.files?.map(file => file.name) || [];
return fileNames
} catch (error) {
console.error('Error listing CSV files:', error);
throw new Error('Failed to list CSV files');
}
}
export async function GET(request: Request) {
try {
const session = await getServerSession({ req: request, authOptions });
if (!session || !session.accessToken) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const fileNames = await listCSVFileNames(session.accessToken);
return NextResponse.json({ fileNames });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@ -0,0 +1,62 @@
"use client";
import Image from "next/image";
import React from "react";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
import { signIn } from "next-auth/react"; // Import signIn function
import { Button } from "@/components/ui/button";
import Link from "next/link";
const page = () => {
return (
<main className="flex bg-[#151515] h-screen">
<section className="w-1/2 flex items-end overflow-hidden">
<Image
src="/image/splash.png"
alt="login splash"
width={1000}
height={1000}
/>
</section>
<section className="w-1/2 text-white flex items-center justify-center">
<div className="h-[700px] w-[500px] flex flex-col justify-between">
<section>
<Link href="/">
<Image
src="/logo/logo-full.svg"
alt="logo"
width={200}
height={1000}
/>
</Link>
</section>
<section className="flex flex-col justify-between h-1/3">
<div className="flex flex-col gap-2">
<h1
className={`text-white text-5xl ${inter.className} font-bold tracking-tighter`}
>
Hi There 👋
</h1>
<p className="text-xl tracking-tighter">
Login to your Google account.
</p>
</div>
<Button
className="bg-[#4285F4] h-[65px] text-lg tracking-tight"
onClick={() => signIn("google", { callbackUrl: "/dashboard" })}
>
Login with Google
</Button>
</section>
<section className="text-[#efefef]/50 tracking-tighter">
<p>&copy; 2024 Torpedo</p>
</section>
</div>
</section>
</main>
);
};
export default page;

View File

@ -0,0 +1,7 @@
import React from "react";
const page = () => {
return <div>Contacts</div>;
};
export default page;

View File

@ -0,0 +1,174 @@
"use client";
import { Avatar } from "@/components/ui/avatar";
import { AvatarImage } from "@radix-ui/react-avatar";
import { useSession } from "next-auth/react";
import { ContactSelector } from "@/components/dashboard/email/ContactSelector";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Image from "next/image";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import RichTextEditor from "@/components/dashboard/email/RichTextEditor";
import { Eye, Send, FileBox, Check } from "lucide-react";
import ContactModal from "@/components/dashboard/email/ContactModal";
const page = () => {
const { data: session } = useSession();
return (
<main className="px-4 py-6 border-white h-full flex flex-col gap-6">
<header>
<h1 className="font-bold text-5xl tracking-tighter text-white">
Send an Email
</h1>
</header>
<section className="flex gap-4">
<section className="w-1/3 h-fit flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Label>From</Label>
<div className="flex justify-between w-full rounded-xl py-2 pl-2 pr-4 border-[1px] border-[#CBD5E1]">
<div className="rounded-xl px-3 py-2 flex items-center gap-2 bg-[#3D3D3D] w-fit">
<Avatar className="h-7 w-7">
<AvatarImage src={session?.user?.image!} />
</Avatar>
<h2 className="tracking-tight text-white text-sm font-semibold">
{session?.user?.email}
</h2>
</div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Image
src="/icons/gmail.svg"
alt="gmail logo"
width={24}
height={24}
/>
</TooltipTrigger>
<TooltipContent className="flex flex-col items-center font-semibold">
Currently logged in as:{" "}
<p className="font-normal"> {session?.user?.email}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<div className="flex flex-col gap-2">
<Label>To</Label>
<div className="flex items-center justify-between w-full rounded-xl p-2 border-[1px] border-[#CBD5E1]">
<ContactSelector />
<ContactModal />
</div>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="subject">Subject</Label>
<Input
type="text"
id="subject"
placeholder="What's the email about?"
className="w-full rounded-xl py-7 px-4 border-[1px] border-[#CBD5E1] bg-transparent"
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="subject">Attachments</Label>
<div className="h-96 border-[1px] border-[#CBD5E1] rounded-xl"></div>
</div>
</section>
<section className="w-2/3 flex flex-col gap-2">
<Label>Body</Label>
<RichTextEditor />
</section>
</section>
<section className="flex justify-between">
<div></div>
<div className="flex gap-4">
<Sheet>
<SheetTrigger className="h-auto flex gap-2 justify-center rounded-lg text-sm items-center text-white w-40 bg-[#363636]">
<Eye size={18} />
Preview
</SheetTrigger>
<SheetContent className="bg-[#222222] flex flex-col gap-10">
<SheetHeader>
<SheetTitle className="text-4xl tracking-tighter">
Preview
</SheetTitle>
<SheetDescription>
This is what your email looks like.
</SheetDescription>
</SheetHeader>
<section className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Label>From</Label>
<div className="flex justify-between w-full rounded-xl py-2 pl-2 pr-4 border-[1px] border-[#CBD5E1]">
<div className="rounded-xl px-3 py-2 flex items-center gap-2 w-fit">
<h2 className="tracking-tight text-white text-sm ">
{session?.user?.email}
</h2>
</div>
</div>
</div>
<div className="flex flex-col gap-2">
<Label>To</Label>
<div className="flex justify-between w-full rounded-xl py-2 pl-2 pr-4 border-[1px] border-[#CBD5E1]">
<div className="rounded-xl px-3 py-2 flex items-center gap-2 w-fit">
<h2 className="tracking-tight text-white text-sm ">
alex.mason@gmail.com
</h2>
</div>
</div>
</div>
<div className="flex flex-col gap-2">
<Label>Subject</Label>
<div className="flex justify-between w-full rounded-xl py-2 pl-2 pr-4 border-[1px] border-[#CBD5E1]">
<div className="rounded-xl px-3 py-2 flex items-center gap-2 w-fit">
<h2 className="tracking-tight text-white text-sm ">
Greetings for the new year!
</h2>
</div>
</div>
</div>
<div className="flex flex-col gap-2">
<Label>Body</Label>
<div className="flex justify-between w-full rounded-xl py-2 pl-2 pr-4 border-[1px] border-[#CBD5E1]">
<div className="rounded-xl px-3 py-2 flex items-center gap-2 w-fit">
<p className=" text-white text-sm">
Hello, Alex Merry Christmas and a Happy New Year to you
all Engineers. Im glad to announce that we will be
arranging a Christmas party on the eve of Christmas. As
such, you are cordially invited to attend the party as
we will have loads of fun events like Dart, Mario Kart
and Beer Pong. Hope to see you at the party! Regards, HR
</p>
</div>
</div>
</div>
</section>
</SheetContent>
</Sheet>
<Button className="flex gap-2 w-40 bg-[#2477FF] text-white">
<Send size={18} />
Send
</Button>
</div>
</section>
</main>
);
};
export default page;

View File

@ -0,0 +1,20 @@
// app/dashboard/layout.tsx
"use client";
import Sidebar from "@/components/dashboard/Sidebar";
import React from "react";
import { SessionProvider } from "next-auth/react";
const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
return (
<div className="flex h-screen">
<SessionProvider>
<Sidebar />
<main className="flex-1 p-6 bg-[#151515]">{children}</main>
</SessionProvider>
</div>
);
};
export default DashboardLayout;

View File

@ -0,0 +1,13 @@
"use client";
import React from "react";
const page = () => {
return (
<>
<h1>Dashboard</h1>
<p>Welcome to your dashboard!</p>
</>
);
};
export default page;

View File

@ -0,0 +1,7 @@
import React from "react";
const page = () => {
return <div>Settings</div>;
};
export default page;

76
torpedo/app/globals.css Normal file
View File

@ -0,0 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
.ProseMirror {
padding: 16px;
border: 1px solid #ccc;
border-radius: 4px;
min-height: 70vh
}

32
torpedo/app/layout.tsx Normal file
View File

@ -0,0 +1,32 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Torpedo",
description: "Torpedo bulk email app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}

84
torpedo/app/page.tsx Normal file
View File

@ -0,0 +1,84 @@
"use client";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import { useRef, useState } from "react";
import { motion } from "framer-motion";
interface Position {
left: number;
width: number;
opacity: number;
}
interface TabProps {
children: React.ReactNode;
setPosition: (position: Position) => void;
}
const Slidetabs: React.FC = () => {
const [position, setPosition] = useState<Position>({
left: 60,
width: 150,
opacity: 1,
});
return (
<ul className="relative mx-auto flex w-fit rounded-full border-2 border-black bg-white p-1">
<Tab setPosition={setPosition}>Home</Tab>
<Tab setPosition={setPosition}>Features</Tab>
<Tab setPosition={setPosition}>Pricing</Tab>
<Tab setPosition={setPosition}>Docs</Tab>
<Tab setPosition={setPosition}>Blog</Tab>
<Cursor position={position} />
</ul>
);
};
const Tab: React.FC<TabProps> = ({ children, setPosition }) => {
const ref = useRef<HTMLLIElement | null>(null);
return (
<li
ref={ref}
onMouseEnter={() => {
if (!ref.current) return;
const { width } = ref.current.getBoundingClientRect();
setPosition({
width,
opacity: 1,
left: ref.current.offsetLeft,
});
}}
className="relative z-10 block cursor-pointer px-3 py-1.5 text-xs uppercase text-white mix-blend-difference md:px-5 md:py-3 md:text-base"
>
{children}
</li>
);
};
const Cursor: React.FC<{ position: Position }> = ({ position }) => {
return (
<motion.li
animate={position}
className="absolute z-0 w-36 h-7 rounded-full bg-black md:h-12"
></motion.li>
);
};
export default function Home() {
return (
<body className="">
<header className="flex justify-between">
<div className="">
<Image src="/logo/logo-full.svg" alt="logo" width={200} height={0} />
</div>
<nav>
<Slidetabs />
</nav>
<div>
<Button>Login</Button>
</div>
</header>
<main className="text-7xl">Welcome to Torpedo</main>
</body>
);
}